expo-iap 2.2.7-rc.1 → 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 +148 -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":"AAAA,OAAO,EACL,aAAa,EACb,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,qBAAqB,EACrB,WAAW,EACX,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,IAAI,yBAAyB,EAC9C,gBAAgB,EAChB,eAAe,IAAI,uBAAuB,GAC3C,MAAM,IAAI,CAAC;AACZ,OAAO,EAAC,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAC,MAAM,OAAO,CAAC;AAY/D,OAAO,EAAC,QAAQ,EAAC,MAAM,cAAc,CAAC;AAyBtC,MAAM,UAAU,MAAM;IACpB,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAU,KAAK,CAAC,CAAC;IAC3D,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAY,EAAE,CAAC,CAAC;IACxD,MAAM,CAAC,mBAAmB,EAAE,sBAAsB,CAAC,GAAG,QAAQ,CAE5D,EAAE,CAAC,CAAC;IACN,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAwB,EAAE,CAAC,CAAC;IAC9E,MAAM,CAAC,iBAAiB,EAAE,oBAAoB,CAAC,GAAG,QAAQ,CACxD,EAAE,CACH,CAAC;IACF,MAAM,CAAC,kBAAkB,EAAE,qBAAqB,CAAC,GAAG,QAAQ,CAE1D,EAAE,CAAC,CAAC;IACN,MAAM,CAAC,eAAe,EAAE,kBAAkB,CAAC,GAAG,QAAQ,EAAmB,CAAC;IAC1E,MAAM,CAAC,oBAAoB,EAAE,uBAAuB,CAAC,GACnD,QAAQ,EAAiB,CAAC;IAE5B,MAAM,gBAAgB,GAAG,MAAM,CAI5B,EAAE,CAAC,CAAC;IAEP,MAAM,mBAAmB,GAAG,WAAW,CACrC,KAAK,EAAE,IAAc,EAAiB,EAAE;QACtC,WAAW,CAAC,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;IACvC,CAAC,EACD,EAAE,CACH,CAAC;IAEF,MAAM,wBAAwB,GAAG,WAAW,CAC1C,KAAK,EAAE,IAAc,EAAiB,EAAE;QACtC,gBAAgB,CAAC,MAAM,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC;IACjD,CAAC,EACD,EAAE,CACH,CAAC;IAEF,MAAM,6BAA6B,GAAG,WAAW,CAAC,KAAK,IAAmB,EAAE;QAC1E,qBAAqB,CAAC,MAAM,qBAAqB,EAAE,CAAC,CAAC;IACvD,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,4BAA4B,GAAG,WAAW,CAAC,KAAK,IAAmB,EAAE;QACzE,oBAAoB,CAAC,MAAM,kBAAkB,EAAE,CAAC,CAAC;IACnD,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,iBAAiB,GAAG,WAAW,CACnC,KAAK,EAAE,EACL,QAAQ,EACR,YAAY,GAIb,EAAqD,EAAE;QACtD,IAAI,CAAC;YACH,OAAO,MAAM,yBAAyB,CAAC;gBACrC,QAAQ;gBACR,YAAY;aACb,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,CAAC;QACZ,CAAC;gBAAS,CAAC;YACT,IAAI,QAAQ,CAAC,EAAE,KAAK,eAAe,EAAE,EAAE,EAAE,CAAC;gBACxC,kBAAkB,CAAC,SAAS,CAAC,CAAC;YAChC,CAAC;YACD,IAAI,QAAQ,CAAC,EAAE,KAAK,oBAAoB,EAAE,SAAS,EAAE,CAAC;gBACpD,uBAAuB,CAAC,SAAS,CAAC,CAAC;YACrC,CAAC;QACH,CAAC;IACH,CAAC,EACD,CAAC,eAAe,EAAE,EAAE,EAAE,oBAAoB,EAAE,SAAS,CAAC,CACvD,CAAC;IAEF,MAAM,wBAAwB,GAAG,WAAW,CAAC,KAAK,IAAmB,EAAE;QACrE,MAAM,MAAM,GAAG,MAAM,cAAc,EAAE,CAAC;QACtC,YAAY,CAAC,MAAM,CAAC,CAAC;QAErB,IAAI,MAAM,EAAE,CAAC;YACX,gBAAgB,CAAC,OAAO,CAAC,cAAc,GAAG,uBAAuB,CAC/D,KAAK,EAAE,QAAyC,EAAE,EAAE;gBAClD,uBAAuB,CAAC,SAAS,CAAC,CAAC;gBACnC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;YAC/B,CAAC,CACF,CAAC;YAEF,gBAAgB,CAAC,OAAO,CAAC,aAAa,GAAG,qBAAqB,CAC5D,CAAC,KAAoB,EAAE,EAAE;gBACvB,kBAAkB,CAAC,SAAS,CAAC,CAAC;gBAC9B,uBAAuB,CAAC,KAAK,CAAC,CAAC;YACjC,CAAC,CACF,CAAC;YAEF,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;gBAC1B,gBAAgB,CAAC,OAAO,CAAC,mBAAmB,GAAG,qBAAqB,CAClE,CAAC,KAAuB,EAAE,EAAE;oBAC1B,sBAAsB,CAAC,CAAC,YAAY,EAAE,EAAE,CACtC,KAAK,CAAC,WAAW;wBACf,CAAC,CAAC,CAAC,GAAG,YAAY,EAAE,KAAK,CAAC,WAAW,CAAC;wBACtC,CAAC,CAAC,YAAY,CACjB,CAAC;gBACJ,CAAC,CACF,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,SAAS,CAAC,GAAG,EAAE;QACb,wBAAwB,EAAE,CAAC;QAC3B,MAAM,oBAAoB,GAAG,gBAAgB,CAAC,OAAO,CAAC;QAEtD,OAAO,GAAG,EAAE;YACV,oBAAoB,CAAC,cAAc,EAAE,MAAM,EAAE,CAAC;YAC9C,oBAAoB,CAAC,aAAa,EAAE,MAAM,EAAE,CAAC;YAC7C,oBAAoB,CAAC,mBAAmB,EAAE,MAAM,EAAE,CAAC;YACnD,aAAa,EAAE,CAAC;YAChB,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,wBAAwB,CAAC,CAAC,CAAC;IAE/B,OAAO;QACL,SAAS;QACT,QAAQ;QACR,mBAAmB;QACnB,aAAa;QACb,iBAAiB;QACjB,iBAAiB;QACjB,kBAAkB;QAClB,eAAe;QACf,oBAAoB;QACpB,qBAAqB,EAAE,6BAA6B;QACpD,oBAAoB,EAAE,4BAA4B;QAClD,WAAW,EAAE,mBAAmB;QAChC,gBAAgB,EAAE,wBAAwB;QAC1C,eAAe,EAAE,uBAAuB;KACzC,CAAC;AACJ,CAAC","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
30
|
|
|
31
|
-
|
|
31
|
+
This plugin automatically configures Android BILLING permissions and iOS setup, making it plug-and-play for managed workflows with a development client.
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
## Managed Expo Projects
|
|
34
34
|
|
|
35
|
-
> **
|
|
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.
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
1. **Run Your App**
|
|
39
|
+
After adding the package and configuring the plugin, build a development client and test with:
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
```bash
|
|
42
|
+
expo run:android
|
|
43
|
+
expo run:ios
|
|
44
|
+
```
|
|
40
45
|
|
|
41
|
-
|
|
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`)
|
|
42
52
|
|
|
43
|
-
|
|
44
|
-
npm install expo-iap
|
|
45
|
-
```
|
|
53
|
+
## Bare React Native Projects
|
|
46
54
|
|
|
47
|
-
|
|
55
|
+
Ensure the [`expo` package is installed and configured](https://docs.expo.dev/bare/installing-expo-modules/) before proceeding.
|
|
56
|
+
|
|
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.
|
|
@@ -147,6 +146,7 @@ This section describes purchase properties in `expo-iap`.
|
|
|
147
146
|
### Android-Only Purchase Types
|
|
148
147
|
|
|
149
148
|
- **`ProductPurchase`**:
|
|
149
|
+
|
|
150
150
|
- Adds the following properties specific to in-app product purchases:
|
|
151
151
|
- **`ids`**: `string[]` - A list of product IDs associated with the purchase (for multi-item purchases).
|
|
152
152
|
- **`dataAndroid`**: `string` - The raw purchase data from Google Play (e.g., JSON payload).
|
|
@@ -154,6 +154,7 @@ This section describes purchase properties in `expo-iap`.
|
|
|
154
154
|
- **`purchaseStateAndroid`**: `number` - The state of the purchase (e.g., 0 = purchased, 1 = canceled, 2 = pending).
|
|
155
155
|
|
|
156
156
|
- **`SubscriptionPurchase`**:
|
|
157
|
+
|
|
157
158
|
- Extends the base properties and includes:
|
|
158
159
|
- **`autoRenewingAndroid`**: `boolean` - Indicates whether the subscription automatically renews (true) or not (false).
|
|
159
160
|
|
|
@@ -163,6 +164,7 @@ This section describes purchase properties in `expo-iap`.
|
|
|
163
164
|
### iOS-Only Purchase Types
|
|
164
165
|
|
|
165
166
|
- **`ProductPurchase`**:
|
|
167
|
+
|
|
166
168
|
- Extends the base purchase properties with iOS-specific fields:
|
|
167
169
|
- **`quantityIos`**: `number` - The quantity of the product purchased (e.g., how many units of an item were bought).
|
|
168
170
|
- **`expirationDateIos`**: `number?` - The expiration date of the purchase as a Unix timestamp (in milliseconds), if applicable (optional, may be null for non-expiring products).
|
|
@@ -182,10 +184,6 @@ This section describes purchase properties in `expo-iap`.
|
|
|
182
184
|
|
|
183
185
|
Transactions are mapped directly to `Purchase` or `SubscriptionPurchase` with platform-specific fields (e.g., `expirationDateIos`, `purchaseStateAndroid`).
|
|
184
186
|
|
|
185
|
-
### Status
|
|
186
|
-
|
|
187
|
-
This module is under development—expect occasional bugs (e.g., Android acknowledgment issues). Test thoroughly and consider contributing fixes!
|
|
188
|
-
|
|
189
187
|
> **Sample Code**: See [example/App.tsx](https://github.com/hyochan/expo-iap/blob/main/example/App.tsx).
|
|
190
188
|
|
|
191
189
|
## Implementation
|
|
@@ -257,7 +255,7 @@ export default function SimpleIAP() {
|
|
|
257
255
|
|
|
258
256
|
## Using useIAP Hook
|
|
259
257
|
|
|
260
|
-
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`).
|
|
261
259
|
|
|
262
260
|
```tsx
|
|
263
261
|
import {useEffect, useState} from 'react';
|
|
@@ -272,13 +270,8 @@ import {
|
|
|
272
270
|
InteractionManager,
|
|
273
271
|
Alert,
|
|
274
272
|
} from 'react-native';
|
|
275
|
-
import {
|
|
276
|
-
import type {
|
|
277
|
-
PurchaseError,
|
|
278
|
-
ProductPurchase,
|
|
279
|
-
SubscriptionProduct,
|
|
280
|
-
} from 'expo-iap';
|
|
281
|
-
import {RequestSubscriptionAndroidProps} from './types/ExpoIapAndroid.types'; // Adjust path as needed
|
|
273
|
+
import {useIAP} from 'expo-iap';
|
|
274
|
+
import type {ProductPurchase, SubscriptionProduct} from 'expo-iap';
|
|
282
275
|
|
|
283
276
|
// Define SKUs
|
|
284
277
|
const productSkus = [
|
|
@@ -294,12 +287,7 @@ const subscriptionSkus = [
|
|
|
294
287
|
];
|
|
295
288
|
|
|
296
289
|
// Define operations
|
|
297
|
-
const operations = [
|
|
298
|
-
'initConnection',
|
|
299
|
-
'getProducts',
|
|
300
|
-
'getSubscriptions',
|
|
301
|
-
'endConnection',
|
|
302
|
-
];
|
|
290
|
+
const operations = ['getProducts', 'getSubscriptions'] as const;
|
|
303
291
|
type Operation = (typeof operations)[number];
|
|
304
292
|
|
|
305
293
|
export default function IAPWithHook() {
|
|
@@ -316,35 +304,25 @@ export default function IAPWithHook() {
|
|
|
316
304
|
requestSubscription,
|
|
317
305
|
} = useIAP();
|
|
318
306
|
|
|
319
|
-
const [
|
|
307
|
+
const [isReady, setIsReady] = useState(false);
|
|
320
308
|
|
|
321
|
-
//
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
try {
|
|
339
|
-
await getSubscriptions(subscriptionSkus);
|
|
340
|
-
} catch (error) {
|
|
341
|
-
console.error('Error fetching subscriptions:', error);
|
|
342
|
-
}
|
|
343
|
-
break;
|
|
344
|
-
default:
|
|
345
|
-
console.log('Unknown operation');
|
|
346
|
-
}
|
|
347
|
-
};
|
|
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]);
|
|
348
326
|
|
|
349
327
|
// Handle purchase updates and errors
|
|
350
328
|
useEffect(() => {
|
|
@@ -353,22 +331,52 @@ export default function IAPWithHook() {
|
|
|
353
331
|
try {
|
|
354
332
|
await finishTransaction({
|
|
355
333
|
purchase: currentPurchase,
|
|
356
|
-
isConsumable:
|
|
334
|
+
isConsumable: currentPurchase.productType === 'inapp', // Consumable for in-app products
|
|
357
335
|
});
|
|
358
|
-
Alert.alert('Purchase
|
|
336
|
+
Alert.alert('Purchase Successful', JSON.stringify(currentPurchase));
|
|
359
337
|
} catch (error) {
|
|
360
338
|
console.error('Error finishing transaction:', error);
|
|
339
|
+
Alert.alert('Transaction Error', String(error));
|
|
361
340
|
}
|
|
362
341
|
});
|
|
363
342
|
}
|
|
364
343
|
|
|
365
344
|
if (currentPurchaseError) {
|
|
366
345
|
InteractionManager.runAfterInteractions(() => {
|
|
367
|
-
Alert.alert('Purchase
|
|
346
|
+
Alert.alert('Purchase Error', JSON.stringify(currentPurchaseError));
|
|
368
347
|
});
|
|
369
348
|
}
|
|
370
349
|
}, [currentPurchase, currentPurchaseError, finishTransaction]);
|
|
371
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
|
+
|
|
372
380
|
return (
|
|
373
381
|
<SafeAreaView style={styles.container}>
|
|
374
382
|
<Text style={styles.title}>Expo IAP with useIAP Hook</Text>
|
|
@@ -387,82 +395,61 @@ export default function IAPWithHook() {
|
|
|
387
395
|
</ScrollView>
|
|
388
396
|
</View>
|
|
389
397
|
<View style={styles.content}>
|
|
390
|
-
{!
|
|
391
|
-
<Text>
|
|
398
|
+
{!isReady ? (
|
|
399
|
+
<Text>Loading...</Text>
|
|
392
400
|
) : (
|
|
393
401
|
<View style={{gap: 12}}>
|
|
394
402
|
<Text style={{fontSize: 20}}>Products</Text>
|
|
395
|
-
{products.map((item) =>
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
</Text>
|
|
416
|
-
<Button
|
|
417
|
-
title="Buy"
|
|
418
|
-
onPress={() => requestPurchase({sku: item.id})}
|
|
419
|
-
/>
|
|
420
|
-
</View>
|
|
421
|
-
);
|
|
422
|
-
}
|
|
423
|
-
})}
|
|
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
|
+
))}
|
|
424
423
|
|
|
425
424
|
<Text style={{fontSize: 20}}>Subscriptions</Text>
|
|
426
|
-
{subscriptions.map((item) =>
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
subscriptionOffers:
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
<View key={item.id} style={{gap: 12}}>
|
|
455
|
-
<Text>
|
|
456
|
-
{item.displayName} - {item.displayPrice}
|
|
457
|
-
</Text>
|
|
458
|
-
<Button
|
|
459
|
-
title="Subscribe"
|
|
460
|
-
onPress={() => requestSubscription({sku: item.id})}
|
|
461
|
-
/>
|
|
462
|
-
</View>
|
|
463
|
-
);
|
|
464
|
-
}
|
|
465
|
-
})}
|
|
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
|
+
))}
|
|
466
453
|
</View>
|
|
467
454
|
)}
|
|
468
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 = {
|