One-Click Upsells, Downsells & Instant Purchases
Available via: API (private key). The initial card collection can be done with the SDK or server-to-server.
Introduction
One-click upsells, downsells, post-purchase offers, and instant purchases all rely on the same capability: charging a payment method that the customer has previously saved, without asking them to enter their card details again.
On Inflow, this is achieved by creating a server payment with the useCustomerPaymentMethod parameter set to true. This guide covers the complete integration:
- Save the payment method during the initial purchase
- Verify that the customer has a saved payment method
- Create the one-click payment
- Confirm the payment
How It Works
Two parameters of POST /api/server/payment control this flow:
| Parameter | When to use it | Effect |
|---|---|---|
savePaymentMethod: true | On the initial payment | The card used for the payment is saved and linked to the customer's email (customerEmail). |
useCustomerPaymentMethod: true | On subsequent payments | The payment is charged to the customer's saved card. No card details are collected. |
The saved payment method is identified by the customer's email address. For the one-click charge to succeed, the subsequent payment must use the same customerEmail and the same currency as the saved card.
A typical upsell funnel therefore looks like this:
- The customer completes an initial purchase and their card is saved.
- Your application presents an additional offer (upsell).
- If the customer accepts, your backend charges the saved card in a single API call — no card form is displayed.
- If the customer declines, you may present a cheaper alternative (downsell) and charge it the same way.
Step 1: Save the Payment Method During the Initial Purchase
A one-click charge requires an existing saved payment method. There are three ways to obtain one, depending on how you collect card details:
| Integration | How to save the card | Documentation |
|---|---|---|
| SDK card form (recommended for web funnels) | Create the initial payment with savePaymentMethod: true, then mount the SDK CardElement as usual. The SDK handles tokenization, 3D Secure, and confirmation. | React, Vanilla JS |
| Full server-to-server | Add savePaymentMethod: true to your standard S2S payment creation call on https://api-card.inflowpay.com. | Server-to-Server Payments |
| Card setup without a charge (signup, free trial) | Create a no-charge PaymentSetup and complete it with the SDK. | Free Trial & Card Setup |
Customer consent (SDK integrations)
When savePaymentMethod: true is set, the SDK card form displays a "save my card" checkbox to the customer. The card is only saved if the customer checks this box.
If your funnel depends on the card always being saved — for example, when the upsell step is a core part of your flow — contact [email protected] to enable card saving without the consent checkbox on your account.
Step 2: Verify That the Customer Has a Saved Payment Method
Before presenting a one-click offer, confirm that the customer actually has a saved payment method. Attempting a useCustomerPaymentMethod payment for a customer without one results in an error (see Error Handling).
You have two options:
Option A — Webhooks (recommended). Listen for payment.* success events (card saved during a payment) or payment_setup.completed (card saved without a charge), and record in your database that the customer has a saved payment method. See Webhooks.
Option B — On-demand lookup. Query the API at the time of the offer. Note that the list endpoint takes the customer id (cus_...), not the email, so resolve the id first:
# 1. Resolve the customer id from the email
curl "https://api.inflowpay.com/api/customer/email/{customerEmail}" \
-H "X-Inflow-Api-Key: your_private_key"
# 2. List the saved payment methods for the relevant currency
curl "https://api.inflowpay.com/api/customer/{customerId}/payment-methods?currency=EUR" \
-H "X-Inflow-Api-Key: your_private_key"API reference: GET /api/customer/email/{email}, GET /api/customer/{customerId}/payment-methods.
Step 3: Create the One-Click Payment
When the customer accepts the offer, create a payment with useCustomerPaymentMethod: true. The request must respect three rules:
- Same
customerEmailas when the card was saved. - Same currency as the saved card — a card saved for
EURcannot be charged inUSD. - No card data and no save flag — do not send
card,tokenIntentId, orsavePaymentMethod. CombiningsavePaymentMethodortokenIntentIdwithuseCustomerPaymentMethodreturns a400error; acardobject is ignored.
Product prices are expressed in cents (1999 = €19.99), as everywhere in the API.
curl -X POST https://api.inflowpay.com/api/server/payment \
-H "X-Inflow-Api-Key: your_private_key" \
-H "Content-Type: application/json" \
-d '{
"products": [{ "name": "Premium Add-on", "price": 1999, "quantity": 1 }],
"currency": "EUR",
"customerEmail": "[email protected]",
"billingCountry": "FR",
"purchasingAsBusiness": false,
"useCustomerPaymentMethod": true,
"metadatas": { "funnelStep": "upsell_1" }
}'This call uses the standard base URL
https://api.inflowpay.com. Theapi-card.inflowpay.combase URL is only required when transmitting raw card data, which is not the case here.
Response
{
"id": "pay_def456",
"amountInCents": 1999,
"currency": "EUR",
"status": "CHECKOUT_PENDING",
"depositStatus": "succeeded",
"threeDsSessionUrl": null,
"customerEmail": "[email protected]",
"customerPaymentMethod": {
"id": "cpm_abc123",
"type": "card",
"cardBrand": "visa",
"cardLast4": "4242"
}
}Unlike a new-card payment (returned with status INITIATION), a saved-card charge is submitted to the card network during creation. When the create call returns, the charge has already been attempted: depositStatus is "succeeded" (or "requires_capture" with manual capture) and the payment moves to CHECKOUT_SUCCESS a moment later, once the processor's notification is received.
Step 4: Do Not Call Confirm — Track the Result
Do not call
POST /api/server/payment/{paymentId}/confirmafter a saved-card charge. The payment is confirmed during creation, whatever the value ofautoConfirm. A confirm call on an already-confirmed payment does nothing useful and adds a concurrent write on the payment while the processor's notification is being handled.
Track the final status through webhooks (payment.* events) rather than polling. A charge on a saved card can still be declined by the customer's bank — handle payment.failed events and offer a card re-entry fallback in that case.
If the bank requires 3D Secure
Occasionally the customer's bank requires authentication even for a saved card. The create response then carries a non-null threeDsSessionUrl and depositStatus: "requires_confirmation":
- Redirect the customer to the
threeDsSessionUrl(passthreeDsSuccessUrlandthreeDsFailureUrlon creation). - After a successful challenge:
- with
autoConfirm: trueon the payment, the charge is completed automatically — nothing to do; - with
autoConfirm: false(the default), you must complete it yourself withPOST /api/server/payment/{paymentId}/confirm.
- with
We recommend sending autoConfirm: true on one-click payments so that the 3DS case needs no extra call either. The rule of thumb for every server payment: call confirm only when depositStatus is "requires_confirmation" and no 3DS redirect is pending.
Implementing a Downsell
A downsell is technically identical to an upsell: only your funnel logic differs. If the customer declines the initial offer, present a cheaper alternative and repeat Step 3 with different products (for example with "funnelStep": "downsell_1" in metadatas), then track the result as in Step 4.
You can chain as many one-click offers as your funnel requires — each one is an independent payment.
Rules and Limitations
| Rule | Detail |
|---|---|
| Mutually exclusive parameters | savePaymentMethod and useCustomerPaymentMethod cannot both be true in the same request (400 error). Either you save a new card, or you use an existing one. |
Same customerEmail | The saved card is tied to the customer's email for your account. The one-click payment must use the exact same customerEmail as when the card was saved. |
| Currency must match | Each saved card is linked to a currency. A useCustomerPaymentMethod payment in EUR requires a card saved for EUR. |
| Last saved card is charged | If a customer saves multiple cards over time, the most recently saved one becomes the default and is charged. Selecting a card per payment is not supported, but the default card can be changed with set-default. |
| No card fields allowed | Sending tokenIntentId together with useCustomerPaymentMethod: true returns a 400 error; a card object is ignored. |
| No confirm call | The saved card is charged during creation, whatever the value of autoConfirm. Call POST /api/server/payment/{paymentId}/confirm only in the 3DS case described in Step 4, when depositStatus is "requires_confirmation". |
| Charge limit per customer | A customer's saved card can be charged at most 5 times per rolling 30 days through your account, successful and declined attempts included. The 6th attempt is refused with a 400 (see Error Handling: Charge Limit Reached) and the card must be collected again through the SDK or Checkout. Subscription payments (first payment, upgrades, renewals) are not blocked by this limit. |
Error Handling: No Saved Payment Method
If the customer has no saved payment method for the requested currency, the payment creation fails with:
"couldn't process payment in the useCustomerPaymentMethod context, the customer [email protected] has no payment method for currency EUR"In this case, fall back to collecting the card normally: display the SDK card form with savePaymentMethod: true (so that the next offer can be one-click), or send the customer through a PaymentSetup.
Error Handling: Charge Limit Reached
Card networks cap how often a stored card may be charged or retried, and repeated attempts on the same card, whether they succeed or get declined, expose your account to excessive-retry fees and to network blocks. Inflow therefore refuses a useCustomerPaymentMethod payment once the customer's saved card has already been charged 5 times in the last 30 days with your account, counting every attempt, successful or declined:
{
"statusCode": 400,
"timestamp": "2026-10-01T09:12:44.000Z",
"path": "/api/server/payment",
"correlationId": "req_abc123",
"message": {
"errorCode": "SAVED_PAYMENT_METHOD_ATTEMPT_LIMIT_EXCEEDED",
"message": "This customer's saved payment method has already been charged 5 times in the last 30 days (successful and failed attempts included). Card network rules cap charges on a stored card; collect the card again through the SDK or Checkout for this payment.",
"details": {
"attempts": 5,
"limit": 5,
"windowDays": 30,
"nextAllowedAt": "2026-10-18T14:03:10.000Z"
}
}
}The payment is not created: nothing is charged and the refused call does not count towards the limit. nextAllowedAt is when the oldest counted attempt leaves the 30-day window.
What to do:
- Do not retry the one-click call. Fall back to collecting the card: display the SDK card form or send the customer through Checkout for this purchase. A payment where the customer enters the card is never subject to this limit.
- Never replay a decline in a loop. A saved-card charge declined by the bank (
payment.failedwebhook) should not be retried within the day, and a hard decline (do_not_honor,lost_card,stolen_card,invalid_account,pickup_card) must not be retried at all. Each retry counts as an attempt. - Plan your funnel within the limit. An initial purchase followed by a few upsells fits comfortably; per-item micro-charges or automatic retries do not. Batch several items into one payment rather than charging the card once per item.
Best Practices
- Store your funnel context (offer id, step, campaign) in
metadatas— it is returned on the payment object and in webhook payloads. - Check for a saved payment method before rendering the one-click button, so that customers without a saved card never trigger the error above.
- Keep the number of saved-card charges per customer low and spaced out: the charge limit is 5 attempts per rolling 30 days, declines included, and going beyond it on a card network's side gets merchants fined or blocked. Never retry a declined saved-card charge automatically.
- One-click charges also work for subscriptions:
useCustomerPaymentMethodis available on subscription initiation.
Updated 6 days ago
