convertToPurchase function
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 that
// canonical payload as the base so newly generated optional fields are not
// silently dropped by this legacy compatibility adapter.
//
// The fallback wire keys below are accepted through
// flutter_inapp_purchase 9.x only and are scheduled for removal in 10.0.0.
// New native bridges, fixtures, and custom MethodChannel producers must emit
// the canonical OpenIAP keys.
final sourcePayload = normalizeDynamicMap(<String, dynamic>{
if (originalJson != null) ...originalJson,
...itemJson,
})!;
final productId = sourcePayload['productId']?.toString() ?? '';
final hasSourceId = sourcePayload.containsKey('id');
final sourceId = sourcePayload['id']?.toString();
final transactionIdSelection = _transactionIdFrom(sourcePayload);
final sourceTransactionId = transactionIdSelection.value;
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 = hasSourceId
? ((sourceId?.isNotEmpty ?? false) ? sourceId : null)
: (sourceTransactionId?.isNotEmpty ?? false)
? sourceTransactionId
: (productId.isNotEmpty ? productId : null);
if (purchaseId == null || purchaseId.isEmpty) {
debugPrint(
'[flutter_inapp_purchase] Skipping purchase with missing identifiers',
);
throw const FormatException('Missing purchase identifier');
}
final transactionDate =
_parseTimestampMilliseconds(sourcePayload['transactionDate']) ?? 0;
if (platformIsAndroid) {
final hasSelectedPurchaseState =
sourcePayload.containsKey('purchaseState') ||
sourcePayload.containsKey('purchaseStateAndroid');
final stateValue = _coerceAndroidPurchaseState(
_canonicalOrLegacy(
sourcePayload,
canonicalKey: 'purchaseState',
legacyKey: 'purchaseStateAndroid',
warningKey: 'purchase.purchaseStateAndroid',
message: 'The purchase `purchaseStateAndroid` field is deprecated and '
'will be removed in flutter_inapp_purchase 10.0.0. Use '
'`purchaseState` instead.',
),
hasSelectedState: hasSelectedPurchaseState,
);
final purchaseState = _mapAndroidPurchaseState(stateValue).toJson();
// Determine store from input or default based on platform
final storeValue = sourcePayload['store']?.toString() ?? 'google';
final androidTransactionId = _resolveAndroidTransactionId(
hasCanonicalTransactionId: transactionIdSelection.isPresent,
canonicalTransactionId: sourceTransactionId,
id: sourceId,
purchaseToken: sourcePayload['purchaseToken']?.toString(),
store: storeValue,
);
if (!transactionIdSelection.isPresent &&
androidTransactionId != null &&
androidTransactionId == sourceId) {
warnLegacyOnce(
'purchase.id-transaction-id',
'Using purchase `id` as `transactionId` is deprecated and will be '
'removed in flutter_inapp_purchase 10.0.0. Emit `transactionId` '
'directly instead.',
);
}
final map = <String, dynamic>{
...sourcePayload,
'id': purchaseId,
'productId': productId,
'platform': gentype.IapPlatform.Android.toJson(),
'store': storeValue,
'isAutoRenewing': sourcePayload['isAutoRenewing'] as bool? ??
sourcePayload['autoRenewingAndroid'] as bool? ??
false,
'purchaseState': purchaseState,
'quantity': quantity,
'transactionDate': transactionDate,
'purchaseToken': sourcePayload['purchaseToken']?.toString(),
'autoRenewingAndroid': sourcePayload['autoRenewingAndroid'] as bool?,
'currentPlanId': sourcePayload['currentPlanId']?.toString(),
'dataAndroid': _canonicalOrLegacy(
sourcePayload,
canonicalKey: 'dataAndroid',
legacyKey: 'originalJsonAndroid',
warningKey: 'purchase.originalJsonAndroid',
message: 'The purchase `originalJsonAndroid` field is deprecated and '
'will be removed in flutter_inapp_purchase 10.0.0. Use '
'`dataAndroid` instead.',
)?.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.
// Legacy native payloads only exposed it through `id`, so recover that
// value only when it is distinguishable from a Google purchase token.
// The `id` transaction fallback ends in 10.0.0.
'transactionId': androidTransactionId,
};
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(
_canonicalOrLegacy(
sourcePayload,
canonicalKey: 'purchaseState',
legacyKey: 'transactionStateIOS',
warningKey: 'purchase.transactionStateIOS',
message: 'The purchase `transactionStateIOS` field is deprecated and '
'will be removed in flutter_inapp_purchase 10.0.0. Use '
'`purchaseState` instead.',
),
).toJson();
final originalTransactionDateIOS = _parseTimestampMilliseconds(
sourcePayload['originalTransactionDateIOS'],
);
// Determine store from input or default based on platform
final storeValueIOS = sourcePayload['store']?.toString() ?? 'apple';
if (!transactionIdSelection.isPresent && (sourceId?.isNotEmpty ?? false)) {
warnLegacyOnce(
'purchase.id-transaction-id',
'Using purchase `id` as `transactionId` is deprecated and will be '
'removed in flutter_inapp_purchase 10.0.0. Emit `transactionId` '
'directly instead.',
);
}
final map = <String, dynamic>{
...sourcePayload,
'id': purchaseId,
'productId': productId,
'platform': gentype.IapPlatform.IOS.toJson(),
'store': storeValueIOS,
'isAutoRenewing': sourcePayload['isAutoRenewing'] as bool? ?? false,
'purchaseState': stateIOS,
'quantity': quantity,
'transactionDate': transactionDate,
'purchaseToken': _canonicalOrLegacy(
sourcePayload,
canonicalKey: 'purchaseToken',
legacyKey: 'transactionReceipt',
warningKey: 'purchase.transactionReceipt',
message: 'The purchase `transactionReceipt` field is deprecated and '
'will be removed in flutter_inapp_purchase 10.0.0. Use '
'`purchaseToken` instead.',
)?.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': transactionIdSelection.isPresent
? sourceTransactionId ?? ''
// The legacy `id` transaction fallback ends in 10.0.0.
: purchaseId,
'transactionReasonIOS': sourcePayload['transactionReasonIOS']?.toString(),
'webOrderLineItemIdIOS':
sourcePayload['webOrderLineItemIdIOS']?.toString(),
'revocationDateIOS':
_parseTimestampMilliseconds(sourcePayload['revocationDateIOS']),
'revocationReasonIOS': sourcePayload['revocationReasonIOS']?.toString(),
};
return gentype.PurchaseIOS.fromJson(map);
}
throw const FormatException('Unsupported platform for legacy purchase');
}