In this tutorial, we will set up and use Formance Ledger for a ride-sharing platform called "RideShare." We will design a Chart of Accounts, create a schema to enforce it, use Numscript to execute transactions, and check account balances.
The Complete Schema#
Here is the full ledger schema for the RideShare tutorial. It defines the account hierarchy and all four transaction patterns we'll walk through below.
Design the Chart of Accounts#
The Chart of Accounts should reflect all account types relevant to your flow of funds and reporting needs.
We recommend starting with the key questions you need the ledger to answer. For RideShare:
- How much did we pay the drivers?
- How much did we collect in service fees?
- How much did an individual rider pay?
- How much did an individual driver earn?
And key events the ledger must support:
- Rider books a ride
- RideShare confirms the ride
- Driver finishes the ride
- RideShare completes ride verification
- Driver requests earnings withdrawal
The schema defines four account groups to support these questions and events:
World#
The @world account represents the external world — funds entering and exiting the ledger.
Rider#
Each rider gets a per-ride payment account (rider:$riderId:ride:$rideId:payment) that records the estimated charge for that ride.
Ride#
Each ride has a main account (total charged) and a fees account (RideShare's service fee).
Driver#
Each driver has per-ride earning accounts and a main account that accumulates total earnings across rides.
The structured naming convention helps organize accounts into segments, making it easier to manage and query them.
Create the Schema#
Now let's create the schema to enforce the Chart of Accounts. First, get your sandbox API endpoint:
fctl stack showThen create the schema using the API. You can copy the YAML from the schema viewer above, or use the JSON equivalent:
curl -X POST $FORMANCE_API_URL/api/ledger/v2/default/schemas/v1.0.0 \
-H "Content-Type: application/json" \
-d '{
"chart": {
"world": {},
"rider": {
"$riderId": {
"ride": {
"$rideId": {
"payment": {}
}
}
}
},
"ride": {
"$rideId": {
"main": {},
"fees": {}
}
},
"driver": {
"$driverId": {
"ride": {
"$rideId": {}
},
"main": {}
}
}
},
"transactions": {}
}'By default, the ledger runs in audit mode—transactions are validated against your schema but allowed through. For strict enforcement, see enforcement modes.
Introduce Money into the Ledger#
When a rider books a ride, we record the estimated payment from @world into the rider's ride payment account.
vars {
number $amount
account $riderId
account $rideId
}
send [USD/2 $amount] (
source = @world
destination = @rider:$riderId:ride:$rideId:payment
)Execute this transaction:
fctl ledger send default --numscript '{script}'This adds $20 to the @rider:xx:ride:yy:payment account.
Run Transactions on the Ledger#
Ride Confirmation#
When RideShare confirms the ride, transfer the payment from the rider's account to the ride's main account.
vars {
number $amount
account $riderId
account $rideId
}
send [USD/2 $amount] (
source = @rider:$riderId:ride:$rideId:payment
destination = @ride:$rideId:main
)fctl ledger send default --numscript '{script}'Ride Completion#
After the ride is completed, split the payment between the driver's earnings and RideShare's service fees.
vars {
number $amount
account $rideId
account $driverId
}
send [USD/2 $amount] (
source = @ride:$rideId:main
destination = {
10% to @ride:$rideId:fees
remaining to @driver:$driverId:main
}
)fctl ledger send default --numscript '{script}'Driver Payout#
After some days, the driver requests their earnings. Transfer all accumulated earnings back to @world to represent funds leaving the ledger. The wildcard * sends the entire balance.
vars {
account $driverId
}
send [USD/2 *] (
source = @driver:$driverId:main
destination = @world
)fctl ledger send default --numscript '{script}'Check Account Balances#
You can check the balances of the accounts using the Formance Console.