Documentation
Connectors
Preview

Worldpay Corporate Gateway

Preview guidance for Worldpay Corporate Gateway card payments, saved-card checkout, Flo-managed subscriptions, 3DS Flex, and refunds, capture, and release through FloPay.

Preview: not ready for production payments

Certified Worldpay gateways can activate and route eligible Full, Auto, and Confirm checkout in Preview. Do not use this Preview for production payments until the required Flo Payments Operations approval is recorded and your production merchant code has completed live 3DS verification with Worldpay and Cardinal.

What you can do

  • Store a Worldpay Corporate Gateway merchant code and its XML credentials as a FloPay gateway, with separate gateways for Worldpay Secure Test and production. When Worldpay has issued a recurring merchant code, register it as a second, off-session gateway.
  • Test the stored XML credentials against Worldpay without changing the gateway.
  • Review the capability approval that Flo Payments Operations records for your merchant code: its currencies, countries, card schemes, operations, and certifications.
  • Activate an approved gateway for Full-mode card payments, eligible saved-card Auto and Confirm checkout, and Flo-managed subscription payments, including certified 3DS Flex journeys.
  • Refund, capture, and release Worldpay payments through the same payments API as Stripe payments, once Flo Payments Operations approves each operation for your merchant code.
  • Keep your checkout code as it is. FloPay decides on the server, from your active gateways and their approval, whether a checkout can use Worldpay. No SDK version, header, or setting opts a checkout in.

Who this is for

This connector is for merchants that hold a Worldpay Corporate Gateway (WPG XML Direct) merchant code and want FloPay to process card payments through it. FloPay client owners and admins configure the gateway. Developers keep using the provider-neutral FloPay SDK and REST API, so there is no Worldpay-specific checkout code to write.

The connector covers the Worldpay Corporate Gateway XML Direct integration only. It does not cover Access Worldpay, Worldpay Hosted Payment Pages, or Worldpay for Platforms.

Prerequisites

  • A buyer-present merchant code for each environment you connect (Worldpay Corporate Gateway XML Direct), with an XML username and password issued for that merchant code and environment. When Worldpay has issued a separate recurring merchant code, you also need that code, its own XML credentials, and a separate Operations approval. A personal Worldpay Merchant Interface login is not an XML credential.
  • A FloPay client where you are an owner or admin.
  • An active, user-owned FloPay API token if you configure the gateway through the REST API.
  • A capability approval from Flo Payments Operations for each merchant code and environment. Merchants cannot create or change it. Contact FloPay to ask about approval.
  • For 3DS Flex, a Cardinal API ID, organization unit ID, and HMAC key issued for the merchant code and environment, plus at least one HTTPS callback origin for Operations to approve. The continuation seal key is FloPay platform configuration, not a merchant credential.
  • A Full-mode checkout that collects the buyer's country, or an eligible saved-card Auto and Confirm checkout, built with @flopay/react, @flopay/js, or your own create request. Routing eligibility does not depend on an SDK version or any other client setting.

Setup

FloPay stores your Worldpay connection as a gateway record whose providerType is worldpay_corporate_gateway. One gateway represents one merchant code in one environment, and a client cannot hold two Worldpay gateways for the same merchant code in the same environment.

When Worldpay has issued a recurring merchant code, register it as a second gateway with sessionMode: off_session, its own XML credentials, and a separate Operations approval. Merchants with one merchant code omit sessionMode (it defaults to both) and see no change. Registering the recurring code makes it eligible for approved merchant-initiated use; FloPay still owns every subscription and decides whether each saved-card charge is eligible.

Client owners and admins manage gateways in Settings > Gateways or through the settings/gateways REST API. Members cannot manage gateway settings. The create and edit forms include Session mode with Both, On-session, and Off-session; new gateways preselect Both. The helper text is: Both serves buyer-present checkout and merchant-initiated charges. On-session is checkout and setup only. Off-session is renewals, rebills, and saved-card charges. For saved cards, Confirm and buyer-started Auto checkout use the on-session pool, while merchant-initiated charges use the off-session pool. Choose Off-session for the recurring merchant code. A Worldpay recurring merchant code belongs on a separate off-session gateway with its own XML credentials and Operations approval. The setup sequence uses POST /v1/settings/gateways, PATCH /v1/settings/gateways/:id, POST /v1/settings/gateways/:id/test, and POST /v1/settings/gateways/:id/activate. See Gateway settings for the provider-neutral sessionMode contract.

Sandbox and production

FloPay environmentWorldpay environmentUse it for
stageSecure TestSetup and connection tests with a Secure Test merchant code and XML credentials. Secure Test moves no funds and does not contact card issuers.
productionProductionYour live merchant code, with XML credentials issued for production.

Each environment needs its own gateway, merchant code, and XML credentials. Never reuse Secure Test credentials on a production gateway. The provider, environment, and merchant code lock once FloPay validates the connection, so moving to production means creating a new gateway. A Secure Test result does not prove production behavior.

Secure Test journey fixtures

Use these public Worldpay cardholder-name values only with a Secure Test merchant code:

JourneyCardholder-name value
Frictionless success3DS_V2_FRICTIONLESS_IDENTIFIED
Frictionless refusal3DS_V2_FRICTIONLESS_REJECTED
Challenge success3DS_V2_CHALLENGE_IDENTIFIED
Challenge failure3DS_V2_CHALLENGE_UNKNOWN_IDENTITY

These are cardholder-name values only. Choose a test card from Worldpay's published 3DS testing values. Do not copy a PAN into application code, documentation, logs, or support requests.

Secure Test moves no funds, does not contact card issuers, and does not prove production Cardinal or live issuer 3DS behavior. Complete the production approval and live verification gates before taking production payments.

Gateway lifecycle

StateMeaning
draftNew or incomplete. It is not used for payments.
activeComplete, approved, validated, and eligible for checkout routing.
inactiveDeactivated. It keeps its configuration and can be reactivated.

Every new gateway starts as a draft. A Worldpay gateway remains draft until its credentials, Operations approval, and applicable activation gates are complete. See Activation.

Credentials and secret handling

FieldBehavior
worldpayMerchantCodeYour Worldpay merchant code. It is the only readable Worldpay value, returned as worldpayMerchantCode and, once the connection is validated, as providerAccountId.
worldpayXmlUsernameThe XML username for the merchant code and environment. Write-only.
worldpayXmlPasswordThe XML password for the merchant code and environment. Write-only.
worldpayThreeDsApiId, worldpayThreeDsOrganizationUnitId, worldpayThreeDsHmacKeyOptional 3DS Flex credentials. Write-only.
sessionModeOptional on create and update. both, on_session, or off_session. Omitted values default to both. Reads always present it. It remains editable after connection validation.

3DS Flex credentials are used only on FloPay's server for a certified 3DS Flex route. They are never returned to SDK or hosted checkout code. Worldpay gateways do not use publishableKey, secretKey, or webhookSigningSecret. The worldpayCapabilities record belongs to Flo Payments Operations; see Operations approval.

Save Cardinal credentials from your server or through the Dashboard, never from checkout code:

curl -X PATCH https://api.flopay.com/v1/settings/gateways/GATEWAY_ID \
  -u "$FLOPAY_CLIENT_ID:$FLOPAY_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "worldpayThreeDsApiId": "YOUR_3DS_API_ID",
    "worldpayThreeDsOrganizationUnitId": "YOUR_3DS_ORGANIZATION_UNIT_ID",
    "worldpayThreeDsHmacKey": "YOUR_3DS_HMAC_KEY"
  }'

Read responses never return a write-only value. They report worldpayXmlUsernameConfigured, worldpayXmlPasswordConfigured, worldpayThreeDsApiIdConfigured, worldpayThreeDsOrganizationUnitIdConfigured, and worldpayThreeDsHmacKeyConfigured instead. There is no way to retrieve a submitted credential through the API or Dashboard.

To rotate a credential, submit its replacement in a normal update. Omitted credential fields keep their stored values. When a create or update includes a credential and the gateway then holds a merchant code, XML username, and XML password, FloPay validates that set with Worldpay before it saves anything. A rejected set returns 422 and leaves the stored credentials unchanged.

Never put real merchant codes, XML credentials, or 3DS keys in code samples, screenshots, URLs, logs, or support requests.

1. Create a draft

In the Dashboard, open Settings > Gateways > Add gateway, choose Worldpay Corporate Gateway and the environment, leave Session mode on Both for a buyer-present merchant code (new gateways preselect Both), then create the draft. Through the API, omit sessionMode so it defaults to both:

curl -X POST https://api.flopay.com/v1/settings/gateways \
  -u "$FLOPAY_CLIENT_ID:$FLOPAY_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Worldpay (Secure Test)",
    "providerType": "worldpay_corporate_gateway",
    "environment": "stage",
    "currency": "YOUR_CURRENCY",
    "countries": ["YOUR_COUNTRY"],
    "priority": 0,
    "worldpayMerchantCode": "YOUR_MERCHANT_CODE"
  }'

currency is the ISO 4217 code the gateway serves, and countries lists the ISO 3166-1 alpha-2 buyer countries it serves. Both must match your capability approval: activation requires the currency and every listed country to be approved, and a Worldpay gateway without countries cannot be activated. Creating a second gateway for a merchant code that already has one in the same environment returns 409 with Another Worldpay gateway already uses this merchant code in the selected environment.

To register a recurring merchant code, create another draft. In the Dashboard, set Session mode to Off-session. Through the API:

curl -X POST https://api.flopay.com/v1/settings/gateways \
  -u "$FLOPAY_CLIENT_ID:$FLOPAY_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Worldpay recurring (Secure Test)",
    "providerType": "worldpay_corporate_gateway",
    "environment": "stage",
    "currency": "YOUR_CURRENCY",
    "countries": ["YOUR_COUNTRY"],
    "priority": 1,
    "sessionMode": "off_session",
    "worldpayMerchantCode": "YOUR_RECURRING_MERCHANT_CODE"
  }'

Save that gateway's own XML credentials, test the connection, and wait for a separate Operations approval. The recurring code is a different merchant code, so it does not hit the duplicate-merchant-code 409.

2. Save and test credentials

Save the XML credentials on the draft. FloPay validates the complete set with Worldpay, captures the merchant code as providerAccountId, and stamps connectionValidatedAt the first time validation succeeds.

curl -X PATCH https://api.flopay.com/v1/settings/gateways/GATEWAY_ID \
  -u "$FLOPAY_CLIENT_ID:$FLOPAY_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "worldpayXmlUsername": "YOUR_XML_USERNAME",
    "worldpayXmlPassword": "YOUR_XML_PASSWORD"
  }'

Test the stored credentials at any time with Test connection in the Dashboard or through the API:

curl -X POST https://api.flopay.com/v1/settings/gateways/GATEWAY_ID/test \
  -u "$FLOPAY_CLIENT_ID:$FLOPAY_API_TOKEN"

The test sends Worldpay a read-only order inquiry with the stored merchant code and XML credentials. It moves no money and does not change the gateway. The response is a GatewayTestResultResponseDto:

{
  "success": true,
  "providerAccountId": "YOUR_MERCHANT_CODE",
  "error": null
}

A failed test returns success: false, providerAccountId: null, and a sanitized error that never contains credentials:

{
  "success": false,
  "providerAccountId": null,
  "error": "Worldpay rejected the XML credentials for the secure-test environment. Check the merchant code, username, password, and environment."
}

Troubleshooting lists every sanitized message and what to do about it.

3. Operations approval

Flo Payments Operations records what each merchant code may do, separately for each environment, in the gateway's worldpayCapabilities record. Merchants can read the record but not write it: a create or update that includes worldpayCapabilities returns 403 with Worldpay capability records require Operations approval. Until a record exists, the Dashboard shows Awaiting Operations approval.

FieldMeaning
versionRecord schema version, currently 1.
approvalReferencePrivacy-safe reference for the Operations approval.
approvedAt, expiresAtThe approval window. Outside it, activation and checkout routing both fail.
currenciesISO 4217 currencies the merchant code may use.
countriesISO 3166-1 alpha-2 countries the merchant code may serve.
cardSchemesCard schemes the merchant code may accept.
operationsApproved operations, from purchase, authorization, capture, void, refund, status, order_inquiry, order_notifications, three_ds, and stored_credentials.
orderNotificationsCertified, pcivaultProxyCertified, pcivaultBinMetadataCertified, threeDsCertified, storedCredentialsCertifiedThe certifications behind those operations.
threeDsCallbackOriginsHTTPS origins allowed to resume a 3DS Flex challenge. Operations records the approved origins for the merchant's checkout sites.
storedCredentialCustomerInitiatedReasonOptional approval for buyer-present saved-card reuse. Its only value is UNSCHEDULED; omission keeps this use disabled.
storedCredentialMerchantInitiatedReasonsOptional approved unattended purposes: RECURRING for Flo-managed subscription rebills and UNSCHEDULED for separately consented one-off charges. Omission keeps merchant-initiated use disabled.
externalSchemeReferenceApprovedOperations approval to quote a card-scheme transaction reference issued through another processor. Operations records it after a Secure Test exercise on this merchant code; omission keeps it disabled.

Every list is an explicit allowlist, and an empty or missing value never means all. An approved operation is not necessarily available: Supported features shows what this release can run.

4. Order Notifications

Worldpay reports order changes through Order Notifications. This is an infrastructure callback from Worldpay to FloPay, not a merchant-facing webhook. FloPay accepts it only over mutual TLS, from a Worldpay client certificate that Flo Payments Operations has certified for the environment. There is no webhook signing secret to copy. Do not configure a Worldpay signing secret, and do not handle a raw Worldpay payload in your application; leave webhookSigningSecret unset. Operations sets up and certifies notifications for your merchant code, and orderNotificationsCertified in the approval record shows the result, as does the gateway's Webhook setup section in the Dashboard.

FloPay keeps each bounded notification envelope private and durably stored before acknowledgment. Deliveries are deduplicated and processed in order. FloPay applies authenticated Order Notifications in status order and reconciles them monotonically to the pinned logical payment, so older evidence cannot downgrade a terminal result. The reconciled outcome updates the FloPay payment, invoice, and subscription state, then drives the normal merchant-facing webhooks. A late capture notification can therefore complete a pending payment without creating a second authorization.

5. Activation

curl -X POST https://api.flopay.com/v1/settings/gateways/GATEWAY_ID/activate \
  -u "$FLOPAY_CLIENT_ID:$FLOPAY_API_TOKEN"

Activation checks completeness first. A gateway missing worldpayMerchantCode, worldpayXmlUsername, worldpayXmlPassword, or worldpayCapabilities returns 409 with Activation requires a complete provider connection. Missing: followed by the missing fields.

Every activation also requires a current approval window, the gateway currency and every gateway country in the approved allowlists, at least one approved card scheme, order_inquiry, certified order_notifications, PCIVault outbound proxy certification, and an approved money operation: purchase or authorization.

If the approval does not include three_ds, an otherwise eligible gateway can activate without 3DS Flex. If the approval includes three_ds, activation additionally requires threeDsCertified; the write-only worldpayThreeDsApiId, worldpayThreeDsOrganizationUnitId, and worldpayThreeDsHmacKey; pcivaultBinMetadataCertified; and at least one entry in threeDsCallbackOrigins that uses HTTPS.

The error Worldpay 3DS capability requires a configured continuation seal key. identifies a FloPay platform condition, not a merchant field. Contact FloPay with the gateway ID and sanitized error rather than changing checkout code or searching for a missing setting.

If the approval includes stored_credentials, activation requires storedCredentialsCertified. The optional reasons control which stored-credential uses are eligible after activation: storedCredentialCustomerInitiatedReason must be UNSCHEDULED for buyer-present saved-card reuse, while storedCredentialMerchantInitiatedReasons may list RECURRING for Flo-managed subscription rebills or UNSCHEDULED for an independently consented unattended one-off charge. A current certified stored_credentials record can activate when both reason fields are omitted, but saved-card reuse remains disabled until Flo Payments Operations records a matching reason. Missing, expired, environment-mismatched, or uncertified approval still fails closed. Flo Payments Operations records these fields; they are not checkout inputs.

Checkout

Worldpay is a server-side card gateway. When a checkout can use a Worldpay gateway, the same hosted card form that buyers already see captures the card, and FloPay charges it through Worldpay server-side. There is no Worldpay browser SDK, key, or checkout code to add.

How a checkout reaches Worldpay

FloPay decides on the server whether a session can use Worldpay, from your active gateways and the approval Flo Payments Operations recorded for them. POST /v1/checkouts/sessions takes the same provider-neutral request for every gateway: there is no header, token, field, or SDK setting that selects Worldpay or opts a checkout in. This applies to every create path:

  • FloPayCheckout with createSession, PaymentAPI.createAndFetchSession, and PaymentAPI.createDetachedSession in Full mode, where the buyer enters the card in the hosted form.
  • Redirect sessions from createCheckoutSession, server-side creates with @flopay/node, and requests you build yourself. FloPay applies the same routing rules whichever supported FloPay client creates the session.
  • Eligible saved-card Auto and Confirm sessions use POST /v1/checkouts/sessions/:id/process without provider fields. Full, Confirm, and buyer-started Auto checkout use the on-session pool (both and on_session). Merchant-initiated charges (renewals, rebills, dunning, and POST /v1/payments) use the off-session pool (both and off_session). FloPay resolves the Flo card, its available processor bindings, and its approved purpose on the server. See Gateway settings.

A checkout needs no Worldpay-specific props:

import { FloPayCheckout } from '@flopay/react';
import type { DeclineEvent, FloPayError, InlineSessionDraft, PaymentResult } from '@flopay/shared';

const createSession: InlineSessionDraft = {
  clientId: 'YOUR_FLOPAY_CLIENT_ID',
  currency: 'YOUR_CURRENCY',
  products: [{ code: 'YOUR_PRODUCT_CODE', quantity: 1 }],
  account: { userId: 'YOUR_BUYER_ID', email: 'BUYER_EMAIL', country: 'BUYER_COUNTRY' },
  successUrl: 'https://YOUR_DOMAIN/success',
  cancelUrl: 'https://YOUR_DOMAIN/checkout',
};

export function Checkout() {
  return (
    <FloPayCheckout
      createSession={createSession}
      onComplete={(result: PaymentResult) => {
        if (result.status === 'succeeded') window.location.assign('/success');
      }}
      onDecline={(decline: DeclineEvent) => console.info(decline.code)}
      onError={(error: FloPayError) => console.error(error.code)}
    />
  );
}

If you build the create request yourself, send the same body the SDK sends and read data.gateways from the response. Do not add gateway selection or capability headers to the request: FloPay owns that decision, and a client-side copy of it would drift from your real gateway state.

Which sessions route to Worldpay

FloPay routes a session's cards to a Worldpay gateway only when all of these hold:

  1. The session is a Full-mode checkout where the buyer enters a card in the hosted form, or an eligible saved-card Auto or Confirm checkout. Standalone card-setup sessions remain unavailable for Worldpay.
  2. The Worldpay gateway is active and its approval window is current.
  3. The approval includes purchase for automatic capture or authorization for manual capture, together with order_inquiry, certified order_notifications, and PCIVault outbound proxy certification.
  4. The session currency matches the gateway currency and is in the approved currencies.
  5. The buyer country (account.country) is set, is one of the gateway countries, and is in the approved countries.
  6. A new card follows the buyer-present route. A saved card needs a first consented charge on record, purpose-specific consent, certified stored_credentials, and the matching customer-initiated or merchant-initiated reason. When its first charge used another processor, the cross-processor approval gates below also apply. A Flo-managed subscription can use Worldpay only when its first payment or rebill passes those checks.

A routed session carries data.gateways.worldpayCorporateGateway in CheckoutSessionResponseDto:

{
  "publishableKey": null,
  "environment": "stage",
  "enabledPaymentMethods": [],
  "enabledPaymentMethodCountries": {}
}

The entry carries no browser credential or payment method, and the SDK never lists it in the normalized providers. Your checkout code does not need to check for it.

Saved cards and Flo-managed subscriptions

After a successful buyer-present first payment made with buyer consent, FloPay retains the opaque card credential in the Flo vault together with the initial network evidence, including the card network's transaction reference. Once that first consented charge is on record, the same Flo card can be charged on Stripe or Worldpay when the selected account and purpose are approved. The card number and security code never enter FloPay application memory, and the checkout client never receives the vault token, scheme reference, consent evidence, merchant code, or gateway selector.

A card first charged on another processor can be prepared on the chosen Worldpay merchant code from the PCIVault credential FloPay already holds. Preparation makes no provider call and performs no Worldpay tokenization. Quoting that first transaction reference to Worldpay requires externalSchemeReferenceApproved in worldpayCapabilities, which Operations records only after a Secure Test exercise on that merchant code. Omission keeps this approval off. Without recorded reuse consent, a Stripe-saved card prepared on Worldpay is eligible for buyer-present payments only.

A later Confirm checkout is customer-initiated reuse. FloPay may use UNSCHEDULED only when that purpose is approved for the chosen Worldpay merchant code. A scheduled Flo-managed subscription rebill is merchant-initiated and uses RECURRING when recurring consent and the account approval both allow it. An unattended one-off charge needs independently recorded unscheduled consent and an approved merchant-initiated UNSCHEDULED purpose. Recurring consent does not authorize that use, and an ordinary one-time item checkout does not create recurring consent.

For the reverse Worldpay to Stripe off-session path, the Stripe gateway must have stripeMitExemptionApproved. Only Operations may set it, and omission keeps it off. The Worldpay and Stripe approvals are independent.

Eligibility is purpose-specific and fails closed before another provider or vault call when consent, certification, network evidence, or an approval is missing or mismatched. The provider-neutral outcome is stored_payment_method_unsupported; show the buyer-safe message and let the buyer choose another payment method. Internal refusal reasons and stored-credential evidence stay server-side.

FloPay owns the saved-card identity, subscription schedule, invoices, retry, dunning, subscription state, reconciliation, and merchant-facing webhooks. Worldpay processes the card charge and does not receive a subscription object or public subscription identifier. It is not the subscription system of record.

Merchant API one-off charges

An eligible saved-card charge through POST /v1/payments can use Worldpay. The request stays provider-neutral and requires Idempotency-Key; see Charge an existing customer for its response and retry contract.

If the caller crashes or retries while the result is uncertain, FloPay uses order_inquiry on the exact pinned Worldpay order and merchant code. It settles that logical payment before returning or advancing it and never submits the same order twice.

Saved-card process contract

POST /v1/checkouts/sessions keeps the existing CheckoutSessionResponseDto envelope: { data }, plus optional autoProcessingError, autoProcessingAttempted, and autoProcessingPending fields for Auto checkout. The released SDK handles those optional fields. It does not add a Worldpay selector to the create body.

For an eligible saved card, a direct REST client calls the provider-neutral process endpoint with the session token. ProcessCheckoutBodyDto has no gateway, merchant-code, vault-token, scheme-reference, eligibility, or consent-evidence field:

curl -X POST https://api.flopay.com/v1/checkouts/sessions/SESSION_ID/process \
  -H "x-checkout-session-token: $CHECKOUT_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "accountData": {
      "userId": "YOUR_BUYER_ID",
      "email": "BUYER_EMAIL"
    }
  }'

An immediate completion returns HTTP 204 with no body. If the existing attempt is still settling, HTTP 202 includes Retry-After and a CheckoutSessionProcessingDto; wait for its statusUrl instead of submitting another payment:

{
  "type": "checkout_processing",
  "sessionId": "SESSION_ID",
  "retryAfterMs": 1000,
  "statusUrl": "/v1/checkouts/sessions/SESSION_ID/status",
  "sessionUrl": "/v1/checkouts/sessions/SESSION_ID"
}

When a retained card cannot fund this purpose, CheckoutProcessExceptionDto.code? is the optional stable discriminator. Show its buyer-safe message and offer another payment method. Do not expose the internal refusal reason:

{
  "type": "unknown",
  "message": "This saved payment method can’t be used for this payment. Choose another payment method.",
  "code": "stored_payment_method_unsupported"
}

Saved-card SDK behavior

@flopay/shared exports STORED_PAYMENT_METHOD_UNSUPPORTED_CODE and STORED_PAYMENT_METHOD_UNSUPPORTED_MESSAGE for the stable machine code and buyer-safe recovery copy. The optional error code keeps older responses compatible.

@flopay/js uses PaymentAPI.processPayment for an eligible saved card without adding tokenizedData. When the API returns HTTP 202, the client follows the authenticated statusUrl with the session nonce until the latest attempt reaches success, authorization, decline, buyer action, or a terminal error. Low-level clients can use waitForCheckoutSessionCompletion for the same attempt.

A direct client sends only the provider-neutral saved-card data. processPayment owns pending reconciliation. If a raw process call has already returned HTTP 202, pass its advertised statusUrl to the same wait helper:

import { PaymentAPI } from '@flopay/js';
import type { ProcessPaymentParams } from '@flopay/shared';

const paymentApi = new PaymentAPI('https://api.flopay.com');

const savedCardPayment: ProcessPaymentParams = {
  sessionId: 'SESSION_ID',
  nonce: 'CHECKOUT_SESSION_TOKEN',
  accountData: {
    userId: 'YOUR_BUYER_ID',
    email: 'BUYER_EMAIL',
  },
};

export function processSavedCard() {
  return paymentApi.processPayment(savedCardPayment.accountData.userId, savedCardPayment);
}

export function waitForRawSavedCardProcess(statusUrl: string) {
  return paymentApi.waitForCheckoutSessionCompletion(savedCardPayment.sessionId, {
    nonce: savedCardPayment.nonce,
    statusUrl,
  });
}

@flopay/react keeps Auto and Confirm flows provider-neutral. FloPayAutomaticPaymentButton handles Auto creation, while FloPayCheckout handles Confirm reuse and subscription checkout. Both keep their existing callbacks through pending reconciliation and buyer action. If the card is ineligible, FloPayError.code is stored_payment_method_unsupported; show the supplied message and let the buyer choose another payment method. Do not silently switch the charging behavior.

An Auto checkout needs only the normal session draft. The billing API resolves the saved card and payment route:

import { FloPayAutomaticPaymentButton } from '@flopay/react';
import type { DeclineEvent, FloPayError, InlineSessionDraft } from '@flopay/shared';

const createSession: InlineSessionDraft = {
  clientId: 'YOUR_FLOPAY_CLIENT_ID',
  currency: 'YOUR_CURRENCY',
  checkoutMode: 'auto',
  products: [{ code: 'YOUR_PRODUCT_CODE', quantity: 1 }],
  account: { userId: 'YOUR_BUYER_ID', email: 'BUYER_EMAIL', country: 'BUYER_COUNTRY' },
  successUrl: 'https://YOUR_DOMAIN/success',
  cancelUrl: 'https://YOUR_DOMAIN/checkout',
};

export function AutomaticSavedCardCheckout() {
  return (
    <FloPayAutomaticPaymentButton
      createSession={createSession}
      onSuccess={({ result }) => {
        if (result.status === 'succeeded') window.location.assign('/success');
      }}
      onDecline={(decline: DeclineEvent) => console.info(decline.code)}
      onError={(error: FloPayError) => console.error(error.code)}
    >
      Pay with saved card
    </FloPayAutomaticPaymentButton>
  );
}

Outcomes

JourneyWhat happens
FrictionlessThe hosted card form collects 3DS Flex device data. The first submission finishes as succeeded or authorized with no bank challenge. A captured payment calls onComplete with status: 'succeeded' and checkoutMethod: 'card'; a manual-capture hold calls onComplete with status: 'authorized', paymentId, sessionId, and authorizationExpiresAt.
ChallengedThe backend records an ACTION_REQUIRED transaction attempt, and the hosted form emits an authenticated action_required message with the Flo-hosted challenge URL. Released SDK clients consume that message and show the bank challenge in their overlay. Application code using the SDK does not receive or construct the challenge URL.
PendingA direct completion probe can return pending or timeout. Do not submit another payment. Continue with session status until the existing attempt settles. If a checkout attempt is not yet confirmed, the SDK reports onError with code: 'capture_failed', never onComplete; a later session read can report the settled result through onComplete or onSessionCompleted.
DeclinedThe SDK calls onDecline with code: 'authorization_declined', and the card form stays ready for another attempt. Direct REST clients receive Flo's decline taxonomy, including 3DS_FAIL or TECHNICAL_ERROR, never a raw Worldpay refusal code or gateway callback value.
AbandonedClosing the overlay emits the non-terminal challenge_dismissed event. A flow that ends without a terminal authentication result records customer_abandoned. The session remains recoverable while the card form returns to a usable state.
RecoveredReturning to an open session restores its challenge without charging again. A late authenticated Order Notification is applied in status order and can settle the existing order without a second authorization, after which the normal terminal callback fires once.

onComplete omits paymentIntentId for Worldpay-routed sessions, because a Worldpay order reference is not a Stripe PaymentIntent. Reconcile those payments with the checkout session and paymentId instead. If you mount the hosted card widget directly from @flopay/js, treat the completion event's intentId as an opaque processor reference.

Rebill declines and recovery

A retryable decline enters FloPay's normal dunning schedule. The invoice remains past due while FloPay applies the configured retry policy and advances the subscription only after a confirmed payment. A terminal refusal suppresses unsafe reuse of that retained binding and requires another payment method before the charge can continue.

If an unattended rebill requires payer action, FloPay stops the attempt. It does not replay the charge and does not cascade it to another route; the invoice and subscription enter recovery so the buyer can take an attended action safely.

Ambiguous transport is not a decline. FloPay must never replay the money-moving request and must never cascade it to another gateway or account. It uses exact-account orderInquiry or an authenticated Order Notification to settle the pinned logical payment. Until that evidence arrives, the invoice and subscription stay in recovery instead of being treated as paid or definitively declined.

SDK and hosted checkout continuation

During Worldpay card processing, the billing API records an ACTION_REQUIRED transaction attempt. The hosted form reads that attempt through the session-status contract and posts an authenticated action_required message with the Flo-hosted nextActionRedirectUrl. The FloPay client consumes that message inside its card-capture adapter and presents the bank challenge in its own overlay.

Released @flopay/js and @flopay/react clients own this continuation and do not call /3ds/complete. When the buyer reaches /vault/3ds/return, FloPay resolves the result server-side. The widget polls GET /v1/checkouts/sessions/:id/status during its bounded authentication window and owns the terminal onComplete, onDecline, or onError outcome.

Your application never receives Worldpay credentials, the ACS URL, Cardinal JWT, merchant code, or challenge cookie. Do not call /3ds/complete from an SDK or hosted checkout integration, and do not add provider-specific challenge handling. Keep the widget mounted and let it report the final result.

Direct REST 3DS continuation

POST /v1/checkouts/sessions/:id/process is part of the eligible saved-card Auto and Confirm path. It is not how a first-use hosted card payment starts: the hosted card form submits that card and FloPay charges it server-side.

A direct REST integration starts a first-use hosted card payment inside the hosted vault form. Mount the session's vault.html and retain its vault.messageToken and optional vault.expectedOrigin. When the backend persists an ACTION_REQUIRED attempt, the hosted form sends this window.postMessage payload:

{
  "source": "flopay-vault",
  "type": "action_required",
  "sessionId": "SESSION_ID",
  "messageToken": "YOUR_VAULT_MESSAGE_TOKEN",
  "intentId": "YOUR_WORLDPAY_ORDER_CODE",
  "nextActionRedirectUrl": "FLO_HOSTED_SIGNED_CHALLENGE_URL"
}

Before using the message, require source: 'flopay-vault', type: 'action_required', the current session ID, and an exact match with vault.messageToken. When vault.expectedOrigin is present, require event.origin to match it exactly. Reject any message that fails a check. The nextActionRedirectUrl points to /v1/vault/3ds/challenge with a server-signed query. Follow it exactly as received in your challenge frame; never construct, modify, or reuse it. Treat intentId as an opaque processor reference.

Keep the hosted form mounted because it continues polling session status and emits the eventual terminal outcome. The challenge return page posts source: 'flopay-vault-3ds-return' to the parent page. Accept that return marker only from the challenge frame's contentWindow and for the current session. It dismisses the frame but is not a payment result.

After that return, call POST /v1/checkouts/sessions/:id/3ds/complete exactly once with the session token and the page origin. This is a completion probe, not a polling endpoint. The request has an empty body:

curl -X POST https://api.flopay.com/v1/checkouts/sessions/SESSION_ID/3ds/complete \
  -H "x-checkout-session-token: $CHECKOUT_SESSION_TOKEN" \
  -H "Origin: https://YOUR_DOMAIN"

HTTP 200 returns an unwrapped ThreeDsCompleteResponseDto:

{
  "status": "pending",
  "declineReason": null,
  "gatewayDeclineReason": null,
  "nextActionRedirectUrl": null,
  "providerIntentId": "YOUR_WORLDPAY_ORDER_CODE"
}

status is authorized, succeeded, declined, requires_action, pending, timeout, or no_attempt. Treat providerIntentId as an opaque Worldpay order code. A pending or timeout result means confirmation is not final, so poll GET /v1/checkouts/sessions/:id/status instead of calling /3ds/complete again or starting another payment.

Status contracts

Read the current session through the same provider-neutral endpoint:

curl -X GET https://api.flopay.com/v1/checkouts/sessions/SESSION_ID/status \
  -H "x-checkout-session-token: $CHECKOUT_SESSION_TOKEN"

GET /v1/checkouts/sessions/:id/status returns CheckoutSessionStatusResponseDto as { data }. Its data is a CheckoutSessionStatusDto:

{
  "data": {
    "uuid": "SESSION_ID",
    "status": "pending",
    "successUrl": null,
    "paymentId": null,
    "authorizationExpiresAt": null,
    "latestTransactionAttempt": {
      "uuid": "ATTEMPT_ID",
      "checkoutSessionsUuid": "SESSION_ID",
      "gateway": "worldpay_corporate_gateway",
      "result": "DECLINE",
      "declineReason": "UNSUPPORTED_CARD_PURCHASE",
      "providerIntentId": null,
      "nextActionRedirectUrl": null,
      "code": "stored_payment_method_unsupported",
      "createdAt": "ATTEMPT_CREATED_AT"
    }
  }
}

CheckoutSessionStatusDto.status is pending, authorized, completed, or voided. The response also carries the latest transaction attempt and, when available, the FloPay paymentId, successUrl, and authorizationExpiresAt. latestTransactionAttempt.code? is optional; when it is stored_payment_method_unsupported, use the same buyer-safe recovery as the process response.

Separately, @flopay/shared exposes CheckoutSession.status as open, authorized, complete, or expired. These are different contracts and their values are not interchangeable. In particular, REST uses pending and completed, while the package session uses open and complete.

Card form recovery

POST /v1/checkouts/sessions/:id/vault/capture is not a money-capture operation. It idempotently reissues hosted card-form credentials for a pending card-capable session when the original vault block was lost, its submit secret expired, or a declined buyer needs to try a new card.

Released @flopay/js and @flopay/react clients call it automatically. A custom client can make the same empty-body request with the session token:

curl -X POST https://api.flopay.com/v1/checkouts/sessions/SESSION_ID/vault/capture \
  -H "x-checkout-session-token: $CHECKOUT_SESSION_TOKEN"

The endpoint returns a VaultCaptureResponse with html and, when required, url, secret, messageToken, and expectedOrigin. Custom hosted-form clients should treat the VaultCaptureResponse as opaque and never log its fields. Reissuing the form credentials does not create a payment, retry an authorization, or add saved-card support.

Error and observability boundaries

SDK callbacks (onComplete, onDecline, and onError) stay provider-neutral. They do not expose Worldpay credentials, Cardinal data, the ACS URL, or a raw Worldpay refusal. The threeDSecureToken field does not carry Worldpay challenge material.

In a Direct REST integration, branch on the stable type, status, and declineReason values. Do not build control flow from gatewayDeclineReason or providerIntentId, and never show a raw provider reason to the buyer. Use the FloPay session and payment IDs for reconciliation.

Log only the session ID, payment ID, gateway ID, environment, time, terminal status, and Flo decline reason needed for diagnosis. Never log nextActionRedirectUrl, Cardinal values, Worldpay credentials, vault response fields, card data, or a raw notification payload.

Refunds, capture, and release

Worldpay payments use the same provider-neutral payments endpoints as Stripe payments: POST /v1/payments/:id/refund, PUT /v1/payments/:id/capture, PUT /v1/payments/:id/cancel, and GET /v1/payments/:id. There is no Worldpay-specific endpoint, request field, response field, or status value. The Payments API reference describes the shared contract.

These are trusted-server operations. Authenticate with Client Basic authentication and a user-owned API token, and never call them from browser code. Every refund, capture, and release requires a nonblank Idempotency-Key.

Eligibility and attribution

OperationEndpointApproved operation valueActs on
Refund, full or partialPOST /v1/payments/:id/refundrefundA captured payment
CapturePUT /v1/payments/:id/capturecaptureAn authorized hold before authorizationExpiresAt
ReleasePUT /v1/payments/:id/cancelvoidAn authorized hold

Each operation is available only while the Flo Payments Operations approval for the payment's merchant code and environment includes it. Operations records that approval per operation, for one environment and approval window, after a passed Secure Test verification and a support approval. Without it, the request returns 409 before anything is sent to Worldpay. Nothing is recorded for the request and no payment.capture_failed is sent, so you can retry with the same Idempotency-Key once the operation is approved. The gateway's worldpayCapabilities.operations lists refund, capture, and void only while each one is approved.

Each operation goes to the payment's originating gateway and account: the Worldpay merchant code and environment that took the payment. No request field selects a gateway, and nothing cascades to another gateway, account, or processor.

Refunds stay within the remaining refundable balance and the currency's precision. Omit amount and currency to refund the whole remaining amount, or send both for a partial refund. A refund in another currency, an amount above the remaining refundable balance, or an amount with more decimal places than the currency allows returns 400. A refund that is still pending holds its share of the balance.

Capture always collects the full authorized amount. The capture request has no amount field, so partial capture is not supported. A multiple capture of the same hold is rejected with 409, as are a capture after a release and a release after a capture.

FloPay also refuses a refund, capture, or release with 409 while a Worldpay chargeback is open on the payment, or while the payment's own outcome is still being confirmed.

Refund a payment

RefundPaymentBodyDto has three optional fields: amount, currency, and reason. A full refund omits amount and currency:

curl -X POST https://api.flopay.com/v1/payments/PAYMENT_ID/refund \
  -u "$FLOPAY_CLIENT_ID:$FLOPAY_API_TOKEN" \
  -H "Idempotency-Key: YOUR_REFUND_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "Order canceled"
  }'

A partial refund sends amount in major currency units together with currency, which must be the payment currency:

curl -X POST https://api.flopay.com/v1/payments/PAYMENT_ID/refund \
  -u "$FLOPAY_CLIENT_ID:$FLOPAY_API_TOKEN" \
  -H "Idempotency-Key: YOUR_PARTIAL_REFUND_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 10,
    "currency": "YOUR_CURRENCY",
    "reason": "Customer returned one item"
  }'

A refund returns 201 with a RefundPaymentResponseDto. Worldpay confirms refunds asynchronously, so a new Worldpay refund has refundStatus pending or processing, and the payment keeps its prior status:

{
  "id": "REFUND_ID",
  "paymentId": "PAYMENT_ID",
  "amount": 10,
  "currency": "YOUR_CURRENCY",
  "reason": "Customer returned one item",
  "refundStatus": "pending",
  "status": "succeeded",
  "gatewayRefundId": null,
  "refundTraceReference": {
    "status": "unknown",
    "type": null,
    "value": null
  },
  "createdAt": "REFUND_CREATED_AT"
}

Worldpay returns no refund identifier when it receives a refund, so FloPay leaves gatewayRefundId as null rather than inventing one. Once Worldpay confirms the refund, its refundStatus is refunded and the payment becomes partially_refunded or refunded:

{
  "id": "REFUND_ID",
  "paymentId": "PAYMENT_ID",
  "amount": 10,
  "currency": "YOUR_CURRENCY",
  "reason": "Customer returned one item",
  "refundStatus": "refunded",
  "status": "partially_refunded",
  "gatewayRefundId": null,
  "refundTraceReference": {
    "status": "unknown",
    "type": null,
    "value": null
  },
  "createdAt": "REFUND_CREATED_AT"
}

If Worldpay refuses the refund when FloPay sends it, the response is 409 with Worldpay refused the request, followed by a code in parentheses. No money moves. FloPay records the refund with refundStatus failed, the payment keeps its prior status rather than becoming refund_failed, and the amount no longer counts against the remaining refundable balance. A refund that Worldpay accepts and later reports as failed ends the same way: its refundStatus becomes failed, and the payment's status changes only for refunds that settle.

A retry of a failed refund with the same Idempotency-Key and body sends nothing to Worldpay. It returns 201 with that refund, refundStatus failed, and marks the payment refund_failed. The payment can still be refunded, so send any new attempt with a new Idempotency-Key.

A 502 is a separate case. If the stored charge is not on the originating gateway account, the response is a RefundChargeNotFoundOnGatewayResponseDto with HTTP 502. retrySafe is false, so contact FloPay instead of retrying. This abridged example omits the field that carries the Flo gateway UUID stamped on the payment:

{
  "statusCode": 502,
  "error": "Bad Gateway",
  "message": "Refund could not be issued by the payment provider.",
  "code": "refund_charge_not_found_on_gateway",
  "retrySafe": false,
  "resolution": "Contact support to verify and repair the payment gateway ownership before attempting another refund."
}

Capture or release a hold

Capture collects the full amount of an authorized hold:

curl -X PUT https://api.flopay.com/v1/payments/PAYMENT_ID/capture \
  -u "$FLOPAY_CLIENT_ID:$FLOPAY_API_TOKEN" \
  -H "Idempotency-Key: YOUR_CAPTURE_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

Release cancels the hold without collecting funds:

curl -X PUT https://api.flopay.com/v1/payments/PAYMENT_ID/cancel \
  -u "$FLOPAY_CLIENT_ID:$FLOPAY_API_TOKEN" \
  -H "Idempotency-Key: YOUR_RELEASE_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

Both return 200 with a PaymentDto. The optional JSON body is part of the request identity, so repeat the same body on every retry. Worldpay only acknowledges receipt, so while it confirms the operation the payment is returned as authorized. The final status arrives through the client webhooks and GET /v1/payments/:id: succeeded after a capture, or voided with authorizationVoidReason merchant_requested after a release. If Worldpay refuses a capture or release outright, the response is 409.

Read the payment at any time:

curl -X GET https://api.flopay.com/v1/payments/PAYMENT_ID \
  -u "$FLOPAY_CLIENT_ID:$FLOPAY_API_TOKEN"

GET /v1/payments/:id returns 200 with a PaymentDto. This abridged example is a hold whose capture Worldpay is still confirming:

{
  "id": "PAYMENT_ID",
  "type": "purchase",
  "status": "authorized",
  "amount": 10,
  "currency": "YOUR_CURRENCY",
  "authorizationExpiresAt": "AUTHORIZATION_EXPIRES_AT",
  "authorizationVoidReason": null,
  "createdAt": "PAYMENT_CREATED_AT"
}

Pending outcomes and retries

  • Each refund, capture, and release stays pending until an authenticated Order Notification or an exact-order inquiry settles it. FloPay applies each outcome once, and older evidence never downgrades a terminal result.
  • Read GET /v1/payments/:id for the current state instead of resubmitting.
  • Repeating the same logical request never moves money twice: FloPay sends each refund, capture, or release to Worldpay at most once.
  • After a timeout, a lost response, or a 5xx, retry with the same Idempotency-Key and the same body. A same-key replay returns the recorded operation without calling Worldpay again. The same key with a different body returns 409.
  • A timed-out or ambiguous answer is resolved by exact-order inquiry before another money-moving command is accepted. Until then, another refund, capture, or release on the same payment returns 409.
  • FloPay checks an unconfirmed operation with an exact-order inquiry about every 30 minutes. It never resubmits the operation, and it flags the operation for review after 144 unanswered inquiries, about three days.

Client webhooks

Each outcome sends the merchant the same client webhooks a Stripe payment sends for it, with the same payload as for Stripe. FloPay sends each webhook once per outcome, when the outcome settles, not when you submit the request. That holds whether FloPay learns of the outcome from a notification, an inquiry, or a retry.

OutcomeClient webhook
A refund settles, full or partialOne item.refunded per refund, at the refunded amount.
A capture succeedsitem.purchased for the captured purchase: one for each one-time item, or one for the first charge of a subscription-only checkout.
A capture fails because Worldpay refuses it, or because the order is canceled or its hold expires before the capture completesOne payment.capture_failed.
A release settlesOne payment.authorization_voided.

Lifecycle convergence

After a payment settles, Worldpay can report more about it through Order Notifications. FloPay converges that evidence to provider-neutral state in its existing payment, transaction, refund, invoice, subscription, and dispute records, attributed to the payment's provider and account.

  • As with every Order Notification, FloPay stores each one durably before acknowledging it. It resolves the notification only for the client whose gateway is bound to the notifying merchant code and environment, against the order that took the payment. A notification for an order FloPay cannot find there changes nothing.
  • A notification from a certificate that is not trusted for that exact merchant code fails closed and changes nothing. While Operations has recorded no trusted certificate for the merchant code, these notifications are held for review instead.
  • A chargeback, including a partial chargeback, is recorded against the original payment for its own amount and sends invoice.updated and item.charged_back, as for Stripe and PayPal. A chargeback reversal reinstates the payment, up to the amount charged back, and sends invoice.updated.
  • Dispute stages, such as a request for information and the information supplied, move FloPay's dispute record for the payment through the same lifecycle as Stripe and PayPal disputes, with the matching dispute events. Chargebacks and reversals move the same record. No money moves at a dispute stage.
  • While a chargeback is open, including during its dispute stages, a refund, capture, or release on the payment returns 409.
  • A refund made outside Flo, such as in Worldpay's console, with no FloPay refund behind it, is recorded once against the original payment, lowers the remaining refundable balance, and sends one item.refunded. This also applies to a payment that already has a completed FloPay refund, and a second console refund of the same amount is recorded again. A console refund on a payment that has, or may have, a chargeback against it is held for review instead.
  • If a capture or release requested through FloPay fails and the order is later captured or canceled in Worldpay's console, FloPay still records that capture or cancellation.
  • Ordering is monotonic. A notification that does not follow from the payment's current dispute state, such as a reversal with no chargeback recorded, is settled by exact-order inquiry, never by when it arrived. A late authorization, capture, or settlement never changes a payment that has a dispute, and older evidence never downgrades a terminal result.
  • FloPay records each chargeback, reversal, dispute stage, and console refund once. A redelivered notification changes nothing and sends no webhook again.
  • Each of these event types needs its own Operations approval for the merchant code and environment: chargeback, chargeback_reversal, dispute_stage, and external_refund. Operations approves an event type only after a passed Secure Test verification and a support approval. An event of an unapproved type changes nothing and is held for review by Flo Payments Operations.
  • FloPay also holds for review, changing nothing, a chargeback, reversal, or console refund in another currency or precision, or for more than the payment can carry, and any notification that an exact-order inquiry contradicts.
  • Missing, late, or conflicting outcomes are settled by exact-order inquiry before another money-moving command is accepted.

These rules are informational. There is no merchant setting for them, and Flo Payments Operations records the approvals. Worldpay chargebacks, reversals, and dispute stages appear in the same dispute records as Stripe and PayPal disputes; see Disputes tools.

Support

If a refund, capture, or release stays pending beyond that three-day review window, or a chargeback, dispute stage, or console refund you expect is missing from the payment because it is held for review, contact FloPay. Share only the gateway ID, payment ID, environment, time, and message. Never send API tokens, XML credentials, raw Worldpay XML, or notification payloads.

Verify your setup

  1. Read the gateway with GET /v1/settings/gateways/:id, or open it in Settings > Gateways. Check that providerType is worldpay_corporate_gateway, environment is the environment you intended, worldpayMerchantCode is your merchant code, and both worldpayXmlUsernameConfigured and worldpayXmlPasswordConfigured are true. connectionValidatedAt shows when FloPay first validated the credentials.
  2. Run the connection test. Confirm that success is true and providerAccountId matches your merchant code.
  3. Check the worldpayCapabilities record: expiresAt is in the future, currencies includes the gateway currency, countries includes every gateway country, and orderNotificationsCertified and pcivaultProxyCertified are both true.
  4. When stored_credentials is approved, confirm storedCredentialsCertified is true. Check that storedCredentialCustomerInitiatedReason is UNSCHEDULED for saved-card Confirm checkout and that storedCredentialMerchantInitiatedReasons includes RECURRING for subscription rebills or UNSCHEDULED for independently consented unattended one-off use. For cross-processor reuse into Worldpay, also confirm Operations recorded externalSchemeReferenceApproved after the Secure Test exercise.
  5. When three_ds is approved, confirm worldpayThreeDsApiIdConfigured, worldpayThreeDsOrganizationUnitIdConfigured, and worldpayThreeDsHmacKeyConfigured are true. The approval also needs threeDsCertified, pcivaultBinMetadataCertified, and at least one approved HTTPS origin in threeDsCallbackOrigins.
  6. Activate the Secure Test gateway, then run the Secure Test journey fixtures through Full checkout, saved-card Confirm checkout, and a subscription rebill when those purposes are approved. Follow GET /v1/checkouts/sessions/:id/status until the expected terminal status and paymentId appear. Reconcile with the FloPay session and payment IDs, not a Stripe identifier or Worldpay order code.
  7. Secure Test does not prove production Cardinal or live issuer behavior. Repeat connection, approval, activation, and checkout verification with the production merchant code before accepting live payments.

Supported features

CapabilityStatus in this release
Gateway drafts, credential updates, and rotationAvailable in Settings > Gateways and the REST API.
Connection testAvailable. It runs a read-only order inquiry with the stored XML credentials.
Capability approvalRecorded by Flo Payments Operations and read-only for merchants.
ActivationAvailable after the fail-closed gates in Activation pass.
One-time card payments (purchase)Available for approved one-time item carts through the hosted card form.
Authorization holds (authorization)Available when approved. The result includes its expiry. Capture or release the hold before it expires; see Refunds, capture, and release.
Refunds (refund)Available when the Operations approval includes refund. Full and partial refunds of a captured payment through POST /v1/payments/:id/refund, settled asynchronously.
Capture (capture)Available when the Operations approval includes capture. One capture of the full authorized amount through PUT /v1/payments/:id/capture; partial and multiple capture are not supported.
Release (void)Available when the Operations approval includes void. Releases an authorized hold through PUT /v1/payments/:id/cancel without collecting funds.
Order status (status)Not used in this release.
Order inquiry (order_inquiry)Used by the connection test and crash-safe recovery on the exact pinned order and merchant code when an attempt is not yet confirmed.
Order Notifications (order_notifications)Accepted over mutual TLS and applied in status order.
3DS Flex (three_ds)Available when certified credentials, BIN metadata handoff, and callback-origin gates pass.
Stored credentials (stored_credentials), saved cards, and card setupAvailable when certified for eligible saved-card Auto and Confirm checkout. Standalone Worldpay card setup is not available.
SubscriptionsAvailable for Flo-managed subscriptions. Worldpay processes an eligible first payment or rebill, but it is not a subscription product or system of record.
Merchant API one-off chargesAvailable through POST /v1/payments, with crash-safe order_inquiry recovery for an uncertain Worldpay result.
Apple Pay, Google Pay, and other payment methodsWallets, local methods, and upsells are not available through this connector.
Currencies, countries, and card schemesLimited to your capability approval. FloPay publishes no general list.

Limitations

  • Preview requires a production Operations approval and live 3DS verification before a merchant takes production payments.
  • Refund, capture, and release are available only for a merchant code and environment whose Operations approval includes refund, capture, or void. Without that approval each request fails closed with 409 before anything is sent to Worldpay. The vault capture endpoint only refreshes hosted card-form credentials.
  • Partial capture and multiple capture are not supported. Each hold can be captured once, for the full authorized amount.
  • Lifecycle convergence recognizes only these Worldpay order statuses: CHARGED_BACK, CHARGEBACK_REVERSED, INFORMATION_REQUESTED, INFORMATION_SUPPLIED, REFUNDED_BY_MERCHANT, and a REFUNDED or PARTIALLY_REFUNDED that no FloPay refund owns. Each applies only for an approved event type, as described in Lifecycle convergence. FloPay books no other Worldpay status as a chargeback, reversal, dispute stage, or refund, and claims no Worldpay event or lifecycle that the selected Corporate Gateway product does not expose.
  • A dispute stage applies only to a payment with an open chargeback. FloPay checks a stage reported for any other payment by exact-order inquiry and never applies it on its own.
  • Standalone Worldpay card setup remains unavailable.
  • Wallets, local methods, and upsells remain unavailable through Worldpay.
  • 3DS Flex requires a current certified approval, complete server-side Cardinal credentials, certified BIN metadata handoff, and approved HTTPS callback origins.
  • Full checkout and eligible saved-card Auto or Confirm checkout can use Worldpay. FloPay makes that decision on the server; no SDK version, header, or setting changes it.
  • Saved-card and subscription use requires purpose-specific consent, current certification, and the matching approved stored-credential reason. Cross-processor use also requires the applicable Operations approval described above.
  • Legacy Stripe Elements and wallet payments do not cascade. There is no separate merchant-facing Full-mode kill switch; per-gateway cascadeEnabled is the only control. New-card first attempts can be divided between processors by share; see Traffic split. See Failover boundaries.
  • Currencies, countries, and card schemes are limited to the Operations approval for each merchant code and environment.
  • The provider, environment, and merchant code cannot change after validation, and a merchant code can have only one gateway per environment.
  • Secure Test does not move funds or contact card issuers, so its results do not prove production behavior.
  • The connector covers the Worldpay Corporate Gateway XML Direct integration only.

Troubleshooting

Match the sanitized message FloPay returns to its fix below. If you need help, contact FloPay and share only the gateway ID, environment, time, and message.

SymptomWhat to do
A test or credential save reports Worldpay rejected the XML credentials for the secure-test environment. Check the merchant code, username, password, and environment., or the same message for the production environmentConfirm that the merchant code, XML username, and XML password were issued together for the gateway's environment. Save corrected credentials, then test again.
A test reports Worldpay credential validation failed. The Corporate Gateway XML API could not be reached or returned an invalid response.Test again later. If the message persists, contact FloPay with the gateway ID and the time of the test.
A test reports Worldpay validation requires merchantCode, xmlUsername, and xmlPassword.Save the missing value, then test again. The ...Configured flags show which credentials are stored.
A credential update returns 422Correct the replacement and submit it again. The previous credentials stay stored.
A request returns 403 with Worldpay capability records require Operations approval.Remove worldpayCapabilities from the request. Only Flo Payments Operations writes that record.
A create or update returns 409 with Another Worldpay gateway already uses this merchant code in the selected environment.Update the existing gateway for that merchant code and environment instead of creating another.
An update returns 409 with Provider and environment are locked once a connection has been validated.Create a new gateway for a different environment or merchant code.
Activation returns 409 with Activation requires a complete provider connection. Missing: worldpayCapabilities.The Operations approval is not recorded yet. Save any other missing fields named in the message.
Activation returns 409 with Worldpay activation is blocked because a money operation is not available end-to-end.Ask Flo Payments Operations to confirm that the Operations approval includes purchase or authorization for this merchant code and environment.
Activation returns 409 with Worldpay 3DS capability requires a configured continuation seal key.This is a FloPay platform condition. There is nothing to add to the gateway or browser integration. Contact FloPay with the gateway ID, environment, time, and message.
A refund, capture, or release returns 409 with Worldpay refund is not available for this gateway (not_certified)., or the same message for capture or void, because the operation is not approvedNothing was sent to Worldpay or recorded. The reason in parentheses names the missing gate. Ask Flo Payments Operations to approve the operation for this merchant code and environment, then retry with the same Idempotency-Key.
A refund, capture, or release returns 409 with Worldpay refund is refused for this payment (payment_disputed_or_charged_back)., or the same message ending in (payment_outcome_unresolved)A Worldpay chargeback is open on the payment, or the payment's own outcome is still being confirmed. Read GET /v1/payments/:id and wait for it to settle instead of resubmitting.
A multiple capture, such as a second capture of the same hold or a capture after a release, returns 409 with Payment PAYMENT_ID is not eligible for authorization capture.Each hold supports one capture of the full amount, and partial capture is not supported. Read GET /v1/payments/:id: succeeded means the earlier capture completed, and voided means the hold was released.
A refund returns 400 with Refund amount exceeds the remaining refundable balance of, followed by the remaining amount and currencySend a smaller amount, or omit amount and currency to refund the whole remaining amount. A pending refund already holds its share of the balance.
A refund, capture, or release returns 409 with Worldpay refused the request, followed by a code in parenthesesWorldpay refused the request when FloPay sent it, so no money moved. A refused refund is recorded with refundStatus failed, the payment keeps its status, and the amount is available to refund again. Send a new refund with a new Idempotency-Key: the same key returns the failed refund and marks the payment refund_failed. If Worldpay keeps refusing, contact FloPay with the gateway ID, payment ID, environment, time, and message.
A refund returns 502 with the code refund_charge_not_found_on_gatewayThe payment's charge is not on its originating gateway account, and retrySafe is false. Do not retry. Contact FloPay with the gateway ID, payment ID, environment, time, and message.
A session response has no data.gateways.worldpayCorporateGatewayConfirm the gateway is active and its approval window is current. For Full mode, check the buyer country and currency. For saved-card Auto or Confirm, check the first-charge evidence, certified stored credentials, consent, approved reason, and any cross-processor approval.
Checkout reports an error after the buyer submits a cardThe payment may still be confirming. Read the session again before you ask the buyer to pay again.
A continuation is rejected as tampered, expired, origin-mismatched, or cross-tenantThe continuation fails closed before another sale. Read session status with the matching session token. Never construct the challenge URL or copy it between sessions, tenants, or origins.

Never send XML passwords, 3DS keys, API tokens, card numbers, security codes, raw Worldpay XML, or notification payloads to FloPay or anyone else, and never paste them into logs, tickets, or chat.

On this page