Use Cases
Every way to save a card and charge it later, with step-by-step flows for trials, subscriptions, repeat checkout, holds, and top-ups.
A checkout can save the customer's card so that you, or the customer, can use it again later. Three choices on the first checkout decide what you can do next.
Choose your combination
- Save the card?
savePaymentMethodisnone,optional(the shopper ticks a box), orrequired(you collected consent on your own page). - Who uses it later?
paymentMethodUsageison_sessionwhen the customer pays again at your checkout, oroff_sessionwhen you charge without them. - How do you charge?
recurringfor a fixed series the customer agreed to,unscheduledwhen the amount or timing varies. Manual capture (captureMethod: 'manual') holds the money first, and you capture it later.
When the customer pays later, you create the payment with initiator: 'customer'; when you charge, with initiator: 'merchant' and the reason.
Every combination the API accepts:
| You want to | First checkout | Later charges | Example |
|---|---|---|---|
| Take a one-off payment | Amount, none | None | Any online order |
| Authorize now, capture later | Amount, none, manual capture | You capture or release it | Made-to-order goods |
| Let shoppers pay faster next time | Amount, optional, on_session | The customer pays | Online stores |
| Require a card on file for repeat checkout | Amount, required, on_session | The customer pays | Members-only shops |
| Offer opt-in auto-renewal | Amount, optional, off_session | You charge, recurring, only if the shopper ticked the box | Annual plans |
| Sell a subscription | Amount, required, off_session | You charge, recurring | Memberships and media |
| Start a free trial | 0, required, off_session | You charge, recurring | Software and apps |
| Reload a balance | Amount, required, off_session | You charge, unscheduled | Prepaid and mobility |
| Hold, then capture the final bill | 0, required, off_session | You hold with manual capture, unscheduled | Hotels and rentals |
Amounts are in minor units, so 990 with EUR is €9.90. Every combination works with Hosted and Embedded Checkout, and with cards, Apple Pay, and Google Pay, within these limits:
- A €0 checkout needs
requiredsaving and noshippingOptions. Later charges need a positive amount. - Saving a card needs a
customerReference.paymentMethodUsageis only allowed when saving is on. - Apple Pay saves for
off_sessiononly with awalletAgreement. A saved Apple Pay or Google Pay method can only be charged by you, never with the shopper present. - Manual capture cannot use
shippingOptions.allowIncrementalAuthorizationneeds manual capture.
Use Embedded Checkout
The flows below create the first checkout as a Hosted session. For Embedded Checkout, pass the same fields to sessions.prepare() in your prepare endpoint, and the saving fields as cardSaving in the browser. The later charges are the same for both.
const session = await tapaya.checkout.sessions.create({
merchantOrderId: 'sub-42-2026-10',
amount: 990,
currency: 'EUR',
customerReference: 'buyer-42',
savePaymentMethod: 'required',
paymentMethodUsage: 'off_session',
successUrl: 'https://shop.example/billing/return',
cancelUrl: 'https://shop.example/pricing',
}, { idempotencyKey: 'sub-42-2026-10' })
return Response.redirect(session.url, 303)Software and apps
Framewise gives new teams 14 days of its Pro plan for free, then charges €9.90 a month. Also fits SaaS apps, streaming, online courses, and fitness apps.
Save the card for €0
A zero amount with required saving verifies the card without charging it. Show the trial terms on your page first, because required does not ask the shopper.
const session = await tapaya.checkout.sessions.create({
merchantOrderId: 'trial-buyer-42',
amount: 0,
currency: 'EUR',
customerReference: 'buyer-42',
savePaymentMethod: 'required',
paymentMethodUsage: 'off_session',
successUrl: 'https://shop.example/billing/return',
cancelUrl: 'https://shop.example/pricing',
}, { idempotencyKey: 'trial-buyer-42:setup' })Store the saved card
After the redirect, read the session on your server and keep paymentMethodId with the customer. Saving can finish a moment after the check, so read again while it is null.
const session = await tapaya.checkout.sessions.get(sessionId)
if (session.paymentStatus === 'successful' && session.paymentMethodId) {
await subscriptions.start({
customerReference: 'buyer-42',
paymentMethodId: session.paymentMethodId,
})
}Charge when the trial ends
Your scheduler charges the card without the shopper. Use one idempotency key per billing period, so a retried job never charges twice.
const renewal = await tapaya.payments.create({
merchantOrderId: 'sub-42-2026-11',
amount: 990,
currency: 'EUR',
paymentMethodId: subscription.paymentMethodId,
initiator: 'merchant',
merchantInitiatedReason: 'recurring',
}, { idempotencyKey: 'sub-42-2026-11' })Stop charges on cancellation
Delete the saved card. Past payments stay readable, and new charges against it fail.
await tapaya.paymentMethods.delete(subscription.paymentMethodId)Memberships and media
Morning Brief sells a €9.90 monthly membership. Readers pay the first month at checkout, and the same card renews every month. Also fits memberships, news, meal kits, and gyms.
Pay and save the card
The first payment saves the card for renewals. Custom consent text in savingTerms needs the locale it is written in.
const session = await tapaya.checkout.sessions.create({
merchantOrderId: 'sub-42-2026-10',
amount: 990,
currency: 'EUR',
customerReference: 'buyer-42',
savePaymentMethod: 'required',
paymentMethodUsage: 'off_session',
locale: 'en',
savingTerms: {
text: 'Save my card for membership renewals.',
url: 'https://shop.example/terms#billing',
},
successUrl: 'https://shop.example/billing/return',
cancelUrl: 'https://shop.example/pricing',
}, { idempotencyKey: 'sub-42-2026-10' })Renew every month
Charge the agreed amount on the agreed date, then branch on status.
const renewal = await tapaya.payments.create({
merchantOrderId: 'sub-42-2026-11',
amount: 990,
currency: 'EUR',
paymentMethodId: subscription.paymentMethodId,
initiator: 'merchant',
merchantInitiatedReason: 'recurring',
}, { idempotencyKey: 'sub-42-2026-11' })
if (renewal.status === 'successful') await subscriptions.extend(subscription.id)
if (renewal.status === 'declined') await subscriptions.markPastDue(subscription.id, renewal.declineReason)Refund a month
Omit amount to refund what is left of the payment.
await tapaya.payments.refunds.create(renewal.id, {
reason: 'requested_by_customer',
}, { idempotencyKey: 'sub-42-2026-11:refund' })Online stores
Roastery 12 sells coffee beans. Shoppers can tick a box to remember their card, and pay for the next order with one click. Also fits food delivery, ticketing, and marketplaces.
Offer to remember the card
optional saving shows an unchecked box, and Tapaya records the shopper's choice as consent.
const session = await tapaya.checkout.sessions.create({
merchantOrderId: 'order-1001',
amount: 2500,
currency: 'EUR',
customerReference: 'buyer-42',
savePaymentMethod: 'optional',
paymentMethodUsage: 'on_session',
successUrl: 'https://shop.example/checkout/return',
cancelUrl: 'https://shop.example/cart',
}, { idempotencyKey: 'order-1001:checkout' })Show the saved cards
On the next order, list the signed-in customer's cards. List typed cards only, because saved wallets cannot be charged with the shopper present.
const { data } = await tapaya.paymentMethods.list({
customerReference: customer.id,
wallet: 'none',
limit: 5,
})Pay with the saved card
Create a customer-initiated payment. The shopper confirms it with their bank: redirect to nextAction.url, or finish it on your page with Tapaya.js handleNextAction({ clientSecret }).
const payment = await tapaya.payments.create({
merchantOrderId: 'order-1002',
amount: 2500,
currency: 'EUR',
paymentMethodId: 'pm_7Hq2x9LmR4',
initiator: 'customer',
returnUrl: 'https://shop.example/checkout/return',
}, { idempotencyKey: 'order-1002:charge' })
if (payment.status === 'action_required' && payment.nextAction) {
return Response.redirect(payment.nextAction.url, 303)
}Confirm before fulfilling
Only a server read proves payment. Compare the order, amount, and currency.
const paid = await tapaya.payments.get(paymentId)
if (paid.status === 'successful' && paid.merchantOrderId === order.id && paid.amount === order.amount) {
await fulfill(order)
}Hotels and rentals
Lipa Hotel guarantees bookings with a card checked for €0, holds €200 at check-in, and captures the final bill at check-out. Also fits car rental, gear hire, and holiday lets.
Guarantee the booking for €0
Save the card without charging it. Explain the hold and any no-show fee in your booking terms.
const session = await tapaya.checkout.sessions.create({
merchantOrderId: 'booking-310',
amount: 0,
currency: 'EUR',
customerReference: 'guest-310',
savePaymentMethod: 'required',
paymentMethodUsage: 'off_session',
successUrl: 'https://hotel.example/booking/confirmed',
cancelUrl: 'https://hotel.example/booking',
}, { idempotencyKey: 'booking-310:setup' })Hold €200 at check-in
Manual capture authorizes the amount without moving money. allowIncrementalAuthorization lets you raise it later.
const hold = await tapaya.payments.create({
merchantOrderId: 'stay-310',
amount: 20000,
currency: 'EUR',
paymentMethodId: guest.paymentMethodId,
initiator: 'merchant',
merchantInitiatedReason: 'unscheduled',
captureMethod: 'manual',
allowIncrementalAuthorization: true,
}, { idempotencyKey: 'stay-310' })Raise the hold for an extra night
If the bank declines the increment, the original €200 stays held and declineReason explains why.
await tapaya.payments.increment(hold.id, { amount: 5000 }, { idempotencyKey: 'stay-310:extra-night' })Capture the final bill
Capture once, before authorizationExpiresAt. A partial capture releases the rest. To charge nothing, release the hold with cancel instead.
await tapaya.payments.capture(hold.id, { amount: 18000 }, { idempotencyKey: 'stay-310:checkout' })Prepaid and mobility
VoltPoint drivers pay from a prepaid balance. The first €20 top-up saves the card, and the balance reloads by €20 whenever it drops below €5. Also fits EV charging, parking, transit cards, and phone credit.
Save the card with the first top-up
An automatic_reload wallet agreement lets Apple Pay save the card too.
const session = await tapaya.checkout.sessions.create({
merchantOrderId: 'topup-buyer-42-1',
amount: 2000,
currency: 'EUR',
customerReference: 'buyer-42',
savePaymentMethod: 'required',
paymentMethodUsage: 'off_session',
walletAgreement: {
type: 'automatic_reload',
description: 'Wallet top-up',
managementUrl: 'https://shop.example/account/wallet',
automaticReload: { amount: 2000, thresholdAmount: 500 },
},
successUrl: 'https://shop.example/wallet',
cancelUrl: 'https://shop.example/wallet',
}, { idempotencyKey: 'topup-buyer-42-1' })Reload when the balance is low
Top-ups have no fixed schedule, so they are unscheduled. Key each reload on your own ledger entry.
const reload = await tapaya.payments.create({
merchantOrderId: 'topup-buyer-42-2',
amount: 2000,
currency: 'EUR',
paymentMethodId: wallet.paymentMethodId,
initiator: 'merchant',
merchantInitiatedReason: 'unscheduled',
}, { idempotencyKey: 'topup-buyer-42-2' })Refund the unused balance
Pass an amount for a partial refund.
await tapaya.payments.refunds.create(reload.id, {
amount: 500,
reason: 'requested_by_customer',
}, { idempotencyKey: 'topup-buyer-42-2:close' })Rules for every use case
- Charge only what the customer agreed to. Keep the saved card's
consentrecord with your billing records. - Use one idempotency key per business operation, such as a billing period, a top-up, or a capture. After a lost response, retry with the same key and body.
- Branch on
status, not on HTTP 200.declinedcarriesdeclineReason, andpendingneeds another server read. Checkout has no webhooks yet, so reconcile unresolved payments on a schedule. - Capture holds before
authorizationExpiresAt. An expired authorization cannot be captured.
Try it in the sandbox
Create any of the checkouts above with a sandbox key and pay with 4242 4242 4242 4242. A €0 hosted session shows a Save card button instead of a Pay button. Save cards and charge later is the full reference for consent, wallets, holds, and refunds.