Inspiration
Every group trip ends the same way: one person has paid for three things, somebody owes somebody else a fraction of a taxi, and the group spends twenty minutes on a spreadsheet that nobody trusts. Splitwise-style apps tell you who owes whom; they do not move the money. PayPal already has the rail - what was missing was a trustworthy answer to 'exactly how much, to whom, and why'.
What it does
Tabby turns a messy group-expense note into the minimum set of PayPal payments that squares everyone up.
Paste a note like Dinner | 186.40 | paid by Ana | split Ana, Ben, Cleo. Tabby extracts the items, keeps every amount as an integer number of cents, nets the group out with a greedy largest-debtor/largest-creditor pass that never emits more than n-1 payments, and renders one PayPal button per remaining transfer. Three items and $219.90 of spending collapse to two payments instead of nine pairwise debts.
How we built it
PayPal is load-bearing, not decorative. The demo uses the official JavaScript SDK (components=buttons, intent=capture): each button calls actions.order.create() so PayPal itself mints a sandbox order for exactly the netted amount, then actions.order.capture(), and the page prints the order id and status. The repo also ships a server-side Orders v2 REST path - an OAuth2 client_credentials token from /v1/oauth2/token, POST /v2/checkout/orders with the amount computed on the server so the client cannot tamper with it, then GET /v2/checkout/orders/{id} to read the captured amount back. The settlement is only marked paid when PayPal's own response agrees with the ledger, to the cent.
The engine is deliberately boring: integer cents end to end, the per-item remainder handed out one cent at a time so the shares always sum back to the item, and a greedy netting pass that provably emits at most n-1 transfers. A malformed line is reported with its line number instead of being silently dropped, because a dropped line is a dropped charge.
Challenges we ran into
The hard part was not the algorithm, it was trusting it. Two implementations now exist - Python for the server and CLI, JavaScript inside the demo page - and the demo's Run cross-check button replays every hand-verified fixture through the browser engine and compares totals, balances, transfers and the settlement id against the values the Python suite asserts. Seven of seven agree. The Python suite adds 200 pseudo-random ledgers checking that balances sum to zero, item shares sum to the item amount, transfers realise every balance exactly, and the plan is invariant to line order.
Accomplishments that we are proud of
Every cent is accounted for in both engines, and the settlement id is a hash of the canonical ledger, so the same note always produces the same plan and the same PayPal description strings. The whole thing is standard library only - no dependencies to install, 23 tests, MIT licensed.
What we learned
Money math belongs in integers, and a payment integration is only as trustworthy as the amount you can read back from the provider afterwards.
What's next for Tabby
Receipt OCR for photos of the bill, per-person PayPal payer identity so each button charges the actual debtor, and a settled-state webhook that closes the loop without a page reload.
Built With
python, javascript, paypal-orders-v2, paypal-js-sdk, oauth2, html, unittest
Built With
- html
- javascript
- oauth2
- paypal-js-sdk
- paypal-orders-v2
- python
- unittest