Reveal Card Details

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

Before You Start

Read the following guides before proceeding:

GuideWhy
Flutter 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.instance 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
WirexRevealField.pan, expiryPOST /api/v1/cards/{cardId}/details
WirexRevealField.cvvPOST /api/v1/cards/{cardId}/cvv
WirexRevealField.pinPOST /api/v1/cards/{cardId}/pin
WirexRevealField.cardholderNameNone — drawn from the cardholderName you pass

Before the calls, the SDK asks your credential provider for an action token (actionToken('cardData') or
actionToken('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
WirexRevealField.pin from fields for a virtual card.


Step 1: Create a Session and Load

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

Future<WirexRevealSession> openReveal(WirexCredentialProvider credentials, String cardId) async {
  await WirexReveal.instance.configure(chainId: '<chain id>', credentials: credentials, testMode: true);
  final WirexRevealSession session = await WirexReveal.instance.createSession(
    cardId: cardId,
    fields: const <WirexRevealField>[
      WirexRevealField.pan, WirexRevealField.expiry, WirexRevealField.cvv, WirexRevealField.pin,
    ],
    cardholderName: 'ALEX GREY',
  );
  session.onSnapshot((WirexRevealSnapshot snapshot) {
    if (snapshot.state == WirexRevealState.error) debugPrint('${snapshot.error?.code}');
  });
  await session.load();
  return session;
}
ParameterTypeDescription
cardIdStringThe issued card's id
fieldsList<WirexRevealField>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

class RevealRow extends StatelessWidget {
  const RevealRow({required this.session, required this.field, super.key});
  final WirexRevealSession session;
  final WirexRevealField field;

  @override
  Widget build(BuildContext context) => WirexRevealFieldView(
        session: session,
        field: field,
        strings: const WirexRevealStrings(
          fieldLabels: <WirexRevealField, String>{WirexRevealField.pan: 'Card number'},
          copy: 'Copy',
          copied: 'Copied',
        ),
        onMaskedChange: (bool masked, bool screenshotProtectionActive) {},
      );
}

The row sizes itself from the native content. Give it a width and let it take its natural height — a box
shorter than the content clips the value.


Session State

PropertyValues
snapshot.stateWirexRevealState.idle, loading, ready, error
snapshot.outcomeFor(field)WirexRevealFieldOutcome.idle, loading, loaded, failed
snapshot.loaded / snapshot.failedSets of fields in that outcome
snapshot.errorWirexRevealError { 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 throws for a request failure — failures land in the snapshot
session.refresh()Reloads
session.clear()Wipes the values
session.destroy()Releases the native session
WirexReveal.instance.destroyAll()Tears down every session

Values are wiped two minutes after the first load.


Masking

pan, cvv and pin start masked.

ParameterDescription
maskedControls masking. Pass false to unmask immediately when the user has just confirmed the step-up
onMaskedChange(bool masked, bool screenshotProtectionActive)
masksWhenDetachedDefault true — a row re-masks when detached
onCopyResult(bool copied) — fires 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 session's 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 extends WirexCredentialProvider {
  ProductionCredentials(this._auth);
  final AuthSession _auth;
  ({String scope, String token})? _offered;

  void offerStepUpToken(String scope, String token) => _offered = (scope: scope, token: token);
  void revokeOfferedStepUpToken() => _offered = null;

  @override
  Future<String> accessToken() => _auth.freshAccessToken();

  @override
  Future<String> actionToken(String scope) async {
    final offered = _offered;
    if (offered != null && offered.scope == scope) {
      _offered = null;
      return offered.token;
    }
    return _auth.signAction(scope);
  }

  @override
  String? get userEmail => null;
  @override
  String? get userWallet => _auth.walletAddress;
}

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 box shorter than its contentLet the row take its natural height
unauthorized in the snapshotStale bearer — the SDK never retries a 401Refresh inside accessToken()
Operation reported as declinedactionToken threw WirexDeclinedNone — the user backed out
pin field failedVirtual card, or card not ActiveOmit WirexRevealField.pin for virtual cards

Full error handling: Errors, Logging and Troubleshooting.


Did this page help you?