Collect a Card

Capture a top-up or payout card in a sealed native form and hand the returned token to the top-up and push-to-card endpoints.

Before You Start

Read the following guides before proceeding:

GuideWhy
React Native SDKSurfaces, isolation model and flows
SDK SetupConfigure and the collect credential provider
Top-Up TransferThe estimate and execute calls a top-up cardId feeds
Card TokenizationRecipient creation with a payout card token

Overview

WirexCollect renders the card inputs as native views. The user types the card inside them; the SDK
registers the card with Wirex and returns a token. JavaScript receives the token, the brand, the BIN and the
last four digits — never the PAN or the CVV.

Collect needs no action token. Configure it with a WirexCollectCredentialProvider (bearer and identity) or
a full WirexCredentialProvider.


Choose a Profile

ProfileUse forTokenize callReturnsHand the token to
'topup'A card that funds a top-uptokenizeTopUp(params)cardIdexternal_card_id on Top-Up Transfer
'oct-destination'A push-to-card payout destinationtokenizeOctDestination(params)externalCardIdcard.card_id on POST /api/v2/recipients, then external_card_id on Push to Card

Choose a Layout

LayoutComponentWhen
Form viewWirexCollectFormViewOne native form with every field, sized from its own content
Headless formWirexCollect.createForm({ profile }) + one WirexCollectFieldView per fieldYour own layout, for example expiry and CVV side by side

Step 1: Render the Form

Form View

import * as React from 'react';
import { useRef, useState } from 'react';
import {
  WirexCollect, WirexCollectFormView,
  type WirexCollectCredentialProvider, type WirexCollectFormHandle, type WirexCollectState,
} from '@wirexapp/mobile-sdk';

declare const credentials: WirexCollectCredentialProvider;

export const configureCollect = () =>
  WirexCollect.configure({ testMode: true, chainId: '<chain id>', credentials });

export function TopUpCardForm() {
  const form = useRef<WirexCollectFormHandle | null>(null);
  const [state, setState] = useState<WirexCollectState | null>(null);

  return (
    <WirexCollectFormView
      profile="topup"
      strings={{ labels: { number: 'Card number' }, errors: { '*': { incomplete: 'Required' } } }}
      onReady={(handle) => { form.current = handle; }}
      onChange={setState}
      style={{ width: '100%' }}
    />
  );
}

Headless Form

import * as React from 'react';
import { useEffect, useState } from 'react';
import { View } from 'react-native';
import {
  WirexCollect, WirexCollectFieldView,
  type WirexCollectFormHandle, type WirexCollectState,
} from '@wirexapp/mobile-sdk';

export function NewCardFields() {
  const [form, setForm] = useState<WirexCollectFormHandle | null>(null);
  const [state, setState] = useState<WirexCollectState | null>(null);

  useEffect(() => {
    let handle: WirexCollectFormHandle | null = null;
    void WirexCollect.createForm({ profile: 'topup', onChange: setState }).then((h) => { handle = h; setForm(h); });
    return () => { if (handle) void WirexCollect.destroyForm(handle); };
  }, []);

  if (!form) return null;
  return (
    <View>
      <WirexCollectFieldView form={form} field="number" />
      <View style={{ flexDirection: 'row', gap: 16 }}>
        <WirexCollectFieldView form={form} field="expiry" style={{ flex: 1 }} />
        <WirexCollectFieldView form={form} field="cvv" style={{ flex: 1 }} />
      </View>
      <WirexCollectFieldView form={form} field="name" />
    </View>
  );
}

errorLine on WirexCollectFieldView sets where the validation message renders: 'hidden', 'reserved' or
'animated'. Release a headless form with WirexCollect.destroyForm(handle) when the screen goes away.


Step 2: Gate Your Submit Button

onChange delivers WirexCollectState:

PropertyValues
isCompletetrue when every field is valid
brand'Visa', 'MasterCard', 'unknown'
fields.<field>.isValidboolean
fields.<field>.error'incomplete', 'invalid', 'expired', null

The sealed fields replace only the card inputs. The billing address and card label come from your own
inputs. Enable submit only when isComplete is true and your billing address is complete.


Step 3: Tokenize

Top-Up Card

const token = await form.current!.tokenizeTopUp({
  billingDetails: { line1: '1 High Street', line2: 'Flat 2', city: 'London', zipCode: 'N1 9GU', country: 'GB' },
  label: 'Personal Visa',
});
FieldTypeRequiredDescription
billingDetails.line1stringYesFirst address line
billingDetails.line2stringNoSecond address line
billingDetails.citystringYesCity
billingDetails.statestringNoState or region
billingDetails.zipCodestringYesPostal code
billingDetails.countrystringYesISO 3166-1 alpha-2 country code
labelstringNoSaved-card nickname

Result (WirexTopUpCardToken):

{
  "cardId": "c34908da-d980-4a9c-9b39-4dabd6f6144e",
  "brand": "Visa",
  "bin": "424242",
  "last4": "4242",
  "expiryMonth": 12,
  "expiryYear": 2028
}

cardId is the only value to store. bin and last4 are out of PCI scope and safe to display.

Payout Card

const token = await form.current!.tokenizeOctDestination({
  cardLabel: 'Alex Visa',
  isSaved: true,
  isThirdParty: false,
});
FieldTypeDescription
cardLabelstringDisplay label for the saved destination card
isSavedbooleanSaves the card for later payouts
isThirdPartybooleanThe card belongs to someone other than the user

The result carries externalCardId — the card token for push to card.


Step 4: Hand the Token to Your Backend

The SDK makes no money-moving call. Your backend continues the REST flow:

ProfileNext calls
'topup'POST /api/v1/cards/topup/estimate and POST /api/v1/cards/topup/execute with cardId as external_card_id. Execute returns three_ds_state when a challenge is required — run it with 3DS Challenge
'oct-destination'POST /api/v2/recipients with externalCardId as card.card_id, then POST /api/v1/cards/transfer/estimate and POST /api/v1/cards/transfer with external_card_id

Errors

tokenizeTopUp and tokenizeOctDestination reject with { code, retryable, reason? }.

reasonMeaningResolution
cardAlreadyLinkedThe card is already linkedUse the existing card, or ask the user for another card
cardLimitReachedThe user holds the maximum number of saved cardsRemove a saved card before adding another
SymptomCauseResolution
Tokenize rejects without reasonBilling details missing, or the user is not enrolled at the acquirerSend line1, zipCode, city, country. Assumption: acquirer enrollment follows the CardTopup capability — check it via GET /api/v2/user
unauthorizedStale bearer — the SDK never retries a 401Refresh inside getAccessToken

Full error handling: Errors, Logging and Troubleshooting.


Did this page help you?