Demo mode: PayPal calls run against a local mock built to PayPal's documented REST shapes; it has not been run against the PayPal sandbox; add sandbox credentials to .env to run live. The app calls PayPal Orders v2, Payments v2 and Payouts v1 with the request and response shapes from PayPal's documentation. In this demo, the screenshots and the video, every one of those calls is answered by a local mock: simulated responses, no real money, and not the PayPal sandbox. Mock ids start with MOCK, and every page shows a banner naming the active mode. With a PayPal sandbox client id and secret in .env, the same code calls the PayPal sandbox REST API. scripts/sandbox_e2e.py checks the keys and then runs the whole flow.

Wrenly Home is a real, early-stage home-cleaning marketplace in Peoria, Illinois, started October 21, 2026. It has not completed a paid job yet. Every job, customer and payment in this project is test data.

Inspiration

We started Wrenly Home, a two-sided cleaning marketplace in Peoria, Illinois, on October 21, 2026. Building the website was the easy part. The hard part is the money in the middle of two sides:

  • Quoting a home consistently from a messy description ("3 bed, 2.5 bath, two dogs, every other week").
  • Collecting from the customer without charging for a clean that has not happened yet.
  • Paying the cleaning crew fairly and fast, without ever paying out more than the job brought in.
  • Refunds when something goes wrong, without an AI giving money away on its own.
  • Never paying twice when a button is double-clicked, a network call times out, or an agent retries.

A small marketplace cannot afford a finance team. It can afford an agent, if that agent is boxed in by rules it cannot talk its way around. So we built agentic commerce where the AI decides what to try and code decides what is allowed.

What it does

Wrenly Agent Checkout is an AI agent that runs the routine money flow of a cleaning marketplace through PayPal's APIs, with a human on every exception.

  1. Quote (booking agent). The customer describes the home in a chat. Using Google Gemini function calling, the agent calls quote_cleaning with bedrooms, bathrooms, frequency and add-ons, then explains the price from the price list. (Only the 3-bedroom biweekly $179 clean is Wrenly's published price. The other prices are labeled samples for the demo.)
  2. Checkout. The agent calls start_checkout. That sends a PayPal Orders v2 create-order request with intent: AUTHORIZE for exactly the server-side quote, so the AI cannot change the price. With sandbox keys, the customer approves with the PayPal JS SDK buttons. In demo mode a labeled stand-in button approves the order against the mock. Either way the order is authorized, not charged.
  3. Clean. The crew checks off the cleaning list on the crew page and logs any extra hours.
  4. Settle (settlement agent). A second Gemini agent runs capture_payment → check_margin → send_crew_payout, or hold_payout_for_review if the margin rule fails. The crew is paid its share through a PayPal Payouts request as soon as the job is done and the numbers check out.
  5. Complaint. If the customer reports a problem, the booking agent picks a refund-policy category and calls propose_refund. A person approves or rejects it in the ops dashboard. Only then is the PayPal refund request sent.
  6. Audit. Every money action, including refused and deduplicated ones, is appended to a ledger with its PayPal id and PayPal-Request-Id, and the ops dashboard shows it live.

The guardrails (in code, not in the prompt)

All money goes through one module, the only code that talks to PayPal (real client or mock). The model's tool calls pass through it, so a wrong or adversarial call is refused and logged as blocked, never executed.

  • Authorize, then capture. Capture is refused until the crew marks the job done with a complete checklist.
  • Margin check before any payout. The crew gets 70% of list (+ $28/hour for logged extra hours). After estimated PayPal fees (3.49% + $0.49 on the capture, $0.25 per payout), Wrenly must keep at least max($15, 10% of list).
    • Example: the $179 clean gives the crew $125.30, fees of about $6.99 and Wrenly $46.71, against a $17.90 floor. The payout goes out automatically.
    • A job where the crew ran long fails the check, and the payout is held for a person.
  • Caps. Orders ≤ $600, automatic payouts ≤ $450, refunds ≤ what is left on the capture (pending proposals count).
  • Human approval for refunds. The agent can only propose. Approval is a separate endpoint that refuses the decider agent.
  • Idempotency, two layers.
    • Every call carries a deterministic PayPal-Request-Id (wrenly-<job>-order|authorize|capture, wrenly-<approval>-refund), and each payout carries a deterministic sender_batch_id.
    • Before calling PayPal, the money module checks the ledger for an earlier success under that id. A retry is recorded as deduped and never reaches the PayPal client.
    • If the process dies mid-call, the retry reuses the same id. PayPal's documentation says a repeated PayPal-Request-Id returns the original result instead of charging twice. The mock simulates that replay, but it has not yet been checked against PayPal itself.
  • Append-only ledger. SQLite triggers reject UPDATE and DELETE on the ledger table, and every row is mirrored to a JSONL file.
  • Settlement always finishes. If the AI stops early or errors, a deterministic rule completes capture → margin check → payout or hold, so a job never sits half-settled.

How we built it

Landon King, 17, built it by directing King AI, an autonomous AI agent built to do anything a person can do on a computer. King AI wrote most of the code, tests and docs. Landon directed and reviewed the work. The entry is from KING HOLDINGS VENTURES LLC.

  • Backend: Python, FastAPI and Uvicorn. httpx makes the PayPal and Gemini REST calls. SQLite holds jobs, approvals, chats, webhook events and the append-only ledger.
  • PayPal (written to PayPal's REST documentation):
    • OAuth 2.0
    • Orders v2: create with intent: AUTHORIZE, get, authorize
    • Payments v2: capture, void, refund
    • Payouts v1: create a batch, get its status
    • Webhooks: verify-webhook-signature
    • The JS SDK paypal.Buttons with intent=authorize on the booking page, used when sandbox keys are set
  • Demo mode (mock PayPal): with no PayPal keys, an in-memory mock answers every one of those endpoints with PayPal's documented shapes. Those include ids, status values, links arrays, the payout batch_header, error bodies with debug_id, and PayPal-Request-Id replay. Anyone can click through the whole flow in seconds, and no request leaves the machine. GET /api/mode reports "paypal_mode": "mock" and "simulated_responses": true.
  • AI: Google Gemini through the REST generateContent API with function declarations: a booking agent with 4 tools and a settlement agent with 5. The default model is gemini-3.5-flash. When a model is overloaded or rate-limited, the app falls back to gemini-2.5-flash, then gemini-3.1-flash-lite.
  • Offline mode: with no Gemini key, a deterministic rules engine drives the same tools, so the app, the demo and the tests run without any AI key. If Gemini errors mid-conversation, that turn falls back to the rules, and the UI shows which engine answered.
  • Frontend: plain HTML, CSS and JavaScript: a booking page with the chat and the PayPal button area, a crew checklist page, and an ops dashboard for approvals and the ledger.
  • Tests: a pytest suite covering quote extraction, the price table, the margin rule, idempotency, every guardrail, the append-only ledger, refunds, voids, webhooks and the full flow through the HTTP API (mock PayPal, offline AI).

Challenges we ran into

  • Building against the documentation alone. We had no PayPal sandbox run, so we built the mock directly from PayPal's documented request and response shapes. We also kept the real client and the mock behind one interface, so turning on sandbox mode changes keys, not code. Checking the documented behavior against the live sandbox is still open.
  • Making "never pay twice" true across crashes, not just double clicks. A ledger check alone fails if the process dies after PayPal accepted a call but before we wrote the row. So the ledger check is paired with deterministic PayPal-Request-Id and sender_batch_id values, which PayPal's documentation says it deduplicates.
  • Keeping the AI out of the money decisions without making it useless. The agent chooses which tool to call and explains itself to the customer. But the price comes from the server-side quote, the payout amount comes from the margin rule, and refunds need a person. Getting that split right took several rounds.
  • Model availability. Gemini models sometimes return rate-limit or overload errors. The client falls back through a list of models and, as a last resort, to the offline rules, without breaking the conversation.
  • Authorize-then-capture timing. Charging only after the clean meant modelling the job state (authorized, done, captured, paid or held) carefully, so that a capture, payout or refund in the wrong state is refused.

Accomplishments that we're proud of

  • A complete two-sided money loop on PayPal's API shapes: customer checkout, crew payout and refund, run by AI agents and guarded by code.
  • Guardrails that hold even against the agent itself. The tests try to approve a refund as the agent, pay before capture, capture before the clean and exceed the caps. Each attempt is refused and logged.
  • Idempotency at two layers, visible in the dashboard. Pressing "Retry crew payout" on a paid job shows up as deduped, and no second payout request is made.
  • Anyone can run it with one command (./run.sh) and no keys at all, with the demo mode shown on every page. The same code is written to call the PayPal sandbox once sandbox keys are added. That path has not been run against PayPal yet.

What we learned

  • In agentic commerce the safety lives in the tools, not the prompt. Give the model narrow tools and let the money layer refuse anything outside the rules.
  • PayPal's authorize-then-capture flow and its PayPal-Request-Id header map almost exactly onto what a marketplace needs: hold the customer's money until the work is done, and make every retry safe.
  • Paying the supply side (the crew) fast and fairly matters as much to a marketplace as taking the customer's payment. With Payouts, that is one API call once the margin is known.

What's next for Wrenly Agent Checkout

  • Run the full flow against the PayPal sandbox (scripts/sandbox_e2e.py) and publish the run's ids.
  • Go live only after that, and once Wrenly Home books its first customers and signs its first crews in Peoria. Its first real quote requests have come in, and none of its 16 cleaner applicants is signed yet. Going live needs a live PayPal business account approved for Payouts, crew onboarding and a 1099 tax flow.
  • Use the actual paypal_fee from each capture in the margin rule instead of the estimate.
  • Replace the shared ops token with real accounts, and add dispute handling (PayPal Disputes API) through the same propose-then-approve pattern.
  • Offer it to other small service marketplaces (lawn care, handyman, pet care) that collect from customers and pay local crews.

Code: https://github.com/landonking0808/wrenly-agent-checkout · Video: https://youtu.be/jNpJko53q3Y

Built With

  • agentic-commerce
  • ai-agents
  • css3
  • fastapi
  • function-calling
  • gemini-api
  • google-gemini
  • html5
  • httpx
  • javascript
  • paypal
  • paypal-js-sdk
  • paypal-orders-api
  • paypal-payments-api
  • paypal-payouts
  • paypal-webhooks
  • playwright
  • pytest
  • python
  • rest-api
  • sqlite
  • uvicorn
Share this project:

Updates

Submission history