Tapaya

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

  1. Save the card? savePaymentMethod is none, optional (the shopper ticks a box), or required (you collected consent on your own page).
  2. Who uses it later? paymentMethodUsage is on_session when the customer pays again at your checkout, or off_session when you charge without them.
  3. How do you charge? recurring for a fixed series the customer agreed to, unscheduled when 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 toFirst checkoutLater chargesExample
Take a one-off paymentAmount, noneNoneAny online order
Authorize now, capture laterAmount, none, manual captureYou capture or release itMade-to-order goods
Let shoppers pay faster next timeAmount, optional, on_sessionThe customer paysOnline stores
Require a card on file for repeat checkoutAmount, required, on_sessionThe customer paysMembers-only shops
Offer opt-in auto-renewalAmount, optional, off_sessionYou charge, recurring, only if the shopper ticked the boxAnnual plans
Sell a subscriptionAmount, required, off_sessionYou charge, recurringMemberships and media
Start a free trial0, required, off_sessionYou charge, recurringSoftware and apps
Reload a balanceAmount, required, off_sessionYou charge, unscheduledPrepaid and mobility
Hold, then capture the final bill0, required, off_sessionYou hold with manual capture, unscheduledHotels 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 required saving and no shippingOptions. Later charges need a positive amount.
  • Saving a card needs a customerReference. paymentMethodUsage is only allowed when saving is on.
  • Apple Pay saves for off_session only with a walletAgreement. A saved Apple Pay or Google Pay method can only be charged by you, never with the shopper present.
  • Manual capture cannot use shippingOptions. allowIncrementalAuthorization needs 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.

server/checkout.ts
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 consent record 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. declined carries declineReason, and pending needs 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.

Trang này còn thiếu điều gì?Đề xuất tính năng. Chúng tôi sẽ đính kèm trang này để đội ngũ nắm được bối cảnh.