Skip to main content

Package

For terminal transaction processing, Flutter has a companion package: flutter_tappa.

Installation

Add the dependency:
# pubspec.yaml
dependencies:
  cloudcard_flutter: ^0.1.1
Then run:
flutter pub get

Android Repository Setup

For Gradle < 7 (android/build.gradle):
allprojects {
  repositories {
    google()
    mavenCentral()
    maven {
      url = uri("https://sdk.sudo.africa/repository/maven-releases/")
      credentials {
        username = project.findProperty("maven.repo.username") ?: ""
        password = project.findProperty("maven.repo.password") ?: ""
      }
    }
  }
}
For Gradle 7+ (android/settings.gradle):
dependencyResolutionManagement {
  repositories {
    google()
    mavenCentral()
    maven {
      url = uri("https://sdk.sudo.africa/repository/maven-releases/")
      credentials {
        username = providers.gradleProperty("maven.repo.username").orNull ?: System.getenv("NEXUS_USER")
        password = providers.gradleProperty("maven.repo.password").orNull ?: System.getenv("NEXUS_PASS")
      }
    }
  }
}
Set credentials in gradle.properties:
maven.repo.username=your_sudo_username
maven.repo.password=your_sudo_password
Or set environment variables:
export NEXUS_USER=your_sudo_username
export NEXUS_PASS=your_sudo_password

Android Manifest Configuration

Register the HCE service and receiver inside <application> in android/app/src/main/AndroidManifest.xml.
<service
    android:name="com.sudo.cloud_card.HCEService"
    android:enableOnBackInvokedCallback="true"
    android:enabled="true"
    android:exported="true"
    android:permission="android.permission.BIND_NFC_SERVICE">
    <intent-filter>
        <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
    <meta-data
        android:name="android.nfc.cardemulation.host_apdu_service"
        android:resource="@xml/apduservice" />
</service>

<receiver
    android:name="africa.sudo.cloudcard_flutter.HceEventReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="${packageName}.CLOUDCARD_EVENT" />
    </intent-filter>
</receiver>
Create android/app/src/main/res/xml/apduservice.xml:
<?xml version="1.0" encoding="utf-8"?>
<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/app_name"
    android:requireDeviceUnlock="false">
    <aid-group android:category="payment" android:description="@string/app_name" />
</host-apdu-service>

iOS Setup

Create .env in your project root:
SUDO_USER=your_sudo_username
SUDO_PASS=your_sudo_password
Run:
export $(grep -v '^#' .env | xargs)
TMPHOME=$(mktemp -d)
trap 'rm -rf "$TMPHOME"' EXIT
export HOME="$TMPHOME"
cat > "$HOME/.netrc" <<EOF_NETRC
machine sdk.sudo.africa
login $SUDO_USER
password $SUDO_PASS
EOF_NETRC
chmod 600 "$HOME/.netrc"
cd ios && pod install

Initialization

Initialize once near app startup.
import 'package:cloudcard_flutter/cloudcard_flutter.dart';

final cloudCard = CloudCardFlutter();

Future<void> initializeCloudCard() async {
  await cloudCard.init(
    isSandBox: true,
    onCardScanned: (CloudCardEvent event) {
      print('scan started: ${event.eventType} ${event.message}');
    },
    onScanComplete: (CloudCardEvent event) {
      print('scan done: ${event.isSuccess} amount=${event.amount}');
    },
  );
}
Environment:
  • isSandBox: true for sandbox
  • isSandBox: false for production

Health Checks

final isSupported = await cloudCard.isDeviceSupported();
final isNfcEnabled = await cloudCard.isNfcEnabled();
final isDefaultApp = await cloudCard.isDefaultPaymentApp();

if (isSupported && isNfcEnabled == true && isDefaultApp == false) {
  await cloudCard.launchDefaultPaymentAppSettings();
}

Optional Security Behavior

await cloudCard.setRequireAuth(true);
Use setRequireAuth(true) if you want foreground access and biometric checks during payment access.

Register Card

You register cards with RegistrationData. Required fields:
  • walletId
  • paymentAppInstanceId
  • accountId
  • jwtToken
Optional fields:
  • secret
  • cardNumber
  • expiryDate
  • cardHolderName
final cloudCard = CloudCardFlutter();

final registrationData = RegistrationData(
  walletId: 'institution-id',
  paymentAppInstanceId: 'device-instance-id',
  accountId: 'card-or-account-id',
  jwtToken: 'onboarding-jwt-token',
  secret: 'optional-secret',
  cardNumber: '4111111111111111',
  expiryDate: '12/25',
  cardHolderName: 'John Doe',
);

final result = await cloudCard.registerCard(registrationData);
if (result.status == Status.SUCCESS) {
  print('Card registered');
} else {
  print('Registration failed: ${result.message}');
}

Digitalization Fetch Pattern

If you already have your own card-management system, you can build and pass your own RegistrationData directly to registerCard using the required fields walletId, paymentAppInstanceId, accountId, and jwtToken. If you do not manage that payload yourself, fetch the onboarding or digitalization payload from your backend first, then pass it to registerCard. For endpoint details, see Card Digitalization Reference.
final digitalizeUrl = Uri.parse(
  'https://api.sandbox.sudo.cards/cards/digitalize/{id}',
);

final res = await http.get(
  digitalizeUrl,
  headers: {
    'Accept': 'text/plain',
    'Content-Type': 'application/json',
    'platform': 'android',
    'Authorization': accessToken,
  },
);

Card Management

Get Cards

final result = await CloudCardFlutter().getCards();
if (result.status == Status.SUCCESS && result.data is List<CardData>) {
  final cards = result.data as List<CardData>;
  for (final card in cards) {
    print('${card.id} ${card.maskedPan} active=${card.isActive}');
  }
}

Freeze or Unfreeze

await CloudCardFlutter().freezeUnfreezeCard(
  cardId: cardId,
  isFreeze: true,
);
Set isFreeze: false to unfreeze.

Delete Card

await CloudCardFlutter().deleteCard(cardId);

Wipe Wallet

await CloudCardFlutter().wipeWallet();
Use this only after explicit user confirmation.

Manual Key Replenishment

final result = await CloudCardFlutter().manualKeyReplenishment();

Token Usage Summary

final result = await CloudCardFlutter().tokenSummary();
if (result.status == Status.SUCCESS && result.data != null) {
  final summary = TokensUsageSummary.fromMap(result.data);
  print('total=${summary.totalTokens} balance=${summary.tokensBalance}');
}

QR and Transactions

Generate EMV QR

final result = await CloudCardFlutter().getEmvQr(
  cardId: cardId,
  amount: '000000000200',
  foregroundColorHex: '#000000',
  backgroundColorHex: '#FFFFFF',
);

if (result.status == Status.SUCCESS && result.data != null) {
  showDialog(
    context: context,
    builder: (_) => AlertDialog(content: Image.memory(result.data)),
  );
}
amount is in the smallest unit and typically zero-padded.

Read Saved Transactions

getSavedTransactions() returns recent cached transactions, up to 5.
final result = await CloudCardFlutter().getSavedTransactions();
if (result.status == Status.SUCCESS && result.data is List<SavedTransaction>) {
  final txs = result.data as List<SavedTransaction>;
  for (final tx in txs) {
    print('${tx.amount} ${tx.currency} ${tx.timestamp} ${tx.type}');
  }
}

NFC and Settings

final cloudCard = CloudCardFlutter();

final isNfcEnabled = await cloudCard.isNfcEnabled();
final isDefaultPaymentApp = await cloudCard.isDefaultPaymentApp();

if (isNfcEnabled == true && isDefaultPaymentApp == false) {
  await cloudCard.launchDefaultPaymentAppSettings();
}

NFC Chip Indicator Helpers

await cloudCard.showNfcChipIndicator(
  duration: const Duration(seconds: 5),
  colorHex: '#AF5298',
);

final position = await cloudCard.getNfcChipPosition();
print('${position?.x} ${position?.y} ${position?.confidence}');

await cloudCard.hideNfcChipIndicator();

Requirements

  • Flutter 2.5.0+
  • Android API 21+
  • iOS 13.0+
NFC-enabled device is required for NFC payments, but not for QR.

Troubleshooting

Initialization Fails

  • Confirm SDK repository credentials are set correctly on Android.
  • Confirm the iOS .netrc auth step was completed before pod install.
  • Check that you called init() before card operations.

NFC Not Working

  • Confirm the device supports NFC.
  • Confirm NFC is enabled in system settings.
  • Confirm your app is set as default payment app when required.
  • Verify the HCE service and receiver entries in AndroidManifest.xml.
  • Confirm the receiver class is africa.sudo.cloudcard_flutter.HceEventReceiver.

Card Registration Fails

  • Verify walletId, paymentAppInstanceId, accountId, and jwtToken.
  • Ensure the onboarding token is still valid.
  • Check that your backend is issuing tokens for the correct environment.

QR Generation Fails

  • Use a valid cardId.
  • Ensure amount is formatted correctly.

Empty Transaction List

  • getSavedTransactions() uses local cache and returns recent transactions only.
  • Complete at least one transaction flow first, then reload.

Support

Contact support@sudo.africa.