Reveal Card Details

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

Before You Start

Read the following guides before proceeding:

GuideWhy
React Native SDKSurfaces, isolation model and flows
SDK SetupConfigure, credential provider, action token scopes
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 native row 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
cardholderNameNone — drawn from the cardholderName you pass

Before the calls, the SDK asks your credential provider for an action token (getActionToken('cardData') or
getActionToken('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

import { WirexReveal, type WirexRevealSession } from '@wirexapp/mobile-sdk';

export async function openReveal(cardId: string): Promise<WirexRevealSession> {
  await WirexReveal.configure({ testMode: true, chainId: '<chain id>', credentials });
  return WirexReveal.createSession({
    cardId,
    fields: ['pan', 'expiry', 'cvv', 'pin'],
    cardholderName: 'ALEX GREY',
  });
}
ParameterTypeDescription
cardIdstringThe issued card's id
fieldsWirexRevealField[]Rows to load: 'cardholderName', 'pan', 'expiry', 'cvv', 'pin'. Drives which endpoints are called
cardholderNamestringDrawn as-is in the cardholderName row

One session per open details view. The session holds the retrieved values natively; destroy it when the view
closes.


Step 2: Render the Rows and Load

import * as React from 'react';
import { useEffect, useState } from 'react';
import {
  WirexRevealFieldView,
  type WirexRevealField, type WirexRevealSession, type WirexRevealSnapshot,
} from '@wirexapp/mobile-sdk';

export function RevealRow({ session, field }: { session: WirexRevealSession; field: WirexRevealField }) {
  const [snapshot, setSnapshot] = useState<WirexRevealSnapshot>(session.snapshot);
  const [height, setHeight] = useState<number | undefined>();

  useEffect(() => {
    const unsubscribe = session.onSnapshot(setSnapshot);
    void session.load();
    return () => { unsubscribe(); void session.destroy(); };
  }, [session]);

  return (
    <WirexRevealFieldView
      session={session}
      field={field}
      strings={{ fieldLabels: { pan: 'Card number' }, copy: 'Copy', copied: 'Copied' }}
      onNaturalHeight={setHeight}
      style={{ height }}
    />
  );
}

Trap: a native row cannot be sized from JavaScript. It reports its content height through
onNaturalHeight; apply that height to the row's style. A box shorter than the content clips the value.


Session State

session.snapshot and onSnapshot report the session and each row:

PropertyValues
snapshot.state'idle', 'loading', 'ready', 'error'
snapshot.fields[i].outcome'idle', 'loading', 'loaded', 'failed'
snapshot.loaded / snapshot.failedArrays of fields in that outcome
snapshot.error, snapshot.fields[i].error{ code, retryable }

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.

MethodEffect
session.load()Loads the requested fields. Idempotent. Resolves with the snapshot and never rejects for a request failure — failures land in the snapshot
session.refresh()Reloads
session.clear()Wipes the values
session.destroy()Releases the native session
WirexReveal.destroyAll()Tears down every session

Masking

pan, cvv and pin start masked.

PropDescription
maskedControls masking. Pass false to unmask immediately when the user has just confirmed the step-up
onMaskedChangeReports { masked, revealRequested, screenshotProtectionActive }
masksWhenDetachedDefault true — a row re-masks when detached
onCopyResult(copied: boolean) — fires when the user copies a copyable field. There is no JavaScript-triggered copy

A row masked because the app left the foreground draws again on return while the session still holds the
value. The session holds values for 120 seconds; past that, 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:

import type { WirexActionScope, WirexCredentialProvider } from '@wirexapp/mobile-sdk';

declare const auth: {
  getAccessToken(): Promise<string>;
  signAction(scope: WirexActionScope): Promise<string>;
  userWallet(): string | undefined;
};

let offered: { scope: WirexActionScope; token: string } | undefined;
export const offerStepUpToken = (scope: WirexActionScope, token: string): void => { offered = { scope, token }; };
export const revokeOfferedStepUpToken = (): void => { offered = undefined; };

export const credentials: WirexCredentialProvider = {
  getAccessToken: () => auth.getAccessToken(),
  async getActionToken(scope) {
    if (offered?.scope === scope) { const { token } = offered; offered = undefined; return token; }
    return auth.signAction(scope);
  },
  get userEmail() { return undefined; },
  get userWallet() { return auth.userWallet(); },
};

Errors

SymptomCauseResolution
Row blank on iOS SimulatorSecure layer renders blank on the SimulatorTest on a device, or dangerouslyDisableScreenProtection in development only
Row blank on deviceRow height smaller than onNaturalHeightApply the reported height
unauthorized in the snapshotStale bearer — the SDK never retries a 401Refresh inside getAccessToken
Operation reported as declinedgetActionToken threw WirexDeclinedNone — the user backed out
pin field failedVirtual card, or card not ActiveOmit 'pin' for virtual cards

Full error handling: Errors, Logging and Troubleshooting.


Did this page help you?