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

  1. 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.
  2. 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.
  3. 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, plus PAYPAL_CLIENT_ID and PAYPAL_CLIENT_SECRET from your own PayPal Developer Sandbox app. Without them, connecting returns provider_not_configured.
  • Gemini brief (one per stage): PAYGUARD_GEMINI_OUTBOUND=enabled and PAYGUARD_VERTEX_PROJECT=<your Google Cloud project id>, signed in with Google Application Default Credentials (gcloud auth application-default login). It calls gemini-3.8-flash on Vertex AI (location global), so the Google Cloud project bills for it. Leave GEMINI_API_KEY, GOOGLE_API_KEY and GOOGLE_GENAI_USE_VERTEXAI unset; 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, unsent DRAFT). 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.

Built With

Share this project:

Updates

Submission history