Inspiration

Small online merchants often share one PayPal account between an owner, a manager and a part-time clerk. Refunds, new orders and dispute replies happen many times a day, often from a phone and often in the clerk's own language. The choice today is to give everyone full access (one mistake or one convincing scam message becomes a real loss) or to route everything through the owner, who becomes the bottleneck. Letting an AI agent call payment APIs makes this worse unless the agent is boxed in.

What it does

Staff ask in plain English or Arabic, for example "Refund $120 on capture 2GG279541U471931P, damaged in transit". An LLM plans the PayPal call through the PayPal Agent Toolkit, but it never executes anything itself. Every proposed tool call goes through a deterministic policy guard that either runs it, puts it in a manager's approval queue, or blocks it with a reason. Each decision is written to a tamper-evident, hash-chained audit log. Approved actions execute exactly once, and instructions hidden in customer messages cannot move a cent.

Default policy: refunds up to $50 run directly, refunds above $50 need a manager, refunds above $1,000 are blocked, a $200 daily refund cap forces approval, orders above $500 need a manager, and accepting a dispute always needs a manager.

How we built it

  • PayPal Agent Toolkit (@paypal/agent-toolkit 1.11.0) supplies the tools (create_order, get_order, pay_order, create_refund, get_refund, list_transactions, list_disputes, get_dispute, accept_dispute_claim), filtered through an explicit allowlist, always with sandbox: true.
  • Orders v2 and Payments v2 (captures and refunds) run in the PayPal sandbox with a PayPal-Request-Id per action so retries cannot double-charge or double-refund.
  • Transaction Search and Disputes power read-only questions for every role.
  • Webhooks: signature verification (CRC32 + SHA256withRSA, cert fetched only from *.paypal.com, verify-webhook-signature API as fallback), duplicate event ids ignored, every event audited.
  • AI: any OpenAI-compatible tool-calling LLM (OpenAI, a local Ollama model and others), up to 6 steps per turn, English and Arabic.
  • Policy as YAML data, first match wins, default deny, integer cents. A provenance check means amounts and ids for money-moving calls must come from the operator's own text or known PayPal objects, never from inside blocks.
  • Approval queue: approver role, no self-approval, 24 h expiry, atomic PENDING to EXECUTED claim.
  • Audit log: SQLite, append-only via DB triggers, SHA-256 hash chain, verify and JSON/CSV export.
  • Next.js UI with chat plan cards, approvals and audit pages, fully localized English and Arabic (RTL).
  • An offline scripted bilingual demo model so judges can try everything with no keys; 37 deterministic end-to-end eval scenarios.

Challenges we ran into

  • Making sure the model can be persuaded of anything and still cannot move money: the guard is deterministic code, not a prompt.
  • Exactly-once execution across retries and approvals.
  • Open-weight models send stringified or Python-literal arguments or write tool calls as text; PayGuard normalizes them with a strict non-eval parser and runs them through the same guard.
  • Arabic-Indic digits and RTL layout.

Accomplishments that we're proud of

Prompt-injected customer messages cannot trigger refunds, every decision is verifiable in the hash-chained log, and the whole product runs in one command with no API keys for judges.

What we learned

Agentic payments are only safe when the policy, approvals and audit live outside the model.

What's next

More PayPal APIs behind the same guard (subscriptions, payouts), per-merchant policy editor, and more languages.

Built With

  • next.js
  • ollama
  • openai-compatible-llm
  • paypal
  • paypal-agent-toolkit
  • paypal-orders-api
  • paypal-payments-api
  • paypal-webhooks
  • sqlite
  • typescript
  • yaml
  • zod
Share this project:

Updates

Submission history