cloudcard_flutter 0.0.8-2 copy "cloudcard_flutter: ^0.0.8-2" to clipboard
cloudcard_flutter: ^0.0.8-2 copied to clipboard

Flutter SDK for card digitization and provisioning with Sudo

example/lib/main.dart

import 'dart:convert';
import 'dart:developer';

import 'package:cloudcard_flutter/cloudcard_flutter.dart';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'dart:async';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'CloudCard Flutter Example',
      theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
      home: const CloudCardHome(),
    );
  }
}

class CloudCardHome extends StatefulWidget {
  const CloudCardHome({Key? key}) : super(key: key);

  @override
  State<CloudCardHome> createState() => _CloudCardHomeState();
}

class _CloudCardHomeState extends State<CloudCardHome> {
  final CloudCardFlutter _cloudCard = CloudCardFlutter();
  bool _isInitialized = false;
  bool? _isDefaultPaymentApp = false;
  bool? _isNfcEnabled = null;
  List<CardData> _cards = [];
  TokensUsageSummary? _tokenSummary;

  // Text controllers for card registration
  final TextEditingController _cardNumberController = TextEditingController();
  final TextEditingController _expiryController = TextEditingController();
  final TextEditingController _cvvController = TextEditingController();
  final TextEditingController _nameController = TextEditingController();
  final TextEditingController _cardIdController = TextEditingController();
  final TextEditingController _tokenController = TextEditingController();

  @override
  void initState() {
    super.initState();
    _initializeCloudCard();
  }

  @override
  void dispose() {
    _cardNumberController.dispose();
    _expiryController.dispose();
    _cvvController.dispose();
    _nameController.dispose();
    _cardIdController.dispose();
    _tokenController.dispose();
    super.dispose();
  }

  // Initialize the CloudCard SDK
  Future<void> _initializeCloudCard() async {
    try {
      // Initialize with sandbox mode for development
      await _cloudCard.init(isSandBox: true,
          onCardScanned: (CloudCardEvent scannedResult){

            print("Card Scanned Launch **********");
            print("Result ${scannedResult.eventType} : ${scannedResult.appIsOnForeground}  : ${scannedResult.message} : ${scannedResult.isSuccess} : ${scannedResult.amount}");
          //Show custom notification of payment with some parameters returned
                //scannedResult.isSuccess
                //scannedResult.eventType
          //Navigate to screen
          //       Navigator.push(context, MaterialPageRoute(builder: (_)=>DummyPage()));
      },
        onScanComplete: (CloudCardEvent scannedResult){
        print("Card Scanned **********");
        print("Result ${scannedResult.eventType} : ${scannedResult.appIsOnForeground} : ${scannedResult.message} : ${scannedResult.isSuccess} : ${scannedResult.amount} : ${scannedResult.merchant} : ${scannedResult.timestamp} : ${scannedResult.transactionCount}");
          // Scan complete - Show UI update
        },
      );

      // Check if NFC is enabled
      final nfcEnabled = await _cloudCard.isNfcEnabled();

      // Check if app is the default payment app
      final isDefault = await _cloudCard.isDefaultPaymentApp();


      log("Is Default ${isDefault}");

      setState(() {
        _isInitialized = true;
        _isNfcEnabled = nfcEnabled;
        _isDefaultPaymentApp = isDefault;
      });

      // Load cards after initialization
      await _loadCards();

      // Check token summary
      await _checkTokenSummary();
    } catch (e) {
      _showErrorSnackBar("Failed to initialize CloudCard: $e");
    }
  }

  // Load all registered cards
  Future<void> _loadCards() async {
    try {
      final result = await _cloudCard.getCards();
      _cloudCard.showNfcChipIndicator(duration: Duration(seconds: 5),colorHex: "#AF5298");
      final nfcChipPosition = await _cloudCard.getNfcChipPosition();

      print("Wesy ${nfcChipPosition?.x} ${nfcChipPosition?.y}"
          " ${nfcChipPosition?.confidence} ${nfcChipPosition?.description}");

      if (result.status == Status.SUCCESS && result.data != null) {
        setState(() {
          if (result.data is List<dynamic>) {

            _cards = result.data;

          } else {
            _cards = [];
          }
        });
            _loadTransHistory();
      } else {
        _showErrorSnackBar("Failed to load cards: ${result.message}");
      }
    } catch (e,s) {
      print(e);
      print(s);

      _showErrorSnackBar("Error loading cards: $e");
    }
  }

  Future<void> _loadTransHistory() async {
    final res = await _cloudCard.getSavedTransactions();
    if(res.status == Status.SUCCESS) {
    final txs = (res.data as List<SavedTransaction>);
    for(int i = 0; i<txs.length; i++){

      print("Index $i");
      print(txs[i].amount);
      print(txs[i].atc);
      print(txs[i].timestamp);
      print(txs[i].type);
      // print(DateTim
      // e.fromMillisecondsSinceEpoch(int.parse(txs[i].timestamp)));
    }
    }
}

  // Check token summary
  Future<void> _checkTokenSummary() async {
    try {
      final result = await _cloudCard.tokenSummary();

      if (result.status == Status.SUCCESS && result.data != null) {
        print(result.data);
        setState(() {
          _tokenSummary = TokensUsageSummary.fromMap(result.data);
        });
      }
    } catch (e) {
      _showErrorSnackBar("Error checking token summary: $e");
    }
  }

  // Register a new card
  Future<void> _registerNewCard({
    required String walletId,
    required String paymentAppInstanceId,
    required String accountId,
    String? secret,
    String? jwtToken,
  }) async {
    try {
      final regData = RegistrationData(
        walletId: walletId,
        paymentAppInstanceId: paymentAppInstanceId,
        accountId: accountId,
        secret: secret ?? '',
        jwtToken: jwtToken ?? '',
        expiryDate: '',
        cardHolderName: '',
        cardNumber: '',
      );

      final result = await _cloudCard.registerCard(regData);

      if (result.status == Status.SUCCESS) {
        _showSuccessSnackBar("Card registered successfully!");

        // Clear form fields
        _cardNumberController.clear();
        _expiryController.clear();
        _cvvController.clear();
        _nameController.clear();

        // Refresh card list
        await _loadCards();
        Navigator.pop(context);
      } else {
        _showErrorSnackBar("Failed to register card: ${result.message}");
      }
    } catch (e) {
      _showErrorSnackBar("Error registering card: $e");
    }
  }

  // Generate EMV QR for a card
  Future<void> _generateEmvQr(String cardId) async {
    try {
      final result = await _cloudCard.getEmvQr(cardId: cardId, amount: "000000000200", );

      if (result.status == Status.SUCCESS && result.data != null) {
        // Show QR code in dialog
        showDialog(
          context: context,
          builder:
              (context) => AlertDialog(
                title: const Text('EMV QR Code'),
                content: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    Image.memory(result.data),
                    const SizedBox(height: 16),
                    Text('Scan this QR code to complete payment'),
                  ],
                ),
                actions: [
                  TextButton(
                    onPressed: () => Navigator.pop(context),
                    child: const Text('Close'),
                  ),
                ],
              ),
        );
      } else {
        _showErrorSnackBar("Failed to generate QR: ${result.message}");
      }
    } catch (e,s) {
      _showErrorSnackBar("Error generating QR: $e");
    }
  }

  // Delete a card
  Future<void> _deleteCard(String cardId) async {
    try {
      final result = await _cloudCard.deleteCard(cardId);

      if (result.status == Status.SUCCESS) {
        _showSuccessSnackBar("Card deleted successfully!");
        await _loadCards();
      } else {
        _showErrorSnackBar("Failed to delete card: ${result.message}");
      }
    } catch (e) {
      _showErrorSnackBar("Error deleting card: $e");
    }
  }

  // Freeze or unfreeze a card
  Future<void> _freezeUnfreezeCard(
    String cardId,
    bool isCurrentlyFrozen,
  ) async {
    try {
      final result = await _cloudCard.freezeUnfreezeCard(
        isFreeze: !isCurrentlyFrozen,
        cardId: cardId,
      );

      if (result.status == Status.SUCCESS) {
        _showSuccessSnackBar(
          isCurrentlyFrozen ? "Card unfrozen!" : "Card frozen!",
        );
        await _loadCards();
      } else {
        _showErrorSnackBar("Operation failed: ${result.message}");
      }
    } catch (e) {
      _showErrorSnackBar("Error updating card status: $e");
    }
  }

  // Wipe wallet data
  Future<void> _wipeWallet() async {
    try {
      // Show confirmation dialog
      final confirm = await showDialog<bool>(
        context: context,
        builder:
            (context) => AlertDialog(
              title: const Text('Wipe Wallet'),
              content: const Text(
                'Are you sure you want to delete all cards and wallet data? This action cannot be undone.',
              ),
              actions: [
                TextButton(
                  onPressed: () => Navigator.pop(context, false),
                  child: const Text('Cancel'),
                ),
                TextButton(
                  onPressed: () => Navigator.pop(context, true),
                  child: const Text(
                    'Wipe Data',
                    style: TextStyle(color: Colors.red),
                  ),
                ),
              ],
            ),
      );

      if (confirm == true) {
        final result = await _cloudCard.wipeWallet();

        if (result.status == Status.SUCCESS) {
          _showSuccessSnackBar("Wallet data wiped successfully!");
          await _loadCards();
          await _checkTokenSummary();
        } else {
          _showErrorSnackBar("Failed to wipe wallet: ${result.message}");
        }
      }
    } catch (e) {
      _showErrorSnackBar("Error wiping wallet: $e");
    }
  }

  // Launch default payment app settings
  Future<void> _launchPaymentSettings() async {
    try {
      final launched = await _cloudCard.launchDefaultPaymentAppSettings();

      if (!launched) {
        _showErrorSnackBar("Could not open payment app settings");
      }
    } catch (e) {
      _showErrorSnackBar("Error opening settings: $e");
    }
  }

  void _showSuccessSnackBar(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message), backgroundColor: Colors.green),
    );
  }

  void _showErrorSnackBar(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message), backgroundColor: Colors.red),
    );
  }

  // Show dialog to add a new card
  void _showAddCardDialog() {
    showDialog(
      context: context,
      builder:
          (context) => AlertDialog(
            title: const Text('Add New Card'),
            content: SingleChildScrollView(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  TextField(
                    controller: _cardIdController,
                    decoration: const InputDecoration(labelText: 'Card Id'),
                  ),
                  TextField(
                    controller: _tokenController,
                    decoration: const InputDecoration(
                      labelText: 'Access Token',
                    ),
                    keyboardType: TextInputType.number,
                  ),
                ],
              ),
            ),
            actions: [
              TextButton(
                onPressed: () => Navigator.pop(context),
                child: const Text('Cancel'),
              ),
              TextButton(
                onPressed: () async {
                  // Navigator.pop(context);
                  final digitalizedCard = await digitalizeCard();
                  if (digitalizedCard != null && digitalizedCard['data'] != null) {
                    _registerNewCard(
                      walletId: digitalizedCard['data']['institutionId'],
                      paymentAppInstanceId: 'uniqueId',
                      accountId: digitalizedCard['data']['cardId'],
                      jwtToken: digitalizedCard['data']['token'],
                      secret: digitalizedCard['data']['secret']??"",
                    );
                  }
                },
                child: const Text('Register'),
              ),
            ],
          ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('CloudCard Wallet'),
        actions: [
          IconButton(
            icon: const Icon(Icons.settings),
            onPressed: () {
              showModalBottomSheet(
                context: context,
                builder: (context) => _buildSettingsSheet(),
              );
            },
          ),
        ],
      ),
      body:
          !_isInitialized
              ? const Center(child: CircularProgressIndicator())
              : _buildBody(),
      floatingActionButton: FloatingActionButton(
        onPressed: _showAddCardDialog,
        child: const Icon(Icons.add),
      ),
    );
  }

  Widget _buildBody() {
    return RefreshIndicator(
      onRefresh: () async {
        await _loadCards();
        await _checkTokenSummary();
      },
      child: Column(
        children: [
          _buildStatusBar(),
          const Divider(),
          _buildTokenSummary(),
          const Divider(),
          Expanded(
            child:
                _cards.isEmpty
                    ? Center(
                      child: Text(
                        'No cards registered yet.\nTap + to add a new card.',
                        textAlign: TextAlign.center,
                        style: TextStyle(color: Colors.grey[600]),
                      ),
                    )
                    : ListView.builder(
                      itemCount: _cards.length,
                      itemBuilder:
                          (context, index) => _buildCardItem(_cards[index]),
                    ),
          ),
        ],
      ),
    );
  }

  Widget _buildStatusBar() {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Row(
        children: [
          if (_isNfcEnabled != null)
            Icon(Icons.nfc, color: _isNfcEnabled! ? Colors.green : Colors.red),
          const SizedBox(width: 8),
          Text(
            _isNfcEnabled == null?
            'NFC not available' : _isNfcEnabled!? 'NFC Enabled' : 'NFC Disabled',
            style: TextStyle(
              color: _isNfcEnabled == true ? Colors.green : Colors.red,
              fontWeight: FontWeight.bold,
            ),
          ),
          const Spacer(),
          if(_isDefaultPaymentApp != null)
          Icon(
            _isDefaultPaymentApp! ? Icons.check_circle : Icons.warning,
            color: _isDefaultPaymentApp! ? Colors.green : Colors.orange,
          ),
          const SizedBox(width: 8),
          if(_isDefaultPaymentApp != null)
          Text(
            _isDefaultPaymentApp! ? 'Default Payment App' : 'Not Default App',
            style: TextStyle(
              color: _isDefaultPaymentApp! ? Colors.green : Colors.orange,
              fontWeight: FontWeight.bold,
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildTokenSummary() {
    if (_tokenSummary == null) {
      return const SizedBox.shrink();
    }

    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            'Token Summary',
            style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
          ),
          const SizedBox(height: 8),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: [
              _buildTokenStat(
                'Total Tokens',
                _tokenSummary!.totalTokens.toString(),
              ),
              _buildTokenStat(
                'Available Tokens',
                _tokenSummary!.tokensBalance.toString(),
              ),
              _buildTokenStat(
                'Used NFC Tokens',
                _tokenSummary!.usedNfcTokens.toString(),
              ),
              _buildTokenStat(
                'Used QR Tokens',
                _tokenSummary!.usedQrTokens.toString(),
              ),
            ],
          ),
        ],
      ),
    );
  }

  Widget _buildTokenStat(String label, String value) {
    return Column(
      children: [
        Text(
          value,
          style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
        ),
        SizedBox(
          width: MediaQuery.of(context).size.width * 0.18,
          child: Text(
            label,
            textAlign: TextAlign.center,
            maxLines: 2,
            style: TextStyle(color: Colors.grey[600], fontSize: 12),
          ),
        ),
      ],
    );
  }

  Widget _buildCardItem(CardData card) {
    final lastFour = card.maskedPan;

    return Card(
      margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
      elevation: 4,
      child: Container(
        height: 200,
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topLeft,
            end: Alignment.bottomRight,
            colors:
                !card.isActive
                    ? [Colors.blueGrey.shade800, Colors.blueGrey.shade500]
                    : [Colors.blue.shade800, Colors.blue.shade500],
          ),
          borderRadius: BorderRadius.circular(12),
        ),
        child: Stack(
          children: [
            // Card content
            Padding(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: [
                      Text(
                        'Digital Card',
                        style: TextStyle(
                          color: Colors.white.withOpacity(0.8),
                          fontSize: 14,
                        ),
                      ),
                      Icon(
                        Icons.credit_card,
                        color: Colors.white.withOpacity(0.8),
                      ),
                    ],
                  ),
                  const Spacer(),
              Row(
                children: [
                  Text(
                    '$lastFour',
                    style: const TextStyle(
                      color: Colors.white,
                      fontSize: 22,
                      letterSpacing: 2,
                    ),
                  ),
                  const Spacer(),
                  Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        'EXPIRES',
                        style: TextStyle(
                          color: Colors.white.withOpacity(0.6),
                          fontSize: 10,
                        ),
                      ),
                      const SizedBox(height: 4),
                      Text(
                        card.exp,
                        style: const TextStyle(
                          color: Colors.white,
                          fontSize: 16,
                        ),
                      ),
                    ],
                  ),
                ],
              ),
                  const SizedBox(height: 16),
                  Row(
                    children: [
                      Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text(
                            'CARD HOLDER',
                            style: TextStyle(
                              color: Colors.white.withOpacity(0.6),
                              fontSize: 10,
                            ),
                          ),
                          const SizedBox(height: 4),
                          Text(
                            card.cardHolder,
                            style: const TextStyle(
                              color: Colors.white,
                              fontSize: 16,
                            ),
                          ),
                        ],
                      ),
                    ],
                  ),
                ],
              ),
            ),

            // Frozen overlay
            if (!card.isActive)
              Container(
                decoration: BoxDecoration(
                  color: Colors.white.withOpacity(0.15),
                  borderRadius: BorderRadius.circular(12),
                ),
                child: const Center(
                  child: Text(
                    'FROZEN',
                    style: TextStyle(
                      color: Colors.white,
                      fontSize: 32,
                      fontWeight: FontWeight.bold,
                      letterSpacing: 8,
                    ),
                  ),
                ),
              ),

            // Actions panel
            Positioned(
              bottom: 0,
              right: 0,
              child: Row(
                children: [
                  IconButton(
                    icon: Icon(
                      !card.isActive ? Icons.ac_unit : Icons.ac_unit_outlined,
                      color: Colors.white,
                    ),
                    onPressed:
                        () => _freezeUnfreezeCard(card.id, !card.isActive),
                    tooltip: !card.isActive ? 'Unfreeze Card' : 'Freeze Card',
                  ),
                  IconButton(
                    icon: const Icon(Icons.qr_code, color: Colors.white),
                    onPressed: () => _generateEmvQr(card.id),
                    tooltip: 'Generate QR Code',
                  ),
                  IconButton(
                    icon: const Icon(Icons.delete_outline, color: Colors.white),
                    onPressed: () => _deleteCard(card.id),
                    tooltip: 'Delete Card',
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildSettingsSheet() {
    return SafeArea(
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            const Text(
              'Settings',
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 16),

            if(_isDefaultPaymentApp != null)
            ListTile(
              leading: const Icon(Icons.payment),
              title: const Text('Set as Default Payment App'),
              subtitle: Text(
                _isDefaultPaymentApp!
                    ? 'App is currently set as default'
                    : 'App is not set as default',
              ),
              onTap: _launchPaymentSettings,
            ),
            ListTile(
              leading: const Icon(Icons.sync),
              title: const Text('Manual Key Replenishment'),
              onTap: () async {
                try {
                  final result = await _cloudCard.manualKeyReplenishment();
                  if (result.status == Status.SUCCESS) {
                    _showSuccessSnackBar("Keys replenished successfully!");
                  } else {
                    _showErrorSnackBar(
                      "Failed to replenish keys: ${result.message}",
                    );
                  }
                } catch (e) {
                  _showErrorSnackBar("Error: $e");
                }
              },
            ),
            ListTile(
              leading: const Icon(Icons.delete_forever, color: Colors.red),
              title: const Text(
                'Wipe Wallet Data',
                style: TextStyle(color: Colors.red),
              ),
              onTap: _wipeWallet,
            ),
          ],
        ),
      ),
    );
  }

  Future<Map?> digitalizeCard() async {
    try {
      final digitalizeUrl = Uri.parse(
        'https://digital-api-sandbox.cardcore.cloud/institution/jwt/${_cardIdController.text}'
        // 'https://api.sandbox.sudo.africa/cards/digitalize/${_cardIdController.text}',
      );

      final digitalizeRes = await http.get(
        digitalizeUrl,
        headers: {
          'Accept': 'text/plain',
          'Content-Type': 'application/json',
          'platform': 'android',
          'ngrok-skip-browser-warning': 'allow-any',
          'Authorization': _tokenController.text,
        },
      );
      final digitalizeData = json.decode(digitalizeRes.body.toString());
      log(digitalizeData.toString(), name: 'digitalizeData');
      return digitalizeData;
    } catch (e) {
      log(e.toString());
      rethrow;
    }
  }
}

class DummyPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    throw UnimplementedError();
  }
}
2
likes
0
points
171
downloads

Publisher

unverified uploader

Weekly Downloads

Flutter SDK for card digitization and provisioning with Sudo

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on cloudcard_flutter

Packages that implement cloudcard_flutter