react-native-iap 15.3.6 → 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 (81) 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 +56 -16
  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 +2 -2
  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/type-bridge.d.ts.map +1 -1
  34. package/lib/typescript/src/vega-adapter.d.ts +76 -0
  35. package/lib/typescript/src/vega-adapter.d.ts.map +1 -0
  36. package/lib/typescript/src/vega.d.ts +4 -0
  37. package/lib/typescript/src/vega.d.ts.map +1 -0
  38. package/lib/typescript/src/vega.kepler.d.ts +4 -0
  39. package/lib/typescript/src/vega.kepler.d.ts.map +1 -0
  40. package/nitrogen/generated/android/NitroIap+autolinking.cmake +1 -0
  41. package/nitrogen/generated/android/c++/JHybridRnIapSpec.cpp +5 -0
  42. package/nitrogen/generated/android/c++/JIapStore.hpp +3 -0
  43. package/nitrogen/generated/android/c++/JNitroVerifyPurchaseWithIapkitAmazonProps.hpp +71 -0
  44. package/nitrogen/generated/android/c++/JNitroVerifyPurchaseWithIapkitProps.hpp +9 -1
  45. package/nitrogen/generated/android/c++/JNitroVerifyPurchaseWithProviderProps.hpp +4 -0
  46. package/nitrogen/generated/android/c++/JVariant_NullType_NitroVerifyPurchaseWithIapkitAmazonProps.cpp +26 -0
  47. package/nitrogen/generated/android/c++/JVariant_NullType_NitroVerifyPurchaseWithIapkitAmazonProps.hpp +75 -0
  48. package/nitrogen/generated/android/c++/JVariant_NullType_NitroVerifyPurchaseWithIapkitProps.hpp +4 -0
  49. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/IapStore.kt +2 -1
  50. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroVerifyPurchaseWithIapkitAmazonProps.kt +44 -0
  51. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroVerifyPurchaseWithIapkitProps.kt +5 -2
  52. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/Variant_NullType_NitroVerifyPurchaseWithIapkitAmazonProps.kt +53 -0
  53. package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Bridge.hpp +47 -0
  54. package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Umbrella.hpp +3 -0
  55. package/nitrogen/generated/ios/c++/HybridRnIapSpecSwift.hpp +3 -0
  56. package/nitrogen/generated/ios/swift/IapStore.swift +4 -0
  57. package/nitrogen/generated/ios/swift/NitroVerifyPurchaseWithIapkitAmazonProps.swift +103 -0
  58. package/nitrogen/generated/ios/swift/NitroVerifyPurchaseWithIapkitProps.swift +38 -1
  59. package/nitrogen/generated/ios/swift/Variant_NullType_NitroVerifyPurchaseWithIapkitAmazonProps.swift +18 -0
  60. package/nitrogen/generated/shared/c++/IapStore.hpp +4 -0
  61. package/nitrogen/generated/shared/c++/NitroVerifyPurchaseWithIapkitAmazonProps.hpp +94 -0
  62. package/nitrogen/generated/shared/c++/NitroVerifyPurchaseWithIapkitProps.hpp +8 -1
  63. package/openiap-versions.json +1 -1
  64. package/package.json +17 -4
  65. package/plugin/build/withIAP.d.ts +33 -0
  66. package/plugin/build/withIAP.js +109 -42
  67. package/plugin/src/withIAP.ts +185 -55
  68. package/src/index.kepler.ts +311 -0
  69. package/src/index.ts +65 -19
  70. package/src/specs/RnIap.nitro.ts +13 -2
  71. package/src/types/amazon-devices-kepler/index.d.ts +14 -0
  72. package/src/types.ts +17 -3
  73. package/src/utils/error.ts +2 -2
  74. package/src/utils/type-bridge.ts +4 -1
  75. package/src/vega-adapter.ts +1503 -0
  76. package/src/vega.kepler.ts +21 -0
  77. package/src/vega.ts +10 -0
  78. package/android/src/test/java/com/margelo/nitro/iap/ListenerThreadSafetyTest.kt +0 -154
  79. package/android/src/test/java/com/margelo/nitro/iap/OpenIapExceptionTest.kt +0 -93
  80. package/android/src/test/java/com/margelo/nitro/iap/ProductQueryHelpersTest.kt +0 -140
  81. package/plugin/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,311 @@
1
+ import {getVegaIapModule} from './vega';
2
+ import type {
3
+ MutationField,
4
+ Product,
5
+ ProductQueryType,
6
+ ProductSubscription,
7
+ Purchase,
8
+ PurchaseError,
9
+ PurchaseUpdatedListenerOptions,
10
+ QueryField,
11
+ RequestPurchasePropsByPlatforms,
12
+ RequestSubscriptionPropsByPlatforms,
13
+ } from './types';
14
+ import type {
15
+ NitroAvailablePurchasesOptions,
16
+ NitroFinishTransactionParams,
17
+ NitroProduct,
18
+ NitroPurchase,
19
+ NitroPurchaseRequest,
20
+ } from './specs/RnIap.nitro';
21
+ import {
22
+ convertNitroProductToProduct,
23
+ convertNitroPurchaseToPurchase,
24
+ convertProductToProductSubscription,
25
+ validateNitroProduct,
26
+ validateNitroPurchase,
27
+ } from './utils/type-bridge';
28
+
29
+ export * from './types';
30
+ export * from './utils/error';
31
+ export * from './vega';
32
+ export {useIAP, type UseIapOptions} from './hooks/useIAP';
33
+ export {connectWebhookStream, parseWebhookEventData} from './webhook-client';
34
+ export {kitApi, KitApiError} from './kit-api';
35
+
36
+ export type ProductTypeInput = 'inapp' | 'in-app' | 'subs';
37
+
38
+ export interface EventSubscription {
39
+ remove(): void;
40
+ }
41
+
42
+ type VegaModuleExtras = {
43
+ acknowledgePurchaseAndroid(purchaseToken: string): Promise<boolean>;
44
+ consumePurchaseAndroid(purchaseToken: string): Promise<boolean>;
45
+ restorePurchases?: () => Promise<void>;
46
+ };
47
+
48
+ const unsupported = (feature: string): never => {
49
+ throw new Error(`${feature} is not supported on Amazon Vega.`);
50
+ };
51
+
52
+ const getModule = () => {
53
+ const module = getVegaIapModule();
54
+ if (!module) {
55
+ throw new Error(
56
+ '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.',
57
+ );
58
+ }
59
+ return module;
60
+ };
61
+
62
+ const getVegaModule = () =>
63
+ getModule() as ReturnType<typeof getModule> & VegaModuleExtras;
64
+
65
+ const normalizeProductQueryType = (
66
+ type?: ProductQueryType | ProductTypeInput | null,
67
+ ): ProductTypeInput | 'all' => {
68
+ if (type === 'subs') return 'subs';
69
+ if (type === 'all') return 'all';
70
+ return 'inapp';
71
+ };
72
+
73
+ const mapProducts = (
74
+ nitroProducts: NitroProduct[],
75
+ type: ProductTypeInput | 'all',
76
+ ) => {
77
+ const converted = nitroProducts
78
+ .filter(validateNitroProduct)
79
+ .map(convertNitroProductToProduct);
80
+
81
+ if (type === 'subs') {
82
+ return converted.map(convertProductToProductSubscription);
83
+ }
84
+
85
+ return converted;
86
+ };
87
+
88
+ export const isNitroReady = (): boolean => false;
89
+ export const isTVOS = (): boolean => false;
90
+ export const isMacOS = (): boolean => false;
91
+ export const isStandardIOS = (): boolean => false;
92
+
93
+ export const initConnection: MutationField<'initConnection'> = async (
94
+ config,
95
+ ) => {
96
+ return getModule().initConnection(config ?? null);
97
+ };
98
+
99
+ export const endConnection: MutationField<'endConnection'> = async () => {
100
+ return getModule().endConnection();
101
+ };
102
+
103
+ export const fetchProducts: QueryField<'fetchProducts'> = async (request) => {
104
+ const {skus, type} = request;
105
+ const normalizedType = normalizeProductQueryType(type);
106
+ const nitroProducts = await getModule().fetchProducts(skus, normalizedType);
107
+ return mapProducts(nitroProducts, normalizedType) as
108
+ | Product[]
109
+ | ProductSubscription[];
110
+ };
111
+
112
+ export const requestPurchase: MutationField<'requestPurchase'> = async (
113
+ request,
114
+ ) => {
115
+ const perPlatformRequest = request.request as
116
+ | RequestPurchasePropsByPlatforms
117
+ | RequestSubscriptionPropsByPlatforms
118
+ | undefined;
119
+ const androidRequest =
120
+ perPlatformRequest?.google ?? perPlatformRequest?.android;
121
+
122
+ if (!androidRequest?.skus?.length) {
123
+ throw new Error(
124
+ 'Invalid request for Amazon Vega. The `request.google.skus` or `request.android.skus` property is required and must be a non-empty array.',
125
+ );
126
+ }
127
+
128
+ const nitroRequest: NitroPurchaseRequest = {
129
+ google: androidRequest,
130
+ android: androidRequest,
131
+ };
132
+ const result = await getModule().requestPurchase(nitroRequest);
133
+ if (!Array.isArray(result)) return null;
134
+ const purchases = result as unknown as NitroPurchase[];
135
+ return purchases
136
+ .filter(validateNitroPurchase)
137
+ .map((purchase) => convertNitroPurchaseToPurchase(purchase));
138
+ };
139
+
140
+ export const getAvailablePurchases: QueryField<
141
+ 'getAvailablePurchases'
142
+ > = async (options) => {
143
+ const nitroOptions: NitroAvailablePurchasesOptions = {
144
+ android: {
145
+ includeSuspended: options?.includeSuspendedAndroid ?? false,
146
+ },
147
+ };
148
+ return getModule()
149
+ .getAvailablePurchases(nitroOptions)
150
+ .then((purchases) =>
151
+ purchases
152
+ .filter(validateNitroPurchase)
153
+ .map(convertNitroPurchaseToPurchase),
154
+ );
155
+ };
156
+
157
+ export const finishTransaction: MutationField<'finishTransaction'> = async (
158
+ args,
159
+ ) => {
160
+ const token = args.purchase.purchaseToken ?? undefined;
161
+ if (!token) {
162
+ throw new Error('purchaseToken required to finish Amazon Vega transaction');
163
+ }
164
+
165
+ const params: NitroFinishTransactionParams = {
166
+ android: {
167
+ purchaseToken: token,
168
+ isConsumable: args.isConsumable ?? false,
169
+ },
170
+ };
171
+ await getModule().finishTransaction(params);
172
+ };
173
+
174
+ export const restorePurchases: MutationField<'restorePurchases'> = async () => {
175
+ await getVegaModule().restorePurchases?.();
176
+ };
177
+
178
+ export const getActiveSubscriptions: QueryField<
179
+ 'getActiveSubscriptions'
180
+ > = async (args) => {
181
+ return getModule().getActiveSubscriptions(args ?? undefined);
182
+ };
183
+
184
+ export const hasActiveSubscriptions: QueryField<
185
+ 'hasActiveSubscriptions'
186
+ > = async (args) => {
187
+ return getModule().hasActiveSubscriptions(args ?? undefined);
188
+ };
189
+
190
+ export const purchaseUpdatedListener = (
191
+ listener: (purchase: Purchase) => void,
192
+ options?: PurchaseUpdatedListenerOptions | null,
193
+ ): EventSubscription => {
194
+ const token = getModule().addPurchaseUpdatedListener((purchase) => {
195
+ if (validateNitroPurchase(purchase)) {
196
+ listener(convertNitroPurchaseToPurchase(purchase));
197
+ }
198
+ }, options ?? undefined);
199
+
200
+ return {
201
+ remove: () => {
202
+ if (typeof token === 'number') {
203
+ getModule().removePurchaseUpdatedListener(token);
204
+ }
205
+ },
206
+ };
207
+ };
208
+
209
+ export const purchaseErrorListener = (
210
+ listener: (error: PurchaseError) => void,
211
+ ): EventSubscription => {
212
+ const nativeListener = (error: {code?: string; message?: string}) => {
213
+ listener({
214
+ code: (error.code ?? 'service-error') as PurchaseError['code'],
215
+ message: error.message ?? 'Amazon Vega purchase failed',
216
+ });
217
+ };
218
+
219
+ getModule().addPurchaseErrorListener(nativeListener);
220
+ return {
221
+ remove: () => getModule().removePurchaseErrorListener(nativeListener),
222
+ };
223
+ };
224
+
225
+ export const getStorefront: QueryField<'getStorefront'> = async () => {
226
+ return getModule().getStorefront();
227
+ };
228
+
229
+ export const verifyPurchaseWithProvider: MutationField<
230
+ 'verifyPurchaseWithProvider'
231
+ > = async (params) => {
232
+ return getModule().verifyPurchaseWithProvider(params) as ReturnType<
233
+ MutationField<'verifyPurchaseWithProvider'>
234
+ >;
235
+ };
236
+
237
+ export const validateReceipt: MutationField<'validateReceipt'> = async () => {
238
+ return unsupported('validateReceipt');
239
+ };
240
+
241
+ export const verifyPurchase: MutationField<'verifyPurchase'> = validateReceipt;
242
+ export const validateReceiptIOS: QueryField<'validateReceiptIOS'> = async () =>
243
+ unsupported('validateReceiptIOS');
244
+ export const syncIOS: MutationField<'syncIOS'> = async () =>
245
+ unsupported('syncIOS');
246
+ export const getAppTransactionIOS: QueryField<
247
+ 'getAppTransactionIOS'
248
+ > = async () => null;
249
+ export const getPromotedProductIOS: QueryField<
250
+ 'getPromotedProductIOS'
251
+ > = async () => null;
252
+ export const requestPromotedProductIOS = getPromotedProductIOS;
253
+ export const requestPurchaseOnPromotedProductIOS = async (): Promise<void> =>
254
+ unsupported('requestPurchaseOnPromotedProductIOS');
255
+ export const showManageSubscriptionsIOS: MutationField<
256
+ 'showManageSubscriptionsIOS'
257
+ > = async () => [];
258
+ export const presentCodeRedemptionSheetIOS: MutationField<
259
+ 'presentCodeRedemptionSheetIOS'
260
+ > = async () => false;
261
+ export const presentExternalPurchaseLinkIOS: MutationField<
262
+ 'presentExternalPurchaseLinkIOS'
263
+ > = async () => unsupported('presentExternalPurchaseLinkIOS');
264
+ export const deepLinkToSubscriptions: MutationField<
265
+ 'deepLinkToSubscriptions'
266
+ > = async () => unsupported('deepLinkToSubscriptions');
267
+ export const promotedProductListenerIOS = (): EventSubscription => ({
268
+ remove: () => {},
269
+ });
270
+
271
+ export const acknowledgePurchaseAndroid: MutationField<
272
+ 'acknowledgePurchaseAndroid'
273
+ > = async (purchaseToken) => {
274
+ return getVegaModule().acknowledgePurchaseAndroid(purchaseToken);
275
+ };
276
+ export const consumePurchaseAndroid: MutationField<
277
+ 'consumePurchaseAndroid'
278
+ > = async (purchaseToken) => {
279
+ return getVegaModule().consumePurchaseAndroid(purchaseToken);
280
+ };
281
+ export const acknowledgePurchase = acknowledgePurchaseAndroid;
282
+ export const consumePurchase = consumePurchaseAndroid;
283
+
284
+ export const checkAlternativeBillingAvailabilityAndroid: MutationField<
285
+ 'checkAlternativeBillingAvailabilityAndroid'
286
+ > = async () => unsupported('checkAlternativeBillingAvailabilityAndroid');
287
+ export const showAlternativeBillingDialogAndroid: MutationField<
288
+ 'showAlternativeBillingDialogAndroid'
289
+ > = async () => unsupported('showAlternativeBillingDialogAndroid');
290
+ export const createAlternativeBillingTokenAndroid: MutationField<
291
+ 'createAlternativeBillingTokenAndroid'
292
+ > = async () => unsupported('createAlternativeBillingTokenAndroid');
293
+ export const isBillingProgramAvailableAndroid: MutationField<
294
+ 'isBillingProgramAvailableAndroid'
295
+ > = async () => unsupported('isBillingProgramAvailableAndroid');
296
+ export const launchExternalLinkAndroid: MutationField<
297
+ 'launchExternalLinkAndroid'
298
+ > = async () => unsupported('launchExternalLinkAndroid');
299
+ export const createBillingProgramReportingDetailsAndroid: MutationField<
300
+ 'createBillingProgramReportingDetailsAndroid'
301
+ > = async () => unsupported('createBillingProgramReportingDetailsAndroid');
302
+ export const userChoiceBillingListenerAndroid = (): EventSubscription => ({
303
+ remove: () => {},
304
+ });
305
+ export const developerProvidedBillingListenerAndroid =
306
+ (): EventSubscription => ({
307
+ remove: () => {},
308
+ });
309
+ export const subscriptionBillingIssueListener = (): EventSubscription => ({
310
+ remove: () => {},
311
+ });
package/src/index.ts CHANGED
@@ -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
 
@@ -178,6 +178,10 @@ let iapRef: RnIap | null = null;
178
178
  */
179
179
  export const isNitroReady = (): boolean => {
180
180
  if (iapRef) return true;
181
+ if (isVegaOS()) {
182
+ iapRef = getVegaIapModule();
183
+ return Boolean(iapRef);
184
+ }
181
185
  try {
182
186
  iapRef = NitroModules.createHybridObject<RnIap>('RnIap');
183
187
  return true;
@@ -212,10 +216,25 @@ export const isStandardIOS = (): boolean => {
212
216
  return Platform.OS === 'ios' && !isTVOS() && !isMacOS();
213
217
  };
214
218
 
219
+ const isAndroidStoreRuntime = (): boolean => {
220
+ return Platform.OS === 'android' || isVegaOS();
221
+ };
222
+
215
223
  const IAP = {
216
224
  get instance(): RnIap {
217
225
  if (iapRef) return iapRef;
218
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
+
219
238
  // Attempt to create the HybridObject and map common Nitro/JSI readiness errors
220
239
  try {
221
240
  iapRef = NitroModules.createHybridObject<RnIap>('RnIap');
@@ -754,7 +773,7 @@ function tryAttachSubscriptionBillingIssueNative(): void {
754
773
  const msg = toErrorMessage(e);
755
774
  if (msg.includes('Nitro runtime not installed')) {
756
775
  RnIapConsole.warn(
757
- '[subscriptionBillingIssueListener] Nitro not ready yet; will retry on next registration after initConnection()',
776
+ '[subscriptionBillingIssueListener] Nitro not ready yet; will retry after initConnection()',
758
777
  );
759
778
  } else {
760
779
  throw e;
@@ -863,7 +882,7 @@ export const fetchProducts: QueryField<'fetchProducts'> = async (request) => {
863
882
  // item.type === 'subs' case
864
883
  // For Android, check if subscription items have actual offers
865
884
  if (
866
- Platform.OS === 'android' &&
885
+ isAndroidStoreRuntime() &&
867
886
  item.platform === 'android' &&
868
887
  item.type === 'subs'
869
888
  ) {
@@ -984,11 +1003,26 @@ export const getAvailablePurchases: QueryField<
984
1003
  }
985
1004
 
986
1005
  return validPurchases.map(convertNitroPurchaseToPurchase);
987
- } else if (Platform.OS === 'android') {
988
- // For Android, we need to call twice for inapp and subs
1006
+ } else if (isAndroidStoreRuntime()) {
989
1007
  const includeSuspended = Boolean(
990
1008
  options?.includeSuspendedAndroid ?? false,
991
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.
992
1026
  const inappNitroPurchases = await IAP.instance.getAvailablePurchases({
993
1027
  android: {type: 'inapp', includeSuspended},
994
1028
  });
@@ -1088,9 +1122,9 @@ export const getStorefrontIOS: QueryField<'getStorefrontIOS'> = async () => {
1088
1122
  * @see {@link https://openiap.dev/docs/apis/get-storefront}
1089
1123
  */
1090
1124
  export const getStorefront: QueryField<'getStorefront'> = async () => {
1091
- if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
1125
+ if (Platform.OS !== 'ios' && !isAndroidStoreRuntime()) {
1092
1126
  RnIapConsole.warn(
1093
- '[getStorefront] Storefront lookup is only supported on iOS and Android.',
1127
+ '[getStorefront] Storefront lookup is only supported on iOS, Android, and Vega OS.',
1094
1128
  );
1095
1129
  return '';
1096
1130
  }
@@ -1598,9 +1632,13 @@ export const initConnection: MutationField<'initConnection'> = async (
1598
1632
  config,
1599
1633
  ) => {
1600
1634
  try {
1601
- return await IAP.instance.initConnection(
1635
+ const result = await IAP.instance.initConnection(
1602
1636
  config as Record<string, unknown> | undefined,
1603
1637
  );
1638
+ if (subscriptionBillingIssueJsListeners.size > 0) {
1639
+ tryAttachSubscriptionBillingIssueNative();
1640
+ }
1641
+ return result;
1604
1642
  } catch (error) {
1605
1643
  const parsedError = parseErrorAndLogIfNeeded(
1606
1644
  'Failed to initialize IAP connection:',
@@ -1719,7 +1757,7 @@ export const requestPurchase: MutationField<'requestPurchase'> = async (
1719
1757
  'Invalid request for iOS. The `sku` property is required.',
1720
1758
  );
1721
1759
  }
1722
- } else if (Platform.OS === 'android') {
1760
+ } else if (isAndroidStoreRuntime()) {
1723
1761
  // Support both 'google' (recommended) and 'android' (deprecated) fields
1724
1762
  const androidRequest =
1725
1763
  perPlatformRequest.google ?? perPlatformRequest.android;
@@ -1787,7 +1825,7 @@ export const requestPurchase: MutationField<'requestPurchase'> = async (
1787
1825
  // Support both 'google' (recommended) and 'android' (deprecated) fields
1788
1826
  const androidRequestSource =
1789
1827
  perPlatformRequest.google ?? perPlatformRequest.android;
1790
- if (Platform.OS === 'android' && androidRequestSource) {
1828
+ if (isAndroidStoreRuntime() && androidRequestSource) {
1791
1829
  const androidRequest = isSubs
1792
1830
  ? (androidRequestSource as RequestSubscriptionAndroidProps)
1793
1831
  : (androidRequestSource as RequestPurchaseAndroidProps);
@@ -1896,7 +1934,7 @@ export const finishTransaction: MutationField<'finishTransaction'> = async (
1896
1934
  transactionId: purchase.id,
1897
1935
  },
1898
1936
  };
1899
- } else if (Platform.OS === 'android') {
1937
+ } else if (isAndroidStoreRuntime()) {
1900
1938
  const token = purchase.purchaseToken ?? undefined;
1901
1939
 
1902
1940
  if (!token) {
@@ -1954,9 +1992,9 @@ export const acknowledgePurchaseAndroid: MutationField<
1954
1992
  'acknowledgePurchaseAndroid'
1955
1993
  > = async (purchaseToken) => {
1956
1994
  try {
1957
- if (Platform.OS !== 'android') {
1995
+ if (!isAndroidStoreRuntime()) {
1958
1996
  throw new Error(
1959
- 'acknowledgePurchaseAndroid is only available on Android',
1997
+ 'acknowledgePurchaseAndroid is only available on Android and Vega OS',
1960
1998
  );
1961
1999
  }
1962
2000
 
@@ -1997,8 +2035,10 @@ export const consumePurchaseAndroid: MutationField<
1997
2035
  'consumePurchaseAndroid'
1998
2036
  > = async (purchaseToken) => {
1999
2037
  try {
2000
- if (Platform.OS !== 'android') {
2001
- 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
+ );
2002
2042
  }
2003
2043
 
2004
2044
  const result = await IAP.instance.finishTransaction({
@@ -2221,8 +2261,14 @@ export const validateReceiptIOS: QueryField<'validateReceiptIOS'> = async (
2221
2261
  * provider: 'iapkit',
2222
2262
  * iapkit: {
2223
2263
  * apiKey: 'your-api-key',
2224
- * apple: { jws: purchase.purchaseToken },
2225
- * 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
+ * },
2226
2272
  * },
2227
2273
  * });
2228
2274
  * ```
@@ -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
+ }
package/src/types.ts CHANGED
@@ -511,7 +511,7 @@ export type IapEvent = 'purchase-updated' | 'purchase-error' | 'promoted-product
511
511
 
512
512
  export type IapPlatform = 'ios' | 'android';
513
513
 
514
- export type IapStore = 'unknown' | 'apple' | 'google' | 'horizon';
514
+ export type IapStore = 'unknown' | 'apple' | 'google' | 'horizon' | 'amazon';
515
515
 
516
516
  /** Unified purchase states from IAPKit verification response. */
517
517
  export type IapkitPurchaseState = 'entitled' | 'pending-acknowledgment' | 'pending' | 'canceled' | 'expired' | 'ready-to-consume' | 'consumed' | 'unknown' | 'inauthentic';
@@ -1614,7 +1614,8 @@ export type RequestPurchaseProps =
1614
1614
  *
1615
1615
  * Note: "Platforms" refers to the SDK/OS level (apple, google), not the store.
1616
1616
  * - apple: Always targets App Store
1617
- * - google: Targets Play Store by default, or Horizon when built with horizon flavor
1617
+ * - google: Targets Play Store by default, Horizon when built with horizon flavor,
1618
+ * or Fire OS when built with amazon flavor
1618
1619
  * (determined at build time, not runtime)
1619
1620
  */
1620
1621
  export interface RequestPurchasePropsByPlatforms {
@@ -1713,7 +1714,8 @@ export interface RequestSubscriptionIosProps {
1713
1714
  *
1714
1715
  * Note: "Platforms" refers to the SDK/OS level (apple, google), not the store.
1715
1716
  * - apple: Always targets App Store
1716
- * - google: Targets Play Store by default, or Horizon when built with horizon flavor
1717
+ * - google: Targets Play Store by default, Horizon when built with horizon flavor,
1718
+ * or Fire OS when built with amazon flavor
1717
1719
  * (determined at build time, not runtime)
1718
1720
  */
1719
1721
  export interface RequestSubscriptionPropsByPlatforms {
@@ -1727,6 +1729,15 @@ export interface RequestSubscriptionPropsByPlatforms {
1727
1729
  ios?: (RequestSubscriptionIosProps | null);
1728
1730
  }
1729
1731
 
1732
+ export interface RequestVerifyPurchaseWithIapkitAmazonProps {
1733
+ /** Amazon Appstore receipt id returned by PurchaseResponse.getReceipt().getReceiptId(). */
1734
+ receiptId: string;
1735
+ /** Use Amazon RVS Cloud Sandbox for App Tester receipts. */
1736
+ sandbox?: (boolean | null);
1737
+ /** Amazon Appstore user id returned by PurchaseResponse.getUserData().getUserId(). */
1738
+ userId?: (string | null);
1739
+ }
1740
+
1730
1741
  export interface RequestVerifyPurchaseWithIapkitAppleProps {
1731
1742
  /** The JWS token returned with the purchase response. */
1732
1743
  jws: string;
@@ -1742,8 +1753,11 @@ export interface RequestVerifyPurchaseWithIapkitGoogleProps {
1742
1753
  *
1743
1754
  * - apple: Verifies via App Store (JWS token)
1744
1755
  * - google: Verifies via Play Store (purchase token)
1756
+ * - amazon: Verifies via Amazon Appstore RVS (userId + receiptId)
1745
1757
  */
1746
1758
  export interface RequestVerifyPurchaseWithIapkitProps {
1759
+ /** Amazon Appstore verification parameters. */
1760
+ amazon?: (RequestVerifyPurchaseWithIapkitAmazonProps | null);
1747
1761
  /** API key used for the Authorization header (Bearer {apiKey}). */
1748
1762
  apiKey?: (string | null);
1749
1763
  /** Apple App Store verification parameters. */
@@ -116,12 +116,12 @@ const parseStructuredError = (error: Error): IapError | null => {
116
116
  unknown
117
117
  >;
118
118
 
119
- if (typeof errorWithCode.code === 'string' && errorWithCode.code.length > 0) {
119
+ if (errorWithCode.code != null) {
120
120
  const {code, message, ...extraFields} = errorWithCode;
121
121
 
122
122
  return {
123
123
  ...extraFields,
124
- code,
124
+ code: String(code),
125
125
  message: typeof message === 'string' ? message : error.message,
126
126
  };
127
127
  }
@@ -35,6 +35,7 @@ const STORE_UNKNOWN: IapStore = 'unknown';
35
35
  const STORE_APPLE: IapStore = 'apple';
36
36
  const STORE_GOOGLE: IapStore = 'google';
37
37
  const STORE_HORIZON: IapStore = 'horizon';
38
+ const STORE_AMAZON: IapStore = 'amazon';
38
39
  const PRODUCT_TYPE_SUBS: ProductType = 'subs';
39
40
  const PRODUCT_TYPE_IN_APP: ProductType = 'in-app';
40
41
  const PURCHASE_STATE_PENDING: PurchaseState = 'pending';
@@ -67,6 +68,8 @@ function normalizeStore(value?: Nullable<string>): IapStore {
67
68
  return STORE_GOOGLE;
68
69
  case 'horizon':
69
70
  return STORE_HORIZON;
71
+ case 'amazon':
72
+ return STORE_AMAZON;
70
73
  default:
71
74
  return STORE_UNKNOWN;
72
75
  }
@@ -191,7 +194,7 @@ function toNullableBoolean(value: unknown): boolean | null {
191
194
  function parseSubscriptionOffers(value?: Nullable<string> | any[]) {
192
195
  if (!value) return undefined;
193
196
 
194
- // If it's already an array (from mocks), return it as-is
197
+ // Keep pre-normalized native values intact.
195
198
  if (Array.isArray(value)) {
196
199
  return value;
197
200
  }