react-native-iap 15.3.5 → 15.4.0-rc.1

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 (83) hide show
  1. package/README.md +1 -1
  2. package/android/build.gradle +13 -5
  3. package/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt +16 -3
  4. package/ios/HybridRnIap.swift +12 -0
  5. package/lib/module/index.js +93 -75
  6. package/lib/module/index.js.map +1 -1
  7. package/lib/module/index.kepler.js +170 -0
  8. package/lib/module/index.kepler.js.map +1 -0
  9. package/lib/module/types/amazon-devices-kepler/index.d.js +2 -0
  10. package/lib/module/types/amazon-devices-kepler/index.d.js.map +1 -0
  11. package/lib/module/types.js +5 -2
  12. package/lib/module/types.js.map +1 -1
  13. package/lib/module/utils/error.js +98 -15
  14. package/lib/module/utils/error.js.map +1 -1
  15. package/lib/module/utils/type-bridge.js +4 -1
  16. package/lib/module/utils/type-bridge.js.map +1 -1
  17. package/lib/module/vega-adapter.js +981 -0
  18. package/lib/module/vega-adapter.js.map +1 -0
  19. package/lib/module/vega.js +10 -0
  20. package/lib/module/vega.js.map +1 -0
  21. package/lib/module/vega.kepler.js +18 -0
  22. package/lib/module/vega.kepler.js.map +1 -0
  23. package/lib/typescript/plugin/src/withIAP.d.ts +33 -0
  24. package/lib/typescript/plugin/src/withIAP.d.ts.map +1 -1
  25. package/lib/typescript/src/index.d.ts +9 -2
  26. package/lib/typescript/src/index.d.ts.map +1 -1
  27. package/lib/typescript/src/index.kepler.d.ts +55 -0
  28. package/lib/typescript/src/index.kepler.d.ts.map +1 -0
  29. package/lib/typescript/src/specs/RnIap.nitro.d.ts +10 -1
  30. package/lib/typescript/src/specs/RnIap.nitro.d.ts.map +1 -1
  31. package/lib/typescript/src/types.d.ts +16 -3
  32. package/lib/typescript/src/types.d.ts.map +1 -1
  33. package/lib/typescript/src/utils/error.d.ts +1 -1
  34. package/lib/typescript/src/utils/error.d.ts.map +1 -1
  35. package/lib/typescript/src/utils/type-bridge.d.ts.map +1 -1
  36. package/lib/typescript/src/vega-adapter.d.ts +76 -0
  37. package/lib/typescript/src/vega-adapter.d.ts.map +1 -0
  38. package/lib/typescript/src/vega.d.ts +4 -0
  39. package/lib/typescript/src/vega.d.ts.map +1 -0
  40. package/lib/typescript/src/vega.kepler.d.ts +4 -0
  41. package/lib/typescript/src/vega.kepler.d.ts.map +1 -0
  42. package/nitrogen/generated/android/NitroIap+autolinking.cmake +1 -0
  43. package/nitrogen/generated/android/c++/JHybridRnIapSpec.cpp +5 -0
  44. package/nitrogen/generated/android/c++/JIapStore.hpp +3 -0
  45. package/nitrogen/generated/android/c++/JNitroVerifyPurchaseWithIapkitAmazonProps.hpp +71 -0
  46. package/nitrogen/generated/android/c++/JNitroVerifyPurchaseWithIapkitProps.hpp +9 -1
  47. package/nitrogen/generated/android/c++/JNitroVerifyPurchaseWithProviderProps.hpp +4 -0
  48. package/nitrogen/generated/android/c++/JVariant_NullType_NitroVerifyPurchaseWithIapkitAmazonProps.cpp +26 -0
  49. package/nitrogen/generated/android/c++/JVariant_NullType_NitroVerifyPurchaseWithIapkitAmazonProps.hpp +75 -0
  50. package/nitrogen/generated/android/c++/JVariant_NullType_NitroVerifyPurchaseWithIapkitProps.hpp +4 -0
  51. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/IapStore.kt +2 -1
  52. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroVerifyPurchaseWithIapkitAmazonProps.kt +44 -0
  53. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroVerifyPurchaseWithIapkitProps.kt +5 -2
  54. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/Variant_NullType_NitroVerifyPurchaseWithIapkitAmazonProps.kt +53 -0
  55. package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Bridge.hpp +47 -0
  56. package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Umbrella.hpp +3 -0
  57. package/nitrogen/generated/ios/c++/HybridRnIapSpecSwift.hpp +3 -0
  58. package/nitrogen/generated/ios/swift/IapStore.swift +4 -0
  59. package/nitrogen/generated/ios/swift/NitroVerifyPurchaseWithIapkitAmazonProps.swift +103 -0
  60. package/nitrogen/generated/ios/swift/NitroVerifyPurchaseWithIapkitProps.swift +38 -1
  61. package/nitrogen/generated/ios/swift/Variant_NullType_NitroVerifyPurchaseWithIapkitAmazonProps.swift +18 -0
  62. package/nitrogen/generated/shared/c++/IapStore.hpp +4 -0
  63. package/nitrogen/generated/shared/c++/NitroVerifyPurchaseWithIapkitAmazonProps.hpp +94 -0
  64. package/nitrogen/generated/shared/c++/NitroVerifyPurchaseWithIapkitProps.hpp +8 -1
  65. package/openiap-versions.json +2 -2
  66. package/package.json +17 -4
  67. package/plugin/build/withIAP.d.ts +33 -0
  68. package/plugin/build/withIAP.js +109 -42
  69. package/plugin/src/withIAP.ts +185 -55
  70. package/src/index.kepler.ts +311 -0
  71. package/src/index.ts +187 -78
  72. package/src/specs/RnIap.nitro.ts +13 -2
  73. package/src/types/amazon-devices-kepler/index.d.ts +14 -0
  74. package/src/types.ts +17 -3
  75. package/src/utils/error.ts +127 -14
  76. package/src/utils/type-bridge.ts +4 -1
  77. package/src/vega-adapter.ts +1503 -0
  78. package/src/vega.kepler.ts +21 -0
  79. package/src/vega.ts +10 -0
  80. package/android/src/test/java/com/margelo/nitro/iap/ListenerThreadSafetyTest.kt +0 -154
  81. package/android/src/test/java/com/margelo/nitro/iap/OpenIapExceptionTest.kt +0 -93
  82. package/android/src/test/java/com/margelo/nitro/iap/ProductQueryHelpersTest.kt +0 -140
  83. package/plugin/tsconfig.tsbuildinfo +0 -1
package/src/index.ts CHANGED
@@ -55,7 +55,7 @@ import {
55
55
  validateNitroPurchase,
56
56
  convertNitroSubscriptionStatusToSubscriptionStatusIOS,
57
57
  } from './utils/type-bridge';
58
- import {parseErrorStringToJsonObj} from './utils/error';
58
+ import {isUserCancelledError, parseErrorStringToJsonObj} from './utils/error';
59
59
  import {
60
60
  normalizeErrorCodeFromNative,
61
61
  createPurchaseError,
@@ -64,6 +64,7 @@ import {
64
64
  import {RnIapConsole} from './utils/debug';
65
65
  import {getSuccessFromPurchaseVariant} from './utils/purchase';
66
66
  import {parseAppTransactionPayload} from './utils';
67
+ import {getVegaIapModule, isVegaOS} from './vega';
67
68
 
68
69
  // ------------------------------
69
70
  // Billing Programs API (Android 8.2.0+)
@@ -72,9 +73,7 @@ import {parseAppTransactionPayload} from './utils';
72
73
  // Note: BillingProgramAndroid, ExternalLinkLaunchModeAndroid, and ExternalLinkTypeAndroid
73
74
  // are exported from './types' (auto-generated from openiap-gql).
74
75
  // Import them here for use in this file's interfaces and functions.
75
- import type {
76
- BillingProgramAndroid,
77
- } from './types';
76
+ import type {BillingProgramAndroid} from './types';
78
77
 
79
78
  // Export all types
80
79
  export type {
@@ -85,6 +84,7 @@ export type {
85
84
  } from './specs/RnIap.nitro';
86
85
  export * from './types';
87
86
  export * from './utils/error';
87
+ export * from './vega';
88
88
 
89
89
  export type ProductTypeInput = 'inapp' | 'in-app' | 'subs';
90
90
 
@@ -121,6 +121,17 @@ const toErrorMessage = (error: unknown): string => {
121
121
  return String(error ?? '');
122
122
  };
123
123
 
124
+ const parseErrorAndLogIfNeeded = (
125
+ message: string,
126
+ error: unknown,
127
+ ): ReturnType<typeof parseErrorStringToJsonObj> => {
128
+ const parsedError = parseErrorStringToJsonObj(error);
129
+ if (!isUserCancelledError(parsedError)) {
130
+ RnIapConsole.error(message, error);
131
+ }
132
+ return parsedError;
133
+ };
134
+
124
135
  const unsupportedPlatformError = (): Error =>
125
136
  new Error(`Unsupported platform: ${Platform.OS}`);
126
137
 
@@ -167,6 +178,10 @@ let iapRef: RnIap | null = null;
167
178
  */
168
179
  export const isNitroReady = (): boolean => {
169
180
  if (iapRef) return true;
181
+ if (isVegaOS()) {
182
+ iapRef = getVegaIapModule();
183
+ return Boolean(iapRef);
184
+ }
170
185
  try {
171
186
  iapRef = NitroModules.createHybridObject<RnIap>('RnIap');
172
187
  return true;
@@ -201,10 +216,25 @@ export const isStandardIOS = (): boolean => {
201
216
  return Platform.OS === 'ios' && !isTVOS() && !isMacOS();
202
217
  };
203
218
 
219
+ const isAndroidStoreRuntime = (): boolean => {
220
+ return Platform.OS === 'android' || isVegaOS();
221
+ };
222
+
204
223
  const IAP = {
205
224
  get instance(): RnIap {
206
225
  if (iapRef) return iapRef;
207
226
 
227
+ if (isVegaOS()) {
228
+ const vegaModule = getVegaIapModule();
229
+ if (!vegaModule) {
230
+ throw new Error(
231
+ 'Amazon Vega IAP module is unavailable. Install @amazon-devices/keplerscript-appstore-iap-lib in the Vega app target and build with the React Native for Vega kepler platform.',
232
+ );
233
+ }
234
+ iapRef = vegaModule;
235
+ return iapRef;
236
+ }
237
+
208
238
  // Attempt to create the HybridObject and map common Nitro/JSI readiness errors
209
239
  try {
210
240
  iapRef = NitroModules.createHybridObject<RnIap>('RnIap');
@@ -743,7 +773,7 @@ function tryAttachSubscriptionBillingIssueNative(): void {
743
773
  const msg = toErrorMessage(e);
744
774
  if (msg.includes('Nitro runtime not installed')) {
745
775
  RnIapConsole.warn(
746
- '[subscriptionBillingIssueListener] Nitro not ready yet; will retry on next registration after initConnection()',
776
+ '[subscriptionBillingIssueListener] Nitro not ready yet; will retry after initConnection()',
747
777
  );
748
778
  } else {
749
779
  throw e;
@@ -852,7 +882,7 @@ export const fetchProducts: QueryField<'fetchProducts'> = async (request) => {
852
882
  // item.type === 'subs' case
853
883
  // For Android, check if subscription items have actual offers
854
884
  if (
855
- Platform.OS === 'android' &&
885
+ isAndroidStoreRuntime() &&
856
886
  item.platform === 'android' &&
857
887
  item.type === 'subs'
858
888
  ) {
@@ -905,8 +935,10 @@ export const fetchProducts: QueryField<'fetchProducts'> = async (request) => {
905
935
 
906
936
  return convertedProducts as FetchProductsResult;
907
937
  } catch (error) {
908
- RnIapConsole.error('[fetchProducts] Failed:', error);
909
- const parsedError = parseErrorStringToJsonObj(error);
938
+ const parsedError = parseErrorAndLogIfNeeded(
939
+ '[fetchProducts] Failed:',
940
+ error,
941
+ );
910
942
  throw createPurchaseError({
911
943
  code: parsedError.code,
912
944
  message: parsedError.message,
@@ -971,11 +1003,26 @@ export const getAvailablePurchases: QueryField<
971
1003
  }
972
1004
 
973
1005
  return validPurchases.map(convertNitroPurchaseToPurchase);
974
- } else if (Platform.OS === 'android') {
975
- // For Android, we need to call twice for inapp and subs
1006
+ } else if (isAndroidStoreRuntime()) {
976
1007
  const includeSuspended = Boolean(
977
1008
  options?.includeSuspendedAndroid ?? false,
978
1009
  );
1010
+
1011
+ if (isVegaOS()) {
1012
+ const nitroPurchases = await IAP.instance.getAvailablePurchases({
1013
+ android: {includeSuspended},
1014
+ });
1015
+ const validPurchases = nitroPurchases.filter(validateNitroPurchase);
1016
+ if (validPurchases.length !== nitroPurchases.length) {
1017
+ RnIapConsole.warn(
1018
+ `[getAvailablePurchases] Some Vega purchases failed validation: ${nitroPurchases.length - validPurchases.length} invalid`,
1019
+ );
1020
+ }
1021
+
1022
+ return validPurchases.map(convertNitroPurchaseToPurchase);
1023
+ }
1024
+
1025
+ // For Android Play/Horizon/Fire OS, query in-app items and subscriptions separately.
979
1026
  const inappNitroPurchases = await IAP.instance.getAvailablePurchases({
980
1027
  android: {type: 'inapp', includeSuspended},
981
1028
  });
@@ -1027,8 +1074,10 @@ export const getPromotedProductIOS: QueryField<
1027
1074
  const converted = convertNitroProductToProduct(nitroProduct);
1028
1075
  return converted.platform === 'ios' ? (converted as ProductIOS) : null;
1029
1076
  } catch (error) {
1030
- RnIapConsole.error('[getPromotedProductIOS] Failed:', error);
1031
- const parsedError = parseErrorStringToJsonObj(error);
1077
+ const parsedError = parseErrorAndLogIfNeeded(
1078
+ '[getPromotedProductIOS] Failed:',
1079
+ error,
1080
+ );
1032
1081
  throw createPurchaseError({
1033
1082
  code: parsedError.code,
1034
1083
  message: parsedError.message,
@@ -1073,9 +1122,9 @@ export const getStorefrontIOS: QueryField<'getStorefrontIOS'> = async () => {
1073
1122
  * @see {@link https://openiap.dev/docs/apis/get-storefront}
1074
1123
  */
1075
1124
  export const getStorefront: QueryField<'getStorefront'> = async () => {
1076
- if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
1125
+ if (Platform.OS !== 'ios' && !isAndroidStoreRuntime()) {
1077
1126
  RnIapConsole.warn(
1078
- '[getStorefront] Storefront lookup is only supported on iOS and Android.',
1127
+ '[getStorefront] Storefront lookup is only supported on iOS, Android, and Vega OS.',
1079
1128
  );
1080
1129
  return '';
1081
1130
  }
@@ -1181,8 +1230,10 @@ export const subscriptionStatusIOS: QueryField<
1181
1230
  .filter((status): status is NitroSubscriptionStatus => status != null)
1182
1231
  .map(convertNitroSubscriptionStatusToSubscriptionStatusIOS);
1183
1232
  } catch (error) {
1184
- RnIapConsole.error('[subscriptionStatusIOS] Failed:', error);
1185
- const parsedError = parseErrorStringToJsonObj(error);
1233
+ const parsedError = parseErrorAndLogIfNeeded(
1234
+ '[subscriptionStatusIOS] Failed:',
1235
+ error,
1236
+ );
1186
1237
  throw createPurchaseError({
1187
1238
  code: parsedError.code,
1188
1239
  message: parsedError.message,
@@ -1215,8 +1266,10 @@ export const currentEntitlementIOS: QueryField<
1215
1266
  }
1216
1267
  return null;
1217
1268
  } catch (error) {
1218
- RnIapConsole.error('[currentEntitlementIOS] Failed:', error);
1219
- const parsedError = parseErrorStringToJsonObj(error);
1269
+ const parsedError = parseErrorAndLogIfNeeded(
1270
+ '[currentEntitlementIOS] Failed:',
1271
+ error,
1272
+ );
1220
1273
  throw createPurchaseError({
1221
1274
  code: parsedError.code,
1222
1275
  message: parsedError.message,
@@ -1249,8 +1302,10 @@ export const latestTransactionIOS: QueryField<'latestTransactionIOS'> = async (
1249
1302
  }
1250
1303
  return null;
1251
1304
  } catch (error) {
1252
- RnIapConsole.error('[latestTransactionIOS] Failed:', error);
1253
- const parsedError = parseErrorStringToJsonObj(error);
1305
+ const parsedError = parseErrorAndLogIfNeeded(
1306
+ '[latestTransactionIOS] Failed:',
1307
+ error,
1308
+ );
1254
1309
  throw createPurchaseError({
1255
1310
  code: parsedError.code,
1256
1311
  message: parsedError.message,
@@ -1282,8 +1337,10 @@ export const getPendingTransactionsIOS: QueryField<
1282
1337
  (purchase): purchase is PurchaseIOS => purchase.platform === 'ios',
1283
1338
  );
1284
1339
  } catch (error) {
1285
- RnIapConsole.error('[getPendingTransactionsIOS] Failed:', error);
1286
- const parsedError = parseErrorStringToJsonObj(error);
1340
+ const parsedError = parseErrorAndLogIfNeeded(
1341
+ '[getPendingTransactionsIOS] Failed:',
1342
+ error,
1343
+ );
1287
1344
  throw createPurchaseError({
1288
1345
  code: parsedError.code,
1289
1346
  message: parsedError.message,
@@ -1313,8 +1370,10 @@ export const getAllTransactionsIOS: QueryField<
1313
1370
  (purchase): purchase is PurchaseIOS => purchase.platform === 'ios',
1314
1371
  );
1315
1372
  } catch (error) {
1316
- RnIapConsole.error('[getAllTransactionsIOS] Failed:', error);
1317
- const parsedError = parseErrorStringToJsonObj(error);
1373
+ const parsedError = parseErrorAndLogIfNeeded(
1374
+ '[getAllTransactionsIOS] Failed:',
1375
+ error,
1376
+ );
1318
1377
  throw createPurchaseError({
1319
1378
  code: parsedError.code,
1320
1379
  message: parsedError.message,
@@ -1346,8 +1405,10 @@ export const showManageSubscriptionsIOS: MutationField<
1346
1405
  (purchase): purchase is PurchaseIOS => purchase.platform === 'ios',
1347
1406
  );
1348
1407
  } catch (error) {
1349
- RnIapConsole.error('[showManageSubscriptionsIOS] Failed:', error);
1350
- const parsedError = parseErrorStringToJsonObj(error);
1408
+ const parsedError = parseErrorAndLogIfNeeded(
1409
+ '[showManageSubscriptionsIOS] Failed:',
1410
+ error,
1411
+ );
1351
1412
  throw createPurchaseError({
1352
1413
  code: parsedError.code,
1353
1414
  message: parsedError.message,
@@ -1375,8 +1436,10 @@ export const isEligibleForIntroOfferIOS: QueryField<
1375
1436
  try {
1376
1437
  return await IAP.instance.isEligibleForIntroOfferIOS(groupID);
1377
1438
  } catch (error) {
1378
- RnIapConsole.error('[isEligibleForIntroOfferIOS] Failed:', error);
1379
- const parsedError = parseErrorStringToJsonObj(error);
1439
+ const parsedError = parseErrorAndLogIfNeeded(
1440
+ '[isEligibleForIntroOfferIOS] Failed:',
1441
+ error,
1442
+ );
1380
1443
  throw createPurchaseError({
1381
1444
  code: parsedError.code,
1382
1445
  message: parsedError.message,
@@ -1407,8 +1470,10 @@ export const getReceiptDataIOS: QueryField<'getReceiptDataIOS'> = async () => {
1407
1470
  try {
1408
1471
  return await IAP.instance.getReceiptDataIOS();
1409
1472
  } catch (error) {
1410
- RnIapConsole.error('[getReceiptDataIOS] Failed:', error);
1411
- const parsedError = parseErrorStringToJsonObj(error);
1473
+ const parsedError = parseErrorAndLogIfNeeded(
1474
+ '[getReceiptDataIOS] Failed:',
1475
+ error,
1476
+ );
1412
1477
  throw createPurchaseError({
1413
1478
  code: parsedError.code,
1414
1479
  message: parsedError.message,
@@ -1435,8 +1500,10 @@ export const getReceiptIOS = async (): Promise<string> => {
1435
1500
  }
1436
1501
  return await IAP.instance.getReceiptDataIOS();
1437
1502
  } catch (error) {
1438
- RnIapConsole.error('[getReceiptIOS] Failed:', error);
1439
- const parsedError = parseErrorStringToJsonObj(error);
1503
+ const parsedError = parseErrorAndLogIfNeeded(
1504
+ '[getReceiptIOS] Failed:',
1505
+ error,
1506
+ );
1440
1507
  throw createPurchaseError({
1441
1508
  code: parsedError.code,
1442
1509
  message: parsedError.message,
@@ -1463,8 +1530,10 @@ export const requestReceiptRefreshIOS = async (): Promise<string> => {
1463
1530
  }
1464
1531
  return await IAP.instance.getReceiptDataIOS();
1465
1532
  } catch (error) {
1466
- RnIapConsole.error('[requestReceiptRefreshIOS] Failed:', error);
1467
- const parsedError = parseErrorStringToJsonObj(error);
1533
+ const parsedError = parseErrorAndLogIfNeeded(
1534
+ '[requestReceiptRefreshIOS] Failed:',
1535
+ error,
1536
+ );
1468
1537
  throw createPurchaseError({
1469
1538
  code: parsedError.code,
1470
1539
  message: parsedError.message,
@@ -1492,8 +1561,10 @@ export const isTransactionVerifiedIOS: QueryField<
1492
1561
  try {
1493
1562
  return await IAP.instance.isTransactionVerifiedIOS(sku);
1494
1563
  } catch (error) {
1495
- RnIapConsole.error('[isTransactionVerifiedIOS] Failed:', error);
1496
- const parsedError = parseErrorStringToJsonObj(error);
1564
+ const parsedError = parseErrorAndLogIfNeeded(
1565
+ '[isTransactionVerifiedIOS] Failed:',
1566
+ error,
1567
+ );
1497
1568
  throw createPurchaseError({
1498
1569
  code: parsedError.code,
1499
1570
  message: parsedError.message,
@@ -1521,8 +1592,10 @@ export const getTransactionJwsIOS: QueryField<'getTransactionJwsIOS'> = async (
1521
1592
  try {
1522
1593
  return await IAP.instance.getTransactionJwsIOS(sku);
1523
1594
  } catch (error) {
1524
- RnIapConsole.error('[getTransactionJwsIOS] Failed:', error);
1525
- const parsedError = parseErrorStringToJsonObj(error);
1595
+ const parsedError = parseErrorAndLogIfNeeded(
1596
+ '[getTransactionJwsIOS] Failed:',
1597
+ error,
1598
+ );
1526
1599
  throw createPurchaseError({
1527
1600
  code: parsedError.code,
1528
1601
  message: parsedError.message,
@@ -1559,12 +1632,18 @@ export const initConnection: MutationField<'initConnection'> = async (
1559
1632
  config,
1560
1633
  ) => {
1561
1634
  try {
1562
- return await IAP.instance.initConnection(
1635
+ const result = await IAP.instance.initConnection(
1563
1636
  config as Record<string, unknown> | undefined,
1564
1637
  );
1638
+ if (subscriptionBillingIssueJsListeners.size > 0) {
1639
+ tryAttachSubscriptionBillingIssueNative();
1640
+ }
1641
+ return result;
1565
1642
  } catch (error) {
1566
- RnIapConsole.error('Failed to initialize IAP connection:', error);
1567
- const parsedError = parseErrorStringToJsonObj(error);
1643
+ const parsedError = parseErrorAndLogIfNeeded(
1644
+ 'Failed to initialize IAP connection:',
1645
+ error,
1646
+ );
1568
1647
  throw createPurchaseError({
1569
1648
  code: parsedError.code,
1570
1649
  message: parsedError.message,
@@ -1586,8 +1665,10 @@ export const endConnection: MutationField<'endConnection'> = async () => {
1586
1665
  resetListenerState();
1587
1666
  return result;
1588
1667
  } catch (error) {
1589
- RnIapConsole.error('Failed to end IAP connection:', error);
1590
- const parsedError = parseErrorStringToJsonObj(error);
1668
+ const parsedError = parseErrorAndLogIfNeeded(
1669
+ 'Failed to end IAP connection:',
1670
+ error,
1671
+ );
1591
1672
  throw createPurchaseError({
1592
1673
  code: parsedError.code,
1593
1674
  message: parsedError.message,
@@ -1613,8 +1694,10 @@ export const restorePurchases: MutationField<'restorePurchases'> = async () => {
1613
1694
  onlyIncludeActiveItemsIOS: true,
1614
1695
  });
1615
1696
  } catch (error) {
1616
- RnIapConsole.error('Failed to restore purchases:', error);
1617
- const parsedError = parseErrorStringToJsonObj(error);
1697
+ const parsedError = parseErrorAndLogIfNeeded(
1698
+ 'Failed to restore purchases:',
1699
+ error,
1700
+ );
1618
1701
  throw createPurchaseError({
1619
1702
  code: parsedError.code,
1620
1703
  message: parsedError.message,
@@ -1674,7 +1757,7 @@ export const requestPurchase: MutationField<'requestPurchase'> = async (
1674
1757
  'Invalid request for iOS. The `sku` property is required.',
1675
1758
  );
1676
1759
  }
1677
- } else if (Platform.OS === 'android') {
1760
+ } else if (isAndroidStoreRuntime()) {
1678
1761
  // Support both 'google' (recommended) and 'android' (deprecated) fields
1679
1762
  const androidRequest =
1680
1763
  perPlatformRequest.google ?? perPlatformRequest.android;
@@ -1742,7 +1825,7 @@ export const requestPurchase: MutationField<'requestPurchase'> = async (
1742
1825
  // Support both 'google' (recommended) and 'android' (deprecated) fields
1743
1826
  const androidRequestSource =
1744
1827
  perPlatformRequest.google ?? perPlatformRequest.android;
1745
- if (Platform.OS === 'android' && androidRequestSource) {
1828
+ if (isAndroidStoreRuntime() && androidRequestSource) {
1746
1829
  const androidRequest = isSubs
1747
1830
  ? (androidRequestSource as RequestSubscriptionAndroidProps)
1748
1831
  : (androidRequestSource as RequestPurchaseAndroidProps);
@@ -1798,8 +1881,10 @@ export const requestPurchase: MutationField<'requestPurchase'> = async (
1798
1881
 
1799
1882
  return await IAP.instance.requestPurchase(unifiedRequest);
1800
1883
  } catch (error) {
1801
- RnIapConsole.error('Failed to request purchase:', error);
1802
- const parsedError = parseErrorStringToJsonObj(error);
1884
+ const parsedError = parseErrorAndLogIfNeeded(
1885
+ 'Failed to request purchase:',
1886
+ error,
1887
+ );
1803
1888
  throw createPurchaseError({
1804
1889
  code: parsedError.code,
1805
1890
  message: parsedError.message,
@@ -1849,7 +1934,7 @@ export const finishTransaction: MutationField<'finishTransaction'> = async (
1849
1934
  transactionId: purchase.id,
1850
1935
  },
1851
1936
  };
1852
- } else if (Platform.OS === 'android') {
1937
+ } else if (isAndroidStoreRuntime()) {
1853
1938
  const token = purchase.purchaseToken ?? undefined;
1854
1939
 
1855
1940
  if (!token) {
@@ -1907,9 +1992,9 @@ export const acknowledgePurchaseAndroid: MutationField<
1907
1992
  'acknowledgePurchaseAndroid'
1908
1993
  > = async (purchaseToken) => {
1909
1994
  try {
1910
- if (Platform.OS !== 'android') {
1995
+ if (!isAndroidStoreRuntime()) {
1911
1996
  throw new Error(
1912
- 'acknowledgePurchaseAndroid is only available on Android',
1997
+ 'acknowledgePurchaseAndroid is only available on Android and Vega OS',
1913
1998
  );
1914
1999
  }
1915
2000
 
@@ -1921,8 +2006,10 @@ export const acknowledgePurchaseAndroid: MutationField<
1921
2006
  });
1922
2007
  return getSuccessFromPurchaseVariant(result, 'acknowledgePurchaseAndroid');
1923
2008
  } catch (error) {
1924
- RnIapConsole.error('Failed to acknowledge purchase Android:', error);
1925
- const parsedError = parseErrorStringToJsonObj(error);
2009
+ const parsedError = parseErrorAndLogIfNeeded(
2010
+ 'Failed to acknowledge purchase Android:',
2011
+ error,
2012
+ );
1926
2013
  throw createPurchaseError({
1927
2014
  code: parsedError.code,
1928
2015
  message: parsedError.message,
@@ -1948,8 +2035,10 @@ export const consumePurchaseAndroid: MutationField<
1948
2035
  'consumePurchaseAndroid'
1949
2036
  > = async (purchaseToken) => {
1950
2037
  try {
1951
- if (Platform.OS !== 'android') {
1952
- throw new Error('consumePurchaseAndroid is only available on Android');
2038
+ if (!isAndroidStoreRuntime()) {
2039
+ throw new Error(
2040
+ 'consumePurchaseAndroid is only available on Android and Vega OS',
2041
+ );
1953
2042
  }
1954
2043
 
1955
2044
  const result = await IAP.instance.finishTransaction({
@@ -1960,8 +2049,10 @@ export const consumePurchaseAndroid: MutationField<
1960
2049
  });
1961
2050
  return getSuccessFromPurchaseVariant(result, 'consumePurchaseAndroid');
1962
2051
  } catch (error) {
1963
- RnIapConsole.error('Failed to consume purchase Android:', error);
1964
- const parsedError = parseErrorStringToJsonObj(error);
2052
+ const parsedError = parseErrorAndLogIfNeeded(
2053
+ 'Failed to consume purchase Android:',
2054
+ error,
2055
+ );
1965
2056
  throw createPurchaseError({
1966
2057
  code: parsedError.code,
1967
2058
  message: parsedError.message,
@@ -2109,8 +2200,10 @@ export const validateReceipt: MutationField<'validateReceipt'> = async (
2109
2200
  return result;
2110
2201
  }
2111
2202
  } catch (error) {
2112
- RnIapConsole.error('[validateReceipt] Failed:', error);
2113
- const parsedError = parseErrorStringToJsonObj(error);
2203
+ const parsedError = parseErrorAndLogIfNeeded(
2204
+ '[validateReceipt] Failed:',
2205
+ error,
2206
+ );
2114
2207
  throw createPurchaseError({
2115
2208
  code: parsedError.code,
2116
2209
  message: parsedError.message,
@@ -2168,8 +2261,14 @@ export const validateReceiptIOS: QueryField<'validateReceiptIOS'> = async (
2168
2261
  * provider: 'iapkit',
2169
2262
  * iapkit: {
2170
2263
  * apiKey: 'your-api-key',
2171
- * apple: { jws: purchase.purchaseToken },
2172
- * google: { purchaseToken: purchase.purchaseToken },
2264
+ * // Choose exactly one store payload.
2265
+ * // apple: { jws: purchase.purchaseToken },
2266
+ * // google: { purchaseToken: purchase.purchaseToken },
2267
+ * amazon: {
2268
+ * userId: amazonUserId,
2269
+ * receiptId: purchase.purchaseToken,
2270
+ * sandbox: __DEV__,
2271
+ * },
2173
2272
  * },
2174
2273
  * });
2175
2274
  * ```
@@ -2203,8 +2302,10 @@ export const verifyPurchaseWithProvider: MutationField<
2203
2302
  errors: result.errors ?? null,
2204
2303
  };
2205
2304
  } catch (error) {
2206
- RnIapConsole.error('[verifyPurchaseWithProvider] Failed:', error);
2207
- const parsedError = parseErrorStringToJsonObj(error);
2305
+ const parsedError = parseErrorAndLogIfNeeded(
2306
+ '[verifyPurchaseWithProvider] Failed:',
2307
+ error,
2308
+ );
2208
2309
  throw createPurchaseError({
2209
2310
  code: parsedError.code,
2210
2311
  message: parsedError.message,
@@ -2230,8 +2331,7 @@ export const syncIOS: MutationField<'syncIOS'> = async () => {
2230
2331
  const result = await IAP.instance.syncIOS();
2231
2332
  return Boolean(result);
2232
2333
  } catch (error) {
2233
- RnIapConsole.error('[syncIOS] Failed:', error);
2234
- const parsedError = parseErrorStringToJsonObj(error);
2334
+ const parsedError = parseErrorAndLogIfNeeded('[syncIOS] Failed:', error);
2235
2335
  throw createPurchaseError({
2236
2336
  code: parsedError.code,
2237
2337
  message: parsedError.message,
@@ -2259,8 +2359,10 @@ export const presentCodeRedemptionSheetIOS: MutationField<
2259
2359
  const result = await IAP.instance.presentCodeRedemptionSheetIOS();
2260
2360
  return Boolean(result);
2261
2361
  } catch (error) {
2262
- RnIapConsole.error('[presentCodeRedemptionSheetIOS] Failed:', error);
2263
- const parsedError = parseErrorStringToJsonObj(error);
2362
+ const parsedError = parseErrorAndLogIfNeeded(
2363
+ '[presentCodeRedemptionSheetIOS] Failed:',
2364
+ error,
2365
+ );
2264
2366
  throw createPurchaseError({
2265
2367
  code: parsedError.code,
2266
2368
  message: parsedError.message,
@@ -2315,11 +2417,10 @@ export const requestPurchaseOnPromotedProductIOS =
2315
2417
 
2316
2418
  return true;
2317
2419
  } catch (error) {
2318
- RnIapConsole.error(
2420
+ const parsedError = parseErrorAndLogIfNeeded(
2319
2421
  '[requestPurchaseOnPromotedProductIOS] Failed:',
2320
2422
  error,
2321
2423
  );
2322
- const parsedError = parseErrorStringToJsonObj(error);
2323
2424
  throw createPurchaseError({
2324
2425
  code: parsedError.code,
2325
2426
  message: parsedError.message,
@@ -2347,8 +2448,10 @@ export const clearTransactionIOS: MutationField<
2347
2448
  await IAP.instance.clearTransactionIOS();
2348
2449
  return true;
2349
2450
  } catch (error) {
2350
- RnIapConsole.error('[clearTransactionIOS] Failed:', error);
2351
- const parsedError = parseErrorStringToJsonObj(error);
2451
+ const parsedError = parseErrorAndLogIfNeeded(
2452
+ '[clearTransactionIOS] Failed:',
2453
+ error,
2454
+ );
2352
2455
  throw createPurchaseError({
2353
2456
  code: parsedError.code,
2354
2457
  message: parsedError.message,
@@ -2377,8 +2480,10 @@ export const beginRefundRequestIOS: MutationField<
2377
2480
  const status = await IAP.instance.beginRefundRequestIOS(sku);
2378
2481
  return status ?? null;
2379
2482
  } catch (error) {
2380
- RnIapConsole.error('[beginRefundRequestIOS] Failed:', error);
2381
- const parsedError = parseErrorStringToJsonObj(error);
2483
+ const parsedError = parseErrorAndLogIfNeeded(
2484
+ '[beginRefundRequestIOS] Failed:',
2485
+ error,
2486
+ );
2382
2487
  throw createPurchaseError({
2383
2488
  code: parsedError.code,
2384
2489
  message: parsedError.message,
@@ -2430,8 +2535,10 @@ export const deepLinkToSubscriptionsIOS = async (): Promise<boolean> => {
2430
2535
  await IAP.instance.showManageSubscriptionsIOS();
2431
2536
  return true;
2432
2537
  } catch (error) {
2433
- RnIapConsole.error('[deepLinkToSubscriptionsIOS] Failed:', error);
2434
- const parsedError = parseErrorStringToJsonObj(error);
2538
+ const parsedError = parseErrorAndLogIfNeeded(
2539
+ '[deepLinkToSubscriptionsIOS] Failed:',
2540
+ error,
2541
+ );
2435
2542
  throw createPurchaseError({
2436
2543
  code: parsedError.code,
2437
2544
  message: parsedError.message,
@@ -2512,8 +2619,10 @@ export const getActiveSubscriptions: QueryField<
2512
2619
  RnIapConsole.error('IAP connection not initialized:', error);
2513
2620
  throw error;
2514
2621
  }
2515
- RnIapConsole.error('Failed to get active subscriptions:', error);
2516
- const parsedError = parseErrorStringToJsonObj(error);
2622
+ const parsedError = parseErrorAndLogIfNeeded(
2623
+ 'Failed to get active subscriptions:',
2624
+ error,
2625
+ );
2517
2626
  throw createPurchaseError({
2518
2627
  code: parsedError.code,
2519
2628
  message: parsedError.message,
@@ -88,7 +88,7 @@ export type IapkitPurchaseState =
88
88
 
89
89
  // Store identifier for purchase origin
90
90
  // Defined locally for Nitro codegen (not in GQL schema)
91
- export type IapStore = 'unknown' | 'apple' | 'google' | 'horizon';
91
+ export type IapStore = 'unknown' | 'apple' | 'google' | 'horizon' | 'amazon';
92
92
 
93
93
  // Purchase verification provider selection
94
94
  // Defined locally for Nitro codegen (not in GQL schema)
@@ -149,7 +149,8 @@ export interface NitroReceiptValidationHorizonOptions {
149
149
  userId: VerifyPurchaseHorizonOptions['userId'];
150
150
  }
151
151
 
152
- export type NitroPurchaseUpdatedListenerOptions = PurchaseUpdatedListenerOptions;
152
+ export type NitroPurchaseUpdatedListenerOptions =
153
+ PurchaseUpdatedListenerOptions;
153
154
 
154
155
  export interface NitroReceiptValidationParams {
155
156
  apple?: NitroReceiptValidationAppleOptions | null;
@@ -383,8 +384,18 @@ export interface NitroVerifyPurchaseWithIapkitGoogleProps {
383
384
  purchaseToken: string;
384
385
  }
385
386
 
387
+ export interface NitroVerifyPurchaseWithIapkitAmazonProps {
388
+ /** Amazon Appstore receipt id returned by PurchaseResponse.getReceipt().getReceiptId(). */
389
+ receiptId: string;
390
+ /** Use Amazon RVS Cloud Sandbox for App Tester receipts. */
391
+ sandbox?: boolean | null;
392
+ /** Amazon Appstore user id returned by PurchaseResponse.getUserData().getUserId(). */
393
+ userId?: string | null;
394
+ }
395
+
386
396
  export interface NitroVerifyPurchaseWithIapkitProps {
387
397
  apiKey?: string | null;
398
+ amazon?: NitroVerifyPurchaseWithIapkitAmazonProps | null;
388
399
  apple?: NitroVerifyPurchaseWithIapkitAppleProps | null;
389
400
  google?: NitroVerifyPurchaseWithIapkitGoogleProps | null;
390
401
  }
@@ -0,0 +1,14 @@
1
+ declare module '@amazon-devices/keplerscript-appstore-iap-lib' {
2
+ export const PurchasingService: {
3
+ getProductData(request: {skus: string[]}): Promise<unknown>;
4
+ getPurchaseUpdates(request: {reset: boolean}): Promise<unknown>;
5
+ getUserData(request: {
6
+ fetchUserProfileAccessConsentStatus: boolean;
7
+ }): Promise<unknown>;
8
+ notifyFulfillment(request: {
9
+ fulfillmentResult: number;
10
+ receiptId: string;
11
+ }): Promise<unknown>;
12
+ purchase(request: {sku: string}): Promise<unknown>;
13
+ };
14
+ }