Search code examples
androidfluttermobilenfccredit-card

Flutter NFC Credit Card Read Informations


Hello, I am trying to obtain credit card or debit card information with nfc on flutter. I will perform payment transactions with this information, I can handle that part myself, but I am having trouble getting card information, can you help? Thanks for your help in advance.

I'm getting this error right now Failed to extract AID from PPSE response

nfc_view_model.dart

    import 'package:flutter/foundation.dart';
    import 'package:flutter/services.dart';
    import 'package:get/get.dart';
    import 'package:nfc_manager/nfc_manager.dart';
    import 'package:nfc_manager/platform_tags.dart';

    class NfcViewModel extends GetxController {
    RxString message = ''.obs;

Future<void> startNFCReading() async {
try {
  final bool isAvailable = await NfcManager.instance.isAvailable();

  if (isAvailable) {
    message.value = 'NFC is available.';

    await NfcManager.instance.startSession(
      onDiscovered: (NfcTag tag) async {
        try {
          if (kDebugMode) {
            print('Tag found: ${tag.data}');
          }
          message.value = 'Tag found: ${tag.data}';

          final IsoDep? isoDep = IsoDep.from(tag);
          if (isoDep != null) {
            // PPSE (Proximity Payment System Environment) seçimi
            var ppseCommand = [
              0x00,
              0xA4,
              0x04,
              0x00,
              0x0E,
              0x32,
              0x50,
              0x41,
              0x59,
              0x2E,
              0x53,
              0x59,
              0x53,
              0x2E,
              0x44,
              0x44,
              0x46,
              0x30,
              0x31,
              0x00
            ];
            var ppseResponse = await isoDep.transceive(
                data: Uint8List.fromList(ppseCommand));
            if (kDebugMode) {
              print('PPSE Response: $ppseResponse');
            }
            if (ppseResponse.isEmpty) {
              message.value = 'PPSE Response is empty.';
              await NfcManager.instance.stopSession();
              return;
            }

            // PPSE yanıtını işle ve AID'yi al
            var aid = extractAidFromPpseResponse(ppseResponse);
            if (aid == null) {
              message.value = 'Failed to extract AID from PPSE response.';
              await NfcManager.instance.stopSession();
              return;
            }

            // AID seçim komutu
            var aidCommand = [
              0x00,
              0xA4,
              0x04,
              0x00,
              aid.length,
              ...aid,
              0x00
            ];
            var aidResponse = await isoDep.transceive(
                data: Uint8List.fromList(aidCommand));
            if (kDebugMode) {
              print('AID Response: $aidResponse');
            }
            if (aidResponse.isEmpty) {
              message.value = 'AID Response is empty.';
              await NfcManager.instance.stopSession();
              return;
            }

            // GET PROCESSING OPTIONS komutu
            var gpoCommand = [
              0x80,
              0xA8,
              0x00,
              0x00,
              0x02,
              0x83,
              0x00,
              0x00
            ];
            var gpoResponse = await isoDep.transceive(
                data: Uint8List.fromList(gpoCommand));
            if (kDebugMode) {
              print('GPO Response: $gpoResponse');
            }
            if (gpoResponse.isEmpty) {
              message.value = 'GPO Response is empty.';
              await NfcManager.instance.stopSession();
              return;
            }

            // RECORD okuma komutu
            var readRecordCommand = [0x00, 0xB2, 0x01, 0x0C, 0x00];
            var readRecordResponse = await isoDep.transceive(
                data: Uint8List.fromList(readRecordCommand));
            if (kDebugMode) {
              print('Read Record Response: $readRecordResponse');
            }
            if (readRecordResponse.isEmpty) {
              message.value = 'Read Record Response is empty.';
              await NfcManager.instance.stopSession();
              return;
            }

            // Kart bilgilerini ayıkla
            final cardDetails = parseCardDetails(readRecordResponse);
            message.value = cardDetails;

            await NfcManager.instance.stopSession();
          } else {
            message.value = 'IsoDep not supported on this tag.';
          }
        } catch (e) {
          message.value = 'Error: $e';
          await NfcManager.instance.stopSession(errorMessage: e.toString());
        }
      },
    );
  } else {
    message.value = 'NFC is not available.';
  }
 } catch (e) {
  message.value = e.toString();
 }
}

List<int>? extractAidFromPpseResponse(Uint8List response) {
try {
  int index = 0;
  while (index < response.length) {
    int tag = response[index++];
    int length = response[index++];
    if (tag == 0x4F) {
      // AID tag
      return response.sublist(index, index + length);
    }
    index += length;
  }
  return null;
} catch (e) {
  if (kDebugMode) {
    print('Error extracting AID: $e');
  }
  return null;
}
}

String parseCardDetails(Uint8List response) {
String hexString = response
    .map((e) => e.toRadixString(16).padLeft(2, '0'))
    .join()
    .toUpperCase();

// Yanıtın uzunluğunu kontrol edin
if (hexString.length < 20) {
  return 'Error: Insufficient data length. Response length is ${hexString.length}';
}

try {
  // Example extraction logic (adjust based on actual card data structure)
  final cardNumber =
      hexString.substring(0, 16); // Example: first 16 characters
  final expiryDate =
      hexString.substring(16, 20); // Example: next 4 characters
  return 'Card Number: $cardNumber, Expiry Date: $expiryDate';
} catch (e) {
  return 'Error parsing card details: $e';
}
}
}

nfc_screen.dart (UI Code)

    import 'package:digipos/core/base/view/base_view.dart';
         import 'package:digipos/view/payment/nfc/view_model/nfc_view_model.dart';
         import 'package:digipos/view/widgets/appbar/custom_appbar_widget.dart';
         import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';

import '../../../core/base/state/base_state.dart';
import '../../../core/constants/icons.dart';

class NfcScreen extends StatefulWidget {
  const NfcScreen({super.key});

  @override
  State<NfcScreen> createState() => _NfcScreenState();
}

class _NfcScreenState extends BaseState<NfcScreen> {
  final NfcViewModel _viewModel = Get.put(NfcViewModel());

  @override
  void initState() {
    super.initState();
    _viewModel.startNFCReading();
  }

  @override
  Widget build(BuildContext context) {
    return BaseView(
      viewModel: _viewModel,
      onPageBuilder: (context, dynamic viewModel) => scaffoldBody(),
    );
  }

  Scaffold scaffoldBody() {
    return Scaffold(
      appBar: const CustomAppBarWidget(appBarType: AppBarType.back),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            SvgPicture.asset(
              AppIcons.nfcPay.iconPath,
              width: dynamicHeight(0.25),
            ),
            const SizedBox(height: 20),
            Obx(
              () => Text(
                _viewModel.message.value,
                textAlign: TextAlign.center,
                style: const TextStyle(
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Solution

  • You are not correctly iterating over PPSE response. It is constructed as a tree and you need to extract all the tags from the response as recursive:

    6F File Control Information (FCI) Template
       840E325041592E5359532E4444463031A514BF0C11610F4F07A0000000031010500456697361
       84 Dedicated File (DF) Name
          325041592E5359532E4444463031 (2PAY.SYS.DDF01)
       A5 File Control Information (FCI) Proprietary Template
          BF0C11610F4F07A0000000031010500456697361
          BF0C File Control Information (FCI) Issuer Discretionary Data
             610F4F07A0000000031010500456697361
             61 Application Template
                4F07A0000000031010500456697361
                4F Application Identifier (ADF Name)
                   A0000000031010
                50 Application Label
                   56697361 (Visa)
    

    Your loop iterates over a list by only root tag and it is exiting in single cycle for 0x6F tag. Implement tree object type and collect all tags.