-
-
€20 from Lena in Berlin, for water only. PayPal places the hold; nothing is captured.
-
The volunteer's Japanese receipt as Meta Muse Spark read it twice: 9 lines pass, beer and a gift card are refused.
-
One receipt split across four holds: €4.58 from Lena, $21.45 from Mark, ¥1,237 from Yuki, ¥5,224 from the company.
-
Haruto, the volunteer, is paid back ¥10,448 through PayPal Payouts.
-
Lena's statement: "Your €4.58 paid for half of 12 bottles of water 2 L (receipt lines 1–3); Kitasora Logistics matched the other half."
-
Drive closed: PayPal voids Lena's hold, and €15.42 of her €20 is never charged.
-
The Ledger in AG Studio: each donor's money on a world map.
-
Ask PayPal yourself: 9 of 9 PayPal objects agree with the book.
-
/cheat: five rigged receipts against the same checks, every line with its outcome and reason.
-
The published receipt: purchase lines only, everything else blank paper.
Inspiration
In BBB Wise Giving Alliance's 2024 survey, an independent review of how a charity spends its money was the accountability check U.S. respondents valued most (75.9%). After the 2021 Texas freeze, Austin Mutual Aid drew more than 14,000 donors and reported $2.6 million, and Vice wrote: "Because AMA has so far failed to show their receipts, there is almost no way to check AMA's math." In Pledge-as-Spent the receipt comes before the charge, and your €20 for water can only buy water.
"I often send money to a fund, get a 'thank you', and six months later a general report. Where my own money went is unclear. A photo of a receipt line… 'Water 2 L × 6' convinces me far more than 'we helped 3,000 families'. And knowing the rest is never charged takes away the main fear, that the money ends up in the fund's overhead." — a donor we asked, November 2026
Lena in Berlin pledged €20 for water for a shelter after an earthquake in Japan. €4.58 of it was spent, and the other €15.42 was never charged: it stayed with her.
Lena and the shelter are our demo drive, made up and running in the PayPal sandbox. A volunteer at the shelter bought 12 two-litre bottles. Lena paid her half, €4.58, and a local company paid the other half. Her statement shows the three water lines of the Japanese receipt that her euros paid for, translated into her language.
The same receipt charged Mark in Columbus for diapers in dollars and Yuki in Osaka for batteries in yen. A single yen line of water is paid from Lena's euro hold and the company's yen match, each captured in its own currency. Each person pays from their own hold, in their own currency, and only after Meta reads each line twice, independently, and both readings agree on its amount and quantity.
Checked on the hosted demo, no login (https://pledge-as-spent.vercel.app, drive demo-810b24be50): captures 4VE88173NF234560B (€4.58), 50398406KN1796230 ($21.45), 2CM40725YN406312S (¥1,237) and 97T59974KS757452K (¥5,224); payout CB379GNVYPU8G (¥10,448); Ask PayPal yourself: 9 of 9 objects agree. In the repo: line F1 100% on 20 synthetic receipts in 4 languages the prompts never saw; 60 attacks on the money path, all held; 1,567 unit and integration tests, 183 browser tests.
What it does
Pledge-as-Spent is for a shelter or a mutual-aid group that buys supplies at local stores after a disaster, and for donors abroad who want to pay for one listed need in euros, dollars or yen. The demo has the Kitasora Community Shelter, three needs (water, size M diapers, AA batteries), three donors, and a local company that matches every yen 1:1 up to ¥500,000.
A pledge is a PayPal hold for one need, and nothing is captured when it is made. The demo drive places its four holds with a sandbox test card. To add your own pledge, approve it in PayPal's window with a sandbox personal account.
Meta Muse Spark 1.3 reads the whole receipt photo once. Code then cuts out the receipt's rows and lays them on a numbered sheet, and Muse Spark reads that sheet as a second, independent reading. A line is funded when both readings agree on its amount, its quantity and its need. A quantity dispute is settled by arithmetic or a third reading; anything else goes to the coordinator. The demo's sample receipt keeps Meta's saved reading and the screen says so; a new photo is read live, within a daily limit per network and a spending cap.
Beer and a gift card are on the demo receipt, so ¥4,198 of its ¥14,646 never reaches a donor. Nine lines pass, ¥10,448 in all. The company match pays half and the donors pay the other half, each in their own currency, and each person's shares are taken in one partial capture on their own hold. Haruto, the volunteer, gets ¥10,448 back through PayPal Payouts.
A donor's statement shows the receipt lines their money paid for in the donor's own language: a Meta model translates them into German or Japanese. When a translation is not available, the line stays in English and the statement says so; no amount depends on a translation. The statement also gives the cost in one line: "Pledge-as-Spent fee: 0 %", and PayPal's fee on the donor's share is paid by the drive, not by the donor.
When the drive closes, PayPal voids every hold. €15.42, $8.55 and ¥1,763 were never charged, so there is nothing to refund. While a hold is still open, its line on the donor's statement says what happens if we go away: "If Pledge-as-Spent disappears today, PayPal ends this hold by [date], and nothing more can be charged after that." When the donor presses Ask PayPal yourself, PayPal's own answer appears beside it: "PayPal confirms this date." After the void, the line says PayPal voided the hold and nothing more can be charged from it.
Ask PayPal yourself reads every hold, capture and payout of the drive back from PayPal's sandbox, GET only, and sets each answer beside the book's own line. Once a day the server asks PayPal the same questions on its own and writes the result under the book: "Checked with PayPal · date · N objects · N differences". On the demo drive: 9 objects, 0 differences.
The shelter can print an A4 poster of what was bought for it, with a QR code. Anyone who scans it opens the drive's book and PayPal's own record of it.
You can also try to cheat it. /cheat runs five rigged receipts through the same checks as every upload: one bought before the disaster, the paid receipt again, a store outside the zone, a total raised by hand, and beer, cigarettes and a gift card among the water. Each line comes out paid, refused, held for a person or blocked, with the reason. The readings there are the receipt set's own truth, so /cheat tests the checks, not the reading; nothing is written and PayPal is not called.
After a hurricane in Florida, help works the same way, in dollars: a second made-up drive, a shelter in Hallandale Beach. But if someone brings an old receipt (in the demo, a real Florida grocery receipt from 2019, published as its closed copy), the checks hold it back for a person: it was bought before the disaster, and even after a person approves it, no donor is charged for it.
More that a donor and the shelter can do:
- When pledging, the donor chooses what happens to the rest once their need is covered: "Is never charged" (the default) or "Keeps buying for this drive", where the rest moves to the open need furthest from covered. Only approved receipt lines are ever charged.
- A donor can set rules for the lines their hold pays: only the needs they pick, and no more than a price they name per item.
- A PayPal hold lives 29 days at most. If the drive runs longer, the donor presses Renew and approves a new PayPal order for what is left; the old hold first pays for what it already bought, then PayPal releases it. Renewal was run live in the PayPal sandbox.
- Only volunteers the shelter invited, by their PayPal address, can be paid.
- The money river: one screen that follows a receipt through its two readings and its lines to each donor's and the company's share, PayPal's holds and captures, the payout to the volunteer, and the rest that is never charged.
A donor can ask the book in their own words, or press "What did my money buy?". Every amount the answer gives with a currency sign or code is checked against the figures the book's own tools returned; if one does not match, the answer is not shown.
Receipts are published closed. Only the purchase lines stay visible; name, card, address and member number become plain paper. On a held-out set, 0 of 42 personal data items stayed visible.
Try it in 90 seconds
Demo: https://pledge-as-spent.vercel.app · no login, no keys · a made-up drive in the PayPal sandbox.
- Press Open the demo drive. Four holds are placed at PayPal.
- On the Coordinator screen press Use the sample receipt (it uses Meta's saved reading of the sample, and the screen says so; a new photo is read live): 9 lines approved, 2 refused, ¥10,448 to the volunteer.
- Press Capture now. Then press Close drive and confirm with Yes, close the drive.
- Press See where the money went, then press a donor to see what they paid for.
Two of you can try it together: one runs the drive, the other pledges for the water with their own PayPal sandbox personal account and sees on their statement which receipt lines their money paid for; the steps are in the README. We ran it on the hosted demo with a sandbox account from another PayPal developer account: €5 pledged, €0.92 captured for three water lines, €4.08 never charged, and PayPal's answers matched the book for all 11 objects of the drive.
To check the claims yourself without the demo: npm ci && npm run judge in the repository (Node 22 or later). It runs offline, with recorded PayPal sandbox answers and in-memory Postgres, and prints each claim with the tests behind it.
How PayPal is used, and why each API
- Orders v2 with intent AUTHORIZE: the pledge stays with the donor until a receipt line uses it.
- Partial capture (final_capture: false): one hold can pay for several lines, and each donor pays only their share.
- Reauthorize from day 4, on holds paid from a PayPal account: PayPal guarantees the rest of the hold for another 3 days. The 29-day limit stays.
- A new AUTHORIZE order to renew a hold: the donor approves it again, so a drive cannot stretch a hold without them.
- Payouts: the volunteer who paid at the till gets the money back.
- Void: whatever no receipt used is released. It was never captured, so there is nothing to refund.
- Webhooks, signature verified, and lookups: a share shows as spent only after PayPal confirms it.
- PayPal-Request-Id, built from the book, on each POST that changes something at PayPal: a repeated request gets the first answer back instead of a second capture.
- GET on authorizations, captures and payouts: Ask PayPal yourself and the daily check with PayPal.
- The JavaScript SDK shows PayPal's approval window for a donor's own pledge. The server uses the official @paypal/paypal-server-sdk, generated by APIMatic, and direct REST where the SDK has no call.
Each donor's shares are captured in batches, so there are fewer fixed fees. On the demo drive that saved €0.78, ¥490 and $0.98 in fixed fees, at the fee rate we fitted from sandbox captures.
How AI is used
Only Meta's models read receipts. Muse Spark 1.3 reads the whole photo once. Code finds each printed row by its pixels and lays the rows on a numbered sheet, and Muse Spark reads that sheet a second time. A photo whose rows code cannot find gets only the first reading, and its lines wait for the coordinator. One Muse Spark text call matches the lines to the drive's needs and rules. A Meta model translates the receipt lines on a donor's statement into the donor's language. Muse Spark is used for reading and labelling; the shares, captures and payouts come out of code.
On 20 synthetic receipts in Japanese, Turkish, Spanish and English that the prompts never saw, and on 20 more that the last changes were tuned on: line F1 100% and totals 20/20 on each, no line funded with a wrong need or quantity. About 25 seconds and about $0.03 per receipt.
The Ledger's assistant also runs on Muse Spark 1.3, in AG Studio's Agent Framework. On seven donor questions in English, German and Japanese, the run on the final code answered 7 of 7 with correct figures, and 6 of 7 by our strict score: one answer also named the other needs' totals. $0.016 a question. Live questions are limited per network and per day; the four quick questions are answered by the book's own code.
How we built it
Next.js 16 and TypeScript on Vercel, Neon Postgres with Drizzle, Zod. The Ledger is one AG Studio report: AG Grid lines, value widgets, a custom map and the chat. Built by Nadezhda Korchagina with Claude Code as the development tool, after October 1, 2026.
60 attacks on the money path, each with its own test; all 60 hold, and five that did not at first were fixed. 29 of them cover keeping or moving the rest, renewal, invited volunteers and donor rules. 1,567 unit and integration tests, 183 browser tests. GitHub Actions runs the type check, every unit and integration test and npm run judge on each push.
Challenges we ran into
- One USD sandbox merchant held authorizations in EUR, USD and JPY with no setup, and several partial captures work on each.
- Every capture pays a percentage plus a fixed fee in its own currency, so shares are batched.
- JPY amounts are whole numbers in both directions.
What's next
- Store check against each country's official registry: in Japan by the qualified-invoice number "T" + 13 digits printed on the receipt, in Florida by the store's name and address in the state's business registry (Sunbiz).
- Money for the volunteer before the purchase. Today the volunteer pays at the till and is reimbursed afterwards.
- Real receipts. Reading was measured on synthetic receipts only; the one real receipt in the demo (Florida, 2019) tests the before-the-disaster check, not reading accuracy, and real crumpled receipts will read worse.
Built With
- ag-grid
- ag-studio
- apimatic
- claude-code
- drizzle-orm
- github-actions
- meta-muse-spark
- neon
- next.js
- paypal-javascript-sdk
- paypal-orders-api
- paypal-payments-api
- paypal-payouts-api
- paypal-server-sdk
- paypal-webhooks
- playwright
- postgresql
- react
- typescript
- vercel
- vitest
- zod
