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.
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
- 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.
- Agent builds a cart. A shopping agent picks a merchant from the demo catalog and builds an itemized cart.
- PayPal authorize. OryvaPay creates a PayPal order with
intent: AUTHORIZEand every line item. The buyer approves in the PayPal popup and OryvaPay authorizes the order. Funds are held. - Verify. OryvaPay reads the order back from PayPal and runs rule checks plus an AI review against the approved constraints.
- Capture or void. PASS captures. BLOCK voids the authorization and releases the money. In Approval mode the buyer decides.
- 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 API | Used for |
|---|---|
POST /v1/oauth2/token | Client credentials OAuth |
POST /v2/checkout/orders with intent: AUTHORIZE | Itemized 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}/authorize | Hold the funds after buyer approval |
GET /v2/checkout/orders/{id} | Read what the merchant actually charged |
POST /v2/payments/authorizations/{id}/capture | Take the money when the order matches intent |
POST /v2/payments/authorizations/{id}/void | Release 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:
| Check | id | Fails when |
|---|---|---|
| Quantity | quantity | The charged quantity differs from the request |
| Budget | budget | The total is over the buyer's max total |
| Merchant | merchant | The merchant is not allowed or not verified |
| Color and size | attributes | Attributes differ from the request |
Sneaky charges detector
| Category | id | Fails when |
|---|---|---|
| Add ons and warranties | addons | An extra line such as a protection plan was added without permission |
| Surprise subscriptions | recurring | A recurring item appears and subscriptions are not allowed |
| Hidden fees | service_fees | A service or handling fee appears and fees are not allowed |
| Price change after approval | price_change | A unit price differs from what the agent quoted |
| Delivery terms | delivery | The 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
| Mode | Behavior | Agent quoted $74.99, PayPal order says $79.99 |
|---|---|---|
| Strict | Any mismatch blocks the payment. | BLOCK, authorization voided |
| Flexible | Price differences inside the tolerance (default $5.00) are allowed. Everything else blocks. | PASS, $79.99 captured |
| Approval | Problems 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
/api/configRuntime status: whether PayPal and AI are live, the PayPal client ID for the JS SDK, scenarios and modes.
{ "paypalLive": true, "aiLive": true, "paypalEnv": "sandbox",
"paypalClientId": "...", "aiModel": "openai/gpt-oss-120b",
"scenarios": {...}, "modes": {...}, "demoVideo": null }/api/intentTurn plain words into constraints.
{ "request": "Black running shoes, size 10, under $80, by Friday" }{ "constraints": { "product": { "category": "running shoes",
"attributes": { "color": "black", "size": "10" } }, "quantity": 1,
"maxBudget": 80, "allowedMerchants": [], "deliveryDeadline": "...",
"recurringAllowed": false, "extrasAllowed": false,
"serviceFeesAllowed": false, "parsedBy": "ai" } }/api/recordsCreate 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": "...", "constraints": {...}, "mode": "strict",
"tolerance": 5, "scenario": "warranty" }{ "id": "IR-20261112-23E2BF", "status": "OPEN", "attempts": [ { "n": 1,
"cart": { "items": [ { "sku": "SL-AERO-BLK-10",
"url": "/store/stridelab/aero-runner-black", ... } ] } } ] }/api/records/{id}/orderCreate the PayPal order (intent AUTHORIZE) for the latest attempt.
{ "id": "6TF029115U822433D", "approveUrl": "https://www.sandbox.paypal.com/checkoutnow?token=..." }/api/records/{id}/authorizeAfter buyer approval, authorize the order. Funds are held.
{ "authorizationId": "1AL2357750936571K", "status": "CREATED",
"amount": { "currency_code": "USD", "value": "87.98" } }/api/records/{id}/verifyRead the order from PayPal, verify it, then capture, void or wait for the buyer. Returns the full record with the chain status.
{ "id": "...", "status": "BLOCKED", "attempts": [ { "verification": {
"verdict": "BLOCK", "failedChecks": ["addons", "budget"], ... } } ],
"chain": { "valid": true, "events": 7 } }/api/records/{id}/decisionApproval mode only. Capture or void based on the buyer.
{ "approve": true }Full record/api/records/{id}/fixAfter a block, the agent rebuilds the cart without the flagged items as a new attempt.
Full record with a new attempt/api/recordsList records.
/api/records/{id}One record with chain verification.
/api/records/{id}/downloadThe record as a JSON file.
/api/catalogDemo merchants and products.
/api/healthHealth check.
{ "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.
- Open Verify a purchase, keep the example request, pick a scenario and a mode, and approve the rules.
- 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 4658with 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. - 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.
- 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)| Variable | Required | Notes |
|---|---|---|
PAYPAL_CLIENT_ID | for real PayPal | Sandbox REST app client ID |
PAYPAL_CLIENT_SECRET | for real PayPal | Sandbox REST app secret. Never commit it. |
PAYPAL_ENV | no | sandbox (default) or live |
AI_API_KEY | for real AI | Key for any OpenAI compatible API |
AI_BASE_URL | no | Default https://api.x.ai/v1. The demo uses https://api.groq.com/openai/v1 |
AI_MODEL | no | Default grok-4. The demo uses openai/gpt-oss-120b |
AI_TIMEOUT_MS | no | Default 45000 |
DEMO_VIDEO_URL | no | YouTube link for the landing page demo player |
PORT | no | Default 3000 |
DATA_DIR | no | Where records.json is stored, default data |
DEMO_MODE | no | true 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=livehas 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