Alternative payment methods (APM)
Accept local payment methods (Bancontact, Multibanco, MB WAY, Wero, Swish) either server-to-server via /payments-api/v1/initialize-payment with paymentType "apm", or through Fynex-hosted checkout. Most methods redirect; Multibanco can also return entity/reference instructions for inline display.
Alternative payment methods (APMs) are the local, non-card ways buyers pay in their own
market — Bancontact in Belgium, Multibanco and MB WAY in Portugal, Wero in Belgium, Germany,
and France, and Swish in Sweden. Fynex offers them through a single payment provider that
fronts all of these schemes, on a dedicated payment rail (apm) that is distinct from the
card rail that carries card, Apple Pay, and Google Pay.
APMs are a sibling to Apple Pay and Google Pay in that they are wallet-like, buyer-initiated methods you surface at checkout — but they are architecturally different. There is no card token to decrypt and no card payload to forward. Most methods send the buyer to the scheme (a bank-selection page, an app handoff, or a QR code) to authorize. Multibanco can instead return an Entity and Reference that your own checkout renders for the buyer.
Fynex accepts APMs two ways:
- Server-to-server API — call
POST /payments-api/v1/initialize-paymentwithpaymentType: "apm"and the APM instrument from your own backend. Follow the redirect action Fynex returns, or render the structured payment instructions returned for Multibanco. Bearer-authenticated, the same endpoint used for card / Apple Pay / Google Pay. See Server-to-server API below. - Fynex-hosted checkout — create a checkout session as you would for a card checkout, and the Fynex-hosted page renders whichever APM tiles are available; the redirect handshake happens inside that page. See Hosted checkout below.
Both paths use the same asynchronous-settlement behavior. Redirect flows also converge on the same Fynex-hosted return step.
Instruments and availability
Five instruments are supported. Each is only offered for the currency (and, where the scheme is country-bound, country) combinations the provider actually supports it on:
| Instrument | Wire value | Currency | Country | Notes |
|---|---|---|---|---|
| Bancontact | bancontact |
EUR |
BE |
Belgium's domestic card/bank scheme. |
| Multibanco | multibanco |
EUR |
PT |
Portuguese reference-number / voucher payment. |
| MB WAY | mbway |
EUR |
PT |
Portuguese mobile-app payment. Offered alongside Multibanco for EUR/PT — the two cannot be told apart by currency/country, so the buyer picks between them at checkout. |
| Wero | wero |
EUR |
BE, DE, FR |
European account-to-account wallet. Requests outside these three countries are rejected before routing. |
| Swish | swish |
SEK |
SE |
Swedish mobile payment. |
A EUR/BE checkout therefore offers Bancontact and Wero; EUR/DE and EUR/FR
offer Wero; EUR/PT offers Multibanco and MB WAY; a SEK/SE checkout offers Swish.
Wero is not offered for other EUR countries, including Italy.
Note
This currency/country matrix is the ceiling of what a checkout can show. The actual set a given buyer sees is narrower — it is also gated on your account being routable for that method. See How hosted-checkout availability is decided.
How the buyer completes each method
The buyer action differs by method and integration style. It matters especially when you test:
- Bancontact completes on the redirect page itself — the buyer authorizes there and comes straight back.
- MB WAY completes on the redirect page by default. Server-to-server, you can instead keep
the buyer on your own checkout: send
apmData.phoneand the authorization is pushed to that number's MB WAY app, so there is nothing to redirect to. See Keeping the buyer on your checkout with MB WAY. - Multibanco server-to-server initiation can return
paymentInstructionscontaining an Entity and Reference. Render both with the response's top-level amount and currency; the buyer completes the payment through Portuguese online banking or an ATM. Fynex-hosted checkout continues to handle the provider redirect flow for you. - Wero hands off to a Wero code / QR flow. In live, the buyer approves in their Wero app. In sandbox, you can complete the flow with the Wero UAT test site — see Testing APMs in sandbox.
- Swish hands off to the buyer's Swish app and BankID. There is no card-number-entry
fallback. Sandbox completion needs Swish test credentials provisioned by Fynex / the
provider; otherwise the payment's
lifecycleStatuscan remainpendingeven though your redirect integration is working.
Important
Returned to your site is not the same as paid. APMs are asynchronous. The buyer can land back on your
returnUrlwhile the scheme is still processing, or while an app-based method is still waiting for approval. Treat the return as a UX step only; confirm the final outcome with webhooks orGET /payments-api/v1/payments/{payment_id}.
Testing APMs in sandbox
Use a Demo account and its token. Whether APM sandboxes are reached is decided by the account's operational mode, not the host — a Demo account reaches them on any Fynex host. Call the base URL your token was issued for: each environment keeps its own credential store, so a token only authenticates against its own host.
Do not send these test values to production. Your seller account still needs APMs enabled and
routable: GET /payments-api/v1/payment-methods should include apm in
allowedPaymentRails and the specific instrument in allowedPaymentMethods.
You can test with either integration style:
- Server-to-server: call
POST /payments-api/v1/initialize-paymentwithpaymentType: "apm", a uniqueexternalOrderRef, a uniqueIdempotency-Key, and the method/currency/country from the table below. The response should be202 AcceptedwithrequiresAction: true. Redirect methods includeredirectFullPage: trueand anactionUrl; Multibanco includespaymentInstructionsand can also include the redirect. - Hosted checkout: create a checkout session in staging, open the hosted checkout URL, and choose the APM tile. The tile only appears if the checkout's currency/country and your seller routing both support the method.
- Complete the buyer action: for redirect methods, open
actionUrlin a top-level browser window (not an iframe). For Multibanco, render the returned Entity and Reference. Then wait for the webhook or pollGET /payments-api/v1/payments/{payment_id}until terminal.
| Method | Request values | How to complete a sandbox payment | Failure-path test |
|---|---|---|---|
| Bancontact | paymentMethod: "bancontact", currencyCode: "EUR", countryCode: "BE" |
On the provider-hosted Bancontact page, choose the card/PAN entry flow and use PAN 60600599999899023 with expiry 01/2035 and any CVV. |
Use PAN 60600599999899080 with expiry 01/2035 to deny authentication. |
| Multibanco | paymentMethod: "multibanco", currencyCode: "EUR", countryCode: "PT" |
For server-to-server integration, verify that paymentInstructions.entity and .reference render on your checkout with the top-level amount/currency. The provider sandbox can also return a mock redirect; approve there if you exercise that fallback. Then wait for webhook/polling because the method is asynchronous. |
Abandon the instructions/redirect and keep polling to verify your pending-state handling, or choose the deny/fail option on the mock page when offered. |
| MB WAY | paymentMethod: "mbway", currencyCode: "EUR", countryCode: "PT" |
MB WAY has no scheme sandbox — the provider simulates it. Redirect flow: approve on the provider's mock-authorization page; if it asks for a phone number use +11111111112, and for a verification code use 777123. Inline flow: send apmData.phone: "+11111111112" and verify your page renders the waiting state from paymentInstructions, then that the payment reaches a terminal status without any redirect being followed. |
Choose the deny/fail option on the mock page when offered, or abandon the flow and keep polling to verify your pending-state handling. For the inline flow, send a well-formed number that is not enrolled and confirm your page handles the payment expiring. |
| Wero | paymentMethod: "wero", currencyCode: "EUR", countryCode: "BE", "DE", or "FR" |
On the provider page, select Pay with Wero code, copy the displayed code, open https://example-consumer-psps.werouat.eu, choose Give consent, paste the code, and choose Approve. |
Repeat the same Wero UAT flow and choose Deny. |
| Swish | paymentMethod: "swish", currencyCode: "SEK", countryCode: "SE" |
Swish sandbox completion requires provider-issued Swish test credentials / app access. If your sandbox account has them, complete the Swish app / BankID flow after redirect. | Use the provider-issued Swish failure path, or abandon the flow and verify that your integration handles a payment that stays pending correctly. |
Note
Bancontact is the only APM with static public card-like test PANs. Multibanco and MB WAY run on the provider's mock authorizer (approve/deny buttons, plus the
777123/+11111111112test values above where prompted), Wero uses the Wero UAT consent site, and Swish test credentials are provisioned separately — the scheme publishes none for sandbox. If you need Swish credentials, contact Fynex support before scheduling your sandbox test. The underlying scheme and provider sandboxes are hosted outside Fynex and can be intermittently flaky. If the API times out or returns502before you receive a definitive payment result, retry the unchanged request with the sameexternalOrderRefandIdempotency-Key; Fynex reconciles the same provider attempt and re-surfaces the current buyer action (a redirect and/or Multibanco payment instructions). While that reconciliation is still active, the unchanged retry can return503withprovider create is pending reconciliation; continue retrying the same request rather than minting a new key. A409withprovider create requires manual reconciliationmeans automated recovery has stopped and Fynex support must resolve the provider charge. After a definitive terminalfailedresult, start a new payment attempt with a fresh pair.
Suggested sandbox amounts
Use small, realistic amounts such as 10.00 in the method's currency. Avoid huge values in
sandbox unless Fynex support has asked you to test a specific limit or edge case. For each
scenario, use a fresh externalOrderRef and Idempotency-Key; reusing an idempotency key is
only for retrying the exact same payment attempt.
What a successful test proves
A complete APM sandbox test proves that:
- your seller token is valid in staging;
- your seller account is configured for the
apmrail and the chosen instrument; - your
returnUrlhost is allow-listed when you send one; - your frontend follows
actionUrlas a full-page redirect, or renders complete Multibanco Entity/Reference instructions when returned; and - your system waits for a terminal status via webhook or polling instead of treating the browser return as final payment success.
If a method does not appear on hosted checkout or initialize-payment rejects it, first
check the method/currency/country combination and GET /payments-api/v1/payment-methods.
An unsupported combination is rejected with 400 before Fynex creates or routes a payment;
for example, Wero with countryCode: "IT" is invalid. If
the buyer returns to your site with status=pending, keep polling or wait for your webhook.
That is normal for asynchronous APMs.
Server-to-server API
Submit the payment directly from your backend to
POST /payments-api/v1/initialize-payment, authenticated with your seller
Authorization: Bearer token. Set paymentType to apm and paymentMethod to one of the
five APM instruments. No card, wallet, or billing data is required — the buyer authenticates
at the scheme, not on your form.
curl -sS -X POST "$FYNEX_API/payments-api/v1/initialize-payment" \
-H "Authorization: Bearer $FYNEX_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"externalOrderRef": "ORDER-1042",
"amount": 49.99,
"paymentType": "apm",
"paymentMethod": "bancontact",
"currencyCode": "EUR",
"countryCode": "BE",
"returnUrl": "https://merchant.example/checkout/return"
}'
paymentMethod must be one of bancontact, multibanco, mbway, wero, or swish, and it
must be compatible with the currency/country you send — Fynex routes on the currency/country
matrix (see Instruments and availability) and on your
apm-rail terminals. An instrument that isn't compatible with paymentType: "apm" returns
400. An otherwise valid APM whose currency/country is outside its supported matrix also
returns 400, for example paymentMethod wero is not supported for currency EUR and country IT.
The example's returnUrl host (merchant.example) must be allow-listed on your seller
account first — see the note below.
Important
returnUrlis optional only when another valid return-link source exists. You may omit it when you send an explicitreturnLinksarray or your seller account already has default return links. If the request has neither and the seller has no defaults, initialization returns400("valid returnLinks are required"). The Fynex-hosted status page is a fallback after a valid return configuration has been resolved; omittingreturnUrlalone does not create that configuration.When you do send
returnUrl, this path fails closed. Your seller account must have an allowed-return-hosts whitelist configured, and the host ofreturnUrlmust be on it. AreturnUrlwhose host isn't allow-listed — or anyreturnUrlsent before you've configured any allowed hosts at all — returns400("returnUrl cannot be used until allowed return hosts are configured"). This fail-closed rule (stricter than the hosted-checkout success/failure URLs, which are opt-in) is what keeps the later return-step redirect a safe, pre-validated one rather than an open redirect. Configure your allowed return hosts before going live.
Note
returnUrlalone is all you need. Fynex derives a single default return link ({ "rel": "default", "href": <returnUrl>, "method": "GET" }) from it, so the redirect provider sends the buyer back to that URL for every outcome. If you need per-outcome destinations — a different landing page for completed vs. failed vs. cancelled — send an explicitreturnLinksarray instead, each entry a{ "rel", "href", "method": "GET" }object withrelone ofdefault,on_completed,on_failed, oron_cancelled. When you sendreturnLinks, it takes precedence andreturnUrlis not used to derive a link (it still drives the Fynex-hosted status-page fallback). You don't need both:returnUrlcovers the common single-destination case.
Response — follow the returned buyer action
A fresh request returns 202 Accepted:
{
"paymentId": "ORDER-1042",
"status": "provider_pending",
"lifecycleStatus": "pending",
"legacyStatus": "provider_pending",
"amount": 49.99,
"currencyCode": "EUR",
"requiresAction": true,
"actionUrl": "https://redirect.provider.example/checkout/abc123",
"redirectFullPage": true
}
| Field | Type | Meaning for APMs |
|---|---|---|
requiresAction |
boolean | true for a successful APM initiation while the buyer still needs to act. |
actionUrl |
string | Provider redirect URL when available. Send the buyer's browser here for redirect flows. |
paymentInstructions |
object | Structured inline instructions when available, so the buyer can complete on your own checkout. type names the format: payment_reference (Multibanco) adds entity and reference; app_notification (MB WAY, when you send apmData.phone) adds phone. Absent when the only action is the redirect. |
redirectFullPage |
boolean | When true, redirect the whole page (navigate the top-level window), not an iframe. |
lifecycleStatus |
string | pending until the scheme reports back — see Payment Lifecycle. (status and legacyStatus carry the previous value, provider_pending.) |
paymentId |
string | Your externalOrderRef; use it to poll the outcome. |
For a redirect response, your UI drives the redirect — Fynex does not redirect the buyer for
you on this path. Send the browser to actionUrl using a full-page navigation when
redirectFullPage is true. After the buyer completes or abandons at the scheme, they pass
back through a Fynex-hosted return step that 302s them to your returnUrl. See
The return leg below.
For Multibanco, the same response can include inline instructions:
{
"paymentId": "ORDER-1043",
"status": "provider_pending",
"lifecycleStatus": "pending",
"legacyStatus": "provider_pending",
"amount": 49.99,
"currencyCode": "EUR",
"requiresAction": true,
"paymentInstructions": {
"type": "payment_reference",
"entity": "11854",
"reference": "999999964"
},
"actionUrl": "https://redirect.provider.example/checkout/def456",
"redirectFullPage": true
}
Display entity and reference as plain text, alongside the top-level amount and
currencyCode; never interpret provider values as HTML. Fynex only returns a structured
instruction after validating the Multibanco Entity as five ASCII digits and the Reference as
nine ASCII digits. Do not treat displaying or copying the instructions as payment success;
keep the payment pending until a webhook or payment lookup reports a terminal state. If both
instructions and actionUrl are present, you may keep the redirect as a fallback.
Keeping the buyer on your checkout with MB WAY
By default MB WAY sends the buyer to a provider-hosted page. To keep them on your own
checkout instead, collect their MB WAY phone number and send it as apmData.phone:
{
"externalOrderRef": "ORDER-100106",
"amount": 10.50,
"paymentType": "apm",
"paymentMethod": "mbway",
"currencyCode": "EUR",
"countryCode": "PT",
"apmData": { "phone": "+351912345678" }
}
The authorization is then pushed to that number's MB WAY app and the response carries an
app_notification instruction:
{
"paymentId": "ORDER-100106",
"status": "provider_pending",
"lifecycleStatus": "pending",
"legacyStatus": "provider_pending",
"requiresAction": true,
"paymentInstructions": {
"type": "app_notification",
"phone": "+351912345678"
},
"actionUrl": "https://redirect.provider.example/checkout/ghi789",
"redirectFullPage": true
}
There is nothing to render but a waiting state — the buyer approves in their app. Rules:
apmData.phonemust be a valid E.164 number (+and 8–15 digits). A malformed value is rejected with400before any payment is created. Omitting it is not an error: the payment simply keeps the redirect flow.apmData.phoneis the only source of the number.billingDetails.phoneis never used for the push, even a Portuguese one — it is a contact detail collected for something else, and the buyer whose handset rings has not agreed to that by giving you a billing contact. The push goes to the number registered with the buyer's MB WAY account, which need not be your billing contact, so ask for it explicitly. NoapmData.phone, no push.actionUrlis still returned — keep it as a fallback. The push has a short window (below) and the same charge is payable either way.- The window is 4 minutes. MB WAY is not a voucher method: if the buyer does not approve
in time the payment moves to
failedand you must start a new one. - Do not treat the instruction as payment success. Wait for a terminal status via your
webhook or by polling
GET /payments-api/v1/payments/{payment_id}. - Send a number the buyer gave you for this payment. Nothing upstream validates it: a number that is not enrolled with MB WAY is accepted and simply never authorizes, so the only symptom is a payment that expires. Do not push to a number the buyer supplied for some other purpose.
This is a server-to-server capability. Fynex-hosted checkout continues to drive MB WAY through
the provider redirect, and apmData is ignored there.
Submitting the same Idempotency-Key with the same business intent returns the existing
payment (200 OK) rather than creating a duplicate; reusing it with a different amount /
currencyCode / externalOrderRef / apmData.phone returns 409.
apmData.phone is part of that intent because it decides where the authorization goes. So if
the buyer mistyped their number, retrying with a corrected apmData.phone under the same
Idempotency-Key is a 409, not a correction — the original payment is already pushing to
the original number. Start a new payment with a fresh Idempotency-Key and externalOrderRef
instead, and let the first one expire.
Hosted checkout
If you already use Fynex-hosted checkout, you get APMs with no extra integration work. Create the checkout session exactly as you would for a card-only checkout; the Fynex-hosted page renders whichever APM tiles are available for that session (see How hosted-checkout availability is decided), and drives the redirect handshake inside the page. Your integration never sees a provider token and never calls a provider endpoint — you only observe the final outcome via webhook, polling, or the return redirect. See Hosted checkout for the session-creation reference.
The return leg
Both entry paths — server-to-server and hosted checkout — return the buyer through the same Fynex-hosted return step once they finish authorizing at the scheme:
On server-to-server payments your UI sends the buyer to actionUrl; on hosted checkout the
Fynex page does it. The buyer lands on the hosted-checkout success / failure URL, the caller's
returnUrl with ?status=&paymentId=, or the Fynex-hosted status page (no caller destination).
The return step carries a signed token Fynex issued when it started the payment; Fynex resolves it server-side and 302s to a destination it reads from already-persisted, already-validated payment data:
- the hosted-checkout success / failure URL configured on the payment, or
- the caller's
returnUrlwith?status=and?paymentId=query params appended, or - a Fynex-hosted status page when the payment has neither of the above.
The destination is never taken from the incoming request beyond the signed token, so the step cannot be turned into an open redirect. A missing, invalid, or expired token renders an "Invalid Link" page rather than redirecting anywhere.
Note
An APM payment can still be pending when the buyer is returned — account-to-account and voucher schemes settle asynchronously. The status page (and the
?status=on yourreturnUrl) can therefore readpending; the payment is finalized later. The return query uses one ofstatus=pending,status=succeeded, orstatus=failed, and includespaymentId=<externalOrderRef>. Confirm the real outcome the same way you would for any payment: register a webhook or pollGET /payments-api/v1/payments/{payment_id}(the returned?paymentId=is yourexternalOrderRef). Do not treat "returned to my URL" as "paid" — wait for a terminal status. See Polling & SSE and Webhooks.
How hosted-checkout availability is decided
On hosted checkout, whether an APM tile appears on a given session is the intersection of two gates:
- The currency/country matrix above — a method is only a candidate when the checkout's currency (and country, for the country-bound schemes) matches the table.
- Routability — your account must have at least one active
apm-rail terminal whose currency / country / operational-mode can carry the checkout. Fynex never renders a tile it cannot route, so a buyer can't pick a method that would then fail on submit.
If nothing survives both gates, the hosted checkout simply doesn't show an APM block — the buyer falls back to card (or whatever else is configured). There is no broken-tile state.
The server-to-server path is gated by the same two constraints, but you learn about a
mismatch differently: an instrument that doesn't match the currency/country you sent, or that
your account can't route, is rejected at initialize-payment (400, or a routing 502)
rather than silently hidden. Either way, don't hard-code which APMs to offer — discover what
your account supports with the payment-methods endpoint, exactly as you would for any other
instrument.
Discovering what's enabled
GET /payments-api/v1/payment-methods returns the instruments and rails configured on your
seller account. Check allowedPaymentRails for apm and allowedPaymentMethods for the
specific instrument values before you rely on APMs being available:
curl -sS "$FYNEX_API/payments-api/v1/payment-methods" \
-H "Authorization: Bearer $FYNEX_TOKEN"
{
"merchantId": "16ddc5f1-39ca-4b7c-9bfa-bf5779b8c117",
"allowedPaymentMethods": ["card", "bancontact", "wero"],
"allowedCurrencies": ["EUR"],
"allowedPaymentRails": ["card", "apm"]
}
See Payment methods & capabilities for the full response shape.
Two things to keep in mind when reading this response for APMs:
allowedPaymentMethodsis the account-level ceiling. It tells you which instruments your account is configured for. It does not guarantee a given instrument works on every payment — the per-checkout currency/country matrix and terminal routability still apply (see above).- Use it to decide what to offer, on either path. For hosted checkout, it tells you
whether to route a buyer to a Fynex-hosted page at all (the tiles themselves are rendered by
Fynex). For the server-to-server path, it tells you which
paymentMethodvalues are worth presenting in your own UI before you callinitialize-payment.
See also
- Hosted checkout — create the checkout session that renders APM tiles for the hosted path.
- Payment methods & capabilities — discover the
apmrail and enabled instruments for your account. - Apple Pay / Google Pay — the other wallet-like instruments, on the
cardrail; like APMs they also offer a server-to-server path. - Polling & SSE / Webhooks — confirm the final outcome of an asynchronously-settling APM payment.