convertToPurchase function

Purchase convertToPurchase(
  1. Map<String, dynamic> itemJson, {
  2. required bool platformIsAndroid,
  3. required bool platformIsIOS,
  4. required Map<String, bool> acknowledgedAndroidPurchaseTokens,
  5. Map<String, dynamic>? originalJson,
})

Implementation

gentype.Purchase convertToPurchase(
  Map<String, dynamic> itemJson, {
  required bool platformIsAndroid,
  required bool platformIsIOS,
  required Map<String, bool> acknowledgedAndroidPurchaseTokens,
  Map<String, dynamic>? originalJson,
}) {
  // Native SDKs serialize the generated Purchase types directly. Preserve the
  // canonical payload as the base so newly generated optional fields are not
  // silently dropped by this adapter.
  final sourcePayload = normalizeDynamicMap(<String, dynamic>{
    if (originalJson != null) ...originalJson,
    ...itemJson,
  })!;

  final productId = sourcePayload['productId']?.toString() ?? '';
  final sourceId = sourcePayload['id']?.toString();
  final sourceTransactionId = sourcePayload['transactionId']?.toString();
  final dynamic quantityValue = sourcePayload['quantity'];
  int quantity = 1;
  if (quantityValue is num) {
    quantity = quantityValue.toInt();
  } else if (quantityValue is String) {
    final parsedQuantity = int.tryParse(quantityValue.trim());
    if (parsedQuantity != null) {
      quantity = parsedQuantity;
    }
  }

  final String? purchaseId = (sourceId?.isNotEmpty ?? false) ? sourceId : null;

  if (purchaseId == null || purchaseId.isEmpty) {
    debugPrint(
      '[flutter_inapp_purchase] Invalid purchase payload: missing identifier',
    );
    throw const FormatException('Missing purchase identifier');
  }

  final transactionDate =
      _parseTimestampMilliseconds(sourcePayload['transactionDate']) ?? 0;

  if (platformIsAndroid) {
    final hasSelectedPurchaseState = sourcePayload.containsKey('purchaseState');
    final stateValue = _coerceAndroidPurchaseState(
      sourcePayload['purchaseState'],
      hasSelectedState: hasSelectedPurchaseState,
    );
    final purchaseState = _mapAndroidPurchaseState(stateValue).toJson();

    // Determine store from input or default based on platform
    final storeValue = sourcePayload['store']?.toString() ?? 'google';
    final map = <String, dynamic>{
      ...sourcePayload,
      'id': purchaseId,
      'productId': productId,
      'store': storeValue,
      'isAutoRenewing': sourcePayload['isAutoRenewing'] as bool? ?? false,
      'purchaseState': purchaseState,
      'quantity': quantity,
      'transactionDate': transactionDate,
      'purchaseToken': sourcePayload['purchaseToken']?.toString(),
      'autoRenewingAndroid': sourcePayload['autoRenewingAndroid'] as bool?,
      'currentPlanId': sourcePayload['currentPlanId']?.toString(),
      'dataAndroid': sourcePayload['dataAndroid']?.toString(),
      'developerPayloadAndroid':
          sourcePayload['developerPayloadAndroid']?.toString(),
      'ids': _toStringList(sourcePayload['ids']),
      'isAcknowledgedAndroid': sourcePayload['isAcknowledgedAndroid'] as bool?,
      'obfuscatedAccountIdAndroid':
          sourcePayload['obfuscatedAccountIdAndroid']?.toString(),
      'obfuscatedProfileIdAndroid':
          sourcePayload['obfuscatedProfileIdAndroid']?.toString(),
      'packageNameAndroid': sourcePayload['packageNameAndroid']?.toString(),
      'signatureAndroid': sourcePayload['signatureAndroid']?.toString(),
      // Pending/orderless Play purchases legitimately have no order ID.
      'transactionId': sourceTransactionId,
    };

    final purchaseToken = sourcePayload['purchaseToken']?.toString();
    if (purchaseToken != null && purchaseToken.isNotEmpty) {
      acknowledgedAndroidPurchaseTokens[purchaseToken] =
          sourcePayload['isAcknowledgedAndroid'] as bool? ?? false;
    }

    return gentype.PurchaseAndroid.fromJson(map);
  }

  if (platformIsIOS) {
    final stateIOS =
        _parsePurchaseStateIOS(sourcePayload['purchaseState']).toJson();

    final originalTransactionDateIOS = _parseTimestampMilliseconds(
      sourcePayload['originalTransactionDateIOS'],
    );

    // Determine store from input or default based on platform
    final storeValueIOS = sourcePayload['store']?.toString() ?? 'apple';
    if (sourceTransactionId == null || sourceTransactionId.isEmpty) {
      throw const FormatException('Missing iOS transactionId');
    }

    final map = <String, dynamic>{
      ...sourcePayload,
      'id': purchaseId,
      'productId': productId,
      'store': storeValueIOS,
      'isAutoRenewing': sourcePayload['isAutoRenewing'] as bool? ?? false,
      'purchaseState': stateIOS,
      'quantity': quantity,
      'transactionDate': transactionDate,
      'purchaseToken': sourcePayload['purchaseToken']?.toString(),
      'ids': _toStringList(sourcePayload['ids']),
      'appAccountToken': sourcePayload['appAccountToken']?.toString(),
      'appBundleIdIOS': sourcePayload['appBundleIdIOS']?.toString(),
      'countryCodeIOS': sourcePayload['countryCodeIOS']?.toString(),
      'currencyCodeIOS': sourcePayload['currencyCodeIOS']?.toString(),
      'currencySymbolIOS': sourcePayload['currencySymbolIOS']?.toString(),
      'environmentIOS': sourcePayload['environmentIOS']?.toString(),
      'expirationDateIOS':
          _parseTimestampMilliseconds(sourcePayload['expirationDateIOS']),
      'originalTransactionIdentifierIOS':
          sourcePayload['originalTransactionIdentifierIOS']?.toString(),
      'originalTransactionDateIOS': originalTransactionDateIOS,
      'subscriptionGroupIdIOS':
          sourcePayload['subscriptionGroupIdIOS']?.toString(),
      'transactionId': sourceTransactionId,
      'transactionReasonIOS': sourcePayload['transactionReasonIOS']?.toString(),
      'webOrderLineItemIdIOS':
          sourcePayload['webOrderLineItemIdIOS']?.toString(),
      'revocationDateIOS':
          _parseTimestampMilliseconds(sourcePayload['revocationDateIOS']),
      'revocationReasonIOS': sourcePayload['revocationReasonIOS']?.toString(),
      'revocationTypeIOS': sourcePayload['revocationTypeIOS']?.toString(),
      'bundleOriginalTransactionIdIOS':
          sourcePayload['bundleOriginalTransactionIdIOS']?.toString(),
      'bundleProductIdIOS': sourcePayload['bundleProductIdIOS']?.toString(),
      'bundleSubscriptionGroupIdIOS':
          sourcePayload['bundleSubscriptionGroupIdIOS']?.toString(),
      'bundleTransactionIdIOS':
          sourcePayload['bundleTransactionIdIOS']?.toString(),
      'previousOriginalTransactionIdIOS':
          sourcePayload['previousOriginalTransactionIdIOS']?.toString(),
    };

    return gentype.PurchaseIOS.fromJson(map);
  }

  throw const FormatException('Unsupported purchase platform');
}