OryvaPay docs

OryvaPay documentation

Verify intent before payment, on PayPal.

Overview

OryvaPay is an AI verification layer that checks an agent's order against the buyer's intent before PayPal captures the money.

A shopping agent builds a cart. The buyer approves a PayPal payment with intent: AUTHORIZE, so the funds are held, not taken. OryvaPay then reads the order PayPal actually received, compares every line with what the buyer asked for, and tells PayPal to capture the payment or void the authorization. Every step is written to a tamper evident Intent Record that both buyer and merchant can use as evidence.

HoldPayPal Orders v2 authorizes the payment. Nothing is captured yet.
VerifyRule checks for the numbers, AI review for the meaning.
Capture or voidPayPal Payments v2 takes the money only when the order matches.

Live demo: Verify a purchase. Source: github.com/rwemmie/oryvapay.

The problem

AI agents now shop for people. The payment can be perfectly valid while the purchase is not what the buyer asked for.

When a buyer approves a payment, the authorization proves consent to pay. It does not prove that the order the merchant charged matches what the buyer wanted. Things slip into an agent built checkout:

  • Add on warranties and protection plans nobody asked for
  • Surprise subscriptions and repeat billing
  • Hidden service or handling fees
  • A price change after the buyer approved the quote
  • Delivery terms that miss the buyer's deadline
  • The wrong item, color, size or quantity

Then the hard question: who pays when an agent buys the wrong thing? Dispute rules were written for humans pressing a buy button.

"Seller Protection is only available on eligible unauthorized transactions and items not received claims."PayPal Agent Ready
"Who pays when an AI agent buys the wrong thing?"NMI, payments industry blog
"Who Said 'Buy'? Agentic Payments and the Evidence Problem."Lowenstein Sandler, October 2026

Google's AP2 specification defines mandates and receipts that can serve as dispute evidence, but leaves detailed dispute resolution, evidence retrieval and retention outside its scope. OryvaPay works on the step before that: checking the order while the money is still only held.

How it works

  1. Buyer intent. The buyer writes what they want in plain words. AI turns it into constraints: product, color and size, quantity, max total, merchant, delivery deadline, and whether subscriptions, add ons and service fees are allowed. The buyer reviews and approves them.
  2. Agent builds a cart. A shopping agent picks a merchant from the demo catalog and builds an itemized cart.
  3. PayPal authorize. OryvaPay creates a PayPal order with intent: AUTHORIZE and every line item. The buyer approves in the PayPal popup and OryvaPay authorizes the order. Funds are held.
  4. Verify. OryvaPay reads the order back from PayPal and runs rule checks plus an AI review against the approved constraints.
  5. Capture or void. PASS captures. BLOCK voids the authorization and releases the money. In Approval mode the buyer decides.
  6. Fix order. After a block, the agent rebuilds the cart without what was flagged. That is a new PayPal order and a new approval, recorded as attempt 2.

Built on PayPal

PayPal is the payment rail for the whole flow. OryvaPay never touches card data; the buyer approves in PayPal.

PayPal APIUsed for
POST /v1/oauth2/tokenClient credentials OAuth
POST /v2/checkout/orders with intent: AUTHORIZEItemized order for the agent's cart, including each item's product URL and image
PayPal JavaScript SDK Buttons (intent=authorize)Buyer approval popup
POST /v2/checkout/orders/{id}/authorizeHold the funds after buyer approval
GET /v2/checkout/orders/{id}Read what the merchant actually charged
POST /v2/payments/authorizations/{id}/captureTake the money when the order matches intent
POST /v2/payments/authorizations/{id}/voidRelease the money when it does not

PayPal is a trademark of PayPal, Inc. OryvaPay is an independent hackathon project and runs in the PayPal sandbox only.

Verification engine

Verification uses the order PayPal returns, not what the agent claims it bought. Two kinds of checks run on every order.

Rule checks

Deterministic checks for anything numeric or exact:

CheckidFails when
QuantityquantityThe charged quantity differs from the request
BudgetbudgetThe total is over the buyer's max total
MerchantmerchantThe merchant is not allowed or not verified
Color and sizeattributesAttributes differ from the request

Sneaky charges detector

CategoryidFails when
Add ons and warrantiesaddonsAn extra line such as a protection plan was added without permission
Surprise subscriptionsrecurringA recurring item appears and subscriptions are not allowed
Hidden feesservice_feesA service or handling fee appears and fees are not allowed
Price change after approvalprice_changeA unit price differs from what the agent quoted
Delivery termsdeliveryThe delivery date misses the buyer's deadline

AI check

The AI reviews meaning: does the item charged actually match the product the buyer described (product_match)? It also writes the plain words explanation shown to the buyer. The live demo uses openai/gpt-oss-120b on Groq through an OpenAI compatible API. If no AI key is set, a local heuristic runs instead and the UI says so.

Modes

ModeBehaviorAgent quoted $74.99, PayPal order says $79.99
StrictAny mismatch blocks the payment.BLOCK, authorization voided
FlexiblePrice differences inside the tolerance (default $5.00) are allowed. Everything else blocks.PASS, $79.99 captured
ApprovalProblems are flagged and the buyer decides before any capture.NEEDS_APPROVAL, buyer approves or rejects

Intent Records and the hash chain

Each purchase gets an Intent Record such as IR-20261112-23E2BF. It stores the buyer's words, the approved constraints, every cart attempt with product links, PayPal order, authorization and capture IDs, and the verdict with every check.

Every event is chained with SHA-256:

hash = sha256(prevHash + canonicalJSON({ seq, type, at, data }))

Event types: request_received, constraints_approved, agent_cart_built, paypal_order_created, buyer_approved_authorization, verification_result, payment_captured, authorization_voided, buyer_decision, agent_cart_rebuilt. Changing any past event breaks the chain, and the record page shows where. Each record has a buyer view and a merchant view.

Evidence Pack

Every record can be exported as a printable Evidence Pack at /record/{id}/evidence (save as PDF from the browser) or as JSON at /api/records/{id}/download (format oryvapay.intent-record.v1). It includes the request, constraints, each line charged with its product page, the PayPal IDs, the verdict and the hash chain, so a merchant can show the charged order matched the buyer's own approved intent.

API reference

GET/api/config

Runtime status: whether PayPal and AI are live, the PayPal client ID for the JS SDK, scenarios and modes.

Response
{ "paypalLive": true, "aiLive": true, "paypalEnv": "sandbox",
  "paypalClientId": "...", "aiModel": "openai/gpt-oss-120b",
  "scenarios": {...}, "modes": {...}, "demoVideo": null }
POST/api/intent

Turn plain words into constraints.

Request
{ "request": "Black running shoes, size 10, under $80, by Friday" }
Response
{ "constraints": { "product": { "category": "running shoes",
  "attributes": { "color": "black", "size": "10" } }, "quantity": 1,
  "maxBudget": 80, "allowedMerchants": [], "deliveryDeadline": "...",
  "recurringAllowed": false, "extrasAllowed": false,
  "serviceFeesAllowed": false, "parsedBy": "ai" } }
POST/api/records

Create an Intent Record and let the agent build the first cart. mode is strict, flexible or approval. scenario picks the demo merchant behavior (for example warranty, clean, price_change).

Request
{ "request": "...", "constraints": {...}, "mode": "strict",
  "tolerance": 5, "scenario": "warranty" }
Response
{ "id": "IR-20261112-23E2BF", "status": "OPEN", "attempts": [ { "n": 1,
  "cart": { "items": [ { "sku": "SL-AERO-BLK-10",
  "url": "/store/stridelab/aero-runner-black", ... } ] } } ] }
POST/api/records/{id}/order

Create the PayPal order (intent AUTHORIZE) for the latest attempt.

Response
{ "id": "6TF029115U822433D", "approveUrl": "https://www.sandbox.paypal.com/checkoutnow?token=..." }
POST/api/records/{id}/authorize

After buyer approval, authorize the order. Funds are held.

Response
{ "authorizationId": "1AL2357750936571K", "status": "CREATED",
  "amount": { "currency_code": "USD", "value": "87.98" } }
POST/api/records/{id}/verify

Read the order from PayPal, verify it, then capture, void or wait for the buyer. Returns the full record with the chain status.

Response
{ "id": "...", "status": "BLOCKED", "attempts": [ { "verification": {
  "verdict": "BLOCK", "failedChecks": ["addons", "budget"], ... } } ],
  "chain": { "valid": true, "events": 7 } }
POST/api/records/{id}/decision

Approval mode only. Capture or void based on the buyer.

Request
{ "approve": true }
Response
Full record
POST/api/records/{id}/fix

After a block, the agent rebuilds the cart without the flagged items as a new attempt.

Response
Full record with a new attempt
GET/api/records

List records.

GET/api/records/{id}

One record with chain verification.

GET/api/records/{id}/download

The record as a JSON file.

GET/api/catalog

Demo merchants and products.

GET/api/health

Health check.

Response
{ "ok": true }

Pages: /app, /records, /record/{id}, /record/{id}/evidence, /store/{merchant}/{slug} (demo store product pages).

Testing in the PayPal sandbox

Everything runs in the PayPal sandbox. No real money moves.

  1. Open Verify a purchase, keep the example request, pick a scenario and a mode, and approve the rules.
  2. Click the PayPal button. In the PayPal window, log in with a PayPal sandbox buyer account, or choose Pay with Debit or Credit Card and use a PayPal sandbox test card such as 4032 0393 1798 4658 with any future expiry, any 3 digit CVV and a US billing address. PayPal may ask you to create a sandbox account with a password; any test email and password work.
  3. After approval, OryvaPay authorizes, verifies and captures or voids automatically. The Protection Plan scenario in Strict mode shows a block; then use Fix order to see the clean attempt captured.
  4. If the popup is blocked, use "Open PayPal in this tab".

Self hosting

git clone https://github.com/rwemmie/oryvapay
cd oryvapay && npm install
npm start                # http://localhost:3000
npm test                 # end to end test (BASE=http://localhost:3000)
VariableRequiredNotes
PAYPAL_CLIENT_IDfor real PayPalSandbox REST app client ID
PAYPAL_CLIENT_SECRETfor real PayPalSandbox REST app secret. Never commit it.
PAYPAL_ENVnosandbox (default) or live
AI_API_KEYfor real AIKey for any OpenAI compatible API
AI_BASE_URLnoDefault https://api.x.ai/v1. The demo uses https://api.groq.com/openai/v1
AI_MODELnoDefault grok-4. The demo uses openai/gpt-oss-120b
AI_TIMEOUT_MSnoDefault 45000
DEMO_VIDEO_URLnoYouTube link for the landing page demo player
PORTnoDefault 3000
DATA_DIRnoWhere records.json is stored, default data
DEMO_MODEnotrue forces demo mode

Without PayPal keys OryvaPay runs a simulated PayPal; without an AI key it uses a local heuristic. The UI always says which is active.

Limitations

  • PayPal sandbox only. No real money, and PAYPAL_ENV=live has not been tested.
  • The hosted demo runs on a free tier with no disk, so Intent Records reset on every deploy or restart. Download the JSON or Evidence Pack to keep one.
  • Records are stored in a JSON file, not a database.
  • The shopping agent and the StrideLab and PeakGear stores are demo simulations with a small catalog. Product images are AI generated renders of unbranded products.
  • Card payments go through PayPal's own approval window; direct card processing is not enabled on the sandbox merchant.

Roadmap

  • Persistent storage for Intent Records
  • Accept agent mandates and receipts in the AP2 format as intent input
  • Merchant webhooks when a payment is blocked
  • More sneaky charge categories and per buyer rules
  • Live PayPal environment after review