react-native-iap 16.0.0 → 16.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt +15 -7
  2. package/ios/HybridRnIap.swift +11 -6
  3. package/ios/RnIapHelper.swift +33 -1
  4. package/lib/module/hooks/useIAP.js +27 -9
  5. package/lib/module/hooks/useIAP.js.map +1 -1
  6. package/lib/module/index.js +34 -22
  7. package/lib/module/index.js.map +1 -1
  8. package/lib/module/index.kepler.js +3 -2
  9. package/lib/module/index.kepler.js.map +1 -1
  10. package/lib/module/types.js.map +1 -1
  11. package/lib/module/utils/available-purchases.js +59 -0
  12. package/lib/module/utils/available-purchases.js.map +1 -0
  13. package/lib/module/utils/type-bridge.js +56 -6
  14. package/lib/module/utils/type-bridge.js.map +1 -1
  15. package/lib/module/vega-adapter.js +13 -7
  16. package/lib/module/vega-adapter.js.map +1 -1
  17. package/lib/typescript/src/hooks/useIAP.d.ts.map +1 -1
  18. package/lib/typescript/src/index.d.ts.map +1 -1
  19. package/lib/typescript/src/index.kepler.d.ts.map +1 -1
  20. package/lib/typescript/src/specs/RnIap.nitro.d.ts +2 -0
  21. package/lib/typescript/src/specs/RnIap.nitro.d.ts.map +1 -1
  22. package/lib/typescript/src/types.d.ts +11 -0
  23. package/lib/typescript/src/types.d.ts.map +1 -1
  24. package/lib/typescript/src/utils/available-purchases.d.ts +8 -0
  25. package/lib/typescript/src/utils/available-purchases.d.ts.map +1 -0
  26. package/lib/typescript/src/utils/type-bridge.d.ts.map +1 -1
  27. package/lib/typescript/src/vega-adapter.d.ts.map +1 -1
  28. package/nitrogen/generated/android/c++/JNitroPurchase.hpp +11 -3
  29. package/nitrogen/generated/android/c++/JPurchaseAndroid.hpp +11 -3
  30. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroPurchase.kt +14 -4
  31. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/PurchaseAndroid.kt +14 -4
  32. package/nitrogen/generated/ios/swift/NitroPurchase.swift +75 -1
  33. package/nitrogen/generated/ios/swift/PurchaseAndroid.swift +75 -1
  34. package/nitrogen/generated/shared/c++/NitroPurchase.hpp +10 -2
  35. package/nitrogen/generated/shared/c++/PurchaseAndroid.hpp +10 -2
  36. package/openiap-versions.json +3 -3
  37. package/package.json +1 -1
  38. package/src/hooks/useIAP.ts +27 -10
  39. package/src/index.kepler.ts +5 -9
  40. package/src/index.ts +41 -45
  41. package/src/specs/RnIap.nitro.ts +2 -0
  42. package/src/types.ts +11 -0
  43. package/src/utils/available-purchases.ts +73 -0
  44. package/src/utils/type-bridge.ts +84 -9
  45. package/src/vega-adapter.ts +24 -3
@@ -117,10 +117,12 @@ namespace margelo::nitro::iap {
117
117
  std::optional<std::variant<nitro::NullType, std::string>> developerPayloadAndroid SWIFT_PRIVATE;
118
118
  std::optional<std::variant<nitro::NullType, bool>> isSuspendedAndroid SWIFT_PRIVATE;
119
119
  std::optional<std::variant<nitro::NullType, PendingPurchaseUpdateAndroid>> pendingPurchaseUpdateAndroid SWIFT_PRIVATE;
120
+ std::optional<std::variant<nitro::NullType, std::string>> userIdAmazon SWIFT_PRIVATE;
121
+ std::optional<std::variant<nitro::NullType, std::string>> userMarketplaceAmazon SWIFT_PRIVATE;
120
122
 
121
123
  public:
122
124
  NitroPurchase() = default;
123
- explicit NitroPurchase(std::string id, std::optional<std::variant<nitro::NullType, std::string>> transactionId, std::string productId, double transactionDate, std::optional<std::variant<nitro::NullType, std::string>> purchaseToken, std::optional<std::variant<nitro::NullType, std::string>> currentPlanId, std::optional<std::variant<nitro::NullType, std::vector<std::string>>> ids, IapStore store, double quantity, PurchaseState purchaseState, bool isAutoRenewing, std::optional<std::variant<nitro::NullType, AdvancedCommerceInfoIOS>> advancedCommerceInfoIOS, std::optional<SubscriptionBillingPlanTypeIOS> billingPlanTypeIOS, std::optional<std::variant<nitro::NullType, std::string>> bundleOriginalTransactionIdIOS, std::optional<std::variant<nitro::NullType, std::string>> bundleProductIdIOS, std::optional<std::variant<nitro::NullType, std::string>> bundleSubscriptionGroupIdIOS, std::optional<std::variant<nitro::NullType, std::string>> bundleTransactionIdIOS, std::optional<std::variant<nitro::NullType, TransactionCommitmentInfoIOS>> commitmentInfoIOS, std::optional<std::variant<nitro::NullType, double>> quantityIOS, std::optional<std::variant<nitro::NullType, double>> originalTransactionDateIOS, std::optional<std::variant<nitro::NullType, std::string>> originalTransactionIdentifierIOS, std::optional<std::variant<nitro::NullType, std::string>> previousOriginalTransactionIdIOS, std::optional<std::variant<nitro::NullType, std::string>> appAccountToken, std::optional<std::variant<nitro::NullType, std::string>> appBundleIdIOS, std::optional<std::variant<nitro::NullType, std::string>> countryCodeIOS, std::optional<std::variant<nitro::NullType, std::string>> currencyCodeIOS, std::optional<std::variant<nitro::NullType, std::string>> currencySymbolIOS, std::optional<std::variant<nitro::NullType, std::string>> environmentIOS, std::optional<std::variant<nitro::NullType, double>> expirationDateIOS, std::optional<std::variant<nitro::NullType, bool>> isUpgradedIOS, std::optional<std::variant<nitro::NullType, std::string>> offerIOS, std::optional<std::variant<nitro::NullType, std::string>> ownershipTypeIOS, std::optional<std::variant<nitro::NullType, std::string>> reasonIOS, std::optional<std::variant<nitro::NullType, std::string>> reasonStringRepresentationIOS, std::optional<std::variant<nitro::NullType, double>> revocationDateIOS, std::optional<std::variant<nitro::NullType, std::string>> revocationReasonIOS, std::optional<std::variant<nitro::NullType, std::string>> revocationTypeIOS, std::optional<std::variant<nitro::NullType, std::string>> storefrontCountryCodeIOS, std::optional<std::variant<nitro::NullType, std::string>> subscriptionGroupIdIOS, std::optional<std::variant<nitro::NullType, std::string>> transactionReasonIOS, std::optional<std::variant<nitro::NullType, std::string>> webOrderLineItemIdIOS, std::optional<std::variant<nitro::NullType, NitroRenewalInfoIOS>> renewalInfoIOS, std::optional<std::variant<nitro::NullType, std::string>> purchaseTokenAndroid, std::optional<std::variant<nitro::NullType, std::string>> dataAndroid, std::optional<std::variant<nitro::NullType, std::string>> signatureAndroid, std::optional<std::variant<nitro::NullType, bool>> autoRenewingAndroid, std::optional<std::variant<nitro::NullType, double>> purchaseStateAndroid, std::optional<std::variant<nitro::NullType, bool>> isAcknowledgedAndroid, std::optional<std::variant<nitro::NullType, std::string>> packageNameAndroid, std::optional<std::variant<nitro::NullType, std::string>> obfuscatedAccountIdAndroid, std::optional<std::variant<nitro::NullType, std::string>> obfuscatedProfileIdAndroid, std::optional<std::variant<nitro::NullType, std::string>> developerPayloadAndroid, std::optional<std::variant<nitro::NullType, bool>> isSuspendedAndroid, std::optional<std::variant<nitro::NullType, PendingPurchaseUpdateAndroid>> pendingPurchaseUpdateAndroid): id(id), transactionId(transactionId), productId(productId), transactionDate(transactionDate), purchaseToken(purchaseToken), currentPlanId(currentPlanId), ids(ids), store(store), quantity(quantity), purchaseState(purchaseState), isAutoRenewing(isAutoRenewing), advancedCommerceInfoIOS(advancedCommerceInfoIOS), billingPlanTypeIOS(billingPlanTypeIOS), bundleOriginalTransactionIdIOS(bundleOriginalTransactionIdIOS), bundleProductIdIOS(bundleProductIdIOS), bundleSubscriptionGroupIdIOS(bundleSubscriptionGroupIdIOS), bundleTransactionIdIOS(bundleTransactionIdIOS), commitmentInfoIOS(commitmentInfoIOS), quantityIOS(quantityIOS), originalTransactionDateIOS(originalTransactionDateIOS), originalTransactionIdentifierIOS(originalTransactionIdentifierIOS), previousOriginalTransactionIdIOS(previousOriginalTransactionIdIOS), appAccountToken(appAccountToken), appBundleIdIOS(appBundleIdIOS), countryCodeIOS(countryCodeIOS), currencyCodeIOS(currencyCodeIOS), currencySymbolIOS(currencySymbolIOS), environmentIOS(environmentIOS), expirationDateIOS(expirationDateIOS), isUpgradedIOS(isUpgradedIOS), offerIOS(offerIOS), ownershipTypeIOS(ownershipTypeIOS), reasonIOS(reasonIOS), reasonStringRepresentationIOS(reasonStringRepresentationIOS), revocationDateIOS(revocationDateIOS), revocationReasonIOS(revocationReasonIOS), revocationTypeIOS(revocationTypeIOS), storefrontCountryCodeIOS(storefrontCountryCodeIOS), subscriptionGroupIdIOS(subscriptionGroupIdIOS), transactionReasonIOS(transactionReasonIOS), webOrderLineItemIdIOS(webOrderLineItemIdIOS), renewalInfoIOS(renewalInfoIOS), purchaseTokenAndroid(purchaseTokenAndroid), dataAndroid(dataAndroid), signatureAndroid(signatureAndroid), autoRenewingAndroid(autoRenewingAndroid), purchaseStateAndroid(purchaseStateAndroid), isAcknowledgedAndroid(isAcknowledgedAndroid), packageNameAndroid(packageNameAndroid), obfuscatedAccountIdAndroid(obfuscatedAccountIdAndroid), obfuscatedProfileIdAndroid(obfuscatedProfileIdAndroid), developerPayloadAndroid(developerPayloadAndroid), isSuspendedAndroid(isSuspendedAndroid), pendingPurchaseUpdateAndroid(pendingPurchaseUpdateAndroid) {}
125
+ explicit NitroPurchase(std::string id, std::optional<std::variant<nitro::NullType, std::string>> transactionId, std::string productId, double transactionDate, std::optional<std::variant<nitro::NullType, std::string>> purchaseToken, std::optional<std::variant<nitro::NullType, std::string>> currentPlanId, std::optional<std::variant<nitro::NullType, std::vector<std::string>>> ids, IapStore store, double quantity, PurchaseState purchaseState, bool isAutoRenewing, std::optional<std::variant<nitro::NullType, AdvancedCommerceInfoIOS>> advancedCommerceInfoIOS, std::optional<SubscriptionBillingPlanTypeIOS> billingPlanTypeIOS, std::optional<std::variant<nitro::NullType, std::string>> bundleOriginalTransactionIdIOS, std::optional<std::variant<nitro::NullType, std::string>> bundleProductIdIOS, std::optional<std::variant<nitro::NullType, std::string>> bundleSubscriptionGroupIdIOS, std::optional<std::variant<nitro::NullType, std::string>> bundleTransactionIdIOS, std::optional<std::variant<nitro::NullType, TransactionCommitmentInfoIOS>> commitmentInfoIOS, std::optional<std::variant<nitro::NullType, double>> quantityIOS, std::optional<std::variant<nitro::NullType, double>> originalTransactionDateIOS, std::optional<std::variant<nitro::NullType, std::string>> originalTransactionIdentifierIOS, std::optional<std::variant<nitro::NullType, std::string>> previousOriginalTransactionIdIOS, std::optional<std::variant<nitro::NullType, std::string>> appAccountToken, std::optional<std::variant<nitro::NullType, std::string>> appBundleIdIOS, std::optional<std::variant<nitro::NullType, std::string>> countryCodeIOS, std::optional<std::variant<nitro::NullType, std::string>> currencyCodeIOS, std::optional<std::variant<nitro::NullType, std::string>> currencySymbolIOS, std::optional<std::variant<nitro::NullType, std::string>> environmentIOS, std::optional<std::variant<nitro::NullType, double>> expirationDateIOS, std::optional<std::variant<nitro::NullType, bool>> isUpgradedIOS, std::optional<std::variant<nitro::NullType, std::string>> offerIOS, std::optional<std::variant<nitro::NullType, std::string>> ownershipTypeIOS, std::optional<std::variant<nitro::NullType, std::string>> reasonIOS, std::optional<std::variant<nitro::NullType, std::string>> reasonStringRepresentationIOS, std::optional<std::variant<nitro::NullType, double>> revocationDateIOS, std::optional<std::variant<nitro::NullType, std::string>> revocationReasonIOS, std::optional<std::variant<nitro::NullType, std::string>> revocationTypeIOS, std::optional<std::variant<nitro::NullType, std::string>> storefrontCountryCodeIOS, std::optional<std::variant<nitro::NullType, std::string>> subscriptionGroupIdIOS, std::optional<std::variant<nitro::NullType, std::string>> transactionReasonIOS, std::optional<std::variant<nitro::NullType, std::string>> webOrderLineItemIdIOS, std::optional<std::variant<nitro::NullType, NitroRenewalInfoIOS>> renewalInfoIOS, std::optional<std::variant<nitro::NullType, std::string>> purchaseTokenAndroid, std::optional<std::variant<nitro::NullType, std::string>> dataAndroid, std::optional<std::variant<nitro::NullType, std::string>> signatureAndroid, std::optional<std::variant<nitro::NullType, bool>> autoRenewingAndroid, std::optional<std::variant<nitro::NullType, double>> purchaseStateAndroid, std::optional<std::variant<nitro::NullType, bool>> isAcknowledgedAndroid, std::optional<std::variant<nitro::NullType, std::string>> packageNameAndroid, std::optional<std::variant<nitro::NullType, std::string>> obfuscatedAccountIdAndroid, std::optional<std::variant<nitro::NullType, std::string>> obfuscatedProfileIdAndroid, std::optional<std::variant<nitro::NullType, std::string>> developerPayloadAndroid, std::optional<std::variant<nitro::NullType, bool>> isSuspendedAndroid, std::optional<std::variant<nitro::NullType, PendingPurchaseUpdateAndroid>> pendingPurchaseUpdateAndroid, std::optional<std::variant<nitro::NullType, std::string>> userIdAmazon, std::optional<std::variant<nitro::NullType, std::string>> userMarketplaceAmazon): id(id), transactionId(transactionId), productId(productId), transactionDate(transactionDate), purchaseToken(purchaseToken), currentPlanId(currentPlanId), ids(ids), store(store), quantity(quantity), purchaseState(purchaseState), isAutoRenewing(isAutoRenewing), advancedCommerceInfoIOS(advancedCommerceInfoIOS), billingPlanTypeIOS(billingPlanTypeIOS), bundleOriginalTransactionIdIOS(bundleOriginalTransactionIdIOS), bundleProductIdIOS(bundleProductIdIOS), bundleSubscriptionGroupIdIOS(bundleSubscriptionGroupIdIOS), bundleTransactionIdIOS(bundleTransactionIdIOS), commitmentInfoIOS(commitmentInfoIOS), quantityIOS(quantityIOS), originalTransactionDateIOS(originalTransactionDateIOS), originalTransactionIdentifierIOS(originalTransactionIdentifierIOS), previousOriginalTransactionIdIOS(previousOriginalTransactionIdIOS), appAccountToken(appAccountToken), appBundleIdIOS(appBundleIdIOS), countryCodeIOS(countryCodeIOS), currencyCodeIOS(currencyCodeIOS), currencySymbolIOS(currencySymbolIOS), environmentIOS(environmentIOS), expirationDateIOS(expirationDateIOS), isUpgradedIOS(isUpgradedIOS), offerIOS(offerIOS), ownershipTypeIOS(ownershipTypeIOS), reasonIOS(reasonIOS), reasonStringRepresentationIOS(reasonStringRepresentationIOS), revocationDateIOS(revocationDateIOS), revocationReasonIOS(revocationReasonIOS), revocationTypeIOS(revocationTypeIOS), storefrontCountryCodeIOS(storefrontCountryCodeIOS), subscriptionGroupIdIOS(subscriptionGroupIdIOS), transactionReasonIOS(transactionReasonIOS), webOrderLineItemIdIOS(webOrderLineItemIdIOS), renewalInfoIOS(renewalInfoIOS), purchaseTokenAndroid(purchaseTokenAndroid), dataAndroid(dataAndroid), signatureAndroid(signatureAndroid), autoRenewingAndroid(autoRenewingAndroid), purchaseStateAndroid(purchaseStateAndroid), isAcknowledgedAndroid(isAcknowledgedAndroid), packageNameAndroid(packageNameAndroid), obfuscatedAccountIdAndroid(obfuscatedAccountIdAndroid), obfuscatedProfileIdAndroid(obfuscatedProfileIdAndroid), developerPayloadAndroid(developerPayloadAndroid), isSuspendedAndroid(isSuspendedAndroid), pendingPurchaseUpdateAndroid(pendingPurchaseUpdateAndroid), userIdAmazon(userIdAmazon), userMarketplaceAmazon(userMarketplaceAmazon) {}
124
126
 
125
127
  public:
126
128
  // NitroPurchase is not equatable because these properties are not equatable: ids, advancedCommerceInfoIOS, pendingPurchaseUpdateAndroid
@@ -189,7 +191,9 @@ namespace margelo::nitro {
189
191
  JSIConverter<std::optional<std::variant<nitro::NullType, std::string>>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "obfuscatedProfileIdAndroid"))),
190
192
  JSIConverter<std::optional<std::variant<nitro::NullType, std::string>>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "developerPayloadAndroid"))),
191
193
  JSIConverter<std::optional<std::variant<nitro::NullType, bool>>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "isSuspendedAndroid"))),
192
- JSIConverter<std::optional<std::variant<nitro::NullType, margelo::nitro::iap::PendingPurchaseUpdateAndroid>>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "pendingPurchaseUpdateAndroid")))
194
+ JSIConverter<std::optional<std::variant<nitro::NullType, margelo::nitro::iap::PendingPurchaseUpdateAndroid>>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "pendingPurchaseUpdateAndroid"))),
195
+ JSIConverter<std::optional<std::variant<nitro::NullType, std::string>>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "userIdAmazon"))),
196
+ JSIConverter<std::optional<std::variant<nitro::NullType, std::string>>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "userMarketplaceAmazon")))
193
197
  );
194
198
  }
195
199
  static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::iap::NitroPurchase& arg) {
@@ -248,6 +252,8 @@ namespace margelo::nitro {
248
252
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "developerPayloadAndroid"), JSIConverter<std::optional<std::variant<nitro::NullType, std::string>>>::toJSI(runtime, arg.developerPayloadAndroid));
249
253
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "isSuspendedAndroid"), JSIConverter<std::optional<std::variant<nitro::NullType, bool>>>::toJSI(runtime, arg.isSuspendedAndroid));
250
254
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "pendingPurchaseUpdateAndroid"), JSIConverter<std::optional<std::variant<nitro::NullType, margelo::nitro::iap::PendingPurchaseUpdateAndroid>>>::toJSI(runtime, arg.pendingPurchaseUpdateAndroid));
255
+ obj.setProperty(runtime, PropNameIDCache::get(runtime, "userIdAmazon"), JSIConverter<std::optional<std::variant<nitro::NullType, std::string>>>::toJSI(runtime, arg.userIdAmazon));
256
+ obj.setProperty(runtime, PropNameIDCache::get(runtime, "userMarketplaceAmazon"), JSIConverter<std::optional<std::variant<nitro::NullType, std::string>>>::toJSI(runtime, arg.userMarketplaceAmazon));
251
257
  return obj;
252
258
  }
253
259
  static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
@@ -312,6 +318,8 @@ namespace margelo::nitro {
312
318
  if (!JSIConverter<std::optional<std::variant<nitro::NullType, std::string>>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "developerPayloadAndroid")))) return false;
313
319
  if (!JSIConverter<std::optional<std::variant<nitro::NullType, bool>>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "isSuspendedAndroid")))) return false;
314
320
  if (!JSIConverter<std::optional<std::variant<nitro::NullType, margelo::nitro::iap::PendingPurchaseUpdateAndroid>>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "pendingPurchaseUpdateAndroid")))) return false;
321
+ if (!JSIConverter<std::optional<std::variant<nitro::NullType, std::string>>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "userIdAmazon")))) return false;
322
+ if (!JSIConverter<std::optional<std::variant<nitro::NullType, std::string>>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "userMarketplaceAmazon")))) return false;
315
323
  return true;
316
324
  }
317
325
  };
@@ -72,10 +72,12 @@ namespace margelo::nitro::iap {
72
72
  IapStore store SWIFT_PRIVATE;
73
73
  double transactionDate SWIFT_PRIVATE;
74
74
  std::optional<std::variant<nitro::NullType, std::string>> transactionId SWIFT_PRIVATE;
75
+ std::optional<std::variant<nitro::NullType, std::string>> userIdAmazon SWIFT_PRIVATE;
76
+ std::optional<std::variant<nitro::NullType, std::string>> userMarketplaceAmazon SWIFT_PRIVATE;
75
77
 
76
78
  public:
77
79
  PurchaseAndroid() = default;
78
- explicit PurchaseAndroid(std::optional<std::variant<nitro::NullType, bool>> autoRenewingAndroid, std::optional<std::variant<nitro::NullType, std::string>> currentPlanId, std::optional<std::variant<nitro::NullType, std::string>> dataAndroid, std::optional<std::variant<nitro::NullType, std::string>> developerPayloadAndroid, std::string id, std::optional<std::variant<nitro::NullType, std::vector<std::string>>> ids, std::optional<std::variant<nitro::NullType, bool>> isAcknowledgedAndroid, bool isAutoRenewing, std::optional<std::variant<nitro::NullType, bool>> isSuspendedAndroid, std::optional<std::variant<nitro::NullType, std::string>> obfuscatedAccountIdAndroid, std::optional<std::variant<nitro::NullType, std::string>> obfuscatedProfileIdAndroid, std::optional<std::variant<nitro::NullType, std::string>> packageNameAndroid, std::optional<std::variant<nitro::NullType, PendingPurchaseUpdateAndroid>> pendingPurchaseUpdateAndroid, std::string productId, PurchaseState purchaseState, std::optional<std::variant<nitro::NullType, std::string>> purchaseToken, double quantity, std::optional<std::variant<nitro::NullType, std::string>> signatureAndroid, IapStore store, double transactionDate, std::optional<std::variant<nitro::NullType, std::string>> transactionId): autoRenewingAndroid(autoRenewingAndroid), currentPlanId(currentPlanId), dataAndroid(dataAndroid), developerPayloadAndroid(developerPayloadAndroid), id(id), ids(ids), isAcknowledgedAndroid(isAcknowledgedAndroid), isAutoRenewing(isAutoRenewing), isSuspendedAndroid(isSuspendedAndroid), obfuscatedAccountIdAndroid(obfuscatedAccountIdAndroid), obfuscatedProfileIdAndroid(obfuscatedProfileIdAndroid), packageNameAndroid(packageNameAndroid), pendingPurchaseUpdateAndroid(pendingPurchaseUpdateAndroid), productId(productId), purchaseState(purchaseState), purchaseToken(purchaseToken), quantity(quantity), signatureAndroid(signatureAndroid), store(store), transactionDate(transactionDate), transactionId(transactionId) {}
80
+ explicit PurchaseAndroid(std::optional<std::variant<nitro::NullType, bool>> autoRenewingAndroid, std::optional<std::variant<nitro::NullType, std::string>> currentPlanId, std::optional<std::variant<nitro::NullType, std::string>> dataAndroid, std::optional<std::variant<nitro::NullType, std::string>> developerPayloadAndroid, std::string id, std::optional<std::variant<nitro::NullType, std::vector<std::string>>> ids, std::optional<std::variant<nitro::NullType, bool>> isAcknowledgedAndroid, bool isAutoRenewing, std::optional<std::variant<nitro::NullType, bool>> isSuspendedAndroid, std::optional<std::variant<nitro::NullType, std::string>> obfuscatedAccountIdAndroid, std::optional<std::variant<nitro::NullType, std::string>> obfuscatedProfileIdAndroid, std::optional<std::variant<nitro::NullType, std::string>> packageNameAndroid, std::optional<std::variant<nitro::NullType, PendingPurchaseUpdateAndroid>> pendingPurchaseUpdateAndroid, std::string productId, PurchaseState purchaseState, std::optional<std::variant<nitro::NullType, std::string>> purchaseToken, double quantity, std::optional<std::variant<nitro::NullType, std::string>> signatureAndroid, IapStore store, double transactionDate, std::optional<std::variant<nitro::NullType, std::string>> transactionId, std::optional<std::variant<nitro::NullType, std::string>> userIdAmazon, std::optional<std::variant<nitro::NullType, std::string>> userMarketplaceAmazon): autoRenewingAndroid(autoRenewingAndroid), currentPlanId(currentPlanId), dataAndroid(dataAndroid), developerPayloadAndroid(developerPayloadAndroid), id(id), ids(ids), isAcknowledgedAndroid(isAcknowledgedAndroid), isAutoRenewing(isAutoRenewing), isSuspendedAndroid(isSuspendedAndroid), obfuscatedAccountIdAndroid(obfuscatedAccountIdAndroid), obfuscatedProfileIdAndroid(obfuscatedProfileIdAndroid), packageNameAndroid(packageNameAndroid), pendingPurchaseUpdateAndroid(pendingPurchaseUpdateAndroid), productId(productId), purchaseState(purchaseState), purchaseToken(purchaseToken), quantity(quantity), signatureAndroid(signatureAndroid), store(store), transactionDate(transactionDate), transactionId(transactionId), userIdAmazon(userIdAmazon), userMarketplaceAmazon(userMarketplaceAmazon) {}
79
81
 
80
82
  public:
81
83
  // PurchaseAndroid is not equatable because these properties are not equatable: ids, pendingPurchaseUpdateAndroid
@@ -111,7 +113,9 @@ namespace margelo::nitro {
111
113
  JSIConverter<std::optional<std::variant<nitro::NullType, std::string>>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "signatureAndroid"))),
112
114
  JSIConverter<margelo::nitro::iap::IapStore>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "store"))),
113
115
  JSIConverter<double>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "transactionDate"))),
114
- JSIConverter<std::optional<std::variant<nitro::NullType, std::string>>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "transactionId")))
116
+ JSIConverter<std::optional<std::variant<nitro::NullType, std::string>>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "transactionId"))),
117
+ JSIConverter<std::optional<std::variant<nitro::NullType, std::string>>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "userIdAmazon"))),
118
+ JSIConverter<std::optional<std::variant<nitro::NullType, std::string>>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "userMarketplaceAmazon")))
115
119
  );
116
120
  }
117
121
  static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::iap::PurchaseAndroid& arg) {
@@ -137,6 +141,8 @@ namespace margelo::nitro {
137
141
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "store"), JSIConverter<margelo::nitro::iap::IapStore>::toJSI(runtime, arg.store));
138
142
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "transactionDate"), JSIConverter<double>::toJSI(runtime, arg.transactionDate));
139
143
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "transactionId"), JSIConverter<std::optional<std::variant<nitro::NullType, std::string>>>::toJSI(runtime, arg.transactionId));
144
+ obj.setProperty(runtime, PropNameIDCache::get(runtime, "userIdAmazon"), JSIConverter<std::optional<std::variant<nitro::NullType, std::string>>>::toJSI(runtime, arg.userIdAmazon));
145
+ obj.setProperty(runtime, PropNameIDCache::get(runtime, "userMarketplaceAmazon"), JSIConverter<std::optional<std::variant<nitro::NullType, std::string>>>::toJSI(runtime, arg.userMarketplaceAmazon));
140
146
  return obj;
141
147
  }
142
148
  static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
@@ -168,6 +174,8 @@ namespace margelo::nitro {
168
174
  if (!JSIConverter<margelo::nitro::iap::IapStore>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "store")))) return false;
169
175
  if (!JSIConverter<double>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "transactionDate")))) return false;
170
176
  if (!JSIConverter<std::optional<std::variant<nitro::NullType, std::string>>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "transactionId")))) return false;
177
+ if (!JSIConverter<std::optional<std::variant<nitro::NullType, std::string>>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "userIdAmazon")))) return false;
178
+ if (!JSIConverter<std::optional<std::variant<nitro::NullType, std::string>>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "userMarketplaceAmazon")))) return false;
171
179
  return true;
172
180
  }
173
181
  };
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "3.0.0",
3
- "google": "3.0.0",
4
- "apple": "3.0.0"
2
+ "spec": "3.0.1",
3
+ "google": "3.0.1",
4
+ "apple": "3.0.1"
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-iap",
3
- "version": "16.0.0",
3
+ "version": "16.0.2",
4
4
  "description": "React Native In-App Purchases module for iOS and Android using Nitro",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -2,6 +2,7 @@
2
2
  import {useCallback, useEffect, useState, useRef} from 'react';
3
3
  import {Platform} from 'react-native';
4
4
  import {RnIapConsole} from '../utils/debug';
5
+ import {createPurchaseError} from '../utils/errorMapping';
5
6
 
6
7
  // Internal modules
7
8
  import {
@@ -467,6 +468,7 @@ export function useIAP(options?: UseIapOptions): UseIap {
467
468
  } catch (error) {
468
469
  RnIapConsole.error('Error fetching available purchases:', error);
469
470
  invokeOnError(error);
471
+ throw error;
470
472
  }
471
473
  },
472
474
  [invokeOnError],
@@ -483,7 +485,7 @@ export function useIAP(options?: UseIapOptions): UseIap {
483
485
  } catch (error) {
484
486
  RnIapConsole.error('Error getting active subscriptions:', error);
485
487
  invokeOnError(error);
486
- return [];
488
+ throw error;
487
489
  }
488
490
  },
489
491
  [invokeOnError],
@@ -496,7 +498,7 @@ export function useIAP(options?: UseIapOptions): UseIap {
496
498
  } catch (error) {
497
499
  RnIapConsole.error('Error checking active subscriptions:', error);
498
500
  invokeOnError(error);
499
- return false;
501
+ throw error;
500
502
  }
501
503
  },
502
504
  [invokeOnError],
@@ -523,16 +525,26 @@ export function useIAP(options?: UseIapOptions): UseIap {
523
525
 
524
526
  const restorePurchases = useCallback(
525
527
  async (options?: PurchaseOptions): Promise<void> => {
526
- try {
527
- if (Platform.OS === 'ios') {
528
- await syncIOS();
528
+ if (Platform.OS === 'ios') {
529
+ try {
530
+ const synced = await syncIOS();
531
+ if (!synced) {
532
+ throw createPurchaseError({
533
+ code: ErrorCode.SyncError,
534
+ message: 'App Store purchase sync did not complete',
535
+ platform: 'ios',
536
+ });
537
+ }
538
+ } catch (error) {
539
+ RnIapConsole.warn('Failed to restore purchases:', error);
540
+ invokeOnError(error);
541
+ throw error;
529
542
  }
530
-
531
- await getAvailablePurchasesInternal(options);
532
- } catch (error) {
533
- RnIapConsole.warn('Failed to restore purchases:', error);
534
- invokeOnError(error);
535
543
  }
544
+
545
+ // The query helper reports and rethrows its own error, avoiding a second
546
+ // onError call while keeping restore failure observable to the caller.
547
+ await getAvailablePurchasesInternal(options);
536
548
  },
537
549
  [getAvailablePurchasesInternal, invokeOnError],
538
550
  );
@@ -591,6 +603,11 @@ export function useIAP(options?: UseIapOptions): UseIap {
591
603
  } catch (e) {
592
604
  RnIapConsole.warn('[useIAP] post-purchase refresh failed:', e);
593
605
  }
606
+
607
+ // Deliver even if the hook unmounted while the refresh was pending:
608
+ // onPurchaseSuccess is where apps call finishTransaction, and a
609
+ // dropped event has no in-session redelivery. Internal setState is
610
+ // mount-guarded inside the refresh helpers.
594
611
  if (optionsRef.current?.onPurchaseSuccess) {
595
612
  optionsRef.current.onPurchaseSuccess(purchase);
596
613
  }
@@ -26,6 +26,7 @@ import {
26
26
  validateNitroProduct,
27
27
  validateNitroPurchase,
28
28
  } from './utils/type-bridge';
29
+ import {convertAndroidPurchasesOrThrow} from './utils/available-purchases';
29
30
 
30
31
  export * from './types';
31
32
  export * from './utils/error';
@@ -128,7 +129,8 @@ export const fetchProducts = async (
128
129
  const normalizedType = normalizeProductQueryType(type);
129
130
  const nitroProducts = await getModule().fetchProducts(skus, normalizedType);
130
131
  return mapProducts(nitroProducts, normalizedType) as
131
- Product[] | ProductSubscription[];
132
+ | Product[]
133
+ | ProductSubscription[];
132
134
  };
133
135
 
134
136
  export const requestPurchase: MutationField<'requestPurchase'> = async (
@@ -152,9 +154,7 @@ export const requestPurchase: MutationField<'requestPurchase'> = async (
152
154
  const result = await getModule().requestPurchase(nitroRequest);
153
155
  if (!Array.isArray(result)) return null;
154
156
  const purchases = result as unknown as NitroPurchase[];
155
- return purchases
156
- .filter(validateNitroPurchase)
157
- .map((purchase) => convertNitroPurchaseToPurchase(purchase));
157
+ return convertAndroidPurchasesOrThrow(purchases);
158
158
  };
159
159
 
160
160
  export const getAvailablePurchases: QueryField<
@@ -167,11 +167,7 @@ export const getAvailablePurchases: QueryField<
167
167
  };
168
168
  return getModule()
169
169
  .getAvailablePurchases(nitroOptions)
170
- .then((purchases) =>
171
- purchases
172
- .filter(validateNitroPurchase)
173
- .map(convertNitroPurchaseToPurchase),
174
- );
170
+ .then(convertAndroidPurchasesOrThrow);
175
171
  };
176
172
 
177
173
  export const finishTransaction: MutationField<'finishTransaction'> = async (
package/src/index.ts CHANGED
@@ -59,6 +59,10 @@ import {
59
59
  import {RnIapConsole} from './utils/debug';
60
60
  import {getSuccessFromPurchaseVariant} from './utils/purchase';
61
61
  import {parseAppTransactionPayload} from './utils';
62
+ import {
63
+ convertAndroidPurchasesOrThrow,
64
+ convertApplePurchasesOrThrow,
65
+ } from './utils/available-purchases';
62
66
  import {getVegaIapModule, isVegaOS} from './vega';
63
67
 
64
68
  // ------------------------------
@@ -451,6 +455,13 @@ export const purchaseUpdatedListener = (
451
455
  return;
452
456
  }
453
457
 
458
+ // StoreKit-backed Nitro listener disposal can abort in iOS release builds
459
+ // when a native modal is being popped. Keep the singleton native listener
460
+ // attached for the app session and only remove the JS callback above.
461
+ if (Platform.OS === 'ios') {
462
+ return;
463
+ }
464
+
454
465
  const token = receiveDuplicateTransactionUpdatesIOS
455
466
  ? purchaseUpdateDuplicateNativeToken
456
467
  : purchaseUpdateNativeToken;
@@ -498,7 +509,19 @@ export const purchaseErrorListener = (
498
509
  return {
499
510
  remove: () => {
500
511
  purchaseErrorJsListeners.delete(listener);
501
- if (purchaseErrorJsListeners.size === 0 && purchaseErrorNativeAttached) {
512
+ if (purchaseErrorJsListeners.size > 0) {
513
+ return;
514
+ }
515
+
516
+ // Same iOS policy as purchaseUpdatedListener: releasing the stored
517
+ // native callback from a JS unsubscribe can race an in-flight dispatch
518
+ // snapshot on another thread. Keep the singleton attached; endConnection
519
+ // owns native disposal.
520
+ if (Platform.OS === 'ios') {
521
+ return;
522
+ }
523
+
524
+ if (purchaseErrorNativeAttached) {
502
525
  try {
503
526
  IAP.instance.removePurchaseErrorListener(purchaseErrorNativeHandler);
504
527
  purchaseErrorNativeAttached = false;
@@ -865,7 +888,8 @@ export const fetchProducts: QueryField<'fetchProducts'> = async (request) => {
865
888
 
866
889
  if (normalizedType === 'all') {
867
890
  const converted = (await fetchAndConvert('all')) as (
868
- Product | ProductSubscription
891
+ | Product
892
+ | ProductSubscription
869
893
  )[];
870
894
 
871
895
  RnIapConsole.debug(
@@ -972,14 +996,7 @@ export const getAvailablePurchases: QueryField<
972
996
  const nitroPurchases =
973
997
  await IAP.instance.getAvailablePurchases(nitroOptions);
974
998
 
975
- const validPurchases = nitroPurchases.filter(validateNitroPurchase);
976
- if (validPurchases.length !== nitroPurchases.length) {
977
- RnIapConsole.warn(
978
- `[getAvailablePurchases] Some purchases failed validation: ${nitroPurchases.length - validPurchases.length} invalid`,
979
- );
980
- }
981
-
982
- return validPurchases.map(convertNitroPurchaseToPurchase);
999
+ return convertApplePurchasesOrThrow(nitroPurchases);
983
1000
  } else if (isAndroidStoreRuntime()) {
984
1001
  const includeSuspended = Boolean(
985
1002
  options?.includeSuspendedAndroid ?? false,
@@ -989,14 +1006,7 @@ export const getAvailablePurchases: QueryField<
989
1006
  const nitroPurchases = await IAP.instance.getAvailablePurchases({
990
1007
  android: {includeSuspended},
991
1008
  });
992
- const validPurchases = nitroPurchases.filter(validateNitroPurchase);
993
- if (validPurchases.length !== nitroPurchases.length) {
994
- RnIapConsole.warn(
995
- `[getAvailablePurchases] Some Vega purchases failed validation: ${nitroPurchases.length - validPurchases.length} invalid`,
996
- );
997
- }
998
-
999
- return validPurchases.map(convertNitroPurchaseToPurchase);
1009
+ return convertAndroidPurchasesOrThrow(nitroPurchases);
1000
1010
  }
1001
1011
 
1002
1012
  // For Android Play/Horizon/Fire OS, query in-app items and subscriptions separately.
@@ -1007,16 +1017,8 @@ export const getAvailablePurchases: QueryField<
1007
1017
  android: {type: 'subs', includeSuspended},
1008
1018
  });
1009
1019
 
1010
- // Validate and convert both sets of purchases
1011
1020
  const allNitroPurchases = [...inappNitroPurchases, ...subsNitroPurchases];
1012
- const validPurchases = allNitroPurchases.filter(validateNitroPurchase);
1013
- if (validPurchases.length !== allNitroPurchases.length) {
1014
- RnIapConsole.warn(
1015
- `[getAvailablePurchases] Some Android purchases failed validation: ${allNitroPurchases.length - validPurchases.length} invalid`,
1016
- );
1017
- }
1018
-
1019
- return validPurchases.map(convertNitroPurchaseToPurchase);
1021
+ return convertAndroidPurchasesOrThrow(allNitroPurchases);
1020
1022
  } else {
1021
1023
  throw unsupportedPlatformError();
1022
1024
  }
@@ -1279,11 +1281,7 @@ export const getPendingTransactionsIOS: QueryField<
1279
1281
 
1280
1282
  try {
1281
1283
  const nitroPurchases = await IAP.instance.getPendingTransactionsIOS();
1282
- return nitroPurchases
1283
- .map(convertNitroPurchaseToPurchase)
1284
- .filter(
1285
- (purchase): purchase is PurchaseIOS => purchase.store === 'apple',
1286
- );
1284
+ return convertApplePurchasesOrThrow(nitroPurchases);
1287
1285
  } catch (error) {
1288
1286
  const parsedError = parseErrorAndLogIfNeeded(
1289
1287
  '[getPendingTransactionsIOS] Failed:',
@@ -1312,11 +1310,7 @@ export const getAllTransactionsIOS: QueryField<
1312
1310
 
1313
1311
  try {
1314
1312
  const nitroPurchases = await IAP.instance.getAllTransactionsIOS();
1315
- return nitroPurchases
1316
- .map(convertNitroPurchaseToPurchase)
1317
- .filter(
1318
- (purchase): purchase is PurchaseIOS => purchase.store === 'apple',
1319
- );
1313
+ return convertApplePurchasesOrThrow(nitroPurchases);
1320
1314
  } catch (error) {
1321
1315
  const parsedError = parseErrorAndLogIfNeeded(
1322
1316
  '[getAllTransactionsIOS] Failed:',
@@ -1347,11 +1341,7 @@ export const showManageSubscriptionsIOS: MutationField<
1347
1341
 
1348
1342
  try {
1349
1343
  const nitroPurchases = await IAP.instance.showManageSubscriptionsIOS();
1350
- return nitroPurchases
1351
- .map(convertNitroPurchaseToPurchase)
1352
- .filter(
1353
- (purchase): purchase is PurchaseIOS => purchase.store === 'apple',
1354
- );
1344
+ return convertApplePurchasesOrThrow(nitroPurchases);
1355
1345
  } catch (error) {
1356
1346
  const parsedError = parseErrorAndLogIfNeeded(
1357
1347
  '[showManageSubscriptionsIOS] Failed:',
@@ -1608,7 +1598,14 @@ export const endConnection: MutationField<'endConnection'> = async () => {
1608
1598
  export const restorePurchases: MutationField<'restorePurchases'> = async () => {
1609
1599
  try {
1610
1600
  if (Platform.OS === 'ios') {
1611
- await syncIOS();
1601
+ const synced = await syncIOS();
1602
+ if (!synced) {
1603
+ throw createPurchaseError({
1604
+ code: ErrorCode.SyncError,
1605
+ message: 'App Store purchase sync did not complete',
1606
+ platform: 'ios',
1607
+ });
1608
+ }
1612
1609
  }
1613
1610
 
1614
1611
  await getAvailablePurchases({
@@ -2466,8 +2463,7 @@ export const getActiveSubscriptions: QueryField<
2466
2463
  sub.renewalInfoIOS.autoRenewPreference ?? null,
2467
2464
  bundleOriginalTransactionId:
2468
2465
  sub.renewalInfoIOS.bundleOriginalTransactionId ?? null,
2469
- bundleProductId:
2470
- sub.renewalInfoIOS.bundleProductId ?? null,
2466
+ bundleProductId: sub.renewalInfoIOS.bundleProductId ?? null,
2471
2467
  bundleSubscriptionGroupId:
2472
2468
  sub.renewalInfoIOS.bundleSubscriptionGroupId ?? null,
2473
2469
  commitmentInfo: sub.renewalInfoIOS.commitmentInfo ?? null,
@@ -590,6 +590,8 @@ export interface NitroPurchase {
590
590
  developerPayloadAndroid?: string | null;
591
591
  isSuspendedAndroid?: null | boolean;
592
592
  pendingPurchaseUpdateAndroid?: PendingPurchaseUpdateAndroid | null;
593
+ userIdAmazon?: string | null;
594
+ userMarketplaceAmazon?: string | null;
593
595
  }
594
596
 
595
597
  /**
package/src/types.ts CHANGED
@@ -1214,6 +1214,17 @@ export interface PurchaseAndroid extends PurchaseCommon {
1214
1214
  /** Unix timestamp in milliseconds since January 1, 1970 UTC. */
1215
1215
  transactionDate: number;
1216
1216
  transactionId?: (string | null);
1217
+ /**
1218
+ * Amazon Appstore user id (PurchaseResponse.getUserData().getUserId()).
1219
+ * Only populated on the Amazon flavor; required for server-side Amazon RVS
1220
+ * receipt verification (userId + receiptId). Null on Google Play and Horizon.
1221
+ */
1222
+ userIdAmazon?: (string | null);
1223
+ /**
1224
+ * Amazon Appstore marketplace (PurchaseResponse.getUserData().getMarketplace()),
1225
+ * for example "US" or "FR". Only populated on the Amazon flavor.
1226
+ */
1227
+ userMarketplaceAmazon?: (string | null);
1217
1228
  }
1218
1229
 
1219
1230
  export interface PurchaseCommon {
@@ -0,0 +1,73 @@
1
+ import {ErrorCode} from '../types';
2
+ import type {Purchase, PurchaseIOS} from '../types';
3
+ import {createPurchaseError} from './errorMapping';
4
+ import {
5
+ convertNitroPurchaseToPurchase,
6
+ validateNitroPurchase,
7
+ } from './type-bridge';
8
+
9
+ const ANDROID_STORES = new Set(['google', 'amazon', 'horizon']);
10
+
11
+ /** Decode an authoritative native purchase list atomically. */
12
+ export const convertAvailablePurchasesOrThrow = (
13
+ purchases: unknown,
14
+ ): Purchase[] => {
15
+ if (!Array.isArray(purchases)) {
16
+ throw createPurchaseError({
17
+ code: ErrorCode.BillingResponseJsonParseError,
18
+ message: 'Malformed purchase list returned by the native bridge',
19
+ });
20
+ }
21
+
22
+ return purchases.map((purchase, index) => {
23
+ if (!validateNitroPurchase(purchase)) {
24
+ throw createPurchaseError({
25
+ code: ErrorCode.BillingResponseJsonParseError,
26
+ message: `Malformed purchase payload returned by the native bridge at index ${index}`,
27
+ });
28
+ }
29
+
30
+ try {
31
+ return convertNitroPurchaseToPurchase(purchase);
32
+ } catch {
33
+ throw createPurchaseError({
34
+ code: ErrorCode.BillingResponseJsonParseError,
35
+ message: `Failed to decode native purchase payload at index ${index}`,
36
+ });
37
+ }
38
+ });
39
+ };
40
+
41
+ /** Decode an authoritative StoreKit purchase list without filtering entries. */
42
+ export const convertApplePurchasesOrThrow = (
43
+ purchases: unknown,
44
+ ): PurchaseIOS[] => {
45
+ const decoded = convertAvailablePurchasesOrThrow(purchases);
46
+ const invalidIndex = decoded.findIndex(
47
+ (purchase) => purchase.store !== 'apple',
48
+ );
49
+ if (invalidIndex !== -1) {
50
+ throw createPurchaseError({
51
+ code: ErrorCode.BillingResponseJsonParseError,
52
+ message: `Native StoreKit bridge returned a non-Apple purchase at index ${invalidIndex}`,
53
+ });
54
+ }
55
+ return decoded as PurchaseIOS[];
56
+ };
57
+
58
+ /** Decode an authoritative Android-family purchase list without foreign stores. */
59
+ export const convertAndroidPurchasesOrThrow = (
60
+ purchases: unknown,
61
+ ): Purchase[] => {
62
+ const decoded = convertAvailablePurchasesOrThrow(purchases);
63
+ const invalidIndex = decoded.findIndex(
64
+ (purchase) => !ANDROID_STORES.has(purchase.store),
65
+ );
66
+ if (invalidIndex !== -1) {
67
+ throw createPurchaseError({
68
+ code: ErrorCode.BillingResponseJsonParseError,
69
+ message: `Native Android bridge returned a foreign purchase at index ${invalidIndex}`,
70
+ });
71
+ }
72
+ return decoded;
73
+ };
@@ -504,6 +504,16 @@ export function convertNitroPurchaseToPurchase(
504
504
  ? legacyAndroidId
505
505
  : null);
506
506
 
507
+ // Amazon identity metadata must never leak onto other stores' purchases.
508
+ const amazonUserId =
509
+ nitroPurchase.userIdAmazon != null && store === STORE_AMAZON
510
+ ? toNullableString(nitroPurchase.userIdAmazon)
511
+ : null;
512
+ const amazonUserMarketplace =
513
+ nitroPurchase.userMarketplaceAmazon != null && store === STORE_AMAZON
514
+ ? toNullableString(nitroPurchase.userMarketplaceAmazon)
515
+ : null;
516
+
507
517
  const androidPurchase: PurchaseAndroid = {
508
518
  id: nitroPurchase.id,
509
519
  productId: nitroPurchase.productId,
@@ -540,6 +550,8 @@ export function convertNitroPurchaseToPurchase(
540
550
  isSuspendedAndroid: toNullableBoolean(nitroPurchase.isSuspendedAndroid),
541
551
  pendingPurchaseUpdateAndroid:
542
552
  nitroPurchase.pendingPurchaseUpdateAndroid ?? null,
553
+ userIdAmazon: amazonUserId,
554
+ userMarketplaceAmazon: amazonUserMarketplace,
543
555
  };
544
556
 
545
557
  return androidPurchase;
@@ -592,17 +604,80 @@ export function validateNitroPurchase(nitroPurchase: NitroPurchase): boolean {
592
604
  return false;
593
605
  }
594
606
 
595
- const required = ['id', 'productId', 'transactionDate', 'store'];
596
- for (const field of required) {
607
+ const invalidField = (field: string): false => {
608
+ RnIapConsole.error(`NitroPurchase has invalid required field: ${field}`);
609
+ return false;
610
+ };
611
+ if (typeof nitroPurchase.id !== 'string' || !nitroPurchase.id) {
612
+ return invalidField('id');
613
+ }
614
+ if (typeof nitroPurchase.productId !== 'string' || !nitroPurchase.productId) {
615
+ return invalidField('productId');
616
+ }
617
+ if (
618
+ typeof nitroPurchase.transactionDate !== 'number' ||
619
+ !Number.isFinite(nitroPurchase.transactionDate)
620
+ ) {
621
+ return invalidField('transactionDate');
622
+ }
623
+ if (typeof nitroPurchase.store !== 'string' || !nitroPurchase.store) {
624
+ return invalidField('store');
625
+ }
626
+ if (
627
+ typeof nitroPurchase.purchaseState !== 'string' ||
628
+ !nitroPurchase.purchaseState
629
+ ) {
630
+ return invalidField('purchaseState');
631
+ }
632
+ if (
633
+ typeof nitroPurchase.quantity !== 'number' ||
634
+ !Number.isInteger(nitroPurchase.quantity)
635
+ ) {
636
+ return invalidField('quantity');
637
+ }
638
+ if (typeof nitroPurchase.isAutoRenewing !== 'boolean') {
639
+ return invalidField('isAutoRenewing');
640
+ }
641
+ if (
642
+ nitroPurchase.store === 'apple' &&
643
+ (typeof nitroPurchase.transactionId !== 'string' ||
644
+ !nitroPurchase.transactionId)
645
+ ) {
646
+ return invalidField('transactionId');
647
+ }
648
+ const candidate = nitroPurchase as unknown as Record<string, unknown>;
649
+ if (
650
+ candidate.ids != null &&
651
+ (!Array.isArray(candidate.ids) ||
652
+ candidate.ids.some((id) => typeof id !== 'string'))
653
+ ) {
654
+ return invalidField('ids');
655
+ }
656
+ for (const field of [
657
+ 'pendingPurchaseUpdateAndroid',
658
+ 'renewalInfoIOS',
659
+ 'commitmentInfoIOS',
660
+ 'advancedCommerceInfoIOS',
661
+ ]) {
662
+ const nested = candidate[field];
597
663
  if (
598
- !(field in nitroPurchase) ||
599
- nitroPurchase[field as keyof NitroPurchase] == null
664
+ nested != null &&
665
+ (typeof nested !== 'object' || Array.isArray(nested))
600
666
  ) {
601
- RnIapConsole.error(
602
- `NitroPurchase missing required field: ${field}`,
603
- nitroPurchase,
604
- );
605
- return false;
667
+ return invalidField(field);
668
+ }
669
+ }
670
+ if (candidate.offerIOS != null) {
671
+ if (typeof candidate.offerIOS !== 'string') {
672
+ return invalidField('offerIOS');
673
+ }
674
+ try {
675
+ const offer = JSON.parse(candidate.offerIOS);
676
+ if (offer == null || typeof offer !== 'object' || Array.isArray(offer)) {
677
+ return invalidField('offerIOS');
678
+ }
679
+ } catch {
680
+ return invalidField('offerIOS');
606
681
  }
607
682
  }
608
683