verification
use it
# add to stunt.yaml
services:
paypal:
adapter: embedded:paypal-style
# then
$ stunt upThe embedded: source extracts this adapter from the binary — no git clone, fully offline.
how to mock the PayPal Orders API locally
Install stunt, paste the service block above into stunt.yaml, run stunt up — then point your client at localhost instead of PayPal Orders API. It works offline, keeps state across requests and restarts, and every response is synthetic: the route shapes, status codes, pagination, and webhook delivery follow the real PayPal Orders API v2 surface — the data is fake, the behavior is real.
backing stores
orderscapturesrefundsaccess_tokens
api surface (17)
generated from the adapter manifest — exactly what boots, nothing more · covers 64% of the real API's 17 routes (derived from spec paypal-rest-api-specifications checkout_orders_v2 + payments_payment_v2 @ 2.32, 2.12)
17 routes
POST/v1/oauth2/token
POST/v2/checkout/orders
GET/v2/checkout/orders/{id}
POST/v2/checkout/orders/{id}/approve
POST/v2/checkout/orders/{id}/capture
POST/v2/checkout/orders/{id}/authorize
GET/v2/payments/authorizations/{id}
POST/v2/payments/authorizations/{id}/reauthorize
POST/v2/payments/authorizations/{id}/void
POST/v2/payments/authorizations/{id}/capture
GET/v2/payments/captures/{id}
POST/v2/payments/captures/{capture_id}/refund
GET/v2/payments/refunds/{id}
POST/v1/notifications/webhooks
GET/v1/notifications/webhooks
DELETE/v1/notifications/webhooks/{id}
POST/v1/notifications/verify-webhook-signature
VM-verified behaviors (37)
- ✓client_credentials over Basic auth mints a distinct Bearer with PayPal's token envelope
- ✓the minted bearer authorizes the Orders API
- ✓minting without HTTP Basic client auth is a 401 AUTHENTICATION_FAILURE envelope
- ✓a bearer that was never minted is rejected with 401
- ✓the advertised 9-hour expires_in is enforced — the token dies past it
- ✓any Basic credentials mint a token — no client registry (deviation, asserted as-is)
- ✓create assigns ORDERID-N, stamps create_time, and echoes intent + purchase_units
- ✓a CREATED order carries self/approve/capture links
- ✓get round-trips the order; unknown ids are 404 INVALID_RESOURCE_ID
- ✓orders are accepted without purchase_units — no create-time validation (deviation, asserted as-is)
- ✓capture or authorize before payer approval is 422 ORDER_NOT_APPROVED
- ✓simulate_fail approval keeps the order CREATED with 422 PAYER_ACTION_REQUIRED
- ✓approval flips CREATED -> APPROVED and swaps in capture/authorize links
- ✓re-approval is idempotent
- ✓capture completes the order and embeds a COMPLETED capture in purchase_units
- ✓the capture resource is readable via the payments API
- ✓re-capturing the order is 422 ORDER_ALREADY_CAPTURED
- ✓authorize completes the order and lands a CREATED authorization
- ✓re-authorizing the order is 422 ORDER_ALREADY_AUTHORIZED
- ✓GET shows the auth with a 3-day honor window from the virtual clock
- ✓reauthorize refreshes the honor window and syncs the embedded order copy
- ✓void is 204, moves the auth to VOIDED, and syncs the order
- ✓a voided authorization rejects every action with 422
- ✓malformed, mismatched-currency, and over-authorization amounts are 400s with real issue codes
- ✓a partial capture keeps final_capture false and moves the auth to CAPTURED
- ✓the capture is appended to the order and links up to the authorization
- ✓a captured authorization is terminal
- ✓refunds are created PENDING and reserve the unrefunded balance
- ✓the 3-second settle window flips PENDING -> COMPLETED on read
- ✓the settled refund is reported as refunded_amount on the capture
- ✓a FAILED refund frees the balance again
- ✓PayPal-Request-Id replays return the same order
- ✓a replay returns the order's current state, not a cached snapshot (deviation, asserted as-is)
- ✓requests without the header (or with a new one) are not deduplicated
- ✓webhook registration round-trips through the list
- ✓signature verification answers SUCCESS only for known webhook ids
- ✓deletion is 204, and repeats are 404
Not implemented (6)
- No PATCH order update (PATCH /v2/checkout/orders/{id})
- No payouts API (POST /v1/payments/payouts)
- No disputes API (/v1/customer/disputes)
- No subscriptions, billing plans, or catalog products APIs
- No webhook event-types list or simulate-event endpoints
- No order patch endpoint (PATCH /v2/checkout/orders/{id})
Differs from the real API (8)
- Payer approval endpoint /approve is simulator-only; real flow is browser rel=approve
- simulate_fail flags on approve and refund are stunt-only test hooks
- Webhook deliveries unsigned; real PayPal uses a certificate-based signature scheme
- verify-webhook-signature returns SUCCESS for any known webhook_id, no real check
- Refund derives terminal state on read after fixed ~3s (PENDING to COMPLETED/FAILED)
- Token endpoint validates only the Basic scheme — any credentials mint, grant_type ignored
- Captures always complete immediately; no PENDING/ON_HOLD capture states
- Idempotency replay re-renders the order's current state, not the cached original response
want more of the PayPal Orders API surface? adapters are YAML + Starlark — the authoring guide covers adding routes, fixtures, and state machines. Open an issue for anything big first, or just file a PR — merged routes ship in the next release.