expo-iap 3.0.7 → 3.0.8

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.
package/CLAUDE.md CHANGED
@@ -8,6 +8,11 @@
8
8
  - Subject must be imperative, lowercase, without a trailing period, and roughly 50 characters
9
9
  - Wrap commit body lines near 72 characters and include footers such as `BREAKING CHANGE:` or `Closes #123` when needed
10
10
 
11
+ ## Tooling & Package Management
12
+
13
+ - **Use Bun exclusively.** Run installs with `bun install`, scripts with `bun run <script>`, add deps via `bun add` / `bun add -d`.
14
+ - Do **not** suggest or create `package-lock.json` or `yarn.lock`; `bun.lock` is the single source of truth.
15
+
11
16
  ## Expo-Specific Guidelines
12
17
 
13
18
  ### iOS Pod Configuration
@@ -38,6 +43,11 @@ Before committing any changes:
38
43
 
39
44
  ### Platform-Specific Naming Conventions
40
45
 
46
+ #### Function Naming
47
+
48
+ - Functions that only operate on one platform must carry the suffix: `nameIOS` or `nameAndroid` (e.g. `getStorefrontIOS`, `deepLinkToSubscriptionsAndroid`).
49
+ - Cross-platform helpers should expose a single name and branch internally via `Platform.select` or equivalent.
50
+
41
51
  #### Field Naming
42
52
 
43
53
  - **iOS-related fields**: Use `IOS` suffix (e.g., `displayNameIOS`, `discountsIOS`, `introductoryPriceIOS`)
@@ -72,6 +82,8 @@ The library follows the OpenIAP type specifications with platform-specific exten
72
82
 
73
83
  > **Important:** `src/types.ts` is generated from the OpenIAP schema. Never edit this file manually or commit hand-written changes. After updating any `*.graphql` schema, run `bun run generate:types` (or the equivalent script in your package manager) to refresh the file.
74
84
 
85
+ - Whenever you need Request/Params/Result types in the JS API surface (`src/index.ts`, hooks, modules, examples), import them directly from the generated `src/types.ts` (e.g., `MutationRequestPurchaseArgs`, `QueryFetchProductsArgs`). Bind exported functions with the generated `QueryField` / `MutationField` helpers so their signatures stay in lockstep with `types.ts` instead of redefining ad-hoc unions like `ProductTypeInput`.
86
+
75
87
  ### React/JSX Conventions
76
88
 
77
89
  - **Conditional Rendering**: Use ternary operator with null instead of logical AND
@@ -58,6 +58,6 @@ dependencies {
58
58
  implementation project(":openiap-google")
59
59
  } else {
60
60
  // Fallback to published artifact when local project isn't linked
61
- implementation "io.github.hyochan.openiap:openiap-google:1.1.11"
61
+ implementation "io.github.hyochan.openiap:openiap-google:1.1.12"
62
62
  }
63
63
  }
package/build/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Product, Purchase, RequestPurchaseProps, RequestPurchasePropsByPlatforms, RequestSubscriptionPropsByPlatforms, ProductSubscription, VoidResult, ReceiptValidationResult } from './types';
1
+ import type { MutationField, Product, ProductQueryType, Purchase, QueryField } from './types';
2
2
  import { PurchaseError } from './purchase-error';
3
3
  export * from './types';
4
4
  export { ErrorCodeUtils, ErrorCodeMapping } from './purchase-error';
@@ -28,24 +28,7 @@ export declare const emitter: ExpoIapEmitter;
28
28
  /**
29
29
  * TODO(v3.1.0): Remove legacy 'inapp' alias once downstream apps migrate to 'in-app'.
30
30
  */
31
- export type ProductTypeInput = 'inapp' | 'in-app' | 'subs';
32
- export type InAppTypeInput = Exclude<ProductTypeInput, 'subs'>;
33
- type PurchaseRequestInApp = {
34
- request: RequestPurchasePropsByPlatforms;
35
- type?: InAppTypeInput;
36
- };
37
- type PurchaseRequestSubscription = {
38
- request: RequestSubscriptionPropsByPlatforms;
39
- type: 'subs';
40
- };
41
- export type PurchaseRequestInput = PurchaseRequestInApp | PurchaseRequestSubscription;
42
- export type PurchaseRequest = {
43
- request: RequestPurchaseProps;
44
- type?: InAppTypeInput;
45
- } | {
46
- request: RequestSubscriptionPropsByPlatforms;
47
- type: 'subs';
48
- };
31
+ export type ProductTypeInput = ProductQueryType | 'inapp';
49
32
  export declare const purchaseUpdatedListener: (listener: (event: Purchase) => void) => {
50
33
  remove: () => void;
51
34
  };
@@ -75,38 +58,17 @@ export declare const purchaseErrorListener: (listener: (error: PurchaseError) =>
75
58
  export declare const promotedProductListenerIOS: (listener: (product: Product) => void) => {
76
59
  remove: () => void;
77
60
  };
78
- export declare function initConnection(): Promise<boolean>;
79
- export declare function endConnection(): Promise<boolean>;
61
+ export declare const initConnection: MutationField<'initConnection'>;
62
+ export declare const endConnection: MutationField<'endConnection'>;
80
63
  /**
81
64
  * Fetch products with unified API (v2.7.0+)
82
65
  *
83
- * @param params - Product fetch configuration
84
- * @param params.skus - Array of product SKUs to fetch
85
- * @param params.type - Type of products: 'in-app' for regular products (default) or 'subs' for subscriptions
86
- *
87
- * @example
88
- * ```typescript
89
- * // Regular products
90
- * const products = await fetchProducts({
91
- * skus: ['product1', 'product2'],
92
- * type: 'in-app'
93
- * });
94
- *
95
- * // Subscriptions
96
- * const subscriptions = await fetchProducts({
97
- * skus: ['sub1', 'sub2'],
98
- * type: 'subs'
99
- * });
100
- * ```
66
+ * @param request - Product fetch configuration
67
+ * @param request.skus - Array of product SKUs to fetch
68
+ * @param request.type - Product query type: 'in-app', 'subs', or 'all'
101
69
  */
102
- export declare const fetchProducts: ({ skus, type, }: {
103
- skus: string[];
104
- type?: ProductTypeInput;
105
- }) => Promise<Product[] | ProductSubscription[]>;
106
- export declare const getAvailablePurchases: ({ alsoPublishToEventListenerIOS, onlyIncludeActiveItemsIOS, }?: {
107
- alsoPublishToEventListenerIOS?: boolean;
108
- onlyIncludeActiveItemsIOS?: boolean;
109
- }) => Promise<Purchase[]>;
70
+ export declare const fetchProducts: QueryField<'fetchProducts'>;
71
+ export declare const getAvailablePurchases: QueryField<'getAvailablePurchases'>;
110
72
  /**
111
73
  * Restore completed transactions (cross-platform behavior)
112
74
  *
@@ -120,10 +82,7 @@ export declare const getAvailablePurchases: ({ alsoPublishToEventListenerIOS, on
120
82
  * @param options.onlyIncludeActiveItemsIOS - iOS only: whether to only include active items
121
83
  * @returns Promise resolving to the list of available/restored purchases
122
84
  */
123
- export declare const restorePurchases: (options?: {
124
- alsoPublishToEventListenerIOS?: boolean;
125
- onlyIncludeActiveItemsIOS?: boolean;
126
- }) => Promise<Purchase[]>;
85
+ export declare const restorePurchases: MutationField<'restorePurchases'>;
127
86
  /**
128
87
  * Request a purchase for products or subscriptions.
129
88
  *
@@ -155,11 +114,8 @@ export declare const restorePurchases: (options?: {
155
114
  * });
156
115
  * ```
157
116
  */
158
- export declare const requestPurchase: (requestObj: PurchaseRequestInput) => Promise<Purchase | Purchase[] | void>;
159
- export declare const finishTransaction: ({ purchase, isConsumable, }: {
160
- purchase: Purchase;
161
- isConsumable?: boolean;
162
- }) => Promise<VoidResult | boolean>;
117
+ export declare const requestPurchase: MutationField<'requestPurchase'>;
118
+ export declare const finishTransaction: MutationField<'finishTransaction'>;
163
119
  /**
164
120
  * Retrieves the current storefront information from iOS App Store
165
121
  *
@@ -191,12 +147,7 @@ export declare const getStorefront: () => Promise<string>;
191
147
  * - iOS: Send receipt data to Apple's verification endpoint from your server
192
148
  * - Android: Use Google Play Developer API with service account credentials
193
149
  */
194
- export declare const validateReceipt: (sku: string, androidOptions?: {
195
- packageName: string;
196
- productToken: string;
197
- accessToken: string;
198
- isSub?: boolean;
199
- }) => Promise<ReceiptValidationResult>;
150
+ export declare const validateReceipt: MutationField<'validateReceipt'>;
200
151
  /**
201
152
  * Deeplinks to native interface that allows users to manage their subscriptions
202
153
  * @param options.skuAndroid - Required for Android to locate specific subscription (ignored on iOS)
@@ -215,10 +166,7 @@ export declare const validateReceipt: (sku: string, androidOptions?: {
215
166
  * packageNameAndroid: 'com.example.app'
216
167
  * });
217
168
  */
218
- export declare const deepLinkToSubscriptions: (options: {
219
- skuAndroid?: string;
220
- packageNameAndroid?: string;
221
- }) => Promise<void>;
169
+ export declare const deepLinkToSubscriptions: MutationField<'deepLinkToSubscriptions'>;
222
170
  export * from './useIAP';
223
171
  export * from './utils/errorMapping';
224
172
  //# 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
@@ -5,9 +5,9 @@ import { Platform } from 'react-native';
5
5
  import ExpoIapModule from './ExpoIapModule';
6
6
  import { isProductIOS, validateReceiptIOS, deepLinkToSubscriptionsIOS, syncIOS, } from './modules/ios';
7
7
  import { isProductAndroid, validateReceiptAndroid, deepLinkToSubscriptionsAndroid, } from './modules/android';
8
- // Types
9
- import { ErrorCode, } from './types';
8
+ import { ErrorCode } from './types';
10
9
  import { PurchaseError } from './purchase-error';
10
+ import { normalizePurchaseId, normalizePurchaseList } from './utils/purchase';
11
11
  // Export all types
12
12
  export * from './types';
13
13
  export { ErrorCodeUtils, ErrorCodeMapping } from './purchase-error';
@@ -45,13 +45,20 @@ const normalizeProductType = (type) => {
45
45
  native: 'subs',
46
46
  };
47
47
  }
48
+ if (type === 'all') {
49
+ return {
50
+ canonical: 'all',
51
+ native: 'all',
52
+ };
53
+ }
48
54
  throw new Error(`Unsupported product type: ${type}`);
49
55
  };
50
56
  export const purchaseUpdatedListener = (listener) => {
51
57
  console.log('[JS] Registering purchaseUpdatedListener');
52
58
  const wrappedListener = (event) => {
53
- console.log('[JS] purchaseUpdatedListener fired:', event);
54
- listener(event);
59
+ const normalized = normalizePurchaseId(event);
60
+ console.log('[JS] purchaseUpdatedListener fired:', normalized);
61
+ listener(normalized);
55
62
  };
56
63
  const emitterSubscription = emitter.addListener(OpenIapEvent.PurchaseUpdated, wrappedListener);
57
64
  console.log('[JS] purchaseUpdatedListener registered successfully');
@@ -94,81 +101,70 @@ export const promotedProductListenerIOS = (listener) => {
94
101
  }
95
102
  return emitter.addListener(OpenIapEvent.PromotedProductIOS, listener);
96
103
  };
97
- export function initConnection() {
98
- const result = ExpoIapModule.initConnection();
99
- return Promise.resolve(result);
100
- }
101
- export async function endConnection() {
102
- return ExpoIapModule.endConnection();
103
- }
104
+ export const initConnection = async () => ExpoIapModule.initConnection();
105
+ export const endConnection = async () => ExpoIapModule.endConnection();
104
106
  /**
105
107
  * Fetch products with unified API (v2.7.0+)
106
108
  *
107
- * @param params - Product fetch configuration
108
- * @param params.skus - Array of product SKUs to fetch
109
- * @param params.type - Type of products: 'in-app' for regular products (default) or 'subs' for subscriptions
110
- *
111
- * @example
112
- * ```typescript
113
- * // Regular products
114
- * const products = await fetchProducts({
115
- * skus: ['product1', 'product2'],
116
- * type: 'in-app'
117
- * });
118
- *
119
- * // Subscriptions
120
- * const subscriptions = await fetchProducts({
121
- * skus: ['sub1', 'sub2'],
122
- * type: 'subs'
123
- * });
124
- * ```
109
+ * @param request - Product fetch configuration
110
+ * @param request.skus - Array of product SKUs to fetch
111
+ * @param request.type - Product query type: 'in-app', 'subs', or 'all'
125
112
  */
126
- export const fetchProducts = async ({ skus, type, }) => {
127
- if (!skus?.length) {
113
+ export const fetchProducts = async (request) => {
114
+ const { skus, type } = request ?? {};
115
+ if (!Array.isArray(skus) || skus.length === 0) {
128
116
  throw new PurchaseError({
129
117
  message: 'No SKUs provided',
130
118
  code: ErrorCode.EmptySkuList,
131
119
  });
132
120
  }
133
121
  const { canonical, native } = normalizeProductType(type);
122
+ const skuSet = new Set(skus);
123
+ const filterIosItems = (items) => items.filter((item) => {
124
+ if (!isProductIOS(item)) {
125
+ return false;
126
+ }
127
+ const candidate = item;
128
+ return typeof candidate.id === 'string' && skuSet.has(candidate.id);
129
+ });
130
+ const filterAndroidItems = (items) => items.filter((item) => {
131
+ if (!isProductAndroid(item)) {
132
+ return false;
133
+ }
134
+ const candidate = item;
135
+ return typeof candidate.id === 'string' && skuSet.has(candidate.id);
136
+ });
137
+ const castResult = (items) => {
138
+ if (canonical === 'in-app') {
139
+ return items;
140
+ }
141
+ if (canonical === 'subs') {
142
+ return items;
143
+ }
144
+ return items;
145
+ };
134
146
  if (Platform.OS === 'ios') {
135
147
  const rawItems = await ExpoIapModule.fetchProducts({ skus, type: native });
136
- const filteredItems = rawItems.filter((item) => {
137
- if (!isProductIOS(item)) {
138
- return false;
139
- }
140
- const isValid = typeof item === 'object' &&
141
- item !== null &&
142
- 'id' in item &&
143
- typeof item.id === 'string' &&
144
- skus.includes(item.id);
145
- return isValid;
146
- });
147
- return canonical === 'in-app'
148
- ? filteredItems
149
- : filteredItems;
148
+ return castResult(filterIosItems(rawItems));
150
149
  }
151
150
  if (Platform.OS === 'android') {
152
- const items = await ExpoIapModule.fetchProducts(native, skus);
153
- const filteredItems = items.filter((item) => {
154
- if (!isProductAndroid(item))
155
- return false;
156
- return (typeof item === 'object' &&
157
- item !== null &&
158
- 'id' in item &&
159
- typeof item.id === 'string' &&
160
- skus.includes(item.id));
161
- });
162
- return canonical === 'in-app'
163
- ? filteredItems
164
- : filteredItems;
151
+ const rawItems = await ExpoIapModule.fetchProducts(native, skus);
152
+ return castResult(filterAndroidItems(rawItems));
165
153
  }
166
154
  throw new Error('Unsupported platform');
167
155
  };
168
- export const getAvailablePurchases = ({ alsoPublishToEventListenerIOS = false, onlyIncludeActiveItemsIOS = true, } = {}) => (Platform.select({
169
- ios: () => ExpoIapModule.getAvailableItems(alsoPublishToEventListenerIOS, onlyIncludeActiveItemsIOS),
170
- android: () => ExpoIapModule.getAvailableItems(),
171
- }) || (() => Promise.resolve([])))();
156
+ export const getAvailablePurchases = async (options) => {
157
+ const normalizedOptions = {
158
+ alsoPublishToEventListenerIOS: options?.alsoPublishToEventListenerIOS ?? false,
159
+ onlyIncludeActiveItemsIOS: options?.onlyIncludeActiveItemsIOS ?? true,
160
+ };
161
+ const resolvePurchases = Platform.select({
162
+ ios: () => ExpoIapModule.getAvailableItems(normalizedOptions.alsoPublishToEventListenerIOS, normalizedOptions.onlyIncludeActiveItemsIOS),
163
+ android: () => ExpoIapModule.getAvailableItems(),
164
+ }) ?? (() => Promise.resolve([]));
165
+ const purchases = await resolvePurchases();
166
+ return normalizePurchaseList(purchases);
167
+ };
172
168
  /**
173
169
  * Restore completed transactions (cross-platform behavior)
174
170
  *
@@ -182,17 +178,14 @@ export const getAvailablePurchases = ({ alsoPublishToEventListenerIOS = false, o
182
178
  * @param options.onlyIncludeActiveItemsIOS - iOS only: whether to only include active items
183
179
  * @returns Promise resolving to the list of available/restored purchases
184
180
  */
185
- export const restorePurchases = async (options = {}) => {
181
+ export const restorePurchases = async () => {
186
182
  if (Platform.OS === 'ios') {
187
- // Perform best-effort sync on iOS and ignore sync errors to avoid blocking restore flow
188
183
  await syncIOS().catch(() => undefined);
189
184
  }
190
- // Then, fetch available purchases for both platforms
191
- const purchases = await getAvailablePurchases({
192
- alsoPublishToEventListenerIOS: options.alsoPublishToEventListenerIOS ?? false,
193
- onlyIncludeActiveItemsIOS: options.onlyIncludeActiveItemsIOS ?? true,
185
+ await getAvailablePurchases({
186
+ alsoPublishToEventListenerIOS: false,
187
+ onlyIncludeActiveItemsIOS: true,
194
188
  });
195
- return purchases;
196
189
  };
197
190
  const offerToRecordIOS = (offer) => {
198
191
  if (!offer)
@@ -240,8 +233,8 @@ function normalizeRequestProps(request, platform) {
240
233
  * });
241
234
  * ```
242
235
  */
243
- export const requestPurchase = (requestObj) => {
244
- const { request, type } = requestObj;
236
+ export const requestPurchase = async (args) => {
237
+ const { request, type } = args;
245
238
  const { canonical, native } = normalizeProductType(type);
246
239
  const isInAppPurchase = canonical === 'in-app';
247
240
  if (Platform.OS === 'ios') {
@@ -250,17 +243,15 @@ export const requestPurchase = (requestObj) => {
250
243
  throw new Error('Invalid request for iOS. The `sku` property is required and must be a string.');
251
244
  }
252
245
  const { sku, andDangerouslyFinishTransactionAutomatically = false, appAccountToken, quantity, withOffer, } = normalizedRequest;
253
- return (async () => {
254
- const offer = offerToRecordIOS(withOffer ?? undefined);
255
- const purchase = await ExpoIapModule.requestPurchase({
256
- sku,
257
- andDangerouslyFinishTransactionAutomatically,
258
- appAccountToken,
259
- quantity,
260
- withOffer: offer,
261
- });
262
- return purchase;
263
- })();
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);
264
255
  }
265
256
  if (Platform.OS === 'android') {
266
257
  if (isInAppPurchase) {
@@ -269,18 +260,17 @@ export const requestPurchase = (requestObj) => {
269
260
  throw new Error('Invalid request for Android. The `skus` property is required and must be a non-empty array.');
270
261
  }
271
262
  const { skus, obfuscatedAccountIdAndroid, obfuscatedProfileIdAndroid, isOfferPersonalized, } = normalizedRequest;
272
- return (async () => {
273
- return ExpoIapModule.requestPurchase({
274
- type: native,
275
- skuArr: skus,
276
- purchaseToken: undefined,
277
- replacementMode: -1,
278
- obfuscatedAccountId: obfuscatedAccountIdAndroid,
279
- obfuscatedProfileId: obfuscatedProfileIdAndroid,
280
- offerTokenArr: [],
281
- isOfferPersonalized: isOfferPersonalized ?? false,
282
- });
283
- })();
263
+ const result = (await ExpoIapModule.requestPurchase({
264
+ type: native,
265
+ skuArr: skus,
266
+ purchaseToken: undefined,
267
+ replacementMode: -1,
268
+ obfuscatedAccountId: obfuscatedAccountIdAndroid,
269
+ obfuscatedProfileId: obfuscatedProfileIdAndroid,
270
+ offerTokenArr: [],
271
+ isOfferPersonalized: isOfferPersonalized ?? false,
272
+ }));
273
+ return normalizePurchaseList(result);
284
274
  }
285
275
  if (canonical === 'subs') {
286
276
  const normalizedRequest = normalizeRequestProps(request, 'android');
@@ -291,52 +281,62 @@ export const requestPurchase = (requestObj) => {
291
281
  const normalizedOffers = subscriptionOffers ?? [];
292
282
  const replacementMode = replacementModeAndroid ?? -1;
293
283
  const purchaseToken = purchaseTokenAndroid ?? undefined;
294
- return (async () => {
295
- return ExpoIapModule.requestPurchase({
296
- type: native,
297
- skuArr: skus,
298
- purchaseToken,
299
- replacementMode,
300
- obfuscatedAccountId: obfuscatedAccountIdAndroid,
301
- obfuscatedProfileId: obfuscatedProfileIdAndroid,
302
- offerTokenArr: normalizedOffers.map((so) => so.offerToken),
303
- subscriptionOffers: normalizedOffers,
304
- isOfferPersonalized: isOfferPersonalized ?? false,
305
- });
306
- })();
284
+ const result = (await ExpoIapModule.requestPurchase({
285
+ type: native,
286
+ skuArr: skus,
287
+ purchaseToken,
288
+ replacementMode,
289
+ obfuscatedAccountId: obfuscatedAccountIdAndroid,
290
+ obfuscatedProfileId: obfuscatedProfileIdAndroid,
291
+ offerTokenArr: normalizedOffers.map((offer) => offer.offerToken),
292
+ subscriptionOffers: normalizedOffers,
293
+ isOfferPersonalized: isOfferPersonalized ?? false,
294
+ }));
295
+ return normalizePurchaseList(result);
307
296
  }
308
297
  throw new Error("Invalid request for Android: Expected a valid request object with 'skus' array.");
309
298
  }
310
- return Promise.resolve(); // Fallback for unsupported platforms
299
+ throw new Error('Platform not supported');
311
300
  };
312
- export const finishTransaction = ({ purchase, isConsumable = false, }) => {
313
- return (Platform.select({
314
- ios: async () => {
315
- const transactionId = purchase.id;
316
- if (!transactionId) {
317
- return Promise.reject(new Error('purchase.id required to finish iOS transaction'));
318
- }
319
- await ExpoIapModule.finishTransaction(transactionId);
320
- return Promise.resolve(true);
321
- },
322
- android: async () => {
323
- const androidPurchase = purchase;
324
- // Use purchaseToken if available, fallback to purchaseTokenAndroid for backward compatibility
325
- const token = androidPurchase.purchaseToken;
326
- if (!token) {
327
- return Promise.reject(new PurchaseError({
328
- message: 'Purchase token is required to finish transaction',
329
- code: ErrorCode.DeveloperError,
330
- productId: androidPurchase.productId,
331
- platform: 'android',
332
- }));
333
- }
334
- if (isConsumable) {
335
- return ExpoIapModule.consumePurchaseAndroid(token);
336
- }
337
- return ExpoIapModule.acknowledgePurchaseAndroid(token);
338
- },
339
- }) || (() => Promise.reject(new Error('Unsupported Platform'))))();
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
+ export const finishTransaction = async ({ purchase, isConsumable = false, }) => {
313
+ const normalizedPurchase = toPurchaseInput(purchase);
314
+ 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);
320
+ return;
321
+ }
322
+ if (Platform.OS === 'android') {
323
+ const token = normalizedPurchase.purchaseToken ?? undefined;
324
+ if (!token) {
325
+ throw new PurchaseError({
326
+ message: 'Purchase token is required to finish transaction',
327
+ code: ErrorCode.DeveloperError,
328
+ productId: normalizedPurchase.productId,
329
+ platform: 'android',
330
+ });
331
+ }
332
+ if (isConsumable) {
333
+ await ExpoIapModule.consumePurchaseAndroid(token);
334
+ return;
335
+ }
336
+ await ExpoIapModule.acknowledgePurchaseAndroid(token);
337
+ return;
338
+ }
339
+ throw new Error('Unsupported Platform');
340
340
  };
341
341
  /**
342
342
  * Retrieves the current storefront information from iOS App Store
@@ -384,28 +384,27 @@ export const getStorefront = () => {
384
384
  * - iOS: Send receipt data to Apple's verification endpoint from your server
385
385
  * - Android: Use Google Play Developer API with service account credentials
386
386
  */
387
- export const validateReceipt = async (sku, androidOptions) => {
387
+ export const validateReceipt = async (options) => {
388
+ const { sku, androidOptions } = options;
388
389
  if (Platform.OS === 'ios') {
389
- return await validateReceiptIOS(sku);
390
+ return validateReceiptIOS({ sku });
390
391
  }
391
- else if (Platform.OS === 'android') {
392
+ if (Platform.OS === 'android') {
392
393
  if (!androidOptions ||
393
394
  !androidOptions.packageName ||
394
395
  !androidOptions.productToken ||
395
396
  !androidOptions.accessToken) {
396
397
  throw new Error('Android validation requires packageName, productToken, and accessToken');
397
398
  }
398
- return await validateReceiptAndroid({
399
+ return validateReceiptAndroid({
399
400
  packageName: androidOptions.packageName,
400
401
  productId: sku,
401
402
  productToken: androidOptions.productToken,
402
403
  accessToken: androidOptions.accessToken,
403
- isSub: androidOptions.isSub,
404
+ isSub: androidOptions.isSub ?? undefined,
404
405
  });
405
406
  }
406
- else {
407
- throw new Error('Platform not supported');
408
- }
407
+ throw new Error('Platform not supported');
409
408
  };
410
409
  /**
411
410
  * Deeplinks to native interface that allows users to manage their subscriptions
@@ -425,17 +424,16 @@ export const validateReceipt = async (sku, androidOptions) => {
425
424
  * packageNameAndroid: 'com.example.app'
426
425
  * });
427
426
  */
428
- export const deepLinkToSubscriptions = (options) => {
427
+ export const deepLinkToSubscriptions = async (options) => {
429
428
  if (Platform.OS === 'ios') {
430
- return deepLinkToSubscriptionsIOS();
429
+ await deepLinkToSubscriptionsIOS();
430
+ return;
431
431
  }
432
432
  if (Platform.OS === 'android') {
433
- return deepLinkToSubscriptionsAndroid({
434
- sku: options?.skuAndroid,
435
- packageName: options?.packageNameAndroid,
436
- });
433
+ await deepLinkToSubscriptionsAndroid(options ?? null);
434
+ return;
437
435
  }
438
- return Promise.reject(new Error(`Unsupported platform: ${Platform.OS}`));
436
+ throw new Error(`Unsupported platform: ${Platform.OS}`);
439
437
  };
440
438
  export * from './useIAP';
441
439
  export * from './utils/errorMapping';