# Use Cases (/sk/online/integration/use_cases)





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 [#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 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](/online/integration/node_sdk#capture-a-hold) it | Made-to-order goods                             |
| Let shoppers pay faster next time          | Amount, `optional`, `on_session`  | The customer pays                                                        | [Online stores](#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](#memberships-and-media) |
| Start a free trial                         | `0`, `required`, `off_session`    | You charge, `recurring`                                                  | [Software and apps](#software-and-apps)         |
| Reload a balance                           | Amount, `required`, `off_session` | You charge, `unscheduled`                                                | [Prepaid and mobility](#prepaid-and-mobility)   |
| Hold, then capture the final bill          | `0`, `required`, `off_session`    | You hold with manual capture, `unscheduled`                              | [Hotels and rentals](#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 `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`](/online/integration/saved_payment_methods). 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 [#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.

<CodeBlockTabs defaultValue="hosted" groupId="checkout-type">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="hosted">
      Hosted
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="embedded">
      Embedded
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="hosted">
    ```ts title="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)
    ```
  </CodeBlockTab>

  <CodeBlockTab value="embedded">
    ```ts title="server/payments.ts"
    // POST /api/payments: your prepare endpoint
    export async function POST(request: Request) {
    	const customer = await requireCustomer(request)
    	const session = await tapaya.checkout.sessions.prepare({
    		merchantOrderId: `sub-${customer.id}-2026-10`,
    		amount: 990,
    		currency: 'EUR',
    		customerReference: customer.id,
    		savePaymentMethod: 'required',
    		paymentMethodUsage: 'off_session',
    	})
    	return Response.json({ clientSecret: session.clientSecret, status: session.status })
    }
    ```

    ```ts title="checkout.ts"
    const payment = browserTapaya.createPayment({
    	prepare: '/api/payments',
    	cardSaving: {
    		savePaymentMethod: 'required',
    		paymentMethodUsage: 'off_session',
    	},
    })
    await payment.mount('#card-fields')
    ```
  </CodeBlockTab>
</CodeBlockTabs>

## Software and apps [#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.

<div className="fd-steps [&_h3]:fd-step">
  ### Save the card for €0 [#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.

  ```ts
  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 [#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`.

  ```ts
  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 [#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.

  ```ts
  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 [#stop-charges-on-cancellation]

  Delete the saved card. Past payments stay readable, and new charges against it fail.

  ```ts
  await tapaya.paymentMethods.delete(subscription.paymentMethodId)
  ```
</div>

## Memberships and media [#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.

<div className="fd-steps [&_h3]:fd-step">
  ### Pay and save the card [#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.

  ```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',
  	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 [#renew-every-month]

  Charge the agreed amount on the agreed date, then branch on `status`.

  ```ts
  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 [#refund-a-month]

  Omit `amount` to refund what is left of the payment.

  ```ts
  await tapaya.payments.refunds.create(renewal.id, {
  	reason: 'requested_by_customer',
  }, { idempotencyKey: 'sub-42-2026-11:refund' })
  ```
</div>

## Online stores [#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.

<div className="fd-steps [&_h3]:fd-step">
  ### Offer to remember the card [#offer-to-remember-the-card]

  `optional` saving shows an unchecked box, and Tapaya records the shopper's choice as consent.

  ```ts
  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 [#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.

  ```ts
  const { data } = await tapaya.paymentMethods.list({
  	customerReference: customer.id,
  	wallet: 'none',
  	limit: 5,
  })
  ```

  ### Pay with the saved card [#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 })`.

  ```ts
  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 [#confirm-before-fulfilling]

  Only a server read proves payment. Compare the order, amount, and currency.

  ```ts
  const paid = await tapaya.payments.get(paymentId)

  if (paid.status === 'successful' && paid.merchantOrderId === order.id && paid.amount === order.amount) {
  	await fulfill(order)
  }
  ```
</div>

## Hotels and rentals [#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.

<div className="fd-steps [&_h3]:fd-step">
  ### Guarantee the booking for €0 [#guarantee-the-booking-for-0]

  Save the card without charging it. Explain the hold and any no-show fee in your booking terms.

  ```ts
  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 [#hold-200-at-check-in]

  Manual capture authorizes the amount without moving money. `allowIncrementalAuthorization` lets you raise it later.

  ```ts
  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 [#raise-the-hold-for-an-extra-night]

  If the bank declines the increment, the original €200 stays held and `declineReason` explains why.

  ```ts
  await tapaya.payments.increment(hold.id, { amount: 5000 }, { idempotencyKey: 'stay-310:extra-night' })
  ```

  ### Capture the final bill [#capture-the-final-bill]

  Capture once, before `authorizationExpiresAt`. A partial capture releases the rest. To charge nothing, release the hold with `cancel` instead.

  ```ts
  await tapaya.payments.capture(hold.id, { amount: 18000 }, { idempotencyKey: 'stay-310:checkout' })
  ```
</div>

## Prepaid and mobility [#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.

<div className="fd-steps [&_h3]:fd-step">
  ### Save the card with the first top-up [#save-the-card-with-the-first-top-up]

  An `automatic_reload` wallet agreement lets Apple Pay save the card too.

  ```ts
  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 [#reload-when-the-balance-is-low]

  Top-ups have no fixed schedule, so they are `unscheduled`. Key each reload on your own ledger entry.

  ```ts
  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 [#refund-the-unused-balance]

  Pass an `amount` for a partial refund.

  ```ts
  await tapaya.payments.refunds.create(reload.id, {
  	amount: 500,
  	reason: 'requested_by_customer',
  }, { idempotencyKey: 'topup-buyer-42-2:close' })
  ```
</div>

## Rules for every use case [#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 [#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](/online/integration/saved_payment_methods) is the full reference for consent, wallets, holds, and refunds.
