Reveal Card Details

Show a cardholder their own PAN, expiry, CVV and PIN in sealed native rows that never pass values to your app code.

Before You Start

Read the following guides before proceeding:

GuideWhy
Android SDKSurfaces, isolation model and flows
SDK Setupcreate(...), credential provider, screen protection
Card DetailsConfirmation methods and action-token rules
Card IssuanceHow the card id is produced

Overview

WirexReveal opens a session for one card; each WirexRevealFieldView is a row you add to your layout and
the SDK draws the value into. The session calls the same endpoints documented in
Card Details:

FieldEndpoint the SDK calls
PAN, EXPIRYPOST /api/v1/cards/{cardId}/details
CVVPOST /api/v1/cards/{cardId}/cvv
PINPOST /api/v1/cards/{cardId}/pin
CARDHOLDER_NAMENone — drawn from the cardholderName you pass, never fetched

Before the calls, the SDK asks your credential provider for an action token — once per scope per operation
(CARD_DATA, PIN). Return a GetCardDetails token — see
Credential Provider.

Virtual cards have no PIN. POST /api/v1/cards/{cardId}/pin rejects a virtual retail card. Omit PIN
from fields for a virtual card.


Step 1: Create a Session and Add the Rows

class RevealScreen(private val activity: Activity, credentials: WirexCredentialProvider) {
    private val scope = CoroutineScope(Dispatchers.Main)
    private val reveal: WirexReveal = WirexReveal.create(credentials = credentials, testMode = true, chainId = "<chain id>")
        ?: error("this build has no endpoints for that environment")

    fun show(container: LinearLayout, cardId: String) {
        val fields = listOf(WirexRevealField.PAN, WirexRevealField.EXPIRY, WirexRevealField.CVV, WirexRevealField.PIN)
        val session = reveal.session(cardId = cardId, fields = fields, cardholderName = "ALEX GREY")
        val strings = WirexRevealStrings(fieldLabels = mapOf(WirexRevealField.PAN to "Card number"))

        for (field in fields) {
            val row = WirexRevealFieldView(activity)
            row.bind(session, field, strings = strings)
            container.addView(row, MATCH_PARENT, WRAP_CONTENT)
        }

        session.onChange = { snapshot ->
            activity.runOnUiThread { render(snapshot) }
        }

        scope.launch {
            try { session.load() } catch (_: WirexDeclined) { /* the user declined the step-up */ }
        }
    }
}
ParameterTypeDescription
cardIdStringThe issued card's id. A malformed id fails with reason INVALID_CARD_ID
fieldsList<WirexRevealField>Rows to load: CARDHOLDER_NAME, PAN, EXPIRY, CVV, PIN. Drives which endpoints are called
cardholderNameStringDrawn as-is in the CARDHOLDER_NAME row

Trap: onChange is called on any thread. Hop to the main thread before touching views.

Trap: a row draws its value at its natural height. Use WRAP_CONTENT or row.naturalHeightPx(widthPx).
A box shorter than the content clips the value.

One session per open details view. The session holds the retrieved values natively; release it when the view
closes, and call row.release() to drop the row's observer.


Session State

PropertyValues
snapshot.stateWirexRevealState.Idle, Loading, Ready, Failed(error)
snapshot.outcome(field)Per-field outcome
snapshot.loaded / snapshot.failedFields in that outcome
  • load() is a suspend function and idempotent. Request failures are reported in the snapshot; it
    throws only WirexDeclined.
  • One failed field does not hide the others. The row shows only that it failed; log the failed fields with the
    error code, because the row never says why.
  • Values are wiped 120 seconds after the first load; session.clear() wipes them earlier.

Masking

PAN, CVV and PIN start masked.

MemberDescription
reveal()Unmasks the row. Call it right after load() when the user has just confirmed the step-up
mask()Masks the row again
isMaskedCurrent state
onRevealRefusedFires when reveal() is refused because screen protection is not active on the window
masksWhenDetachedA row re-masks when detached
onCopyFires when the user copies a copyable field

A row masked because the app left the foreground draws again on return while the session still holds the
value. Past the 120-second lifetime the row stays masked until load() runs again.


Sign Once

The user confirms a reveal before the details view opens. Mint the action token at that point, hand it to the
provider one-shot, and revoke it if the flow is abandoned — otherwise the SDK asks for a second confirmation
for the same action:

class ProductionCredentials(private val auth: AuthSession) : WirexCredentialProvider {
    @Volatile private var offered: Pair<WirexActionScope, String>? = null

    fun offerStepUpToken(scope: WirexActionScope, token: String) { offered = scope to token }
    fun revokeOfferedStepUpToken() { offered = null }

    override suspend fun accessToken(): String {
        val current = auth.accessToken()
        if (current != null && current.second - System.currentTimeMillis() > 30_000) return current.first
        return auth.refreshAccessToken() ?: throw WirexError(WirexError.Code.UNAUTHORIZED, retryable = false)
    }

    override suspend fun actionToken(scope: WirexActionScope): String {
        offered?.takeIf { it.first == scope }?.let { offered = null; return it.second }
        return auth.signAction(scope)
    }

    override val userEmail: String? get() = null
    override val userWallet: String? get() = auth.walletAddress
}

Errors

SymptomCauseResolution
Row will not unmask, onRevealRefused firesFLAG_SECURE not active — the row is in a Dialog or PopupWindowPass the window with secureWindow(window)
Row blankBox shorter than the contentWRAP_CONTENT or naturalHeightPx(widthPx)
Failed with code UNAUTHORIZEDStale bearer — the SDK never retries a 401Refresh inside accessToken()
Failed with reason ACTION_TOKEN_REJECTEDThe API rejected the action token — see the action_token errors in Card DetailsMint a fresh GetCardDetails token
load() throws WirexDeclinedactionToken threw WirexDeclinedNone — the user backed out
PIN failedVirtual card, or card not ActiveOmit PIN for virtual cards

Full error handling: Errors, Logging and Troubleshooting.


Did this page help you?