Payments

On this page

Mock payment gateways

Test checkout, refunds and payment webhooks without a payment provider's sandbox account and without real cards. Testlie runs Stripe-, Adyen- and PayPal-compatible mock APIs: point your app's SDK or HTTP client at a gateway's base URL and it answers in the provider's own wire format, with its field names, ids, error shapes and signed webhooks. Test cards decide the outcome, hosted pages stand in for checkout, 3-D Secure and PayPal approval, and every call and webhook is logged.

No money moves and no card data leaves Testlie: the gateways never contact Stripe, Adyen or PayPal. Only test card numbers are accepted; card numbers and security codes are masked before calls are stored. Never enter a real card on a gateway's hosted pages.

Testlie is not affiliated with, endorsed by or sponsored by Stripe, Adyen or PayPal. Their names are trademarks of their owners, used here only to say which API each mock is compatible with.

Create a gateway

Open Payment gateways in the workspace, pick a provider and create a gateway. Each gateway gets:

Base URL https://testlie.com/mock/<slug>, used in place of the provider's API host. The slug is 14 hexadecimal characters; copy the whole URL from the gateway's page, or read baseUrl from GET /api/mocks/:id (it is not the gateway's id)
Test credentials The provider's own kind: Stripe API keys and webhook secret, an Adyen API key, merchant account and HMAC key, PayPal client id and secret
Webhook URL Where your app receives the provider's events (optional)
Scenarios Force outcomes, outages, slow responses and webhook problems

Admins can rotate a credential at any time; the old value stops working at once.

Stripe

Replace https://api.stripe.com with the base URL. The official Node library only lets you change the host, port and protocol, so it can call Testlie's host directly; the gateway's secret key (sk_test_testlie_…) tells the gateways apart:

js
import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
  host: "testlie.com",
  protocol: "https",
});
const pi = await stripe.paymentIntents.create({
  amount: 2000,
  currency: "usd",
  payment_method: "pm_card_visa",
  confirm: true,
  automatic_payment_methods: { enabled: true, allow_redirects: "never" },
});
// pi.status === "succeeded"

(Add port too when Testlie runs on a port other than 443; the gateway page's snippet has the exact values.)

Python, Ruby, PHP and Go take the full base URL (stripe.api_base, Stripe.api_base, Stripe::$apiBase, stripe.BackendConfig.URL); the gateway page has a snippet for each.

Supported

Resource Endpoints
PaymentIntents create (with confirm), retrieve, update, list, confirm, capture (also partial), cancel
PaymentMethods create (type=card), retrieve; pm_card_* test ids and tok_* tokens
Charges retrieve, list (created by PaymentIntents)
Refunds create (full or partial, by payment_intent or charge), retrieve, update metadata, list
Customers create, retrieve, update, delete, list
Checkout Sessions create (mode=payment with price_data line items), retrieve, list, line items, expire, and a hosted checkout page
Events retrieve, list
Identity VerificationSessions create, retrieve (with expand[]=last_verification_report), list, update, cancel, redact, and a hosted capture page; see Stripe Identity
Identity VerificationReports retrieve, list

Requests are form-encoded as Stripe expects, Idempotency-Key replays the first response for 24 hours, and errors use Stripe's envelope: a declined card is a 402 with type: "card_error", code, decline_code and the payment_intent. Unknown ids answer 404 resource_missing.

Not supported yet: expand[] (except last_verification_report on Identity VerificationSessions), search, subscriptions, invoices, prices and products (use price_data), SetupIntents, Connect, Payment Links and Elements/Stripe.js confirmation in the browser (confirm server-side or use Checkout).

3-D Secure. Cards that require authentication return status: "requires_action" with a next_action.redirect_to_url pointing at a hosted challenge page. Choosing Complete or Fail there sends the browser to your return_url with payment_intent, payment_intent_client_secret and redirect_status, like Stripe.

Checkout. The session's url opens a hosted page with the test cards. Paying redirects to success_url (with {CHECKOUT_SESSION_ID} filled in) and sends checkout.session.completed; cancelling goes to cancel_url.

Webhooks are signed with the gateway's tl_whsec_… secret in a Stripe-Signature header (t=…,v1=…), so stripe.webhooks.constructEvent verifies them. Events: payment_intent.*, charge.succeeded|failed|captured|refunded, refund.created, checkout.session.completed|expired, customer.* and identity.verification_session.*.

Stripe test cards

Card or PaymentMethod Result
4242 4242 4242 4242, pm_card_visa Succeeds (also Mastercard 5555 5555 5555 4444, Amex 3782 822463 10005 and the other brands Stripe lists)
4000 0025 0000 3155, 4000 0027 6000 3184, pm_card_threeDSecure2Required 3-D Secure, then succeeds
4000 0082 6000 3178 3-D Secure, then declined for insufficient funds
4000 0000 0000 0002, pm_card_chargeDeclined Declined: generic_decline
4000 0000 0000 9995, pm_card_chargeDeclinedInsufficientFunds Declined: insufficient_funds
4000 0000 0000 9987 / 9979 Declined: lost_card / stolen_card
4000 0000 0000 0069 expired_card
4000 0000 0000 0127 incorrect_cvc
4000 0000 0000 0119 processing_error
4100 0000 0000 0019 Blocked as fraudulent
Any other valid number Declined: test_mode_live_card, as Stripe does in test mode

Adyen

Use the base URL in place of https://checkout-test.adyen.com, keeping the version: https://testlie.com/mock/<slug>/v71/payments. The Node library has no base URL option, so set it on its services:

js
import { Client, CheckoutAPI } from "@adyen/api-library";

const checkout = new CheckoutAPI(new Client({ apiKey: process.env.ADYEN_API_KEY, environment: "TEST" }));
checkout.PaymentsApi.baseUrl = "https://testlie.com/mock/<slug>/v71";
checkout.ModificationsApi.baseUrl = "https://testlie.com/mock/<slug>/v71";

Supported: POST /payments (card scheme payments with raw or test_-encrypted card fields), /payments/details (redirectResult after 3-D Secure), /sessions (with a hosted payment page) and GET /sessions/{id}, and the modifications /payments/{pspReference}/captures, /refunds, /cancels and /reversals. Calls are authenticated with X-API-Key and must name the gateway's merchant account. Errors use Adyen's status, errorCode, message, errorType shape.

As on Adyen, modifications answer 201 with status: "received"; the result arrives as a webhook. Captures happen automatically unless you send captureDelayHours: -1, additionalData.manualCapture: "true" or turn on the authorise only scenario.

Webhooks are standard notifications (live, notificationItems[].NotificationRequestItem) for AUTHORISATION, CAPTURE, REFUND, CANCELLATION and CANCEL_OR_REFUND, each signed in additionalData.hmacSignature with the gateway's HMAC key, so Adyen's hmacValidator verifies them. Reply [accepted].

Not supported yet: other payment methods (iDEAL, wallets, …), /paymentMethods, Drop-in/Components in the browser, tokenization, idempotency keys and the Management API.

Adyen test values

Value Result
4111 1111 4555 1142, 5555 3412 4444 1115, 3700 0000 0000 002 Authorised
4917 6100 0000 0000, 5454 5454 5454 5454, 3714 496353 98431 3-D Secure 2 challenge (RedirectShopper)
Holder name DECLINED, NOT_ENOUGH_BALANCE, CARD_EXPIRED, FRAUD, CVC_DECLINED, BLOCK_CARD, ISSUER_UNAVAILABLE, … Refused with Adyen's matching refusalReason and code
Any other card number Refused: Invalid Card Number

PayPal

Use the base URL in place of https://api-m.sandbox.paypal.com. Get a token with the gateway's client id and secret, then use the Orders API:

bash
curl -u "$PAYPAL_CLIENT_ID:$PAYPAL_CLIENT_SECRET" -d grant_type=client_credentials \
  https://testlie.com/mock/<slug>/v1/oauth2/token

Supported: POST /v1/oauth2/token, Orders v2 (create, get, capture, authorize), GET|capture|void authorizations, GET and refund captures, GET refunds, POST /v1/notifications/verify-webhook-signature and GET /v1/notifications/webhooks-events/{id}. Resending an event through the API is not supported: ignore the resend link in event links and use Send again on the gateway's page instead. Prefer: return=representation returns the full resource. Errors use PayPal's name, message, debug_id, details[].issue shape.

Orders with payment_source.paypal.experience_context (or the older application_context) return PAYER_ACTION_REQUIRED and a payer-action link to a hosted approval page. Approve redirects to your return_url with token and PayerID; Cancel goes to cancel_url; Approve (capture will be declined) makes the capture fail with INSTRUMENT_DECLINED.

PayPal's negative-testing header works too: PayPal-Mock-Response: {"mock_application_codes": "INSTRUMENT_DECLINED"} (or TRANSACTION_REFUSED, INTERNAL_SERVER_ERROR, …) makes that call fail.

Webhooks (CHECKOUT.ORDER.APPROVED, PAYMENT.CAPTURE.COMPLETED|PENDING|DENIED|REFUNDED, PAYMENT.AUTHORIZATION.CREATED|VOIDED) carry PayPal's PAYPAL-TRANSMISSION-ID, -TIME, -SIG, PAYPAL-CERT-URL and PAYPAL-AUTH-ALGO: SHA256withRSA headers. Verify them as on PayPal, either by calling the gateway's verify-webhook-signature endpoint or offline: the signature is RSA-SHA256 over <transmission id>|<transmission time>|<webhook id>|<CRC32 of the body> with the certificate at PAYPAL-CERT-URL. The webhook id is one of the gateway's credentials.

Not supported yet: card payment_source (use PayPal approval), PATCH orders, vaulting, subscriptions and payouts.

Scenarios

Each gateway has scenarios that apply to every new call, for tests that cannot pick a card:

Scenario Effect
Payment outcome Follow the test card (default), every payment succeeds, every payment is declined (with the decline code you pick) or every payment needs 3-D Secure / approval
API failure Every call answers 429 or 500 in the provider's error format
Response latency Up to 10 seconds added to every call
Asynchronous steps How long simulated background work takes, such as a Stripe Identity verification's processing. Default 2 seconds, up to an hour
Webhooks Send right away, delay (up to an hour), never send (recorded as dropped), send twice, or sign with a wrong key
Authorise only Stripe and Adyen payments wait for a capture call

Calls and webhooks

The gateway page lists every API call with its request, response, status and timing, filterable by payment, and every webhook with the headers it was signed with, the attempts, your endpoint's answer and the timing. Webhooks that fail with a network error, 408, 429 or 5xx are retried on the webhook schedule; Send again replays any webhook, freshly signed, to the current webhook URL. Gateway webhooks work on every plan, Free included, and do not count against any quota. The payments tab shows the objects the gateway keeps (payment intents, charges, orders, refunds, customers).

Limits

The number of gateways and of mock API calls per month depend on your plan. Over the monthly limit, calls answer the provider's rate-limit error until the next month. Calls, webhooks and stored objects (payment intents, customers, orders...) follow the plan's retention, which is 3 days on Free; each gateway also keeps at most its newest 5,000 objects. See API mocks.