expo-iap 3.0.8 → 3.1.1-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 (99) hide show
  1. package/CLAUDE.md +2 -2
  2. package/CONTRIBUTING.md +19 -0
  3. package/README.md +18 -6
  4. package/android/build.gradle +24 -1
  5. package/android/src/main/java/expo/modules/iap/ExpoIapLog.kt +69 -0
  6. package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +190 -59
  7. package/build/ExpoIapModule.d.ts +1 -0
  8. package/build/ExpoIapModule.d.ts.map +1 -1
  9. package/build/ExpoIapModule.js +29 -4
  10. package/build/ExpoIapModule.js.map +1 -1
  11. package/build/index.d.ts +20 -47
  12. package/build/index.d.ts.map +1 -1
  13. package/build/index.js +94 -137
  14. package/build/index.js.map +1 -1
  15. package/build/modules/android.d.ts.map +1 -1
  16. package/build/modules/android.js +2 -1
  17. package/build/modules/android.js.map +1 -1
  18. package/build/modules/ios.d.ts +16 -1
  19. package/build/modules/ios.d.ts.map +1 -1
  20. package/build/modules/ios.js +29 -16
  21. package/build/modules/ios.js.map +1 -1
  22. package/build/types.d.ts +8 -6
  23. package/build/types.d.ts.map +1 -1
  24. package/build/types.js.map +1 -1
  25. package/build/useIAP.d.ts +1 -1
  26. package/build/useIAP.d.ts.map +1 -1
  27. package/build/useIAP.js +12 -15
  28. package/build/useIAP.js.map +1 -1
  29. package/build/utils/constants.d.ts +2 -0
  30. package/build/utils/constants.d.ts.map +1 -1
  31. package/build/utils/constants.js +9 -2
  32. package/build/utils/constants.js.map +1 -1
  33. package/build/utils/errorMapping.d.ts +32 -23
  34. package/build/utils/errorMapping.d.ts.map +1 -1
  35. package/build/utils/errorMapping.js +117 -22
  36. package/build/utils/errorMapping.js.map +1 -1
  37. package/coverage/clover.xml +497 -0
  38. package/coverage/coverage-final.json +6 -0
  39. package/coverage/lcov-report/base.css +224 -0
  40. package/coverage/lcov-report/block-navigation.js +87 -0
  41. package/coverage/lcov-report/favicon.png +0 -0
  42. package/coverage/lcov-report/index.html +161 -0
  43. package/coverage/lcov-report/prettify.css +1 -0
  44. package/coverage/lcov-report/prettify.js +2 -0
  45. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  46. package/coverage/lcov-report/sorter.js +196 -0
  47. package/coverage/lcov-report/src/ExpoIap.types.ts.html +1243 -0
  48. package/coverage/lcov-report/src/PurchaseError.ts.html +787 -0
  49. package/coverage/lcov-report/src/helpers/index.html +116 -0
  50. package/coverage/lcov-report/src/helpers/subscription.ts.html +496 -0
  51. package/coverage/lcov-report/src/index.html +116 -0
  52. package/coverage/lcov-report/src/index.ts.html +1993 -0
  53. package/coverage/lcov-report/src/modules/android.ts.html +550 -0
  54. package/coverage/lcov-report/src/modules/index.html +131 -0
  55. package/coverage/lcov-report/src/modules/ios.ts.html +1222 -0
  56. package/coverage/lcov-report/src/purchase-error.ts.html +880 -0
  57. package/coverage/lcov-report/src/types/ExpoIapAndroid.types.ts.html +493 -0
  58. package/coverage/lcov-report/src/types/index.html +116 -0
  59. package/coverage/lcov-report/src/useIap.ts.html +1483 -0
  60. package/coverage/lcov-report/src/utils/errorMapping.ts.html +1069 -0
  61. package/coverage/lcov-report/src/utils/index.html +116 -0
  62. package/coverage/lcov-report/src/utils/purchase.ts.html +241 -0
  63. package/coverage/lcov.info +929 -0
  64. package/expo-module.config.json +10 -3
  65. package/ios/ExpoIap.podspec +3 -2
  66. package/ios/ExpoIapHelper.swift +96 -0
  67. package/ios/ExpoIapLog.swift +127 -0
  68. package/ios/ExpoIapModule.swift +218 -340
  69. package/ios/OneSideModule.swift +489 -0
  70. package/ios/expoiap.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  71. package/ios/expoiap.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  72. package/openiap-versions.json +5 -0
  73. package/package.json +4 -3
  74. package/plugin/build/withIAP.d.ts +22 -9
  75. package/plugin/build/withIAP.js +163 -13
  76. package/plugin/jest.config.js +13 -3
  77. package/plugin/src/expoConfig.augmentation.d.ts +38 -0
  78. package/plugin/src/withIAP.ts +272 -22
  79. package/plugin/tsconfig.json +2 -1
  80. package/plugin/tsconfig.tsbuildinfo +1 -1
  81. package/scripts/update-types.mjs +20 -1
  82. package/src/ExpoIapModule.ts +45 -4
  83. package/src/index.ts +122 -165
  84. package/src/modules/android.ts +2 -1
  85. package/src/modules/ios.ts +31 -19
  86. package/src/types.ts +8 -6
  87. package/src/useIAP.ts +17 -25
  88. package/src/utils/constants.ts +11 -2
  89. package/src/utils/errorMapping.ts +203 -23
  90. package/build/purchase-error.d.ts +0 -67
  91. package/build/purchase-error.d.ts.map +0 -1
  92. package/build/purchase-error.js +0 -166
  93. package/build/purchase-error.js.map +0 -1
  94. package/build/utils/purchase.d.ts +0 -9
  95. package/build/utils/purchase.d.ts.map +0 -1
  96. package/build/utils/purchase.js +0 -34
  97. package/build/utils/purchase.js.map +0 -1
  98. package/src/purchase-error.ts +0 -265
  99. package/src/utils/purchase.ts +0 -52
@@ -1,8 +1,33 @@
1
- import { requireNativeModule } from 'expo-modules-core';
2
- // It loads the native module object from the JSI or falls back to
3
- // the bridge module (from NativeModulesProxy) if the remote debugger is on.
4
- const ExpoIapModule = requireNativeModule('ExpoIap');
1
+ import { requireNativeModule, UnavailabilityError } from 'expo-modules-core';
2
+ const { module: ExpoIapModule, name: resolvedNativeModuleName } = resolveNativeModule();
3
+ export const USING_ONSIDE_SDK = resolvedNativeModuleName === 'ExpoIapOnside';
5
4
  // Platform-specific error codes from native modules
6
5
  export const NATIVE_ERROR_CODES = ExpoIapModule.ERROR_CODES || {};
7
6
  export default ExpoIapModule;
7
+ function resolveNativeModule() {
8
+ const candidates = ['ExpoIapOnside', 'ExpoIap'];
9
+ for (const name of candidates) {
10
+ try {
11
+ const module = requireNativeModule(name);
12
+ return { module, name };
13
+ }
14
+ catch (error) {
15
+ if (name === 'ExpoIapOnside' && isMissingModuleError(error, name)) {
16
+ // Onside module is optional. If unavailable, fall back to ExpoIap.
17
+ continue;
18
+ }
19
+ throw error;
20
+ }
21
+ }
22
+ throw new UnavailabilityError('expo-iap', 'ExpoIap native module is unavailable');
23
+ }
24
+ function isMissingModuleError(error, moduleName) {
25
+ if (error instanceof UnavailabilityError) {
26
+ return true;
27
+ }
28
+ if (error instanceof Error) {
29
+ return error.message.includes(`Cannot find native module '${moduleName}'`);
30
+ }
31
+ return false;
32
+ }
8
33
  //# sourceMappingURL=ExpoIapModule.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoIapModule.js","sourceRoot":"","sources":["../src/ExpoIapModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AAEtD,kEAAkE;AAClE,4EAA4E;AAC5E,MAAM,aAAa,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;AAErD,oDAAoD;AACpD,MAAM,CAAC,MAAM,kBAAkB,GAAG,aAAa,CAAC,WAAW,IAAI,EAAE,CAAC;AAElE,eAAe,aAAa,CAAC","sourcesContent":["import {requireNativeModule} from 'expo-modules-core';\n\n// It loads the native module object from the JSI or falls back to\n// the bridge module (from NativeModulesProxy) if the remote debugger is on.\nconst ExpoIapModule = requireNativeModule('ExpoIap');\n\n// Platform-specific error codes from native modules\nexport const NATIVE_ERROR_CODES = ExpoIapModule.ERROR_CODES || {};\n\nexport default ExpoIapModule;\n"]}
1
+ {"version":3,"file":"ExpoIapModule.js","sourceRoot":"","sources":["../src/ExpoIapModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,mBAAmB,EAAE,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AAI3E,MAAM,EAAC,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,wBAAwB,EAAC,GAC3D,mBAAmB,EAAE,CAAC;AAExB,MAAM,CAAC,MAAM,gBAAgB,GAAG,wBAAwB,KAAK,eAAe,CAAC;AAE7E,oDAAoD;AACpD,MAAM,CAAC,MAAM,kBAAkB,GAAG,aAAa,CAAC,WAAW,IAAI,EAAE,CAAC;AAElE,eAAe,aAAa,CAAC;AAE7B,SAAS,mBAAmB;IAI1B,MAAM,UAAU,GAA0B,CAAC,eAAe,EAAE,SAAS,CAAC,CAAC;IAEvE,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;YACzC,OAAO,EAAC,MAAM,EAAE,IAAI,EAAC,CAAC;QACxB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,IAAI,KAAK,eAAe,IAAI,oBAAoB,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC;gBAClE,mEAAmE;gBACnE,SAAS;YACX,CAAC;YAED,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED,MAAM,IAAI,mBAAmB,CAC3B,UAAU,EACV,sCAAsC,CACvC,CAAC;AACJ,CAAC;AAED,SAAS,oBAAoB,CAAC,KAAc,EAAE,UAAkB;IAC9D,IAAI,KAAK,YAAY,mBAAmB,EAAE,CAAC;QACzC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,8BAA8B,UAAU,GAAG,CAAC,CAAC;IAC7E,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["import {requireNativeModule, UnavailabilityError} from 'expo-modules-core';\n\ntype NativeIapModuleName = 'ExpoIapOnside' | 'ExpoIap';\n\nconst {module: ExpoIapModule, name: resolvedNativeModuleName} =\n resolveNativeModule();\n\nexport const USING_ONSIDE_SDK = resolvedNativeModuleName === 'ExpoIapOnside';\n\n// Platform-specific error codes from native modules\nexport const NATIVE_ERROR_CODES = ExpoIapModule.ERROR_CODES || {};\n\nexport default ExpoIapModule;\n\nfunction resolveNativeModule(): {\n module: any;\n name: NativeIapModuleName;\n} {\n const candidates: NativeIapModuleName[] = ['ExpoIapOnside', 'ExpoIap'];\n\n for (const name of candidates) {\n try {\n const module = requireNativeModule(name);\n return {module, name};\n } catch (error) {\n if (name === 'ExpoIapOnside' && isMissingModuleError(error, name)) {\n // Onside module is optional. If unavailable, fall back to ExpoIap.\n continue;\n }\n\n throw error;\n }\n }\n\n throw new UnavailabilityError(\n 'expo-iap',\n 'ExpoIap native module is unavailable',\n );\n}\n\nfunction isMissingModuleError(error: unknown, moduleName: string): boolean {\n if (error instanceof UnavailabilityError) {\n return true;\n }\n\n if (error instanceof Error) {\n return error.message.includes(`Cannot find native module '${moduleName}'`);\n }\n\n return false;\n}\n"]}
package/build/index.d.ts CHANGED
@@ -1,17 +1,14 @@
1
1
  import type { MutationField, Product, ProductQueryType, Purchase, QueryField } from './types';
2
- import { PurchaseError } from './purchase-error';
2
+ import { type PurchaseError } from './utils/errorMapping';
3
3
  export * from './types';
4
- export { ErrorCodeUtils, ErrorCodeMapping } from './purchase-error';
5
4
  export * from './modules/android';
6
5
  export * from './modules/ios';
7
6
  export { getActiveSubscriptions, hasActiveSubscriptions, } from './helpers/subscription';
8
- export declare const PI: any;
9
7
  export declare enum OpenIapEvent {
10
8
  PurchaseUpdated = "purchase-updated",
11
9
  PurchaseError = "purchase-error",
12
10
  PromotedProductIOS = "promoted-product-ios"
13
11
  }
14
- export declare function setValueAsync(value: string): any;
15
12
  type ExpoIapEventPayloads = {
16
13
  [OpenIapEvent.PurchaseUpdated]: Purchase;
17
14
  [OpenIapEvent.PurchaseError]: PurchaseError;
@@ -69,20 +66,7 @@ export declare const endConnection: MutationField<'endConnection'>;
69
66
  */
70
67
  export declare const fetchProducts: QueryField<'fetchProducts'>;
71
68
  export declare const getAvailablePurchases: QueryField<'getAvailablePurchases'>;
72
- /**
73
- * Restore completed transactions (cross-platform behavior)
74
- *
75
- * - iOS: perform a lightweight sync to refresh transactions and ignore sync errors,
76
- * then fetch available purchases to surface restored items to the app.
77
- * - Android: simply fetch available purchases (restoration happens via query).
78
- *
79
- * This helper returns the restored/available purchases so callers can update UI/state.
80
- *
81
- * @param options.alsoPublishToEventListenerIOS - iOS only: whether to also publish to the event listener
82
- * @param options.onlyIncludeActiveItemsIOS - iOS only: whether to only include active items
83
- * @returns Promise resolving to the list of available/restored purchases
84
- */
85
- export declare const restorePurchases: MutationField<'restorePurchases'>;
69
+ export declare const getStorefront: QueryField<'getStorefrontIOS'>;
86
70
  /**
87
71
  * Request a purchase for products or subscriptions.
88
72
  *
@@ -117,37 +101,16 @@ export declare const restorePurchases: MutationField<'restorePurchases'>;
117
101
  export declare const requestPurchase: MutationField<'requestPurchase'>;
118
102
  export declare const finishTransaction: MutationField<'finishTransaction'>;
119
103
  /**
120
- * Retrieves the current storefront information from iOS App Store
121
- *
122
- * @returns Promise resolving to the storefront country code
123
- * @throws Error if called on non-iOS platform
124
- *
125
- * @example
126
- * ```typescript
127
- * const storefront = await getStorefrontIOS();
128
- * console.log(storefront); // 'US'
129
- * ```
130
- *
131
- * @platform iOS
132
- */
133
- export declare const getStorefrontIOS: () => Promise<string>;
134
- /**
135
- * Gets the storefront country code from the underlying native store.
136
- * Returns a two-letter country code such as 'US', 'KR', or empty string on failure.
104
+ * Restore completed transactions (cross-platform behavior)
137
105
  *
138
- * @platform ios
139
- * @platform android
140
- */
141
- export declare const getStorefront: () => Promise<string>;
142
- /**
143
- * Internal receipt validation function (NOT RECOMMENDED for production use)
106
+ * - iOS: perform a lightweight sync to refresh transactions and ignore sync errors,
107
+ * then fetch available purchases to surface restored items to the app.
108
+ * - Android: simply fetch available purchases (restoration happens via query).
144
109
  *
145
- * WARNING: This function performs client-side validation which is NOT secure.
146
- * For production apps, always validate receipts on your secure server:
147
- * - iOS: Send receipt data to Apple's verification endpoint from your server
148
- * - Android: Use Google Play Developer API with service account credentials
110
+ * This helper triggers the refresh flows but does not return the purchases; consumers should
111
+ * call `getAvailablePurchases` or rely on hook state to inspect the latest items.
149
112
  */
150
- export declare const validateReceipt: MutationField<'validateReceipt'>;
113
+ export declare const restorePurchases: MutationField<'restorePurchases'>;
151
114
  /**
152
115
  * Deeplinks to native interface that allows users to manage their subscriptions
153
116
  * @param options.skuAndroid - Required for Android to locate specific subscription (ignored on iOS)
@@ -167,6 +130,16 @@ export declare const validateReceipt: MutationField<'validateReceipt'>;
167
130
  * });
168
131
  */
169
132
  export declare const deepLinkToSubscriptions: MutationField<'deepLinkToSubscriptions'>;
133
+ /**
134
+ * Internal receipt validation function (NOT RECOMMENDED for production use)
135
+ *
136
+ * WARNING: This function performs client-side validation which is NOT secure.
137
+ * For production apps, always validate receipts on your secure server:
138
+ * - iOS: Send receipt data to Apple's verification endpoint from your server
139
+ * - Android: Use Google Play Developer API with service account credentials
140
+ */
141
+ export declare const validateReceipt: MutationField<'validateReceipt'>;
170
142
  export * from './useIAP';
171
- export * from './utils/errorMapping';
143
+ export { ErrorCodeUtils, ErrorCodeMapping, createPurchaseError, createPurchaseErrorFromPlatform, } from './utils/errorMapping';
144
+ export type { PurchaseError as ExpoPurchaseError, PurchaseErrorProps, } from './utils/errorMapping';
172
145
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/build/index.js CHANGED
@@ -3,29 +3,23 @@ import { NativeModulesProxy } from 'expo-modules-core';
3
3
  import { Platform } from 'react-native';
4
4
  // Internal modules
5
5
  import ExpoIapModule from './ExpoIapModule';
6
- import { isProductIOS, validateReceiptIOS, deepLinkToSubscriptionsIOS, syncIOS, } from './modules/ios';
6
+ import { isProductIOS, validateReceiptIOS, deepLinkToSubscriptionsIOS, syncIOS, getStorefrontIOS, } from './modules/ios';
7
7
  import { isProductAndroid, validateReceiptAndroid, deepLinkToSubscriptionsAndroid, } from './modules/android';
8
8
  import { ErrorCode } from './types';
9
- import { PurchaseError } from './purchase-error';
10
- import { normalizePurchaseId, normalizePurchaseList } from './utils/purchase';
9
+ import { createPurchaseError } from './utils/errorMapping';
11
10
  // Export all types
12
11
  export * from './types';
13
- export { ErrorCodeUtils, ErrorCodeMapping } from './purchase-error';
14
12
  export * from './modules/android';
15
13
  export * from './modules/ios';
16
14
  // Export subscription helpers
17
15
  export { getActiveSubscriptions, hasActiveSubscriptions, } from './helpers/subscription';
18
16
  // Get the native constant value
19
- export const PI = ExpoIapModule.PI;
20
17
  export var OpenIapEvent;
21
18
  (function (OpenIapEvent) {
22
19
  OpenIapEvent["PurchaseUpdated"] = "purchase-updated";
23
20
  OpenIapEvent["PurchaseError"] = "purchase-error";
24
21
  OpenIapEvent["PromotedProductIOS"] = "promoted-product-ios";
25
22
  })(OpenIapEvent || (OpenIapEvent = {}));
26
- export function setValueAsync(value) {
27
- return ExpoIapModule.setValueAsync(value);
28
- }
29
23
  // Ensure the emitter has proper EventEmitter interface
30
24
  export const emitter = (ExpoIapModule ||
31
25
  NativeModulesProxy.ExpoIap);
@@ -36,7 +30,7 @@ const normalizeProductType = (type) => {
36
30
  if (!type || type === 'inapp' || type === 'in-app') {
37
31
  return {
38
32
  canonical: 'in-app',
39
- native: 'inapp',
33
+ native: 'in-app',
40
34
  };
41
35
  }
42
36
  if (type === 'subs') {
@@ -53,25 +47,31 @@ const normalizeProductType = (type) => {
53
47
  }
54
48
  throw new Error(`Unsupported product type: ${type}`);
55
49
  };
50
+ const normalizePurchasePlatform = (purchase) => {
51
+ const platform = purchase.platform;
52
+ if (typeof platform !== 'string') {
53
+ return purchase;
54
+ }
55
+ const lowered = platform.toLowerCase();
56
+ if (lowered === platform || (lowered !== 'ios' && lowered !== 'android')) {
57
+ return purchase;
58
+ }
59
+ return { ...purchase, platform: lowered };
60
+ };
61
+ const normalizePurchaseArray = (purchases) => purchases.map((purchase) => normalizePurchasePlatform(purchase));
56
62
  export const purchaseUpdatedListener = (listener) => {
57
- console.log('[JS] Registering purchaseUpdatedListener');
58
63
  const wrappedListener = (event) => {
59
- const normalized = normalizePurchaseId(event);
60
- console.log('[JS] purchaseUpdatedListener fired:', normalized);
64
+ const normalized = normalizePurchasePlatform(event);
61
65
  listener(normalized);
62
66
  };
63
67
  const emitterSubscription = emitter.addListener(OpenIapEvent.PurchaseUpdated, wrappedListener);
64
- console.log('[JS] purchaseUpdatedListener registered successfully');
65
68
  return emitterSubscription;
66
69
  };
67
70
  export const purchaseErrorListener = (listener) => {
68
- console.log('[JS] Registering purchaseErrorListener');
69
71
  const wrappedListener = (error) => {
70
- console.log('[JS] purchaseErrorListener fired:', error);
71
72
  listener(error);
72
73
  };
73
74
  const emitterSubscription = emitter.addListener(OpenIapEvent.PurchaseError, wrappedListener);
74
- console.log('[JS] purchaseErrorListener registered successfully');
75
75
  return emitterSubscription;
76
76
  };
77
77
  /**
@@ -111,9 +111,10 @@ export const endConnection = async () => ExpoIapModule.endConnection();
111
111
  * @param request.type - Product query type: 'in-app', 'subs', or 'all'
112
112
  */
113
113
  export const fetchProducts = async (request) => {
114
+ console.log('fetchProducts called with:', request);
114
115
  const { skus, type } = request ?? {};
115
116
  if (!Array.isArray(skus) || skus.length === 0) {
116
- throw new PurchaseError({
117
+ throw createPurchaseError({
117
118
  message: 'No SKUs provided',
118
119
  code: ErrorCode.EmptySkuList,
119
120
  });
@@ -163,40 +164,17 @@ export const getAvailablePurchases = async (options) => {
163
164
  android: () => ExpoIapModule.getAvailableItems(),
164
165
  }) ?? (() => Promise.resolve([]));
165
166
  const purchases = await resolvePurchases();
166
- return normalizePurchaseList(purchases);
167
+ return normalizePurchaseArray(purchases);
167
168
  };
168
- /**
169
- * Restore completed transactions (cross-platform behavior)
170
- *
171
- * - iOS: perform a lightweight sync to refresh transactions and ignore sync errors,
172
- * then fetch available purchases to surface restored items to the app.
173
- * - Android: simply fetch available purchases (restoration happens via query).
174
- *
175
- * This helper returns the restored/available purchases so callers can update UI/state.
176
- *
177
- * @param options.alsoPublishToEventListenerIOS - iOS only: whether to also publish to the event listener
178
- * @param options.onlyIncludeActiveItemsIOS - iOS only: whether to only include active items
179
- * @returns Promise resolving to the list of available/restored purchases
180
- */
181
- export const restorePurchases = async () => {
182
- if (Platform.OS === 'ios') {
183
- await syncIOS().catch(() => undefined);
169
+ export const getStorefront = async () => {
170
+ // Cross-platform storefront
171
+ if (Platform.OS === 'android') {
172
+ if (typeof ExpoIapModule.getStorefrontAndroid === 'function') {
173
+ return ExpoIapModule.getStorefrontAndroid();
174
+ }
175
+ return '';
184
176
  }
185
- await getAvailablePurchases({
186
- alsoPublishToEventListenerIOS: false,
187
- onlyIncludeActiveItemsIOS: true,
188
- });
189
- };
190
- const offerToRecordIOS = (offer) => {
191
- if (!offer)
192
- return undefined;
193
- return {
194
- identifier: offer.identifier,
195
- keyIdentifier: offer.keyIdentifier,
196
- nonce: offer.nonce,
197
- signature: offer.signature,
198
- timestamp: offer.timestamp.toString(),
199
- };
177
+ return getStorefrontIOS();
200
178
  };
201
179
  function normalizeRequestProps(request, platform) {
202
180
  // Platform-specific format - directly return the appropriate platform data
@@ -242,16 +220,30 @@ export const requestPurchase = async (args) => {
242
220
  if (!normalizedRequest?.sku) {
243
221
  throw new Error('Invalid request for iOS. The `sku` property is required and must be a string.');
244
222
  }
245
- const { sku, andDangerouslyFinishTransactionAutomatically = false, appAccountToken, quantity, withOffer, } = normalizedRequest;
246
- const offer = offerToRecordIOS(withOffer ?? undefined);
247
- const purchase = await ExpoIapModule.requestPurchase({
248
- sku,
249
- andDangerouslyFinishTransactionAutomatically,
250
- appAccountToken,
251
- quantity,
252
- withOffer: offer,
253
- });
254
- return normalizePurchaseId(purchase);
223
+ let payload;
224
+ if (canonical === 'in-app') {
225
+ payload = {
226
+ type: 'in-app',
227
+ request: request,
228
+ };
229
+ }
230
+ else if (canonical === 'subs') {
231
+ payload = {
232
+ type: 'subs',
233
+ request: request,
234
+ };
235
+ }
236
+ else {
237
+ throw new Error(`Unsupported product type: ${canonical}`);
238
+ }
239
+ const purchase = (await ExpoIapModule.requestPurchase(payload));
240
+ if (Array.isArray(purchase)) {
241
+ return normalizePurchaseArray(purchase);
242
+ }
243
+ if (purchase) {
244
+ return normalizePurchasePlatform(purchase);
245
+ }
246
+ return canonical === 'subs' ? [] : null;
255
247
  }
256
248
  if (Platform.OS === 'android') {
257
249
  if (isInAppPurchase) {
@@ -270,7 +262,7 @@ export const requestPurchase = async (args) => {
270
262
  offerTokenArr: [],
271
263
  isOfferPersonalized: isOfferPersonalized ?? false,
272
264
  }));
273
- return normalizePurchaseList(result);
265
+ return normalizePurchaseArray(result);
274
266
  }
275
267
  if (canonical === 'subs') {
276
268
  const normalizedRequest = normalizeRequestProps(request, 'android');
@@ -292,40 +284,24 @@ export const requestPurchase = async (args) => {
292
284
  subscriptionOffers: normalizedOffers,
293
285
  isOfferPersonalized: isOfferPersonalized ?? false,
294
286
  }));
295
- return normalizePurchaseList(result);
287
+ return normalizePurchaseArray(result);
296
288
  }
297
289
  throw new Error("Invalid request for Android: Expected a valid request object with 'skus' array.");
298
290
  }
299
291
  throw new Error('Platform not supported');
300
292
  };
301
- const toPurchaseInput = (purchase) => ({
302
- id: purchase.id,
303
- ids: purchase.ids ?? undefined,
304
- isAutoRenewing: purchase.isAutoRenewing,
305
- platform: purchase.platform,
306
- productId: purchase.productId,
307
- purchaseState: purchase.purchaseState,
308
- purchaseToken: purchase.purchaseToken ?? null,
309
- quantity: purchase.quantity,
310
- transactionDate: purchase.transactionDate,
311
- });
312
293
  export const finishTransaction = async ({ purchase, isConsumable = false, }) => {
313
- const normalizedPurchase = toPurchaseInput(purchase);
314
294
  if (Platform.OS === 'ios') {
315
- const transactionId = normalizedPurchase.id;
316
- if (!transactionId) {
317
- throw new Error('purchase.id required to finish iOS transaction');
318
- }
319
- await ExpoIapModule.finishTransaction(transactionId);
295
+ await ExpoIapModule.finishTransaction(purchase, isConsumable);
320
296
  return;
321
297
  }
322
298
  if (Platform.OS === 'android') {
323
- const token = normalizedPurchase.purchaseToken ?? undefined;
299
+ const token = purchase.purchaseToken ?? undefined;
324
300
  if (!token) {
325
- throw new PurchaseError({
301
+ throw createPurchaseError({
326
302
  message: 'Purchase token is required to finish transaction',
327
303
  code: ErrorCode.DeveloperError,
328
- productId: normalizedPurchase.productId,
304
+ productId: purchase.productId,
329
305
  platform: 'android',
330
306
  });
331
307
  }
@@ -339,42 +315,52 @@ export const finishTransaction = async ({ purchase, isConsumable = false, }) =>
339
315
  throw new Error('Unsupported Platform');
340
316
  };
341
317
  /**
342
- * Retrieves the current storefront information from iOS App Store
343
- *
344
- * @returns Promise resolving to the storefront country code
345
- * @throws Error if called on non-iOS platform
318
+ * Restore completed transactions (cross-platform behavior)
346
319
  *
347
- * @example
348
- * ```typescript
349
- * const storefront = await getStorefrontIOS();
350
- * console.log(storefront); // 'US'
351
- * ```
320
+ * - iOS: perform a lightweight sync to refresh transactions and ignore sync errors,
321
+ * then fetch available purchases to surface restored items to the app.
322
+ * - Android: simply fetch available purchases (restoration happens via query).
352
323
  *
353
- * @platform iOS
324
+ * This helper triggers the refresh flows but does not return the purchases; consumers should
325
+ * call `getAvailablePurchases` or rely on hook state to inspect the latest items.
354
326
  */
355
- export const getStorefrontIOS = () => {
356
- if (Platform.OS !== 'ios') {
357
- console.warn('getStorefrontIOS: This method is only available on iOS');
358
- return Promise.resolve('');
327
+ export const restorePurchases = async () => {
328
+ if (Platform.OS === 'ios') {
329
+ await syncIOS().catch(() => undefined);
359
330
  }
360
- return ExpoIapModule.getStorefrontIOS();
331
+ await getAvailablePurchases({
332
+ alsoPublishToEventListenerIOS: false,
333
+ onlyIncludeActiveItemsIOS: true,
334
+ });
361
335
  };
362
336
  /**
363
- * Gets the storefront country code from the underlying native store.
364
- * Returns a two-letter country code such as 'US', 'KR', or empty string on failure.
337
+ * Deeplinks to native interface that allows users to manage their subscriptions
338
+ * @param options.skuAndroid - Required for Android to locate specific subscription (ignored on iOS)
339
+ * @param options.packageNameAndroid - Required for Android to identify your app (ignored on iOS)
340
+ *
341
+ * @returns Promise that resolves when the deep link is successfully opened
342
+ *
343
+ * @throws {Error} When called on unsupported platform or when required Android parameters are missing
344
+ *
345
+ * @example
346
+ * import { deepLinkToSubscriptions } from 'expo-iap';
365
347
  *
366
- * @platform ios
367
- * @platform android
348
+ * // Works on both iOS and Android
349
+ * await deepLinkToSubscriptions({
350
+ * skuAndroid: 'your_subscription_sku',
351
+ * packageNameAndroid: 'com.example.app'
352
+ * });
368
353
  */
369
- export const getStorefront = () => {
370
- // Cross-platform storefront
354
+ export const deepLinkToSubscriptions = async (options) => {
355
+ if (Platform.OS === 'ios') {
356
+ await deepLinkToSubscriptionsIOS();
357
+ return;
358
+ }
371
359
  if (Platform.OS === 'android') {
372
- if (typeof ExpoIapModule.getStorefrontAndroid === 'function') {
373
- return ExpoIapModule.getStorefrontAndroid();
374
- }
375
- return Promise.resolve('');
360
+ await deepLinkToSubscriptionsAndroid(options ?? null);
361
+ return;
376
362
  }
377
- return getStorefrontIOS();
363
+ throw new Error(`Unsupported platform: ${Platform.OS}`);
378
364
  };
379
365
  /**
380
366
  * Internal receipt validation function (NOT RECOMMENDED for production use)
@@ -406,35 +392,6 @@ export const validateReceipt = async (options) => {
406
392
  }
407
393
  throw new Error('Platform not supported');
408
394
  };
409
- /**
410
- * Deeplinks to native interface that allows users to manage their subscriptions
411
- * @param options.skuAndroid - Required for Android to locate specific subscription (ignored on iOS)
412
- * @param options.packageNameAndroid - Required for Android to identify your app (ignored on iOS)
413
- *
414
- * @returns Promise that resolves when the deep link is successfully opened
415
- *
416
- * @throws {Error} When called on unsupported platform or when required Android parameters are missing
417
- *
418
- * @example
419
- * import { deepLinkToSubscriptions } from 'expo-iap';
420
- *
421
- * // Works on both iOS and Android
422
- * await deepLinkToSubscriptions({
423
- * skuAndroid: 'your_subscription_sku',
424
- * packageNameAndroid: 'com.example.app'
425
- * });
426
- */
427
- export const deepLinkToSubscriptions = async (options) => {
428
- if (Platform.OS === 'ios') {
429
- await deepLinkToSubscriptionsIOS();
430
- return;
431
- }
432
- if (Platform.OS === 'android') {
433
- await deepLinkToSubscriptionsAndroid(options ?? null);
434
- return;
435
- }
436
- throw new Error(`Unsupported platform: ${Platform.OS}`);
437
- };
438
395
  export * from './useIAP';
439
- export * from './utils/errorMapping';
396
+ export { ErrorCodeUtils, ErrorCodeMapping, createPurchaseError, createPurchaseErrorFromPlatform, } from './utils/errorMapping';
440
397
  //# sourceMappingURL=index.js.map