Skip to content

Payment methods

Configuring card, Apple Pay, PayPal, Wero, ANCV and Oney — the archetypes behind them, partial authorization policies, BNPL plans, and per-method amount windows.

A session’s methods array is the merchant configuration: which methods the customer sees, in which order, with which behaviours. Every entry shares a few fields, then each method pins its own. This page is the per-method tour; the shapes come from the same TypeBox schemas the API validates against.

Field Role
method The archetype: card, applepay, paypal, wero, ancv, oney.
provider The PSP serving this method — optional. Left out, the one your account is contracted with for this method is used; several would fit and the creation refuses, naming them. Named, it must carry a contract for THIS method.
id Optional instance key — lets one archetype appear twice (see promoted instances). Defaults to the method name; unique across the session.
label Display override for a promoted instance (“American Express”); absent, the UI uses the localized method name.
range Optional per-method tranche window, overriding the session default (min/max per tranche, in minor units). Oney ignores it — BNPL eligibility comes from its plans.
partialAuth Card only, boolean: the issuer may authorize below the requested amount — enable it only if your acquirer contract carries partial approvals. Who decides a lower amount is derived per method, never configured — see partial authorization.

Two behaviours are deliberately not configuration:

  • On-page vs redirect. Whether the customer leaves the page is decided per-authorization by the PSP’s acknowledgement (it carries a redirect URL when a hop is needed — 3DS challenge, PayPal approval…), not declared per method. Your page needs no routing either way: the components follow.
  • The verdict is always asynchronous. Every authorization resolves out of band (webhook, redirect return). The elements show a single waiting screen while a verdict is due; your integration never polls or resumes anything by hand.

A contract is rarely worldwide: an Amex opened for a few countries, a PayPal for the SEPA zone, a financing plan reserved to one sales channel. Those limits live in your configuration on our side, never in your payload — you declare the context of the sale, we filter what it is for.

{
currency: "EUR", total: 1000_00, orderId: "EP-123456",
paymentMethodFilters: {
country: "ES", // ISO 3166-1 alpha-2 — WHERE this order is sold
channel: "LBM", // YOUR sales channel, free text
},
methods: [ /* … */ ],
}
  • country is the market, and you declare it: we never read it off billing.country. That one is the payer’s address, and your offer must not follow your customers around — you may well sell in France to someone billed in Spain. A method restricted to countries is simply not served when it is absent.
  • channel is your vocabulary. We never validate it against a list; we only match it against what your contracts declare.

A method the context is not for does not enter the session at all. No error: you made no mistake, and a payer must not be shown a method nobody can honour. Two consequences worth knowing:

  • Declaring nothing is not neutral for a restricted method. A method reserved to a list of channels cannot be served when the order declares no channel — a whitelist does not contain “unknown”. If a method you expect is missing, that is the first thing to check.
  • If nothing at all can pay the order, the page says so rather than showing an empty list. A session may legitimately end up with no payable method — a balance below every window, financing in an unserved country.

What stays a refusal is a mistake you can fix: naming a PSP you hold no contract with (ProviderNotContracted), or a method no contract of yours serves at all (NoProviderForMethod).

{
method: "card",
provider: "payplug", // optional: inferred when only one PSP fits
partialAuth: true, // only if the acquirer supports it
schemes: ["cb", "visa", "mastercard", "maestro"], // display order = this order
cardHolder: true, // ask for the holder's name
threeDSecure: "sca", // default: ask for the challenge
}
  • schemes — the accepted card marks, shown on the method row and the form heading in the order you list them. Available: cb, visa, mastercard, amex, maestro, diners, discover, jcb, china_union_pay. Unset, a default set applies — without Amex, which is the typical candidate for promotion to its own entry.

  • cardHolder — adds a “Cardholder name” field, first in the form.

  • partialAuth: true — the issuer may authorize less than requested. Omit it unless your acquirer supports partial approvals: a lower grant on an unsupported method is rejected by the orchestrator. A boolean, because that is the whole question — who decides the amount is never yours to configure (on a card, always the issuer).

  • createAliashow to register this card for later payments, as one block: { mode: "oneclick" | "subscription", recurringFrequency, recurringExpiry } — days between two payments, and the ISO date the registration expires. The three go together or not at all. Collecting the payer’s consent is yours, upstream: the orchestrator carries the request, it does not ask the question. It requires the authentication challenge, so a session that asks for a registration and threeDSecure: "frictionless" is refused at creation rather than quietly corrected.

    It is a recipe, not a decision: nothing is registered unless the order asks for it — see mustCreateAlias below.

  • threeDSecure — which 3-D Secure outcome you would prefer: sca asks for the authentication challenge, frictionless asks to be spared it. It is a preference: the issuer decides, and may challenge either way. Unset, sca applies — asking for frictionless is asking for an exemption (TRA), which moves the fraud liability to whoever requested it, so it is a decision to take deliberately rather than a speed knob.

Orders that will be charged again: mustCreateAlias

Section titled “Orders that will be charged again: mustCreateAlias”

Set mustCreateAlias: true on POST /api/sessions when the order will be followed by payments you initiate — a subscription, a deferred balance, a deposit released later. It says the payer’s means must be registered now, so you can charge it without them.

{
currency: "EUR",
total: 1000_00,
orderId: "EP-123456",
mustCreateAlias: true,
methods: [ /* … */ ],
}

It is the need; the card’s createAlias is the recipe. Neither works without the other: a card carrying the recipe registers nothing on an ordinary order, and an order asking for a registration with no method able to produce one is refused at creation (AliasNeedsRegistrableMethod) rather than handed to a payer with nothing payable on it.

And your payer is told: under the pay button, the card form states that the card will be saved and may be charged later for this order. The authorization is given knowingly, before the click — nothing for you to add on your page.

One archetype can appear twice with its own identity — a dedicated Amex entry routed to a dedicated acquirer, next to the domestic card entry:

{ method: "card", provider: "payplug", schemes: ["cb", "visa", "mastercard"], partialAuth: true },
{ method: "card", id: "card-amex", label: "American Express", schemes: ["amex"], provider: "amex-acquirer" },

Each instance is its own row, its own form, its own tranche label on the confirmation — and behaviours differ per instance (here the promoted Amex takes no partial approvals).

{ method: "applepay", provider: "payplug-retail" }

The real Apple Pay sheet — the one the OS draws — opens on the page. The elements run the whole exchange for you: the sheet opens on the click, the merchant validation and the encrypted token travel through your session, and the sheet closes on the real verdict. Your page composes <payplug-applepay> like any other method and does nothing else.

Three things are yours to provide, and none of them is code:

  • your domain, verified by Apple. Serve the association file at https://your-shop.example/.well-known/apple-developer-merchantid-domain-association (ask us for the file), and have the domain registered — Apple validates the page the payer is on, so this is per shop domain, not per platform;
  • a customer file on the session. Their platform refuses an Apple Pay payment without customer.email, firstName and lastName — create the session with them;
  • HTTPS, everywhere. The sheet only exists on secure pages.

The method’s row only appears in browsers that can pay — Safari, an Apple device with a card enrolled. Nothing to configure: the elements ask the browser, and a browser that cannot open the sheet is never offered a button that cannot act.

{ method: "paypal", provider: "payplug" },
{ method: "wero", provider: "payplug-retail" },

Both forms announce the redirect before it happens (“You will be redirected to…”), send the customer out, and the return lands back on your returnUrl — or on cancelUrl when the customer abandons, if you give it one. The landing is rebuilt from state: a refusal greets the customer with the refused form and its banner, a cancellation returns to the methods list with the tranche released — your page does nothing.

{ method: "ancv", provider: "payplug" }

The customer enters their Chèque-Vacances Connect id on the page, then confirms in the ANCV app — where they may approve a lower amount than requested. There is nothing to configure about that: partial approval is intrinsic to the method, always the customer’s own decision.

{
method: "oney",
provider: "payplug",
// WHICH plans this order proposes — by name. Required, and never empty.
plans: [
{ installments: 3, withFees: true },
{ installments: 3, withFees: false }, // a fee-free 3×, if your contract has one
// … a 4×, etc.
],
}

The form shows only the plans the amount is eligible for — each plan finances within its own window, and the ones out of reach are not sent to the browser at all. It renders the installment schedule (dates, per-installment amounts, financing fees, total cost, the credit advanced and the fixed APR), all computed by the platform: the page displays those figures, it does not derive them, and no rate ever reaches the browser. The chosen plan is recorded on the tranche — the confirmation recap reads “Oney 3×” — and the legal credit mention is built in.

A lender’s terms are a contract’s terms, and one contract may hold several plans with different reaches: a 3× open to France and Spain, a fee-free 3× reserved to France and to one sales channel, a 4× elsewhere. Those reaches live in your configuration on our side (see a method may not be offered everywhere), and each plan is filtered on its own:

  • a plan your contract carries but does not serve here is withdrawn, the others stand. Declaring the same plans for every market is therefore safe: a Spanish order simply gets the Spanish ones;
  • a plan no contract of yours carries is refused (UnknownBnplPlan) — that one is a mistake in your payload, and you hear it while integrating;
  • so is a plan whose amount window is in another currency than the session: it is withdrawn too, the lender not financing that trade.

If no plan is left, the method itself does not enter the session.

Financing needs the payer’s billing file

Section titled “Financing needs the payer’s billing file”

A financed order carries billing in full — the whole address, not just a country:

Field Why
country which national contract prices the loan
firstName, lastName a lender lends to an identified person
address, postalCode, city where that person lives
phone or mobilePhone how the lender reaches them

Missing any of them, the order is refused at creation (BnplNeedsBillingAddress) and the message names the first one. This is the one place where an incomplete context is a refusal rather than a filter, and the difference is deliberate: an ordinary method quietly withdraws, but you explicitly asked to offer credit. The root country does not stand in for this address — it exists so an ordinary method can be filtered without one.

One more thing is checked when the authorization is built, and named rather than forwarded as an opaque platform error: Oney wants a non-empty shipping.companyName — a working reference integration sends the merchant’s own name there when the buyer is an individual.

A lender does not always answer at the till: when the payer’s file goes under review, the session turns AWAITING and the page shows the waiting screen (<payplug-awaiting> — the clock, the e-mail promise, the financing tranche recapped). The payer’s journey is over; the verdict is not. If the lender accepts, the session ends PAID — the ending you already handle, just later. If it refuses, or the wait reaches its bound, it ends AWAITED_REFUSED: nothing was ever taken, the order was never paid. Either way your server hears the ending through the webhook — the payer left the page long before, so do not wait for a browser event to ship or cancel the order.

A lender does not finance an amount, it finances goods. So a session that offers Oney must describe what it sells, line by line: the session is refused at creation (BnplRequiresItemizedCart) if any cartItems line lacks one of the fields below, and the error names the line and the field.

There is deliberately no default for these. A platform that filled the gaps would be telling a lender that your goods are servicesforindividuals delivered by e-mail — a claim about your business that nobody made. It is refused while you are watching the response instead.

cartItems: [
{
label: "Forfaits de ski",
quantity: 1,
amount: { amount: 620_00, currency: "EUR" },
detail: {
itemId: "FORFAIT-6J", // your catalogue reference
category: "sport", // closed list, below
brand: "Espace Ski France",
discount: 0, // optional — minor units, 0 when there is none
tax: 0, // optional — minor units
delivery: {
type: "edelivery", // closed list, below
speed: "express", // standard | express
expectedDate: "2026-09-01", // YYYY-MM-DD
expectedDelay: 1, // in days
label: "email", // free text
},
},
},
];

Everything above is required except discount and tax — those two default to 0, which is what “none” means rather than a value we picked for you. The lender’s own documentation calls several of the others optional; it is wrong, and an authorization missing any of them is refused. We follow the platform’s behaviour, not its prose.

category — one of:

computersandsoftware, electronics, appliances, homeandgarden, fashion, healthandbeauty, jewellery, sport, leisureandhobbies, carsandmotorbikes, furniture, kidsandbaby, videogamesandconsoles, toysandgames, pets, foodanddrink, giftandflowers, entertainment, travel, auctionsandgroupbuying, servicesforindividuals, servicesforprofessionals.

delivery.type — one of storepickup, networkpickup, travelpickup, billingaddress, carrier, edelivery. delivery.speedstandard or express.

Who decided the lower amount changes what the customer must be told. You never write these words — they are the platform’s own classification, derived per method: a card whose config says partialAuth: true is mandated (the issuer imposes the amount), ANCV is always chosen (the holder picks it in their app), and no other method has the mechanics at all:

Policy Who decides The banner reads Then
"chosen" The customer (ANCV) “You authorized X of the Y requested” — no surprise, no question. The remaining balance simply grows; the customer completes with another method.
"mandated" The issuer (card) “Your bank only authorized X of the Y requested” — a genuine surprise. The customer chooses: accept the partial hold and pay the rest another way, or refuse it (the tranche is voided, the full amount is payable again).

The decision survives a page reload: it derives from session state, not from a UI transition.

One operation or two: how a tranche settles

Section titled “One operation or two: how a tranche settles”

The uniform model holds the money first (an authorization) and takes it when the session is covered (a capture). Some tranches skip the pair and take it in one operation. This is not a setting — the orchestrator decides per tranche — but it changes what your customer sees, so it is worth knowing why.

The reason is the bank statement. Some banks display the hold and the capture as two separate lines. The customer sees their amount twice, concludes they were charged twice, and calls you. One operation, one line.

A tranche settles directly when all of these hold:

  • it closes the session — its amount is exactly the remaining balance (first tranche or last, it makes no difference);
  • the PSP can do it on that method;
  • no partial grant is possible — a card’s partialAuth: true on a contract that actually supports it rules the shortcut out, since a grant that comes back lower would leave a balance owed with the money already taken;
  • nothing else is in flight — a verdict still out could free capacity and reopen the balance;
  • and where the method can do both, the settlement must be undoable. A hold costs nothing to release; money taken that no PSP can return is a debt to a person.

Methods that can only settle (Wero and Apple Pay at PayplugRetail have no authorization at all) never offer a partial amount: they pay the whole remaining balance or nothing. They stay in the list at all times — they simply close the session. A tranche started on them for a partial amount is refused by the orchestrator, not merely hidden by the form.

The waiting screen says what is happening — “Payment in progress…” rather than “Authorization in progress…”. Your customer never learns the word “authorization”, which names a plumbing they have no use for.

The way out changes. When a payer comes back from the PSP without finishing, the wait offers to resume or to abandon. On a tranche whose money could not be handed back, the abandon button is withdrawn: ending the WAIT would not end the CHARGE. Resuming stays.

A tranche can go straight from AUTHORIZING to CAPTURED, with no AUTHORIZED in between: there was no hold to sit in. attempt.operation in the session view tells you which of the two happened.

Nothing else moves: same statuses, same flags, same API.

Each tranche must fit a window: the session default, a per-method range override, or — for Oney — the matched plan’s window. The amount form enforces it with localized messages (“The minimum amount is…”, “The amount cannot exceed the remaining balance…”), and the orchestrator re-validates: the UI can’t be talked out of the rules.