Inspiration
Freelancers and small teams lose hours every week on billing admin — opening a dashboard, filling fields, copying a link, then going back to check whether the customer actually paid. Every invoice is a context switch away from the real work.
What it does
Type one sentence in plain English and get a real, payable PayPal sandbox payment link back.
Type: "Create a 25 dollar payment link for a logo sketch"
The copilot:
- sends your sentence, plus a strict JSON schema, to an LLM;
- receives back a typed action - {action: "create_order", params: {amount: 25, currency: "USD", description: "logo sketch"}};
- exchanges the PayPal sandbox client id + secret for an OAuth 2.0 token;
- calls POST /v2/checkout/orders and gets back a real PayPal order;
- renders the order id, status, amount and a working payment link in the chat.
Tap "Open payment link" and you land on the real PayPal sandbox checkout showing the exact amount. Nothing is mocked — every order id, status and dollar figure comes straight from PayPal's API response.
It also handles natural variations: "Bill Mike Smith 40 dollars for the landing page" and "Create a 15 dollar payment link for a blog post" both produce real orders. Ask "what invoices are outstanding?" and it lists what PayPal reports.
How I built it
- Browser: a single-page chat UI, no framework, no build step.
- Server: zero-dependency Node.js using only the built-in http and fetch. Nothing to npm install.
- AI layer: an OpenAI-compatible /chat/completions call with temperature 0 and response_format: json_object. The system prompt pins the exact action schema, so the same sentence always maps to the same typed action instead of free-form prose.
- PayPal layer: OAuth 2.0 client-credentials token, then POST /v2/checkout/orders (payment link).
- Secrets stay server-side: the PayPal secret and AI key live in .env and are never sent to the browser.
Design decisions
- The AI output is a typed contract, not text. The model decides what to do; the PayPal layer decides whether it is allowed. That boundary is what keeps an LLM honest when it touches real payments.
- Failures are never silent. PayPal's own error body is surfaced in the chat verbatim.
- Graceful degradation. Requests needing a formal invoice fall back to a real, payable payment link when the Invoicing scope is unavailable.
- Zero dependencies means the whole thing runs with node server.js on any machine.
Challenges I ran into
A fresh PayPal sandbox app does not automatically grant every API scope. The Invoicing endpoints returned 403 NOT_AUTHORIZED while Orders worked fine. I verified this with a probe against /v1/oauth2/token, /v2/checkout/orders and /v2/invoicing/invoices, then designed around it: the copilot prefers the Orders API for anything billable, and falls back to a real payment link if an invoice request hits a missing scope.
Accomplishments I'm proud of
- Both PayPal and AI are central, not decorative.
- Zero runtime dependencies.
- Credentials stay server-side.
- Every response is real — the UI surfaces PayPal's own id, status and pay link.
What I learned
Constraining an LLM to a strict JSON schema with temperature 0 makes agent behaviour predictable enough to drive a payment API safely. Separating intent (the model) from authorisation (the API) is what makes that trustworthy.
What's next
The same typed-action contract extends naturally to payment reminders, recurring billing, multi-currency invoices and refunds without touching the UI.
Built With
- ai
- css
- html
- javascript
- json
- llm
- node.js
- oauth-2.0
- paypal-rest-api
- paypal-sandbox