Inspiration

A three-chair salon loses a slot, an hour of a stylist's day and a customer's spot in the queue every time someone forgets an appointment. The usual fix is a prepaid deposit, but that punishes the customers who do show up: they pay before the service, refunds take days, and fees eat the margin. Most small salons end up with no protection at all.

What it does

ShowUp holds instead of charging. When a customer books, PayPal places an authorization for the deposit. If they arrive, the hold is voided and no money ever moves. If they don't, an agent captures only the disclosed no-show fee and releases the rest.

  • AI front desk (customer). A chat assistant books, reschedules and cancels by calling tools (find_slots, create_booking, cancel_booking, reschedule_booking, running_late). It never invents a time or a price. Before the PayPal button unlocks the customer sees the exact policy and ticks an unticked box; consent, policy version, time and IP are stored.
  • Back-office agent (owner). It reviews every open hold and settles what is due: void on check-in or early cancellation, a partial capture with final_capture on a no-show or late cancellation, a reauthorization when a hold passes PayPal's 3-day honor period, and a reminder the day before. A customer who says "I'm running 10 minutes late" gets extra grace.
  • Disputes. If a charged customer disputes, the agent drafts the evidence from the consent record and the logs and states only what actually happened. The owner reviews and submits.
  • Owner desk. A day calendar per stylist, KPIs (slots protected, money on hold, holds released, fees recovered), the agent's plain-language log, every message sent to customers, and the PayPal ledger in an AG Grid themed in the salon colours (status pills, formatted amounts, PayPal ids) with AG Charts showing money on hold, released and collected per stylist.
  • Ledger built on AG Grid + AG Charts (Community). A toolbar drives the grid: kind chips (external filter), a quick-filter search over customer, PayPal id and reason, and CSV export for the accountant. A pinned totals row recomputes for whatever is on screen (held, released for $0, fees). Click a stylist's bar in the AG Charts chart and the ledger filters to that stylist; click a ledger row and the calendar jumps to that booking's day and flashes it.

How we built it

Cloudflare Workers + D1, the APIMatic-generated PayPal Server SDK for Orders v2 (intent: AUTHORIZE, authorize) and Payments v2 (capture/void/reauthorize/get), plain REST for webhooks with signature verification and the Disputes API, the PayPal JS SDK buttons (wallet and card), and Workers AI (@cf/zai-org/glm-4.7-flash) for tool-calling and copy. Money moves only through a small deterministic policy engine (decide()), never through free-form model output, and every money-moving request carries a PayPal-Request-Id so a retried run returns the same capture instead of charging twice (tested in sandbox).

Built with the APIMatic Context Plugin for PayPal. The plugin's TypeScript skills (in the repo under .claude/skills) made the coding agent plan before coding: paypal-plan.md is the contract sheet for the six SDK operations we call, with duplicate-claim and unknown-outcome tables. It caught two things: the SDK defaults Prefer to return=minimal, which would have dropped the authorization id the agent needs, and a real race where a check-in and the back-office tick could both void the same hold. A conditional UPDATE ... WHERE status = 'held' now lets exactly one of them move money, and a capture or void whose answer is lost is re-read with getAuthorizedPayment instead of being reported as failed. Verified with tsc --checkJs against the SDK's types and a live sandbox run (scripts/sdk-smoke.mjs, 8/8 pass).

Challenges

  • PayPal sandbox merchants outside the US cannot take card payments; we moved the demo merchant to a US sandbox business.
  • Workers Free allows five cron triggers per account, so the agent also ticks on traffic every two minutes.
  • The model reasons before answering; for short customer copy that burned the whole token budget until we disabled thinking.
  • One check-in on the live demo logged two releases. The guard was a read followed by a write, so the check-in and the tick it triggers both got through; the plugin's duplicate-claim rule pointed at the fix (a conditional claim on the booking row).

Accomplishments

A real end-to-end hold lifecycle in sandbox: hold, check-in void, no-show partial capture with the remainder released, verified webhooks, and an evidence pack, all driven by agents, with a demo clock so judges can watch a day happen in a minute.

What's next

Partner onboarding so each salon is the payee with its own PayPal account; SMS delivery for reminders; an MCP endpoint so any assistant (ChatGPT, Claude) can book with a guaranteed slot.

AI tools used

  • Cloudflare Workers AI @cf/zai-org/glm-4.7-flash: front-desk agent with tool calling; customer messages; dispute drafts.
  • Claude Code: pair-programming the build.

Try it

Open the owner desk and press Reset demo (it places four real sandbox holds, so give it about a minute): the demo clock jumps to 11:00 AM and four bookings appear with real sandbox holds. Check in Ana (void), press +1 hour (Ben no-shows: partial capture), then open Dispute draft on Dev. Click a stylist's bar in the chart to filter the ledger, or a ledger row to find that booking on the calendar. To place a hold yourself, book in chat on the customer page and pay with the card option and a test card from PayPal's sandbox card generator (developer.paypal.com/tools/sandbox/card-testing). PayPal sandbox only; no real money moves.

Built by a solo developer in Korea. English text was written with AI assistance.

Built With

  • ag-charts
  • ag-grid
  • apimatic
  • cloudflare-d1
  • cloudflare-workers
  • javascript
  • paypal
  • paypal-js-sdk
  • paypal-orders-api
  • paypal-server-sdk
  • workers-ai
Share this project:

Updates

Submission history