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
Android 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 view 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
WirexCollectProfile.TOPUPA card that funds a top-uptokenizeTopUp(WirexTopUpTokenizeParams)cardIdexternal_card_id on Top-Up Transfer
WirexCollectProfile.OCT_DESTINATIONA push-to-card payout destinationtokenizeOctDestination(WirexOctTokenizeParams)externalCardIdcard.card_id on POST /api/v2/recipients, then external_card_id on Push to Card

Step 1: Add the Form

class CollectScreen(private val activity: Activity, credentials: WirexCredentialProvider) {
    private val scope = CoroutineScope(Dispatchers.Main)
    private val collect: WirexCollect = WirexCollect.create(credentials = credentials, testMode = true, chainId = "<chain id>")
        ?: error("this build has no endpoints for that environment")
    private lateinit var form: WirexCollectForm

    fun mount(holder: FrameLayout) {
        form = collect.form(context = activity, profile = WirexCollectProfile.TOPUP)
        form.onChange = { state -> updateSubmit(state.isComplete) }
        holder.addView(form, MATCH_PARENT, WRAP_CONTENT)
    }
}

Use WRAP_CONTENT or form.naturalHeightPx(widthPx) for the height. To lay fields out yourself,
form.row(field) 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].isValidBoolean
fields[field].errorINCOMPLETE, INVALID, EXPIRED, or null

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

fun tokenize() {
    scope.launch {
        try {
            val token = form.tokenizeTopUp(WirexTopUpTokenizeParams(
                billingDetails = WirexBillingDetails(line1 = "1 High Street", zipCode = "N1 9GU", city = "London", country = "GB"),
                label = "Personal Visa",
            ))
            backend.topUp(externalCardId = token.cardId)
        } catch (error: WirexError) {
            showError(error.code, error.retryable, 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
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

val token = form.tokenizeOctDestination(WirexOctTokenizeParams(
    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.

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
OCT_DESTINATIONPOST /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).

code / reasonMeaningResolution
INVALID_INPUT / CARD_ALREADY_LINKEDThe card is already linkedAsk the user for a different card
INVALID_INPUT / CARD_LIMIT_REACHEDThe user holds the maximum number of saved cardsRemove a saved card before adding another
INVALID_INPUT, no reasonA field is invalidFix the highlighted field
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?