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
Flutter SDKSurfaces, isolation model and flows
SDK SetupWirexCollect.configure and its credentials
Top-Up TransferThe estimate and execute calls a top-up cardId feeds
Card TokenizationRecipient creation with a payout card token

Overview

WirexCollectFormView renders the card inputs as a native view. The user types the card inside it; the SDK
registers the card with Wirex and returns a token. Dart 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 WirexCollect.configure(chainId:, accessToken:, userEmail: | userWallet:, testMode:) —
see Collect Credentials.


Choose a Profile

ProfileUse forTokenize callReturnsHand the token to
WirexCollectProfile.topupA card that funds a top-uptokenizeTopUp(WirexTopUpTokenizeParams)cardIdexternal_card_id on Top-Up Transfer
WirexCollectProfile.octDestinationA push-to-card payout destinationtokenizeOctDestination(WirexOctTokenizeParams)externalCardIdcard.card_id on POST /api/v2/recipients, then external_card_id on Push to Card

Step 1: Render the Form

import 'package:flutter/widgets.dart';
import 'package:wirex_mobile_sdk/wirex_mobile_sdk.dart';

class TopUpForm extends StatefulWidget {
  const TopUpForm({super.key});
  @override
  State<TopUpForm> createState() => _TopUpFormState();
}

class _TopUpFormState extends State<TopUpForm> {
  WirexCollectFormController? _controller;
  WirexCollectState _state = const WirexCollectState.empty();

  @override
  Widget build(BuildContext context) => WirexCollectFormView(
        profile: WirexCollectProfile.topup,
        strings: const WirexCollectStrings(
          labels: <WirexCollectField, String>{WirexCollectField.number: 'Card number'},
        ),
        onCreated: (WirexCollectFormController controller) => _controller = controller,
        onChange: (WirexCollectState state) => setState(() => _state = state),
      );
}

The form sizes itself from the native content.

Custom Layout

To lay the fields out yourself, keep the form mounted and render one
WirexCollectFieldView(controller:, field:) per field. errorLine sets where the validation message renders:
WirexErrorLine.hidden, reserved or animated.


Step 2: Gate Your Submit Button

onChange delivers WirexCollectState:

PropertyValues
isCompletetrue when every field is valid
brand'Visa', 'MasterCard', 'unknown'
fields[field].isValidbool
fields[field].errorWirexCollectFieldError.incomplete, invalid, expired, or null

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


Step 3: Tokenize

Top-Up Card

Future<void> submit() async {
  final WirexCollectFormController? controller = _controller;
  if (controller == null || !_state.isComplete) return;
  try {
    final WirexTopUpCardToken token = await controller.tokenizeTopUp(const WirexTopUpTokenizeParams(
      billingDetails: WirexBillingDetails(
        line1: '1 High Street', line2: 'Flat 2', city: 'London', zipCode: 'N1 9GU', country: 'GB',
      ),
      label: 'Personal Visa',
    ));
    await sendToBackend(token.cardId);
  } on WirexCollectError catch (error) {
    debugPrint('${error.code} retryable=${error.retryable} reason=${error.reason}');
  }
}
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):

FieldDescription
cardIdCard identifier. The only value to store
brandVisa, MasterCard or unknown
binFirst 6 digits of the PAN
last4Last 4 digits of the PAN
expiryMonthExpiry month, 1–12
expiryYearExpiry year, four digits

bin and last4 are out of PCI scope and safe to display.

Payout Card

final token = await controller.tokenizeOctDestination(const WirexOctTokenizeParams(
  cardLabel: 'Alex Visa',
  isSaved: true,
  isThirdParty: false,
));
FieldTypeDescription
cardLabelStringDisplay label for the saved destination card
isSavedboolSaves the card for later payouts
isThirdPartyboolThe 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
topupPOST /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
octDestinationPOST /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 throw WirexCollectError { 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 throws 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 the accessToken callback

Full error handling: Errors, Logging and Troubleshooting.


Did this page help you?