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:
| Guide | Why |
|---|---|
| PCI Compliant SDK | Isolation model and package map |
| SDK Setup | Client id, environments, installation, CSP |
| Push to Card | The REST flow the collected card feeds |
| Card Tokenization | Recipient 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-field | Combined | |
|---|---|---|
| Creator | createOctDestinationFields(sdk, config) | createOctDestinationForm(sdk, config) |
| Iframes | One, for the card-number field | One, with the stock layout |
| Layout control | Yours — the field mounts into your own container | Wirex's stock layout |
| Mount call | field('number').mount(container) | mount(container) |
| Tokenizer | tokenizeOctDestination(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.
| Field | Type | Required | Description |
|---|---|---|---|
auth.getBearerToken | () => Promise<string> | Yes | Returns a fresh user token. Called at tokenize in the per-field variant; at mount and at tokenize in the combined variant |
appearance | object | No | Whitelisted styling tokens — see Appearance and Localization |
strings | object | No | Translated labels and validation messages — see Strings Keys |
onChange | (state) => void | No | Validity 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',
});| Field | Type | Required | Description |
|---|---|---|---|
cardholderName | string | Yes | Name as printed on the card, uppercase |
cardLabel | string | Yes | Display label for the saved destination |
isSaved | boolean | Yes | true saves the destination for future transfers |
isThirdParty | boolean | Yes | false when the sender is the cardholder, true when the card belongs to another person |
userId | string (UUID) | No | Target user id. Sent as the user_id query parameter of the tokenize call |
Result:
{
"externalCardId": "tok_a1b2c3d4e5f6789012345678",
"brand": "Visa",
"bin": "424242",
"last4": "4242"
}| Field | Description |
|---|---|
externalCardId | Card token. The only value to store |
brand | Visa, MasterCard or unknown |
bin | First 6 digits of the PAN |
last4 | Last 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());
externalCardIdis the card token, not the recipient's payment-details id. The same value is passed
ascard.card_idwhen creating the recipient and asexternal_card_idon both transfer endpoints. The
payment_details[].idreturned byPOST /api/v2/recipientsis a different identifier and is not
accepted by the transfer endpoints.
Field Validation
Only the card-number rules apply to this profile.
| Rule | Message key |
|---|---|
| Not empty | errRequired |
| 13–19 digits after stripping non-digits | errCardNumber |
| Brand detected as Visa or Mastercard | errCardBrand |
| Passes the Luhn check | errCardLuhn |
Strings Keys
The profile uses the collect key set; only the card-number keys are rendered.
| Key | Default |
|---|---|
cardNumberLabel | Card Number |
errRequired | Required |
errCardNumber | Card number must be 13-19 digits |
errCardBrand | Only Visa/Mastercard cards are supported |
errCardLuhn | Card 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();Updated 15 days ago

