Skip to content
In this section

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-payment with paymentType: "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.phone and 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 paymentInstructions containing 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 lifecycleStatus can remain pending even 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 returnUrl while 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 or GET /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:

  1. Server-to-server: call POST /payments-api/v1/initialize-payment with paymentType: "apm", a unique externalOrderRef, a unique Idempotency-Key, and the method/currency/country from the table below. The response should be 202 Accepted with requiresAction: true. Redirect methods include redirectFullPage: true and an actionUrl; Multibanco includes paymentInstructions and can also include the redirect.
  2. 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.
  3. Complete the buyer action: for redirect methods, open actionUrl in a top-level browser window (not an iframe). For Multibanco, render the returned Entity and Reference. Then wait for the webhook or poll GET /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 / +11111111112 test 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 returns 502 before you receive a definitive payment result, retry the unchanged request with the same externalOrderRef and Idempotency-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 return 503 with provider create is pending reconciliation; continue retrying the same request rather than minting a new key. A 409 with provider create requires manual reconciliation means automated recovery has stopped and Fynex support must resolve the provider charge. After a definitive terminal failed result, 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 apm rail and the chosen instrument;
  • your returnUrl host is allow-listed when you send one;
  • your frontend follows actionUrl as 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

returnUrl is optional only when another valid return-link source exists. You may omit it when you send an explicit returnLinks array or your seller account already has default return links. If the request has neither and the seller has no defaults, initialization returns 400 ("valid returnLinks are required"). The Fynex-hosted status page is a fallback after a valid return configuration has been resolved; omitting returnUrl alone 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 of returnUrl must be on it. A returnUrl whose host isn't allow-listed — or any returnUrl sent before you've configured any allowed hosts at all — returns 400 ("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

returnUrl alone 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 explicit returnLinks array instead, each entry a { "rel", "href", "method": "GET" } object with rel one of default, on_completed, on_failed, or on_cancelled. When you send returnLinks, it takes precedence and returnUrl is not used to derive a link (it still drives the Fynex-hosted status-page fallback). You don't need both: returnUrl covers 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.phone must be a valid E.164 number (+ and 8–15 digits). A malformed value is rejected with 400 before any payment is created. Omitting it is not an error: the payment simply keeps the redirect flow.
  • apmData.phone is the only source of the number. billingDetails.phone is 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. No apmData.phone, no push.
  • actionUrl is 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 failed and 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:

Buyer browserScheme / providerFynexredirectredirect backresolve token302Go to actionUrlYour UI, or the hosted pageAuthorizeBank page, app or QRFynex return handlerSigned token, server-sideFind destinationPayment's pre-validated targetBuyer landsOne of three destinations

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:

  1. the hosted-checkout success / failure URL configured on the payment, or
  2. the caller's returnUrl with ?status= and ?paymentId= query params appended, or
  3. 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 your returnUrl) can therefore read pending; the payment is finalized later. The return query uses one of status=pending, status=succeeded, or status=failed, and includes paymentId=<externalOrderRef>. Confirm the real outcome the same way you would for any payment: register a webhook or poll GET /payments-api/v1/payments/{payment_id} (the returned ?paymentId= is your externalOrderRef). 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:

  1. 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.
  2. 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:

  • allowedPaymentMethods is 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 paymentMethod values are worth presenting in your own UI before you call initialize-payment.

See also