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.
What every method shares
Section titled “What every method shares”| 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 method may not be offered everywhere
Section titled “A method may not be offered everywhere”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: [ /* … */ ],}countryis the market, and you declare it: we never read it offbilling.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.channelis 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). -
createAlias— how 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 andthreeDSecure: "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
mustCreateAliasbelow. -
threeDSecure— which 3-D Secure outcome you would prefer:scaasks for the authentication challenge,frictionlessasks to be spared it. It is a preference: the issuer decides, and may challenge either way. Unset,scaapplies — asking forfrictionlessis 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.
Promoted instances
Section titled “Promoted instances”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).
Apple Pay
Section titled “Apple Pay”{ 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,firstNameandlastName— 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.
PayPal, Wero
Section titled “PayPal, Wero”{ 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.
ANCV (Chèques-Vacances)
Section titled “ANCV (Chèques-Vacances)”{ 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.
Oney (pay in installments)
Section titled “Oney (pay in installments)”{ 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.
Each plan carries its own reach
Section titled “Each plan carries its own reach”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.
The verdict can take days
Section titled “The verdict can take days”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.
The cart becomes part of the payment file
Section titled “The cart becomes part of the payment file”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.speed — standard or express.
Partial authorization: chosen vs mandated
Section titled “Partial authorization: chosen vs mandated”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: trueon 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.
What you will notice
Section titled “What you will notice”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.
Amount windows
Section titled “Amount windows”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.