expo-iap 2.2.7 → 2.2.8-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.
- package/build/ExpoIap.types.d.ts +1 -4
- package/build/ExpoIap.types.d.ts.map +1 -1
- package/build/ExpoIap.types.js +0 -5
- package/build/ExpoIap.types.js.map +1 -1
- package/build/index.d.ts +13 -3
- package/build/index.d.ts.map +1 -1
- package/build/index.js +72 -60
- package/build/index.js.map +1 -1
- package/build/modules/android.d.ts +0 -5
- package/build/modules/android.d.ts.map +1 -1
- package/build/modules/android.js +0 -3
- package/build/modules/android.js.map +1 -1
- package/build/modules/ios.d.ts +0 -5
- package/build/modules/ios.d.ts.map +1 -1
- package/build/modules/ios.js +0 -3
- package/build/modules/ios.js.map +1 -1
- package/build/useIap.d.ts +4 -2
- package/build/useIap.d.ts.map +1 -1
- package/build/useIap.js +10 -10
- package/build/useIap.js.map +1 -1
- package/iap.md +145 -161
- package/package.json +1 -1
- package/src/ExpoIap.types.ts +1 -5
- package/src/index.ts +139 -111
- package/src/modules/android.ts +0 -6
- package/src/modules/ios.ts +0 -6
- package/src/useIap.ts +18 -13
package/build/useIap.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useIap.js","sourceRoot":"","sources":["../src/useIap.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,qBAAqB,EACrB,WAAW,EACX,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,IAAI,yBAAyB,EAC9C,gBAAgB,
|
|
1
|
+
{"version":3,"file":"useIap.js","sourceRoot":"","sources":["../src/useIap.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n endConnection,\n initConnection,\n purchaseErrorListener,\n purchaseUpdatedListener,\n transactionUpdatedIos,\n getProducts,\n getAvailablePurchases,\n getPurchaseHistory,\n finishTransaction as finishTransactionInternal,\n getSubscriptions,\n requestPurchase as requestPurchaseInternal,\n} from './';\nimport {useCallback, useEffect, useState, useRef} from 'react';\nimport {\n Product,\n ProductPurchase,\n Purchase,\n PurchaseError,\n PurchaseResult,\n SubscriptionProduct,\n SubscriptionPurchase,\n} from './ExpoIap.types';\nimport {TransactionEvent} from './modules/ios';\nimport {Subscription} from 'expo-modules-core';\nimport {Platform} from 'react-native';\n\ntype IAP_STATUS = {\n connected: boolean;\n products: Product[];\n promotedProductsIOS: ProductPurchase[];\n subscriptions: SubscriptionProduct[];\n purchaseHistories: ProductPurchase[];\n availablePurchases: ProductPurchase[];\n currentPurchase?: ProductPurchase;\n currentPurchaseError?: PurchaseError;\n finishTransaction: ({\n purchase,\n isConsumable,\n }: {\n purchase: Purchase;\n isConsumable?: boolean;\n }) => Promise<string | boolean | PurchaseResult | void>;\n getAvailablePurchases: (skus: string[]) => Promise<void>;\n getPurchaseHistories: (skus: string[]) => Promise<void>;\n getProducts: (skus: string[]) => Promise<void>;\n getSubscriptions: (skus: string[]) => Promise<void>;\n requestPurchase: typeof requestPurchaseInternal;\n};\n\nexport function useIAP(): IAP_STATUS {\n const [connected, setConnected] = useState<boolean>(false);\n const [products, setProducts] = useState<Product[]>([]);\n const [promotedProductsIOS, setPromotedProductsIOS] = useState<\n ProductPurchase[]\n >([]);\n const [subscriptions, setSubscriptions] = useState<SubscriptionProduct[]>([]);\n const [purchaseHistories, setPurchaseHistories] = useState<ProductPurchase[]>(\n [],\n );\n const [availablePurchases, setAvailablePurchases] = useState<\n ProductPurchase[]\n >([]);\n const [currentPurchase, setCurrentPurchase] = useState<ProductPurchase>();\n const [currentPurchaseError, setCurrentPurchaseError] =\n useState<PurchaseError>();\n\n const subscriptionsRef = useRef<{\n purchaseUpdate?: Subscription;\n purchaseError?: Subscription;\n promotedProductsIos?: Subscription;\n }>({});\n\n const getProductsInternal = useCallback(\n async (skus: string[]): Promise<void> => {\n setProducts(await getProducts(skus));\n },\n [],\n );\n\n const getSubscriptionsInternal = useCallback(\n async (skus: string[]): Promise<void> => {\n setSubscriptions(await getSubscriptions(skus));\n },\n [],\n );\n\n const getAvailablePurchasesInternal = useCallback(async (): Promise<void> => {\n setAvailablePurchases(await getAvailablePurchases());\n }, []);\n\n const getPurchaseHistoriesInternal = useCallback(async (): Promise<void> => {\n setPurchaseHistories(await getPurchaseHistory());\n }, []);\n\n const finishTransaction = useCallback(\n async ({\n purchase,\n isConsumable,\n }: {\n purchase: ProductPurchase;\n isConsumable?: boolean;\n }): Promise<string | boolean | PurchaseResult | void> => {\n try {\n return await finishTransactionInternal({\n purchase,\n isConsumable,\n });\n } catch (err) {\n throw err;\n } finally {\n if (purchase.id === currentPurchase?.id) {\n setCurrentPurchase(undefined);\n }\n if (purchase.id === currentPurchaseError?.productId) {\n setCurrentPurchaseError(undefined);\n }\n }\n },\n [currentPurchase?.id, currentPurchaseError?.productId],\n );\n\n const initIapWithSubscriptions = useCallback(async (): Promise<void> => {\n const result = await initConnection();\n setConnected(result);\n\n if (result) {\n subscriptionsRef.current.purchaseUpdate = purchaseUpdatedListener(\n async (purchase: Purchase | SubscriptionPurchase) => {\n setCurrentPurchaseError(undefined);\n setCurrentPurchase(purchase);\n },\n );\n\n subscriptionsRef.current.purchaseError = purchaseErrorListener(\n (error: PurchaseError) => {\n setCurrentPurchase(undefined);\n setCurrentPurchaseError(error);\n },\n );\n\n if (Platform.OS === 'ios') {\n subscriptionsRef.current.promotedProductsIos = transactionUpdatedIos(\n (event: TransactionEvent) => {\n setPromotedProductsIOS((prevProducts) =>\n event.transaction\n ? [...prevProducts, event.transaction]\n : prevProducts,\n );\n },\n );\n }\n }\n }, []);\n\n useEffect(() => {\n initIapWithSubscriptions();\n const currentSubscriptions = subscriptionsRef.current;\n\n return () => {\n currentSubscriptions.purchaseUpdate?.remove();\n currentSubscriptions.purchaseError?.remove();\n currentSubscriptions.promotedProductsIos?.remove();\n endConnection();\n setConnected(false);\n };\n }, [initIapWithSubscriptions]);\n\n return {\n connected,\n products,\n promotedProductsIOS,\n subscriptions,\n purchaseHistories,\n finishTransaction,\n availablePurchases,\n currentPurchase,\n currentPurchaseError,\n getAvailablePurchases: getAvailablePurchasesInternal,\n getPurchaseHistories: getPurchaseHistoriesInternal,\n getProducts: getProductsInternal,\n getSubscriptions: getSubscriptionsInternal,\n requestPurchase: requestPurchaseInternal,\n };\n}\n"]}
|
package/iap.md
CHANGED
|
@@ -4,47 +4,57 @@
|
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
`expo-iap` is an Expo module for handling in-app purchases (IAP) on iOS (StoreKit 2) and Android (Google Play Billing). It supports consumables, non-consumables, and subscriptions.
|
|
7
|
+
`expo-iap` is an Expo module for handling in-app purchases (IAP) on iOS (StoreKit 2) and Android (Google Play Billing). It supports consumables, non-consumables, and subscriptions. Unlike [`react-native-iap`](https://github.com/hyochan/react-native-iap), which requires a bare workflow due to native dependencies, `expo-iap` is designed to work seamlessly in Expo's [managed workflow](https://docs.expo.dev/archive/managed-vs-bare)—no ejecting required! However, you’ll need to use a [development client](https://docs.expo.dev/development/introduction/) for full functionality. Starting from version 2.2.7, most features of `react-native-iap` have been ported.
|
|
8
8
|
|
|
9
|
-
## Installation
|
|
9
|
+
## Installation
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
`expo-iap` is compatible with [Expo SDK](https://expo.dev) 51+ and supports both managed and bare React Native projects. Official documentation is in progress for SDK inclusion (see [Expo IAP Documentation](link-to-docs)).
|
|
12
12
|
|
|
13
|
-
###
|
|
13
|
+
### Add the Package
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
```bash
|
|
16
|
+
npm install expo-iap
|
|
17
|
+
```
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
npm install expo-iap
|
|
19
|
-
```
|
|
19
|
+
### Configure with Expo Config Plugin
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
Add `'expo-iap'` to the `plugins` array in your `app.json` or `app.config.js`:
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"expo": {
|
|
26
|
+
"plugins": ["expo-iap"]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This plugin automatically configures Android BILLING permissions and iOS setup, making it plug-and-play for managed workflows with a development client.
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
## Managed Expo Projects
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
> **No Ejecting Required—Use a Development Client!**
|
|
36
|
+
> Unlike `react-native-iap`, `expo-iap` integrates into Expo’s managed workflow. However, you’ll need to use a [development client](https://docs.expo.dev/development/introduction/) instead of the default Expo Go app for full compatibility.
|
|
34
37
|
|
|
35
|
-
|
|
38
|
+
1. **Run Your App**
|
|
39
|
+
After adding the package and configuring the plugin, build a development client and test with:
|
|
36
40
|
|
|
37
|
-
|
|
41
|
+
```bash
|
|
42
|
+
expo run:android
|
|
43
|
+
expo run:ios
|
|
44
|
+
```
|
|
38
45
|
|
|
39
|
-
|
|
46
|
+
2. **Example**
|
|
47
|
+
Check out a working sample at [example/App.tsx](https://github.com/hyochan/expo-iap/blob/main/example/App.tsx). It demonstrates:
|
|
48
|
+
- Initializing the connection (`initConnection`)
|
|
49
|
+
- Fetching products/subscriptions (`getProducts`, `getSubscriptions`)
|
|
50
|
+
- Handling purchases (`requestPurchase`, `requestSubscription`)
|
|
51
|
+
- Listening for updates/errors (`purchaseUpdatedListener`, `purchaseErrorListener`)
|
|
40
52
|
|
|
41
|
-
|
|
53
|
+
## Bare React Native Projects
|
|
42
54
|
|
|
43
|
-
|
|
44
|
-
npm install expo-iap
|
|
45
|
-
```
|
|
55
|
+
Ensure the [`expo` package is installed and configured](https://docs.expo.dev/bare/installing-expo-modules/) before proceeding.
|
|
46
56
|
|
|
47
|
-
###
|
|
57
|
+
### Additional Configuration for iOS
|
|
48
58
|
|
|
49
59
|
Run `npx pod-install`. Set `deploymentTarget` to `15.0` or higher (StoreKit 2 requirement):
|
|
50
60
|
|
|
@@ -54,23 +64,13 @@ Run `npx pod-install`. Set `deploymentTarget` to `15.0` or higher (StoreKit 2 re
|
|
|
54
64
|
}
|
|
55
65
|
```
|
|
56
66
|
|
|
57
|
-
###
|
|
58
|
-
|
|
59
|
-
No additional configuration is needed—Google Play Billing is handled internally.
|
|
60
|
-
|
|
61
|
-
### Configure with Expo Config Plugin
|
|
67
|
+
### Additional Configuration for Android
|
|
62
68
|
|
|
63
|
-
|
|
69
|
+
No additional configuration is needed—Google Play Billing is handled internally by the plugin.
|
|
64
70
|
|
|
65
|
-
|
|
66
|
-
{
|
|
67
|
-
"expo": {
|
|
68
|
-
"plugins": ["expo-iap"]
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
```
|
|
71
|
+
## Current State & Feedback
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
Updates are in progress to improve reliability and address remaining edge cases. For production apps, test thoroughly. Contributions (docs, code, or bug reports) are welcome—especially detailed error logs or use cases!
|
|
74
74
|
|
|
75
75
|
## IAP Types
|
|
76
76
|
|
|
@@ -123,7 +123,6 @@ This section outlines the properties of products supported by `expo-iap`.
|
|
|
123
123
|
### iOS-Only Product Types
|
|
124
124
|
|
|
125
125
|
- **`ProductIos`**
|
|
126
|
-
- `displayPrice: string`: Formatted price.
|
|
127
126
|
- `isFamilyShareable: boolean`: Family sharing support.
|
|
128
127
|
- `jsonRepresentation: string`: StoreKit 2 JSON data.
|
|
129
128
|
- `subscription: SubscriptionInfo`: Subscription details.
|
|
@@ -185,10 +184,6 @@ This section describes purchase properties in `expo-iap`.
|
|
|
185
184
|
|
|
186
185
|
Transactions are mapped directly to `Purchase` or `SubscriptionPurchase` with platform-specific fields (e.g., `expirationDateIos`, `purchaseStateAndroid`).
|
|
187
186
|
|
|
188
|
-
### Status
|
|
189
|
-
|
|
190
|
-
This module is under development—expect occasional bugs (e.g., Android acknowledgment issues). Test thoroughly and consider contributing fixes!
|
|
191
|
-
|
|
192
187
|
> **Sample Code**: See [example/App.tsx](https://github.com/hyochan/expo-iap/blob/main/example/App.tsx).
|
|
193
188
|
|
|
194
189
|
## Implementation
|
|
@@ -260,7 +255,7 @@ export default function SimpleIAP() {
|
|
|
260
255
|
|
|
261
256
|
## Using useIAP Hook
|
|
262
257
|
|
|
263
|
-
The `useIAP` hook simplifies managing in-app purchases with `expo-iap`. Below is an example that fetches products and subscriptions, and
|
|
258
|
+
The `useIAP` hook simplifies managing in-app purchases with `expo-iap`. Below is an example that fetches products and subscriptions only when connected, handles purchases, and displays them in a styled UI, similar to the `expo-iap` example app. This assumes you’ve implemented the `useIAP` hook in your project (e.g., as shown in `useIap.ts`).
|
|
264
259
|
|
|
265
260
|
```tsx
|
|
266
261
|
import {useEffect, useState} from 'react';
|
|
@@ -275,13 +270,8 @@ import {
|
|
|
275
270
|
InteractionManager,
|
|
276
271
|
Alert,
|
|
277
272
|
} from 'react-native';
|
|
278
|
-
import {
|
|
279
|
-
import type {
|
|
280
|
-
PurchaseError,
|
|
281
|
-
ProductPurchase,
|
|
282
|
-
SubscriptionProduct,
|
|
283
|
-
} from 'expo-iap';
|
|
284
|
-
import {RequestSubscriptionAndroidProps} from './types/ExpoIapAndroid.types'; // Adjust path as needed
|
|
273
|
+
import {useIAP} from 'expo-iap';
|
|
274
|
+
import type {ProductPurchase, SubscriptionProduct} from 'expo-iap';
|
|
285
275
|
|
|
286
276
|
// Define SKUs
|
|
287
277
|
const productSkus = [
|
|
@@ -297,12 +287,7 @@ const subscriptionSkus = [
|
|
|
297
287
|
];
|
|
298
288
|
|
|
299
289
|
// Define operations
|
|
300
|
-
const operations = [
|
|
301
|
-
'initConnection',
|
|
302
|
-
'getProducts',
|
|
303
|
-
'getSubscriptions',
|
|
304
|
-
'endConnection',
|
|
305
|
-
];
|
|
290
|
+
const operations = ['getProducts', 'getSubscriptions'] as const;
|
|
306
291
|
type Operation = (typeof operations)[number];
|
|
307
292
|
|
|
308
293
|
export default function IAPWithHook() {
|
|
@@ -319,35 +304,25 @@ export default function IAPWithHook() {
|
|
|
319
304
|
requestSubscription,
|
|
320
305
|
} = useIAP();
|
|
321
306
|
|
|
322
|
-
const [
|
|
307
|
+
const [isReady, setIsReady] = useState(false);
|
|
323
308
|
|
|
324
|
-
//
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
try {
|
|
342
|
-
await getSubscriptions(subscriptionSkus);
|
|
343
|
-
} catch (error) {
|
|
344
|
-
console.error('Error fetching subscriptions:', error);
|
|
345
|
-
}
|
|
346
|
-
break;
|
|
347
|
-
default:
|
|
348
|
-
console.log('Unknown operation');
|
|
349
|
-
}
|
|
350
|
-
};
|
|
309
|
+
// Fetch products and subscriptions only when connected
|
|
310
|
+
useEffect(() => {
|
|
311
|
+
if (!connected) return;
|
|
312
|
+
|
|
313
|
+
const initializeIAP = async () => {
|
|
314
|
+
try {
|
|
315
|
+
await Promise.all([
|
|
316
|
+
getProducts(productSkus),
|
|
317
|
+
getSubscriptions(subscriptionSkus),
|
|
318
|
+
]);
|
|
319
|
+
setIsReady(true);
|
|
320
|
+
} catch (error) {
|
|
321
|
+
console.error('Error initializing IAP:', error);
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
initializeIAP();
|
|
325
|
+
}, [connected, getProducts, getSubscriptions]);
|
|
351
326
|
|
|
352
327
|
// Handle purchase updates and errors
|
|
353
328
|
useEffect(() => {
|
|
@@ -356,22 +331,52 @@ export default function IAPWithHook() {
|
|
|
356
331
|
try {
|
|
357
332
|
await finishTransaction({
|
|
358
333
|
purchase: currentPurchase,
|
|
359
|
-
isConsumable:
|
|
334
|
+
isConsumable: currentPurchase.productType === 'inapp', // Consumable for in-app products
|
|
360
335
|
});
|
|
361
|
-
Alert.alert('Purchase
|
|
336
|
+
Alert.alert('Purchase Successful', JSON.stringify(currentPurchase));
|
|
362
337
|
} catch (error) {
|
|
363
338
|
console.error('Error finishing transaction:', error);
|
|
339
|
+
Alert.alert('Transaction Error', String(error));
|
|
364
340
|
}
|
|
365
341
|
});
|
|
366
342
|
}
|
|
367
343
|
|
|
368
344
|
if (currentPurchaseError) {
|
|
369
345
|
InteractionManager.runAfterInteractions(() => {
|
|
370
|
-
Alert.alert('Purchase
|
|
346
|
+
Alert.alert('Purchase Error', JSON.stringify(currentPurchaseError));
|
|
371
347
|
});
|
|
372
348
|
}
|
|
373
349
|
}, [currentPurchase, currentPurchaseError, finishTransaction]);
|
|
374
350
|
|
|
351
|
+
// Handle operation buttons
|
|
352
|
+
const handleOperation = async (operation: Operation) => {
|
|
353
|
+
if (!connected) {
|
|
354
|
+
Alert.alert('Not Connected', 'Please wait for IAP to connect.');
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
try {
|
|
359
|
+
switch (operation) {
|
|
360
|
+
case 'getProducts':
|
|
361
|
+
await getProducts(productSkus);
|
|
362
|
+
break;
|
|
363
|
+
case 'getSubscriptions':
|
|
364
|
+
await getSubscriptions(subscriptionSkus);
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
} catch (error) {
|
|
368
|
+
console.error(`Error in ${operation}:`, error);
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
if (!connected) {
|
|
373
|
+
return (
|
|
374
|
+
<SafeAreaView style={styles.container}>
|
|
375
|
+
<Text style={styles.title}>Connecting to IAP...</Text>
|
|
376
|
+
</SafeAreaView>
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
|
|
375
380
|
return (
|
|
376
381
|
<SafeAreaView style={styles.container}>
|
|
377
382
|
<Text style={styles.title}>Expo IAP with useIAP Hook</Text>
|
|
@@ -390,82 +395,61 @@ export default function IAPWithHook() {
|
|
|
390
395
|
</ScrollView>
|
|
391
396
|
</View>
|
|
392
397
|
<View style={styles.content}>
|
|
393
|
-
{!
|
|
394
|
-
<Text>
|
|
398
|
+
{!isReady ? (
|
|
399
|
+
<Text>Loading...</Text>
|
|
395
400
|
) : (
|
|
396
401
|
<View style={{gap: 12}}>
|
|
397
402
|
<Text style={{fontSize: 20}}>Products</Text>
|
|
398
|
-
{products.map((item) =>
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
</Text>
|
|
419
|
-
<Button
|
|
420
|
-
title="Buy"
|
|
421
|
-
onPress={() => requestPurchase({sku: item.id})}
|
|
422
|
-
/>
|
|
423
|
-
</View>
|
|
424
|
-
);
|
|
425
|
-
}
|
|
426
|
-
})}
|
|
403
|
+
{products.map((item) => (
|
|
404
|
+
<View key={item.id} style={{gap: 12}}>
|
|
405
|
+
<Text>
|
|
406
|
+
{item.title} -{' '}
|
|
407
|
+
{item.platform === 'android'
|
|
408
|
+
? item.oneTimePurchaseOfferDetails?.formattedPrice
|
|
409
|
+
: item.displayPrice}
|
|
410
|
+
</Text>
|
|
411
|
+
<Button
|
|
412
|
+
title="Buy"
|
|
413
|
+
onPress={() =>
|
|
414
|
+
requestPurchase(
|
|
415
|
+
item.platform === 'android'
|
|
416
|
+
? {skus: [item.id]}
|
|
417
|
+
: {sku: item.id},
|
|
418
|
+
)
|
|
419
|
+
}
|
|
420
|
+
/>
|
|
421
|
+
</View>
|
|
422
|
+
))}
|
|
427
423
|
|
|
428
424
|
<Text style={{fontSize: 20}}>Subscriptions</Text>
|
|
429
|
-
{subscriptions.map((item) =>
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
subscriptionOffers:
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
<View key={item.id} style={{gap: 12}}>
|
|
458
|
-
<Text>
|
|
459
|
-
{item.displayName} - {item.displayPrice}
|
|
460
|
-
</Text>
|
|
461
|
-
<Button
|
|
462
|
-
title="Subscribe"
|
|
463
|
-
onPress={() => requestSubscription({sku: item.id})}
|
|
464
|
-
/>
|
|
465
|
-
</View>
|
|
466
|
-
);
|
|
467
|
-
}
|
|
468
|
-
})}
|
|
425
|
+
{subscriptions.map((item) => (
|
|
426
|
+
<View key={item.id} style={{gap: 12}}>
|
|
427
|
+
<Text>
|
|
428
|
+
{item.title || item.displayName} -{' '}
|
|
429
|
+
{item.platform === 'android' && item.subscriptionOfferDetails
|
|
430
|
+
? item.subscriptionOfferDetails[0]?.pricingPhases
|
|
431
|
+
.pricingPhaseList[0].formattedPrice
|
|
432
|
+
: item.displayPrice}
|
|
433
|
+
</Text>
|
|
434
|
+
<Button
|
|
435
|
+
title="Subscribe"
|
|
436
|
+
onPress={() =>
|
|
437
|
+
requestSubscription(
|
|
438
|
+
item.platform === 'android'
|
|
439
|
+
? {
|
|
440
|
+
skus: [item.id],
|
|
441
|
+
subscriptionOffers:
|
|
442
|
+
item.subscriptionOfferDetails?.map((offer) => ({
|
|
443
|
+
sku: item.id,
|
|
444
|
+
offerToken: offer.offerToken,
|
|
445
|
+
})) || [],
|
|
446
|
+
}
|
|
447
|
+
: {sku: item.id},
|
|
448
|
+
)
|
|
449
|
+
}
|
|
450
|
+
/>
|
|
451
|
+
</View>
|
|
452
|
+
))}
|
|
469
453
|
</View>
|
|
470
454
|
)}
|
|
471
455
|
</View>
|
package/package.json
CHANGED
package/src/ExpoIap.types.ts
CHANGED
|
@@ -34,11 +34,7 @@ export type ProductBase = {
|
|
|
34
34
|
// Define literal platform types for better type discrimination
|
|
35
35
|
export type IosPlatform = {platform: 'ios'};
|
|
36
36
|
export type AndroidPlatform = {platform: 'android'};
|
|
37
|
-
|
|
38
|
-
export enum ProductType {
|
|
39
|
-
InAppPurchase = 'inapp',
|
|
40
|
-
Subscription = 'subs',
|
|
41
|
-
}
|
|
37
|
+
export type ProductType = 'inapp' | 'subs';
|
|
42
38
|
|
|
43
39
|
// Common base purchase type
|
|
44
40
|
export type PurchaseBase = {
|