Tapaya

Save Cards & Charge Later

Save a card, Apple Pay, or Google Pay with the customer's consent, then charge it for renewals, top-ups, hotel holds, or faster repeat checkout.

Two ways to use a saved card

You charge later

Merchant-initiated (MIT)

Who starts it
Your server, while the customer is away
Typical use
Subscriptions, free trials, auto top-ups, hotel holds
Methods
Cards, Apple Pay, and Google Pay
// Save the cardpaymentMethodUsage: 'off_session'// Charge itinitiator: 'merchant'

Customer pays faster

Customer-initiated (CIT)

Who starts it
The customer, on your site
Typical use
One-click repeat checkout
Methods
Typed cards only
// Save the cardpaymentMethodUsage: 'on_session'// Charge itinitiator: 'customer'

Want to see a whole flow for a real business first? Try Use cases.

Implementation

Your server uses the same merchant credentials for checkout, saved cards, and later charges.

Save the card at checkout

Add three fields when you create a hosted session or an embedded payment:

const session = await tapaya.checkout.sessions.create({
	mode: 'embedded',
	merchantOrderId: 'order-42',
	amount: 1200,
	currency: 'EUR',
	customerReference: 'buyer-42',
	savePaymentMethod: 'optional',
	paymentMethodUsage: 'off_session',
}, { idempotencyKey: 'order-42:initial' })
FieldWhat it does
customerReferenceYour own ID for the customer. Authenticate the customer on your server before you use it.
savePaymentMethodoptional shows an unchecked checkbox. required saves the card with consent you already collected. none turns saving off.
paymentMethodUsageoff_session if you will charge later. on_session for faster repeat checkout. Omit it when saving is none.
savingTermsOptional { text, url } with your own consent wording and terms link.

For a hosted session, omit mode and set its return destinations.

Get consent first with required saving

required does not ask the customer. Collect agreement on your own page or in your subscription terms before you create the payment. If the customer declines your checkbox, create the payment with saving set to none.

Store the saved card ID

Read the session or payment on your server until paymentMethodId is not null, and store it with your customer. Saving can finish a moment after the payment succeeds. A failed save does not undo the payment; never charge again to retry saving.

The method's consent record holds type, acceptedAt, wordingVersion, savingTerms, ipAddress, and userAgent. type is checkbox for Tapaya's optional checkbox or merchant_asserted for required saving. Browser details can be null for server confirmations. Keep this record with your billing records.

Charge the saved card

Use a method saved for off_session and an amount the customer agreed to. Give each renewal or top-up its own merchantOrderId: an order allows one unresolved payment at a time.

const renewal = await tapaya.payments.create({
	merchantOrderId: 'sub-42-2026-11',
	amount: 990,
	currency: 'EUR',
	paymentMethodId: 'pm_7Hq2x9LmR4',
	initiator: 'merchant',
	merchantInitiatedReason: 'recurring',
}, { idempotencyKey: 'sub-42-2026-11' })

Set merchantInitiatedReason to one of:

  • recurring for a fixed series the customer agreed to, such as a monthly plan.
  • unscheduled for charges that vary in amount or timing, such as top-ups or a hotel bill.

Automatic capture charges the card before the call returns. Check status: declined includes declineReason, and pending needs another server read. HTTP 200 alone does not prove payment.

Confirm on your server

Read tapaya.payments.get(paymentId) and compare its order reference, amount, and currency before you fulfill. Only successful proves a captured payment. Checkout has no webhooks yet, so keep reconciling unresolved payments on a schedule.

Save Apple Pay and Google Pay

Wallets use the same fields. To charge an Apple Pay card later, also pass a walletAgreement, which the native sheet shows to the customer:

const walletAgreement = {
	type: 'recurring' as const,
	description: 'Pro plan',
	managementUrl: 'https://shop.example/account/billing',
	recurring: { amount: 990, interval: 'month' as const, intervalCount: 1 },
}
  • Pass it on the server and as cardSaving.walletAgreement in the browser.
  • managementUrl must use HTTPS on an allowed checkout website.
  • For top-ups, use type: 'automatic_reload' with automaticReload: { amount, thresholdAmount }, in minor units.
  • Without an agreement, Apple Pay is hidden when saving for later charges.

Saved wallet methods expose card.wallet.type and card.wallet.deviceBound, and support merchant-initiated charges only. A device-bound method can stop working after the customer changes device.

Check a card without charging it

For free trials and bookings, save the card with a €0 verification. Use a unique order reference and required saving:

const setup = await tapaya.payments.create({
	merchantOrderId: 'setup-buyer-42',
	amount: 0,
	currency: 'EUR',
	customerReference: 'buyer-42',
	savePaymentMethod: 'required',
	paymentMethodUsage: 'off_session',
}, { idempotencyKey: 'setup-buyer-42:initial' })

Pass setup.clientSecret to the normal embedded form. Hosted sessions also accept a zero amount with required saving. Cards, Apple Pay, and Google Pay all work, without shippingOptions. For wallets, pass wallets: { amount: 0, currency: 'EUR' } and the matching saving policy; Apple Pay still needs its agreement. Later charges need a positive amount.

Hold, capture, and refund

To hold funds first, as a hotel or rental does, add captureMethod: 'manual'. authorized means the amount is held; capture it before authorizationExpiresAt.

const hold = await tapaya.payments.create({
	merchantOrderId: 'stay-310',
	amount: 20000,
	currency: 'EUR',
	paymentMethodId: 'pm_7Hq2x9LmR4',
	initiator: 'merchant',
	merchantInitiatedReason: 'unscheduled',
	captureMethod: 'manual',
	allowIncrementalAuthorization: true,
}, { idempotencyKey: 'stay-310' })

// After a server read confirms authorization, increase the hold by EUR 50.
await tapaya.payments.increment(hold.id, { amount: 5000 }, {
	idempotencyKey: 'stay-310:extra-night',
})
await tapaya.payments.capture(hold.id, { amount: 18000 }, {
	idempotencyKey: 'stay-310:checkout',
})
ActionCallNotes
Raise the holdpayments.increment(id, { amount })Adds to the hold. A decline keeps the original authorization and sets declineReason.
Capturepayments.capture(id, { amount })Runs once. A partial capture releases the rest; omit amount to capture everything.
Releasepayments.cancel(id, options)Frees the held amount without charging. Works only before capture; refund a captured payment instead.
Refundpayments.refunds.create(id, { amount, reason })Refund in parts, as often as needed, up to amountCaptured. Omit amount to refund what is left. Reasons: requested_by_customer, duplicate, fraudulent, other.

To refund a Hosted Checkout payment, pass the session's paymentReference as the payment ID. Read a refund until status is succeeded or failed; pending is unresolved and holds back its amount. Use a separate idempotency key for each create, increment, capture, cancel, or refund, and reuse it with the same body after a lost response. Track amountAuthorized, amountCaptured, and amountRefunded on server reads.

Manage saved cards

const page = await tapaya.paymentMethods.list({
	customerReference: 'buyer-42',
	limit: 25,
})
const method = await tapaya.paymentMethods.get('pm_7Hq2x9LmR4')
await tapaya.paymentMethods.update(method.id, { expMonth: 12, expYear: 2028 })
await tapaya.paymentMethods.delete(method.id)
  • Lists return { data, nextCursor }. Pass cursor: page.nextCursor for the next page while it is not null. Filter wallet by none, apple_pay, or google_pay.
  • Updates accept expMonth, expYear, cardholderName, and billingAddress. Omitted and null fields stay unchanged. A card saved through Apple Pay or Google Pay cannot be updated and returns 409; the wallet keeps it current.
  • Deleting a method stops future charges and keeps past payments. Deleting it again returns the same confirmation.
  • Always check that the requested method belongs to the signed-in customer and merchant.

Chybí vám něco na této stránce?Navrhněte funkci. Připojíme tuto stránku, aby tým viděl souvislosti.