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
iOS SDKSurfaces, isolation model and flows
SDK Setupcreate(...) and the credential provider
Top-Up TransferThe estimate and execute calls a top-up cardId feeds
Card TokenizationRecipient creation with a payout card token

Overview

WirexCollectForm is one UIView holding the card inputs. The user types the card inside it; the SDK
registers the card with Wirex and returns a token. Your code receives the token, the brand, the BIN and the
last four digits — never the PAN or the CVV.

Collect needs no action token: it uses accessToken() and the identity from your WirexCredentialProvider.


Choose a Profile

ProfileUse forTokenize callReturnsHand the token to
.topupA card that funds a top-uptokenizeTopUp(WirexTopUpTokenizeParams)cardIdexternal_card_id on Top-Up Transfer
.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: Place the Form

import UIKit
import WirexMobileSDK

final class CollectViewController: UIViewController {
    private let credentials = AppCredentials()
    private var form: WirexCollectForm?

    override func viewDidLoad() {
        super.viewDidLoad()
        guard let collect = WirexCollect.create(credentials: credentials, testMode: true, chainId: "<chain id>") else { return }
        let form = collect.form(profile: .topup)
        self.form = form
        view.addSubview(form)
        form.frame = CGRect(x: 16, y: 100, width: view.bounds.width - 32, height: form.naturalHeight)
        form.onChange = { [weak self] state in self?.updateSubmit(isComplete: state.isComplete) }
    }
}

Size the form to form.naturalHeight. To lay fields out yourself, form.row(for:) returns a single sealed row.
form.errorLine sets where validation messages render: hidden, reserved or animated.


Step 2: Gate Your Submit Button

onChange delivers WirexCollectState:

PropertyValues
isCompletetrue when every field is valid
brandvisa, mastercard, unknown
fields[field].isValidBool
fields[field].errorincomplete, invalid, expired, or nil

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


Step 3: Tokenize

Top-Up Card

func tokenize() {
    guard let form else { return }
    Task { @MainActor in
        do {
            let token = try await form.tokenizeTopUp(WirexTopUpTokenizeParams(
                billingDetails: WirexBillingDetails(line1: "1 High Street", zipCode: "N1 9GU", city: "London", country: "GB"),
                label: "Personal Visa"))
            try await backend.topUp(externalCardId: token.cardId)
        } catch let error as WirexError {
            print(error.code, error.retryable, error.reason as Any)
        }
    }
}
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
brandCard brand
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

let token = try await form.tokenizeOctDestination(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.

Call form.clear() when the screen goes away.


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 WirexError { 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 fails 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
Code unauthorizedStale bearer — the SDK never retries a 401Refresh inside accessToken()

Full error handling: Errors, Logging and Troubleshooting.


Did this page help you?