Collect a Payout Card

Collect a push-to-card destination card inside Wirex-served iframes and hand the resulting token to the recipient and transfer endpoints.

Before You Start

Read the following guides before proceeding:

GuideWhy
PCI Compliant SDKIsolation model and package map
SDK SetupClient id, environments, installation, CSP
Push to CardThe REST flow the collected card feeds
Card TokenizationRecipient creation with a card token

Overview

@wirexapp/card-oct-web collects a push-to-card destination card inside Wirex-served iframes and tokenizes
it straight from the frame. Your page receives a card token and non-sensitive metadata.

The oct-destination profile renders the card-number field only. No CVV and no expiry are collected
for a payout destination. Cardholder name, label and the third-party flag are non-sensitive inputs
collected with your own form components and passed at tokenize.

The token this package returns is the same value that
Card Tokenization returns from POST {pci_base_url}/b2b/cards/oct. Using
the SDK replaces that direct call: the PAN reaches Wirex from inside the frame.

Push to card requires the CardTransfer capability to be active. Check it with GET /api/v2/user before
mounting the field.


Choose a Layout Variant

Per-fieldCombined
CreatorcreateOctDestinationFields(sdk, config)createOctDestinationForm(sdk, config)
IframesOne, for the card-number fieldOne, with the stock layout
Layout controlYours — the field mounts into your own containerWirex's stock layout
Mount callfield('number').mount(container)mount(container)
TokenizertokenizeOctDestination(params)tokenizeOctDestination(params)

Because the profile has a single field, the two variants differ only in the mount call. Per-field is the
recommended variant.


Configuration

Identical to the top-up collector, with the profile pre-bound to oct-destination.

FieldTypeRequiredDescription
auth.getBearerToken() => Promise<string>YesReturns a fresh user token. Called at tokenize in the per-field variant; at mount and at tokenize in the combined variant
appearanceobjectNoWhitelisted styling tokens — see Appearance and Localization
stringsobjectNoTranslated labels and validation messages — see Strings Keys
onChange(state) => voidNoValidity and detected brand. Never carries card values

Step 1: Mount the Card-Number Field

import { createOctDestinationFields } from '@wirexapp/card-oct-web';

const oct = createOctDestinationFields(
  { environment: 'uat', clientId: 'a4f6c1e2-9b03-4d77-8a51-2f0c6b8e4d19' },
  {
    auth: { getBearerToken: () => session.getAccessToken() },
    onChange: state => {
      sendButton.disabled = !state.complete;
    },
  },
);

await oct.field('number').mount(document.querySelector('#destination-card-number'));

Requesting any other field throws Field "<name>" is not part of the "oct-destination" profile.


Step 2: Tokenize

const result = await oct.tokenizeOctDestination({
  cardholderName: 'JANE DOE',
  cardLabel: 'Sister — Visa',
  isSaved: true,
  isThirdParty: true,
  userId: '64120850-73a1-4df5-a074-d463258c9deb',
});
FieldTypeRequiredDescription
cardholderNamestringYesName as printed on the card, uppercase
cardLabelstringYesDisplay label for the saved destination
isSavedbooleanYestrue saves the destination for future transfers
isThirdPartybooleanYesfalse when the sender is the cardholder, true when the card belongs to another person
userIdstring (UUID)NoTarget user id. Sent as the user_id query parameter of the tokenize call

Result:

{
  "externalCardId": "tok_a1b2c3d4e5f6789012345678",
  "brand": "Visa",
  "bin": "424242",
  "last4": "4242"
}
FieldDescription
externalCardIdCard token. The only value to store
brandVisa, MasterCard or unknown
binFirst 6 digits of the PAN
last4Last 4 digits of the PAN

Third-party transfers carry additional compliance requirements and limits. See
First-Party vs Third-Party Cards.


Step 3: Create the Card Recipient

externalCardId is passed as card.card_id, and last4 as card.card_pan_last. Full request and
response documentation is in Card Tokenization.

const recipient = await fetch(`${baseUrl}/api/v2/recipients`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'X-User-Wallet': userEoaAddress,
    'X-Chain-Id': chainId,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    first_name: 'Jane',
    last_name: 'Doe',
    is_business: false,
    type: 'Card',
    currencies: ['EUR', 'USD', 'GBP'],
    card: {
      card_id: result.externalCardId,
      card_pan_last: result.last4
    }
  })
}).then(response => response.json());

Step 4: Estimate and Execute the Transfer

external_card_id on the transfer endpoints is the card token — the same externalCardId value. Full
documentation is in Card Transfer.

const estimate = await fetch(`${baseUrl}/api/v1/cards/transfer/estimate`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'X-User-Wallet': userEoaAddress,
    'X-Chain-Id': chainId,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    external_card_id: result.externalCardId,
    currency: 'EUR',
    amount: 100.00,
    tokens: [tokenAddress]
  })
}).then(response => response.json());

const transfer = await fetch(`${baseUrl}/api/v1/cards/transfer`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'X-User-Wallet': userEoaAddress,
    'X-Chain-Id': chainId,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    estimation_id: estimate.estimation_id,
    token_address: tokenAddress
  })
}).then(response => response.json());

externalCardId is the card token, not the recipient's payment-details id. The same value is passed
as card.card_id when creating the recipient and as external_card_id on both transfer endpoints. The
payment_details[].id returned by POST /api/v2/recipients is a different identifier and is not
accepted by the transfer endpoints.


Field Validation

Only the card-number rules apply to this profile.

RuleMessage key
Not emptyerrRequired
13–19 digits after stripping non-digitserrCardNumber
Brand detected as Visa or MastercarderrCardBrand
Passes the Luhn checkerrCardLuhn

Strings Keys

The profile uses the collect key set; only the card-number keys are rendered.

KeyDefault
cardNumberLabelCard Number
errRequiredRequired
errCardNumberCard number must be 13-19 digits
errCardBrandOnly Visa/Mastercard cards are supported
errCardLuhnCard number is invalid

The full key table is in Collect a Top-Up Card.


Error Handling

tokenizeOctDestination rejects with PciFrameRequestError, with the same message classes as the top-up
tokenizer. See Error Handling and
Events and Error Handling.


Lifecycle

Call destroy() when your view unmounts. Elements are single-use: after destroy(), create a new one
rather than remounting.

oct.destroy();

Did this page help you?