Why I built PayGuard AI
A merchant may notice a policy warning, a sudden sales spike, or a dispute only after the records have become difficult to assemble. PayGuard AI is a merchant defense buffer layer: it helps the merchant identify evidence gaps early and prepare evidence, while keeping every decision with the merchant and the relevant payment provider.
Real Sandbox call, 2026-10-06 17:44 UTC (2026-10-07 in UTC+8): OAuth client-credentials, then POST /v2/invoicing/invoices created one unsent USD 10.00 draft, id INV2-2MTA-PRPQ-4CAJ-W5UW, status DRAFT. The full breakdown of live and fixture calls is under "What really calls PayPal" below.
What it does
- Before an invoice draft — AUP preflight. Deterministic checks compare a synthetic product description with pinned public PayPal US AUP references. The merchant can edit, cancel, or acknowledge the warning. After that review, the PayPal Sandbox path creates one unsent invoice draft.
- During fulfillment — sales-velocity readiness. A backend comparison flags a synthetic increase against a declared local baseline. AG Grid Community (v36.2.0) renders the transaction stream with per-column sorting and filtering, resizable columns, a quick-filter search box, a custom cell renderer on the order id, value formatters for amount and time, stable row ids, animated rows, loading and empty-state overlays, and the Quartz theme; clicking a row highlights it and shows that order's amount, currency and capture time. A second AG Grid table in the Merchant Evidence Buffer uses sort, filter, quick filter, value formatters, row ids, a no-rows overlay and the Quartz theme.
- After a dispute — evidence preparation. A case-specific checklist identifies requested seller items. PayGuard builds a pseudonymized, integrity-checked Internal Review ZIP and calls Gemini for a bounded evidence brief: Gemini selects each sentence from a closed, pre-approved inventory for that stage, and fixed code validates the choice. The brief points to what is still missing. A person reviews the package, and nothing is sent automatically.
How I built it
The working stack is PayPal Sandbox REST APIs and OAuth, FastAPI, deterministic US policy-reference rules, React, Tailwind, AG Grid Community, and Gemini on Vertex AI (gemini-3.8-flash) for the evidence brief. Rules run before AI, and fixed code keeps every decision: the AUP findings, the velocity flag, the dispute checklist, the stage, the pinned citation list and every authority flag (advisory only, human review required, no external action, no workflow transition). Gemini selects the brief's sentences from the stage's closed inventory, and fixed code validates them against a closed schema of four fields: one summary string, 1 to 5 evidence_points and 1 to 5 missing_evidence strings, each chosen from a server-supplied enum, and citation_ids that must equal the server's list in order; the backend rejects anything else. The AI path receives fixed synthetic facts and pinned citation identities, produces advisory text, and has no tool authority to change a rule, send an invoice, submit evidence, or decide a dispute. The public repository includes an MIT license, a runnable local setup path, tests, a visual architecture map, and the source for all three stages.
Challenge and learning
The hardest design choice was joining three moments in a merchant's workflow without implying that an AI system can replace provider review. I separated deterministic signals, evidence inspection, bounded selection of approved wording, and human action. The result is a coherent merchant-side workflow rather than an automated verdict engine.
Scope and access
This is a US Sandbox demo with synthetic data. It gives the merchant local preparation signals, not a PayPal risk score; PayPal decides policy and dispute matters, and a bank or card issuer decides an external dispute. Judges run the build from the public repository and quickstart locally (steps under Testing instructions). The 2:02 demonstration shows the merchant workflow.
Turning on PayPal and Gemini (for judges running it)
The console and all three stages run in fixture mode with no keys; the Gemini evidence brief and the live PayPal calls switch on when you set these in the shell that starts the API (./tools/run.sh api):
- PayPal Sandbox:
PAYGUARD_PAYPAL_OUTBOUND=enabled, plusPAYPAL_CLIENT_IDandPAYPAL_CLIENT_SECRETfrom your own PayPal Developer Sandbox app. Without them, connecting returnsprovider_not_configured. - Gemini brief (one per stage):
PAYGUARD_GEMINI_OUTBOUND=enabledandPAYGUARD_VERTEX_PROJECT=<your Google Cloud project id>, signed in with Google Application Default Credentials (gcloud auth application-default login). It callsgemini-3.8-flashon Vertex AI (locationglobal), so the Google Cloud project bills for it. LeaveGEMINI_API_KEY,GOOGLE_API_KEYandGOOGLE_GENAI_USE_VERTEXAIunset; the worker refuses to run if they are present. Set up the separate Gemini environment first (README, Gemini profile section).
What really calls PayPal (and what is fixture)
PayGuard runs against the PayPal Sandbox REST API (api-m.sandbox.paypal.com) through one reviewed gateway in the backend. The table shows what runs against the live Sandbox and what runs on fixtures.
| Call | Endpoint | Real or fixture |
|---|---|---|
| Merchant connection | POST /v1/oauth2/token (client-credentials) |
Real Sandbox call with PAYGUARD_PAYPAL_OUTBOUND=enabled and your Sandbox app credentials; without them the call answers 503 provider_disabled and nothing is simulated |
| Unsent invoice draft after the AUP preflight | POST /v2/invoicing/invoices |
Real Sandbox call once connected, status DRAFT, never sent; without a connection the console shows the AUP warning and stops before the draft |
| Webhook signature check | POST /v1/notifications/verify-webhook-signature |
Allow-listed Sandbox endpoint; exercised in tests with hand-written mock responses; needs PAYPAL_WEBHOOK_ID; no UI path |
| Dispute list and detail | GET /v1/customer/disputes, GET /v1/customer/disputes/{id} |
Implemented for the Sandbox and covered by mocked-transport tests; connect your own Sandbox app to run it live. Without a connection a synthetic fixture in the same JSON shape is used, labelled Fixture data in the UI |
| Evidence request | POST /v1/customer/disputes/{id}/provide-evidence |
Preview by default; sending needs a dispute id the console has loaded, a recorded local approval, and PAYGUARD_PAYPAL_DISPUTE_SUBMIT=enabled; covered by mocked-transport tests |
| Sales-velocity stream, evidence ZIP | local synthetic data | Fixture by design |
An authentic end-to-end run on 2026-10-06 17:44 UTC (2026-10-07 in UTC+8) used a US Business Sandbox account to connect through OAuth and create one unsent USD 10.00 invoice draft, Sandbox id INV2-2MTA-PRPQ-4CAJ-W5UW, status DRAFT. Credentials never enter the repository, logs, or evidence files; judges can run the same path with their own Sandbox credentials, or the console in fixture mode.
Testing instructions (judges)
The console runs end to end in fixture mode with no PayPal account. It needs Python 3.13 and uv, Node 20.19+ or 22.12+ for the frontend, and Python 3.12 for the Gemini profile (Windows: WSL 2):
git clone https://github.com/jiarong0423/payguard-ai
cd payguard-ai
python3.13 -m venv .venv
uv pip install --python .venv/bin/python3 -r requirements.lock
./tools/run.sh test # expected: Ran 436 tests, OK (2 skipped)
./tools/run.sh api # API on http://127.0.0.1:8000
Second terminal:
cd frontend && npm ci --ignore-scripts --no-audit --no-fund && npm run dev # open http://127.0.0.1:5173
What to look for, in order: (1) type the synthetic product description → the AUP preflight warning appears before any draft; acknowledge it; (2) the AG Grid transaction stream → sort and filter the synthetic spike against the declared baseline; (3) the dispute checklist → build the Internal Review ZIP and open its manifest (pseudonymized, integrity-checked). AI step: press Generate AI brief in the console. It calls gemini-3.8-flash on Vertex AI and returns the evidence brief; ./tools/run.sh gemini-check verifies the Gemini profile before that first call. Without the Gemini settings the button shows "Gemini profile not configured"; with them it returns the brief. A live call is on record: 2026-10-06 10:48 UTC, dispute stage, completed in 3.5 s.
Update log
- 2026-10-29: Added the testing walkthrough and the real-call versus fixture table, with the authenticated Sandbox run of 2026-10-06 17:44 UTC (
INV2-2MTA-PRPQ-4CAJ-W5UW, unsentDRAFT). The public repository also reads Sandbox disputes (list and detail; covered by mocked tests, not yet run against a live Sandbox dispute), prepares the provide-evidence request as a preview behind an approval and a flag, and loads each stage's fixture automatically.

Log in or sign up for Devpost to join the conversation.