parse static method

VietQRParsedData parse(
  1. String payload
)

Parses a VietQR payload string and extracts all the information.

This method analyzes a VietQR payload string and returns a VietQRParsedData object containing all the extracted information including bank details, account number, amount, message, and other metadata.

Parameters:

  • payload: The VietQR payload string to parse

Returns a VietQRParsedData object with all parsed information.

Throws FormatException if the payload is malformed or invalid.

Example:

try {
  final parsedData = VietQR.parse('0002010102113802A000000727...');
  print('Bank: ${parsedData.bankName}');
  print('Account: ${parsedData.accountNumber}');
  print('Amount: ${parsedData.formattedAmount}');
} catch (e) {
  print('Invalid VietQR payload: $e');
}

Implementation

static VietQRParsedData parse(String payload) {
  if (payload.isEmpty) {
    throw FormatException('Payload cannot be empty');
  }

  // Validate CRC first
  _validateCRC(payload);

  final fields = <String, String>{};
  int index = 0;

  // Parse all TLV fields
  while (index < payload.length - 4) {
    final fieldId = payload.substring(index, index + 2);
    final lengthStr = payload.substring(index + 2, index + 4);

    if (!RegExp(r'^\d{2}$').hasMatch(lengthStr)) {
      throw FormatException('Invalid length format at position $index');
    }

    final length = int.parse(lengthStr);

    if (index + 4 + length > payload.length) {
      throw FormatException('Field $fieldId extends beyond payload length');
    }

    final value = payload.substring(index + 4, index + 4 + length);
    fields[fieldId] = value;

    index += 4 + length;
  }

  // Extract and validate required fields
  final payloadFormat = fields['00'] ?? '';
  final pointOfInitiation = fields['01'] ?? '';
  final merchantAccount = fields['38'] ?? '';
  final currency = fields['53'] ?? '';
  final amount = fields['54'];
  final countryCode = fields['58'] ?? '';
  final additionalData = fields['62'];
  final crc = fields['63'] ?? '';

  // Validate required fields
  if (payloadFormat != '01') {
    throw FormatException('Invalid payload format: $payloadFormat');
  }

  if (pointOfInitiation != '11' && pointOfInitiation != '12') {
    throw FormatException('Invalid point of initiation: $pointOfInitiation');
  }

  if (currency != '704') {
    throw FormatException('Invalid currency code: $currency');
  }

  if (countryCode != 'VN') {
    throw FormatException('Invalid country code: $countryCode');
  }

  // Parse merchant account information
  final merchantInfo = _parseMerchantAccountInfo(merchantAccount);

  // Parse additional data (message)
  String? message;
  if (additionalData != null && additionalData.isNotEmpty) {
    message = _parseAdditionalData(additionalData);
  }

  // Parse amount
  double? parsedAmount;
  if (amount != null && amount.isNotEmpty) {
    try {
      parsedAmount = double.parse(amount);
    } catch (e) {
      throw FormatException('Invalid amount format: $amount');
    }
  }

  // Determine if dynamic QR
  final isDynamic = pointOfInitiation == '12';

  // Find matching bank
  Bank? bank;
  try {
    bank = Bank.values.firstWhere(
      (b) => b.bin == merchantInfo['bankBin'],
      orElse: () => throw StateError('Bank not found'),
    );
  } catch (e) {
    // Bank not found in enum, will be null
  }

  return VietQRParsedData(
    bank: bank,
    bankBin: merchantInfo['bankBin']!,
    accountNumber: merchantInfo['accountNumber']!,
    amount: parsedAmount,
    message: message,
    isDynamic: isDynamic,
    payloadFormat: payloadFormat,
    pointOfInitiation: pointOfInitiation,
    currency: currency,
    countryCode: countryCode,
    crc: crc,
  );
}