react-native-iap 15.5.4 → 15.6.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 (77) hide show
  1. package/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt +163 -15
  2. package/android/src/main/java/com/margelo/nitro/iap/PendingEventBuffer.kt +86 -0
  3. package/android/src/main/java/com/margelo/nitro/iap/RnIapLog.kt +13 -0
  4. package/ios/HybridRnIap.swift +31 -4
  5. package/ios/RnIapHelper.swift +3 -2
  6. package/ios/RnIapLog.swift +14 -0
  7. package/lib/module/hooks/useIAP.js +8 -2
  8. package/lib/module/hooks/useIAP.js.map +1 -1
  9. package/lib/module/index.js +105 -30
  10. package/lib/module/index.js.map +1 -1
  11. package/lib/module/index.kepler.js +72 -5
  12. package/lib/module/index.kepler.js.map +1 -1
  13. package/lib/module/kit-api.js +78 -3
  14. package/lib/module/kit-api.js.map +1 -1
  15. package/lib/module/types.js +29 -25
  16. package/lib/module/types.js.map +1 -1
  17. package/lib/module/utils/deprecation.js +15 -0
  18. package/lib/module/utils/deprecation.js.map +1 -0
  19. package/lib/module/utils/platform-request.js +28 -0
  20. package/lib/module/utils/platform-request.js.map +1 -0
  21. package/lib/module/utils/type-bridge.js +9 -4
  22. package/lib/module/utils/type-bridge.js.map +1 -1
  23. package/lib/module/vega-adapter.js +34 -22
  24. package/lib/module/vega-adapter.js.map +1 -1
  25. package/lib/typescript/src/hooks/useIAP.d.ts +16 -2
  26. package/lib/typescript/src/hooks/useIAP.d.ts.map +1 -1
  27. package/lib/typescript/src/index.d.ts +56 -12
  28. package/lib/typescript/src/index.d.ts.map +1 -1
  29. package/lib/typescript/src/index.kepler.d.ts +50 -4
  30. package/lib/typescript/src/index.kepler.d.ts.map +1 -1
  31. package/lib/typescript/src/kit-api.d.ts +18 -8
  32. package/lib/typescript/src/kit-api.d.ts.map +1 -1
  33. package/lib/typescript/src/specs/RnIap.nitro.d.ts +32 -6
  34. package/lib/typescript/src/specs/RnIap.nitro.d.ts.map +1 -1
  35. package/lib/typescript/src/types.d.ts +119 -175
  36. package/lib/typescript/src/types.d.ts.map +1 -1
  37. package/lib/typescript/src/utils/deprecation.d.ts +3 -0
  38. package/lib/typescript/src/utils/deprecation.d.ts.map +1 -0
  39. package/lib/typescript/src/utils/platform-request.d.ts +12 -0
  40. package/lib/typescript/src/utils/platform-request.d.ts.map +1 -0
  41. package/lib/typescript/src/utils/type-bridge.d.ts.map +1 -1
  42. package/lib/typescript/src/vega-adapter.d.ts +1 -0
  43. package/lib/typescript/src/vega-adapter.d.ts.map +1 -1
  44. package/nitrogen/generated/android/c++/JHybridRnIapSpec.cpp +16 -0
  45. package/nitrogen/generated/android/c++/JHybridRnIapSpec.hpp +1 -0
  46. package/nitrogen/generated/android/c++/JNitroPurchase.hpp +5 -1
  47. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/HybridRnIapSpec.kt +4 -0
  48. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroPurchase.kt +7 -2
  49. package/nitrogen/generated/ios/c++/HybridRnIapSpecSwift.hpp +8 -0
  50. package/nitrogen/generated/ios/swift/HybridRnIapSpec.swift +1 -0
  51. package/nitrogen/generated/ios/swift/HybridRnIapSpec_cxx.swift +19 -0
  52. package/nitrogen/generated/ios/swift/NitroPurchase.swift +39 -2
  53. package/nitrogen/generated/shared/c++/HybridRnIapSpec.cpp +1 -0
  54. package/nitrogen/generated/shared/c++/HybridRnIapSpec.hpp +1 -0
  55. package/nitrogen/generated/shared/c++/NitroPurchase.hpp +5 -1
  56. package/openiap-versions.json +3 -3
  57. package/package.json +1 -1
  58. package/src/hooks/useIAP.ts +22 -2
  59. package/src/index.kepler.ts +86 -8
  60. package/src/index.ts +139 -47
  61. package/src/kit-api.ts +136 -13
  62. package/src/specs/RnIap.nitro.ts +33 -6
  63. package/src/types.ts +130 -203
  64. package/src/utils/deprecation.ts +16 -0
  65. package/src/utils/platform-request.ts +37 -0
  66. package/src/utils/type-bridge.ts +20 -4
  67. package/src/vega-adapter.ts +50 -24
  68. package/lib/module/hooks/useWebhookEvents.js +0 -113
  69. package/lib/module/hooks/useWebhookEvents.js.map +0 -1
  70. package/lib/module/webhook-client.js +0 -164
  71. package/lib/module/webhook-client.js.map +0 -1
  72. package/lib/typescript/src/hooks/useWebhookEvents.d.ts +0 -55
  73. package/lib/typescript/src/hooks/useWebhookEvents.d.ts.map +0 -1
  74. package/lib/typescript/src/webhook-client.d.ts +0 -82
  75. package/lib/typescript/src/webhook-client.d.ts.map +0 -1
  76. package/src/hooks/useWebhookEvents.ts +0 -180
  77. package/src/webhook-client.ts +0 -312
package/src/kit-api.ts CHANGED
@@ -10,6 +10,21 @@ export type KitApiOptions = {
10
10
  // Optional fetch override for runtimes without a global (older RN
11
11
  // builds) or for injection in tests.
12
12
  fetchImpl?: (input: string, init?: RequestInit) => Promise<Response>;
13
+ /** Optional AsyncStorage-compatible persistent cache for direct client
14
+ * payload reads. Cache failures never change a successful API result. */
15
+ clientPayloadCache?: KitClientPayloadCache;
16
+ };
17
+
18
+ export type KitClientPayloadCache = {
19
+ getItem: (key: string) => Promise<string | null> | string | null;
20
+ setItem: (key: string, value: string) => Promise<void> | void;
21
+ removeItem?: (key: string) => Promise<void> | void;
22
+ };
23
+
24
+ export type KitClientPayloadOptions = {
25
+ /** Revalidate a cached payload with its scoped ETag. Without this flag,
26
+ * a valid persistent entry is returned without a network request. */
27
+ refresh?: boolean;
13
28
  };
14
29
 
15
30
  export type KitSubscription = {
@@ -85,19 +100,16 @@ export type KitProductsOptions = {
85
100
  platform?: KitProductPlatform;
86
101
  /** Include public client payload bodies in a bounded platform page. */
87
102
  includeClientPayload?: boolean;
88
- /** Page size for payload-inclusive reads (default 25, maximum 50).
89
- * Ignored by the legacy non-payload catalog path. */
103
+ /** Page size for every catalog read (default 25, maximum 50). */
90
104
  limit?: number;
91
- /** Opaque cursor returned as `nextCursor` by the previous payload page.
92
- * Ignored by the legacy non-payload catalog path. */
105
+ /** Opaque cursor returned as `nextCursor` by the previous page. */
93
106
  cursor?: string;
94
107
  };
95
108
 
96
109
  export type KitProductsResponse = {
97
110
  products: KitProduct[];
98
- /** Present on payload-inclusive catalog pages. */
99
- hasMore?: boolean;
100
- /** Present when a payload-inclusive catalog page has another page. */
111
+ hasMore: boolean;
112
+ /** Present when a catalog page has another page. */
101
113
  nextCursor?: string;
102
114
  };
103
115
 
@@ -105,6 +117,10 @@ export type KitClientPayloadResponse = {
105
117
  clientPayload: KitProductClientPayload;
106
118
  };
107
119
 
120
+ type CachedClientPayload = KitClientPayloadResponse & {
121
+ etag?: string;
122
+ };
123
+
108
124
  const DEFAULT_BASE_URL = "https://kit.openiap.dev";
109
125
 
110
126
  // Merge caller-supplied headers with kit defaults (`accept`,
@@ -196,7 +212,7 @@ export function kitApi(options: KitApiOptions) {
196
212
  );
197
213
  })();
198
214
 
199
- async function call<T>(path: string, init?: RequestInit): Promise<T> {
215
+ async function request(path: string, init?: RequestInit): Promise<Response> {
200
216
  // Normalize headers without depending on a global `Headers`
201
217
  // constructor: older React Native runtimes ship `fetch` (or a
202
218
  // polyfill via `fetchImpl`) without exposing `Headers` globally.
@@ -213,10 +229,16 @@ export function kitApi(options: KitApiOptions) {
213
229
  // already-stripped `baseUrl` (PR #124
214
230
  // (https://github.com/hyodotdev/openiap/pull/124) review).
215
231
  const normalizedPath = path.startsWith("/") ? path : `/${path}`;
216
- const response = await fetchImpl(`${baseUrl}${normalizedPath}`, {
232
+ return fetchImpl(`${baseUrl}${normalizedPath}`, {
217
233
  ...init,
218
234
  headers,
219
235
  });
236
+ }
237
+
238
+ async function parseResponse<T>(
239
+ response: Response,
240
+ path: string,
241
+ ): Promise<T> {
220
242
  const text = await response.text();
221
243
  // Empty body normalizes to null so callers expecting JSON
222
244
  // (status / entitlements / list*) don't get a truthy ""
@@ -256,6 +278,75 @@ export function kitApi(options: KitApiOptions) {
256
278
  return parsed as T;
257
279
  }
258
280
 
281
+ async function call<T>(path: string, init?: RequestInit): Promise<T> {
282
+ return parseResponse<T>(await request(path, init), path);
283
+ }
284
+
285
+ function clientPayloadCacheKey(
286
+ productId: string,
287
+ platform: KitProductPlatform,
288
+ ): string {
289
+ return [
290
+ "iapkit-client-payload-v1",
291
+ baseUrl,
292
+ options.apiKey,
293
+ platform,
294
+ productId,
295
+ ]
296
+ .map(encodeURIComponent)
297
+ .join(":");
298
+ }
299
+
300
+ async function readCachedClientPayload(
301
+ cacheKey: string,
302
+ ): Promise<CachedClientPayload | null> {
303
+ if (!options.clientPayloadCache) return null;
304
+ try {
305
+ const raw = await options.clientPayloadCache.getItem(cacheKey);
306
+ if (!raw) return null;
307
+ const candidate = JSON.parse(raw) as Partial<CachedClientPayload>;
308
+ const payload = candidate.clientPayload;
309
+ if (
310
+ !payload ||
311
+ !["toml", "json", "text"].includes(payload.format) ||
312
+ typeof payload.body !== "string" ||
313
+ !Number.isSafeInteger(payload.version) ||
314
+ payload.version < 1 ||
315
+ typeof payload.updatedAt !== "number" ||
316
+ (candidate.etag !== undefined && typeof candidate.etag !== "string")
317
+ ) {
318
+ await options.clientPayloadCache.removeItem?.(cacheKey);
319
+ return null;
320
+ }
321
+ return candidate as CachedClientPayload;
322
+ } catch {
323
+ return null;
324
+ }
325
+ }
326
+
327
+ async function writeCachedClientPayload(
328
+ cacheKey: string,
329
+ value: CachedClientPayload,
330
+ ): Promise<void> {
331
+ try {
332
+ await options.clientPayloadCache?.setItem(
333
+ cacheKey,
334
+ JSON.stringify(value),
335
+ );
336
+ } catch {
337
+ // Persistence is an optimization. A device storage failure must not
338
+ // turn a successful IAPKit read into an application error.
339
+ }
340
+ }
341
+
342
+ async function removeCachedClientPayload(cacheKey: string): Promise<void> {
343
+ try {
344
+ await options.clientPayloadCache?.removeItem?.(cacheKey);
345
+ } catch {
346
+ // See writeCachedClientPayload: cache maintenance is best effort.
347
+ }
348
+ }
349
+
259
350
  return {
260
351
  apiKey: options.apiKey,
261
352
  baseUrl,
@@ -312,10 +403,42 @@ export function kitApi(options: KitApiOptions) {
312
403
 
313
404
  /** GET one public client payload by its store-specific natural key.
314
405
  * Payloads are app-facing data; never store secrets in them. */
315
- clientPayload: (productId: string, platform: KitProductPlatform) =>
316
- call<KitClientPayloadResponse>(
317
- `/v1/products/${encodeURIComponent(options.apiKey)}/${encodeURIComponent(productId)}/client-payload?platform=${encodeURIComponent(platform)}`,
318
- ),
406
+ clientPayload: async (
407
+ productId: string,
408
+ platform: KitProductPlatform,
409
+ payloadOptions: KitClientPayloadOptions = {},
410
+ ) => {
411
+ const path = `/v1/products/${encodeURIComponent(options.apiKey)}/${encodeURIComponent(productId)}/client-payload?platform=${encodeURIComponent(platform)}`;
412
+ const cacheKey = clientPayloadCacheKey(productId, platform);
413
+ const cached = await readCachedClientPayload(cacheKey);
414
+ if (cached && payloadOptions.refresh !== true) {
415
+ return { clientPayload: cached.clientPayload };
416
+ }
417
+ const response = await request(path, {
418
+ ...(cached?.etag ? { headers: { "If-None-Match": cached.etag } } : {}),
419
+ });
420
+ if (response.status === 304 && cached) {
421
+ return { clientPayload: cached.clientPayload };
422
+ }
423
+ try {
424
+ const result = await parseResponse<KitClientPayloadResponse>(
425
+ response,
426
+ path,
427
+ );
428
+ await writeCachedClientPayload(cacheKey, {
429
+ ...result,
430
+ ...(response.headers.get("etag")
431
+ ? { etag: response.headers.get("etag")! }
432
+ : {}),
433
+ });
434
+ return result;
435
+ } catch (error) {
436
+ if (error instanceof KitApiError && error.status === 404) {
437
+ await removeCachedClientPayload(cacheKey);
438
+ }
439
+ throw error;
440
+ }
441
+ },
319
442
 
320
443
  /** POST /v1/subscriptions/bind-user — call after a successful
321
444
  * verifyReceipt so kit knows which userId owns the verified
@@ -256,7 +256,7 @@ export interface NitroRequestPurchaseAndroid {
256
256
  */
257
257
  offerToken?: string | null;
258
258
  subscriptionOffers?: AndroidSubscriptionOfferInput[] | null;
259
- /** @deprecated Use subscriptionProductReplacementParams instead for item-level replacement (8.1.0+) */
259
+ /** @deprecated Use subscriptionProductReplacementParams instead for item-level replacement (8.1.0+). Scheduled for removal in react-native-iap 16.0.0. */
260
260
  replacementMode?: RequestSubscriptionAndroidProps['replacementMode'];
261
261
  purchaseToken?: RequestSubscriptionAndroidProps['purchaseToken'];
262
262
  /** Original external transaction ID for developer-billed subscription replacement (9.1.0+). */
@@ -271,9 +271,9 @@ export interface NitroRequestPurchaseAndroid {
271
271
  }
272
272
 
273
273
  export interface NitroPurchaseRequest {
274
- /** @deprecated Use apple instead */
274
+ /** @deprecated Use apple instead. Scheduled for removal in react-native-iap 16.0.0. */
275
275
  ios?: NitroRequestPurchaseIos | null;
276
- /** @deprecated Use google instead */
276
+ /** @deprecated Use google instead. Scheduled for removal in react-native-iap 16.0.0. */
277
277
  android?: NitroRequestPurchaseAndroid | null;
278
278
  /** Apple-specific purchase parameters */
279
279
  apple?: NitroRequestPurchaseIos | null;
@@ -609,12 +609,13 @@ export interface NitroOneTimePurchaseOfferDetail {
609
609
 
610
610
  export interface NitroPurchase {
611
611
  id: PurchaseCommon['id'];
612
+ transactionId?: string | null;
612
613
  productId: PurchaseCommon['productId'];
613
614
  transactionDate: PurchaseCommon['transactionDate'];
614
615
  purchaseToken?: PurchaseCommon['purchaseToken'];
615
616
  currentPlanId?: PurchaseCommon['currentPlanId'];
616
617
  ids?: PurchaseCommon['ids'];
617
- /** @deprecated Use store instead */
618
+ /** @deprecated Use store instead. Scheduled for removal in react-native-iap 16.0.0. */
618
619
  platform: IapPlatform;
619
620
  /** Store where purchase was made */
620
621
  store: IapStore;
@@ -674,6 +675,7 @@ export interface NitroActiveSubscription {
674
675
  // iOS specific fields
675
676
  expirationDateIOS?: ActiveSubscription['expirationDateIOS'];
676
677
  environmentIOS?: ActiveSubscription['environmentIOS'];
678
+ /** @deprecated Use daysUntilExpirationIOS instead. Scheduled for removal in react-native-iap 16.0.0. */
677
679
  willExpireSoon?: ActiveSubscription['willExpireSoon'];
678
680
  daysUntilExpirationIOS?: ActiveSubscription['daysUntilExpirationIOS'];
679
681
  renewalInfoIOS?: NitroRenewalInfoIOS | null; // 🆕 Key field for upgrade/downgrade detection
@@ -720,7 +722,9 @@ export interface NitroProduct {
720
722
  isFamilyShareableIOS?: null | boolean;
721
723
  jsonRepresentationIOS?: string | null;
722
724
  pricingTermsIOS?: string | null;
725
+ /** @deprecated Use subscriptionOffers and subscriptionGroupIdIOS instead. Scheduled for removal in react-native-iap 16.0.0. */
723
726
  subscriptionInfoIOS?: string | null;
727
+ /** @deprecated Use subscriptionOffers instead. Scheduled for removal in react-native-iap 16.0.0. */
724
728
  discountsIOS?: string | null;
725
729
  introductoryPriceIOS?: string | null;
726
730
  introductoryPriceAsAmountIOS?: number | null;
@@ -744,7 +748,9 @@ export interface NitroProduct {
744
748
  introductoryPriceValueAndroid?: number | null;
745
749
  subscriptionPeriodAndroid?: string | null;
746
750
  freeTrialPeriodAndroid?: string | null;
751
+ /** @deprecated Use subscriptionOffers instead. Scheduled for removal in react-native-iap 16.0.0. */
747
752
  subscriptionOfferDetailsAndroid?: string | null;
753
+ /** @deprecated Use discountOffers instead. Scheduled for removal in react-native-iap 16.0.0. */
748
754
  oneTimePurchaseOfferDetailsAndroid?: NitroOneTimePurchaseOfferDetail[] | null;
749
755
  /**
750
756
  * Product-level status code indicating fetch result (Android 8.0+)
@@ -784,7 +790,7 @@ export interface RnIap extends HybridObject<{ios: 'swift'; android: 'kotlin'}> {
784
790
  /**
785
791
  * Fetch products from the store
786
792
  * @param skus - Array of product SKUs to fetch
787
- * @param type - Type of products: 'inapp' or 'subs'
793
+ * @param type - Type of products: 'in-app' or 'subs'
788
794
  * @returns Promise<NitroProduct[]> - Array of products from the store
789
795
  */
790
796
  fetchProducts(skus: string[], type: string): Promise<NitroProduct[]>;
@@ -905,6 +911,8 @@ export interface RnIap extends HybridObject<{ios: 'swift'; android: 'kotlin'}> {
905
911
  * Request the promoted product from the App Store (iOS only)
906
912
  * @returns Promise<NitroProduct | null> - The promoted product or null if none available
907
913
  * @platform iOS
914
+ * @deprecated Use `getPromotedProductIOS` instead. This compatibility alias
915
+ * will be removed in react-native-iap 16.0.0.
908
916
  */
909
917
  requestPromotedProductIOS(): Promise<NitroProduct | null>;
910
918
 
@@ -1041,6 +1049,8 @@ export interface RnIap extends HybridObject<{ios: 'swift'; android: 'kotlin'}> {
1041
1049
  *
1042
1050
  * @returns Promise<string> - Base64 encoded receipt data containing all app transactions
1043
1051
  * @platform iOS
1052
+ * @deprecated Use `getReceiptDataIOS` instead. This compatibility alias will
1053
+ * be removed in react-native-iap 16.0.0.
1044
1054
  * @see getReceiptDataIOS for full documentation
1045
1055
  * @see getTransactionJwsIOS for validating individual transactions (recommended)
1046
1056
  */
@@ -1095,7 +1105,8 @@ export interface RnIap extends HybridObject<{ios: 'swift'; android: 'kotlin'}> {
1095
1105
 
1096
1106
  /**
1097
1107
  * Validate a receipt on the appropriate platform
1098
- * @deprecated Use `verifyPurchase` instead. This function will be removed in a future version.
1108
+ * @deprecated Use `verifyPurchase` instead. This function will be removed in
1109
+ * react-native-iap 16.0.0.
1099
1110
  * @param params - Receipt validation parameters including SKU and platform-specific options
1100
1111
  * @returns Promise<NitroReceiptValidationResultIOS | NitroReceiptValidationResultAndroid> - Platform-specific validation result
1101
1112
  */
@@ -1144,6 +1155,8 @@ export interface RnIap extends HybridObject<{ios: 'swift'; android: 'kotlin'}> {
1144
1155
  * @returns Promise<boolean> - true if available, false otherwise
1145
1156
  * @throws Error if billing client not ready
1146
1157
  * @platform Android
1158
+ * @deprecated Use `isBillingProgramAvailableAndroid('external-offer')`
1159
+ * instead. Scheduled for removal in react-native-iap 16.0.0.
1147
1160
  */
1148
1161
  checkAlternativeBillingAvailabilityAndroid(): Promise<boolean>;
1149
1162
 
@@ -1155,6 +1168,8 @@ export interface RnIap extends HybridObject<{ios: 'swift'; android: 'kotlin'}> {
1155
1168
  * @returns Promise<boolean> - true if user accepted, false if user canceled
1156
1169
  * @throws Error if billing client not ready
1157
1170
  * @platform Android
1171
+ * @deprecated Use `launchExternalLinkAndroid` instead. Scheduled for removal in
1172
+ * react-native-iap 16.0.0.
1158
1173
  */
1159
1174
  showAlternativeBillingDialogAndroid(): Promise<boolean>;
1160
1175
 
@@ -1168,6 +1183,9 @@ export interface RnIap extends HybridObject<{ios: 'swift'; android: 'kotlin'}> {
1168
1183
  * @returns Promise<string | null> - Token string or null if creation failed
1169
1184
  * @throws Error if billing client not ready
1170
1185
  * @platform Android
1186
+ * @deprecated Use
1187
+ * `createBillingProgramReportingDetailsAndroid('external-offer')` instead.
1188
+ * Scheduled for removal in react-native-iap 16.0.0.
1171
1189
  */
1172
1190
  createAlternativeBillingTokenAndroid(
1173
1191
  sku?: string | null,
@@ -1331,6 +1349,15 @@ export interface RnIap extends HybridObject<{ios: 'swift'; android: 'kotlin'}> {
1331
1349
  params: NitroLaunchExternalLinkParamsAndroid,
1332
1350
  ): Promise<boolean>;
1333
1351
 
1352
+ /**
1353
+ * Open the platform offer-code redemption flow (Android only).
1354
+ * Does not require a billing-client connection.
1355
+ *
1356
+ * @returns Promise<boolean> - true when the flow was launched
1357
+ * @platform Android
1358
+ */
1359
+ openRedeemOfferCodeAndroid(): Promise<boolean>;
1360
+
1334
1361
  // ╔════════════════════════════════════════════════════════════════════════╗
1335
1362
  // ║ EXTERNAL PURCHASE LINKS (iOS 16.0+) ║
1336
1363
  // ╚════════════════════════════════════════════════════════════════════════╝