expo-iap 3.0.3 → 3.0.5
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/.eslintignore +1 -1
- package/.eslintrc.js +1 -0
- package/.prettierignore +1 -0
- package/CHANGELOG.md +13 -0
- package/CLAUDE.md +2 -0
- package/CONTRIBUTING.md +10 -0
- package/android/build.gradle +1 -1
- package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +12 -12
- package/android/src/main/java/expo/modules/iap/PromiseUtils.kt +2 -2
- package/build/helpers/subscription.d.ts +1 -12
- package/build/helpers/subscription.d.ts.map +1 -1
- package/build/helpers/subscription.js +12 -7
- package/build/helpers/subscription.js.map +1 -1
- package/build/index.d.ts +34 -18
- package/build/index.d.ts.map +1 -1
- package/build/index.js +40 -17
- package/build/index.js.map +1 -1
- package/build/modules/android.d.ts +5 -5
- package/build/modules/android.d.ts.map +1 -1
- package/build/modules/android.js +17 -4
- package/build/modules/android.js.map +1 -1
- package/build/modules/ios.d.ts +4 -8
- package/build/modules/ios.d.ts.map +1 -1
- package/build/modules/ios.js.map +1 -1
- package/build/purchase-error.d.ts +67 -0
- package/build/purchase-error.d.ts.map +1 -0
- package/build/purchase-error.js +166 -0
- package/build/purchase-error.js.map +1 -0
- package/build/types.d.ts +604 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +42 -0
- package/build/types.js.map +1 -0
- package/build/useIAP.d.ts +8 -10
- package/build/useIAP.d.ts.map +1 -1
- package/build/useIAP.js +1 -1
- package/build/useIAP.js.map +1 -1
- package/build/utils/errorMapping.d.ts +1 -1
- package/build/utils/errorMapping.d.ts.map +1 -1
- package/build/utils/errorMapping.js +19 -3
- package/build/utils/errorMapping.js.map +1 -1
- package/ios/ExpoIap.podspec +1 -1
- package/ios/ExpoIapModule.swift +103 -89
- package/jest.config.js +1 -1
- package/package.json +2 -1
- package/plugin/build/withIAP.js +4 -5
- package/plugin/src/withIAP.ts +4 -5
- package/scripts/update-types.mjs +61 -0
- package/src/helpers/subscription.ts +12 -20
- package/src/index.ts +89 -41
- package/src/modules/android.ts +24 -8
- package/src/modules/ios.ts +7 -11
- package/src/purchase-error.ts +265 -0
- package/src/types.ts +705 -0
- package/src/useIAP.ts +16 -16
- package/src/utils/errorMapping.ts +24 -3
- package/build/ExpoIap.types.d.ts +0 -307
- package/build/ExpoIap.types.d.ts.map +0 -1
- package/build/ExpoIap.types.js +0 -235
- package/build/ExpoIap.types.js.map +0 -1
- package/build/types/ExpoIapAndroid.types.d.ts +0 -114
- package/build/types/ExpoIapAndroid.types.d.ts.map +0 -1
- package/build/types/ExpoIapAndroid.types.js +0 -29
- package/build/types/ExpoIapAndroid.types.js.map +0 -1
- package/build/types/ExpoIapIOS.types.d.ts +0 -149
- package/build/types/ExpoIapIOS.types.d.ts.map +0 -1
- package/build/types/ExpoIapIOS.types.js +0 -8
- package/build/types/ExpoIapIOS.types.js.map +0 -1
- package/src/ExpoIap.types.ts +0 -444
- package/src/types/ExpoIapAndroid.types.ts +0 -133
- package/src/types/ExpoIapIOS.types.ts +0 -172
package/.eslintignore
CHANGED
package/.eslintrc.js
CHANGED
package/.prettierignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
src/types.ts
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 3.0.5 - 2025-09-17
|
|
4
|
+
|
|
5
|
+
- Types: Normalize OpenIAP literal unions to `'in-app'`, `'ios'`, and `'android'`, update `useIAP` helpers to the new `PurchaseRequestInput`, and refresh docs/examples/tests to the lowercase schema tokens.
|
|
6
|
+
- Tooling: Regenerate `src/types.ts` with `openiap-gql` 1.0.2, add lint/format ignores for the generated file, and document the type update workflow.
|
|
7
|
+
- Native: Upgrade the Android fallback/config plugin to [openiap-google 1.1.10](https://github.com/hyodotdev/openiap-google/releases/tag/1.1.10), bump the iOS pod to [openiap 1.1.12](https://github.com/hyodotdev/openiap-apple/releases/tag/1.1.12), adopt PascalCase error codes, and wire the new request parameter models through the bridges and config plugin.
|
|
8
|
+
|
|
9
|
+
## 3.0.4 - 2025-09-16
|
|
10
|
+
|
|
11
|
+
- Types: Regenerate the OpenIAP schema with the canonical PascalCase names (`ProductIOS`, `PurchaseIOS`, etc.) and align docs/tests/examples with the new exports.
|
|
12
|
+
- Errors: Promote `PurchaseError` to extend `Error`, tighten typings for platform error input/output, and ensure Android acknowledgement resolves a `VoidResult {success}` object.
|
|
13
|
+
- Docs: Refresh iOS setup examples to use platform-specific request shapes, fix legacy `ErrorCode` references in versioned guides, and trim example helpers to the updated API surface.
|
|
14
|
+
- Build: Adopt [openiap-gql 1.0.0](https://github.com/hyodotdev/openiap-gql/releases/tag/1.0.0) for the transport layer to stay aligned with the GraphQL contract shipped across the ecosystem.
|
|
15
|
+
|
|
3
16
|
## 3.0.3 - 2025-09-14
|
|
4
17
|
|
|
5
18
|
- Types: Align Expo IAP surface with [react-native-iap #3006](https://github.com/hyochan/react-native-iap/pull/3006) by renaming subscription aliases, adding StoreKit product enums, and exposing optional purchase metadata (quantity, purchaseState, isAutoRenewing).
|
package/CLAUDE.md
CHANGED
|
@@ -62,6 +62,8 @@ For complete type definitions and documentation, see: <https://www.openiap.dev/d
|
|
|
62
62
|
|
|
63
63
|
The library follows the OpenIAP type specifications with platform-specific extensions using iOS/Android suffixes.
|
|
64
64
|
|
|
65
|
+
> **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.
|
|
66
|
+
|
|
65
67
|
### React/JSX Conventions
|
|
66
68
|
|
|
67
69
|
- **Conditional Rendering**: Use ternary operator with null instead of logical AND
|
package/CONTRIBUTING.md
CHANGED
|
@@ -213,6 +213,16 @@ For detailed code conventions, naming standards, and implementation guidelines,
|
|
|
213
213
|
- Pre-commit checks
|
|
214
214
|
- OpenIAP specification compliance
|
|
215
215
|
|
|
216
|
+
### Updating OpenIAP Types
|
|
217
|
+
|
|
218
|
+
The generated TypeScript definitions in `src/types.ts` come from the [`openiap-gql`](https://github.com/hyodotdev/openiap-gql) release artifacts. Never edit this file by hand. When the schema changes or you need to pull newer types:
|
|
219
|
+
|
|
220
|
+
- Run `bun run generate:types` to download the latest pinned release and overwrite `src/types.ts`.
|
|
221
|
+
- To target a specific release, pass the tag: `bun run generate:types --tag <version>`.
|
|
222
|
+
- Commit the updated file alongside any related schema or documentation changes.
|
|
223
|
+
|
|
224
|
+
Always ensure the repository builds and tests succeed after regenerating the types.
|
|
225
|
+
|
|
216
226
|
### Development Workflow
|
|
217
227
|
|
|
218
228
|
1. **Before starting work**:
|
package/android/build.gradle
CHANGED
|
@@ -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.
|
|
61
|
+
implementation "io.github.hyochan.openiap:openiap-google:1.1.10"
|
|
62
62
|
}
|
|
63
63
|
}
|
|
@@ -6,7 +6,7 @@ import dev.hyo.openiap.OpenIapError
|
|
|
6
6
|
import dev.hyo.openiap.OpenIapModule
|
|
7
7
|
import dev.hyo.openiap.models.DeepLinkOptions
|
|
8
8
|
import dev.hyo.openiap.models.ProductRequest
|
|
9
|
-
import dev.hyo.openiap.models.
|
|
9
|
+
import dev.hyo.openiap.models.RequestPurchaseParams
|
|
10
10
|
import expo.modules.kotlin.Promise
|
|
11
11
|
import expo.modules.kotlin.exception.Exceptions
|
|
12
12
|
import expo.modules.kotlin.modules.Module
|
|
@@ -103,7 +103,7 @@ class ExpoIapModule : Module() {
|
|
|
103
103
|
if (!ok) {
|
|
104
104
|
// Clear any buffered events from a failed init
|
|
105
105
|
pendingEvents.clear()
|
|
106
|
-
promise.reject(OpenIapError.
|
|
106
|
+
promise.reject(OpenIapError.InitConnection.CODE, "Failed to initialize connection", null)
|
|
107
107
|
return@withLock
|
|
108
108
|
}
|
|
109
109
|
|
|
@@ -118,7 +118,7 @@ class ExpoIapModule : Module() {
|
|
|
118
118
|
|
|
119
119
|
promise.resolve(true)
|
|
120
120
|
} catch (e: Exception) {
|
|
121
|
-
promise.reject(OpenIapError.
|
|
121
|
+
promise.reject(OpenIapError.InitConnection.CODE, e.message, e)
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
}
|
|
@@ -143,7 +143,7 @@ class ExpoIapModule : Module() {
|
|
|
143
143
|
val products = openIap.fetchProducts(ProductRequest(skuArr.toList(), reqType))
|
|
144
144
|
promise.resolve(products.map { it.toJSON() })
|
|
145
145
|
} catch (e: Exception) {
|
|
146
|
-
promise.reject(OpenIapError.
|
|
146
|
+
promise.reject(OpenIapError.QueryProduct.CODE, e.message, null)
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
}
|
|
@@ -154,7 +154,7 @@ class ExpoIapModule : Module() {
|
|
|
154
154
|
val purchases = openIap.getAvailablePurchases(null)
|
|
155
155
|
promise.resolve(purchases.map { it.toJSON() })
|
|
156
156
|
} catch (e: Exception) {
|
|
157
|
-
promise.reject(OpenIapError.
|
|
157
|
+
promise.reject(OpenIapError.ServiceUnavailable.CODE, e.message, null)
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
}
|
|
@@ -168,7 +168,7 @@ class ExpoIapModule : Module() {
|
|
|
168
168
|
openIap.deepLinkToSubscriptions(DeepLinkOptions(sku, packageName))
|
|
169
169
|
promise.resolve(null)
|
|
170
170
|
} catch (e: Exception) {
|
|
171
|
-
promise.reject(OpenIapError.
|
|
171
|
+
promise.reject(OpenIapError.ServiceUnavailable.CODE, e.message, null)
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
174
|
}
|
|
@@ -180,7 +180,7 @@ class ExpoIapModule : Module() {
|
|
|
180
180
|
val code = openIap.getStorefront()
|
|
181
181
|
promise.resolve(code)
|
|
182
182
|
} catch (e: Exception) {
|
|
183
|
-
promise.reject(OpenIapError.
|
|
183
|
+
promise.reject(OpenIapError.ServiceUnavailable.CODE, e.message, e)
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
186
|
}
|
|
@@ -204,7 +204,7 @@ class ExpoIapModule : Module() {
|
|
|
204
204
|
val reqType = ProductRequest.ProductRequestType.fromString(type)
|
|
205
205
|
val result =
|
|
206
206
|
openIap.requestPurchase(
|
|
207
|
-
|
|
207
|
+
RequestPurchaseParams(
|
|
208
208
|
skus = skus,
|
|
209
209
|
obfuscatedAccountIdAndroid = obfuscatedAccountId,
|
|
210
210
|
obfuscatedProfileIdAndroid = obfuscatedProfileId,
|
|
@@ -223,7 +223,7 @@ class ExpoIapModule : Module() {
|
|
|
223
223
|
} catch (e: Exception) {
|
|
224
224
|
val errorMap =
|
|
225
225
|
mapOf(
|
|
226
|
-
"code" to OpenIapError.
|
|
226
|
+
"code" to OpenIapError.PurchaseFailed.CODE,
|
|
227
227
|
"message" to (e.message ?: "Purchase failed"),
|
|
228
228
|
"platform" to "android",
|
|
229
229
|
)
|
|
@@ -235,7 +235,7 @@ class ExpoIapModule : Module() {
|
|
|
235
235
|
// Reject and clear any pending promises for this purchase flow
|
|
236
236
|
PromiseUtils.rejectPromisesForKey(
|
|
237
237
|
PromiseUtils.PROMISE_BUY_ITEM,
|
|
238
|
-
OpenIapError.
|
|
238
|
+
OpenIapError.PurchaseFailed.CODE,
|
|
239
239
|
e.message,
|
|
240
240
|
null,
|
|
241
241
|
)
|
|
@@ -249,7 +249,7 @@ class ExpoIapModule : Module() {
|
|
|
249
249
|
openIap.acknowledgePurchaseAndroid(token)
|
|
250
250
|
promise.resolve(mapOf("responseCode" to 0))
|
|
251
251
|
} catch (e: Exception) {
|
|
252
|
-
promise.reject(OpenIapError.
|
|
252
|
+
promise.reject(OpenIapError.ServiceUnavailable.CODE, e.message, null)
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
255
|
}
|
|
@@ -261,7 +261,7 @@ class ExpoIapModule : Module() {
|
|
|
261
261
|
openIap.consumePurchaseAndroid(token)
|
|
262
262
|
promise.resolve(mapOf("responseCode" to 0, "purchaseToken" to token))
|
|
263
263
|
} catch (e: Exception) {
|
|
264
|
-
promise.reject(OpenIapError.
|
|
264
|
+
promise.reject(OpenIapError.ServiceUnavailable.CODE, e.message, null)
|
|
265
265
|
}
|
|
266
266
|
}
|
|
267
267
|
}
|
|
@@ -32,7 +32,7 @@ object PromiseUtils {
|
|
|
32
32
|
fun rejectAllPendingPromises() {
|
|
33
33
|
// Snapshot to avoid concurrent modification
|
|
34
34
|
promises.values.flatMap { it.toList() }.forEach { promise ->
|
|
35
|
-
promise.safeReject(OpenIapError.
|
|
35
|
+
promise.safeReject(OpenIapError.ServiceDisconnected.CODE, "Connection has been closed", null)
|
|
36
36
|
}
|
|
37
37
|
promises.clear()
|
|
38
38
|
}
|
|
@@ -60,7 +60,7 @@ fun Promise.safeResolve(value: Any?) {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
fun Promise.safeReject(message: String) = this.safeReject(
|
|
63
|
+
fun Promise.safeReject(message: String) = this.safeReject(OpenIapError.UnknownError.CODE, message, null)
|
|
64
64
|
|
|
65
65
|
fun Promise.safeReject(
|
|
66
66
|
code: String,
|
|
@@ -1,15 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
productId: string;
|
|
3
|
-
isActive: boolean;
|
|
4
|
-
transactionId: string;
|
|
5
|
-
purchaseToken?: string;
|
|
6
|
-
transactionDate: number;
|
|
7
|
-
expirationDateIOS?: Date;
|
|
8
|
-
autoRenewingAndroid?: boolean;
|
|
9
|
-
environmentIOS?: string;
|
|
10
|
-
willExpireSoon?: boolean;
|
|
11
|
-
daysUntilExpirationIOS?: number;
|
|
12
|
-
}
|
|
1
|
+
import type { ActiveSubscription } from '../types';
|
|
13
2
|
/**
|
|
14
3
|
* Get all active subscriptions with detailed information
|
|
15
4
|
* @param subscriptionIds - Optional array of subscription product IDs to filter. If not provided, returns all active subscriptions.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subscription.d.ts","sourceRoot":"","sources":["../../src/helpers/subscription.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"subscription.d.ts","sourceRoot":"","sources":["../../src/helpers/subscription.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,UAAU,CAAC;AAEjD;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GACjC,kBAAkB,MAAM,EAAE,KACzB,OAAO,CAAC,kBAAkB,EAAE,CAiH9B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GACjC,kBAAkB,MAAM,EAAE,KACzB,OAAO,CAAC,OAAO,CAGjB,CAAC"}
|
|
@@ -65,30 +65,35 @@ export const getActiveSubscriptions = async (subscriptionIds) => {
|
|
|
65
65
|
const subscription = {
|
|
66
66
|
productId: purchase.productId,
|
|
67
67
|
isActive: true,
|
|
68
|
-
|
|
69
|
-
transactionId: purchase.id,
|
|
68
|
+
transactionId: String(purchase.id),
|
|
70
69
|
purchaseToken: purchase.purchaseToken,
|
|
71
70
|
transactionDate: purchase.transactionDate,
|
|
72
71
|
};
|
|
73
72
|
// Add platform-specific details
|
|
74
73
|
if (Platform.OS === 'ios') {
|
|
75
74
|
if ('expirationDateIOS' in purchase && purchase.expirationDateIOS) {
|
|
76
|
-
|
|
77
|
-
subscription.expirationDateIOS = expirationDate;
|
|
75
|
+
subscription.expirationDateIOS = purchase.expirationDateIOS;
|
|
78
76
|
// Calculate days until expiration (round to nearest day)
|
|
79
77
|
const daysUntilExpiration = Math.round((purchase.expirationDateIOS - currentTime) / (1000 * 60 * 60 * 24));
|
|
80
78
|
subscription.daysUntilExpirationIOS = daysUntilExpiration;
|
|
81
79
|
subscription.willExpireSoon = daysUntilExpiration <= 7;
|
|
82
80
|
}
|
|
83
81
|
if ('environmentIOS' in purchase) {
|
|
84
|
-
subscription.environmentIOS = purchase.environmentIOS;
|
|
82
|
+
subscription.environmentIOS = purchase.environmentIOS ?? undefined;
|
|
85
83
|
}
|
|
86
84
|
}
|
|
87
85
|
else if (Platform.OS === 'android') {
|
|
88
86
|
if ('autoRenewingAndroid' in purchase) {
|
|
89
|
-
|
|
87
|
+
if (typeof purchase.autoRenewingAndroid !== 'undefined') {
|
|
88
|
+
subscription.autoRenewingAndroid = purchase.autoRenewingAndroid;
|
|
89
|
+
}
|
|
90
90
|
// If auto-renewing is false, subscription will expire soon
|
|
91
|
-
|
|
91
|
+
if (purchase.autoRenewingAndroid === false) {
|
|
92
|
+
subscription.willExpireSoon = true;
|
|
93
|
+
}
|
|
94
|
+
else if (purchase.autoRenewingAndroid === true) {
|
|
95
|
+
subscription.willExpireSoon = false;
|
|
96
|
+
}
|
|
92
97
|
}
|
|
93
98
|
}
|
|
94
99
|
activeSubscriptions.push(subscription);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subscription.js","sourceRoot":"","sources":["../../src/helpers/subscription.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAAC,MAAM,cAAc,CAAC;AACtC,OAAO,EAAC,qBAAqB,EAAC,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"subscription.js","sourceRoot":"","sources":["../../src/helpers/subscription.ts"],"names":[],"mappings":"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","sourcesContent":["import {Platform} from 'react-native';\nimport {getAvailablePurchases} from '../index';\nimport type {ActiveSubscription} from '../types';\n\n/**\n * Get all active subscriptions with detailed information\n * @param subscriptionIds - Optional array of subscription product IDs to filter. If not provided, returns all active subscriptions.\n * @returns Promise<ActiveSubscription[]> array of active subscriptions with details\n */\nexport const getActiveSubscriptions = async (\n subscriptionIds?: string[],\n): Promise<ActiveSubscription[]> => {\n try {\n const purchases = await getAvailablePurchases();\n const currentTime = Date.now();\n const activeSubscriptions: ActiveSubscription[] = [];\n\n // Filter purchases to find active subscriptions\n const filteredPurchases = purchases.filter((purchase) => {\n // If specific IDs provided, filter by them\n if (subscriptionIds && subscriptionIds.length > 0) {\n if (!subscriptionIds.includes(purchase.productId)) {\n return false;\n }\n }\n\n // Check if this purchase has subscription-specific fields\n const hasSubscriptionFields =\n ('expirationDateIOS' in purchase && !!purchase.expirationDateIOS) ||\n 'autoRenewingAndroid' in purchase ||\n ('environmentIOS' in purchase && !!(purchase as any).environmentIOS);\n\n if (!hasSubscriptionFields) {\n return false;\n }\n\n // Check if it's actually active\n if (Platform.OS === 'ios') {\n if ('expirationDateIOS' in purchase && purchase.expirationDateIOS) {\n return purchase.expirationDateIOS > currentTime;\n }\n // For iOS purchases without expiration date (like Sandbox), we consider them active\n // if they have the environmentIOS field and were created recently\n if ('environmentIOS' in purchase && purchase.environmentIOS) {\n const dayInMs = 24 * 60 * 60 * 1000;\n // If no expiration date, consider active if transaction is recent (within 24 hours for Sandbox)\n if (\n !('expirationDateIOS' in purchase) ||\n !purchase.expirationDateIOS\n ) {\n if (\n purchase.environmentIOS === 'Sandbox' &&\n purchase.transactionDate &&\n currentTime - purchase.transactionDate < dayInMs\n ) {\n return true;\n }\n }\n }\n } else if (Platform.OS === 'android') {\n // For Android, if it's in the purchases list, it's active\n return true;\n }\n\n return false;\n });\n\n // Deduplicate by transaction identifier (id)\n const seen = new Set<string>();\n const dedupedPurchases = filteredPurchases.filter((p) => {\n const key = String(p.id);\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n\n // Convert to ActiveSubscription format\n for (const purchase of dedupedPurchases) {\n const subscription: ActiveSubscription = {\n productId: purchase.productId,\n isActive: true,\n transactionId: String(purchase.id),\n purchaseToken: purchase.purchaseToken,\n transactionDate: purchase.transactionDate,\n };\n\n // Add platform-specific details\n if (Platform.OS === 'ios') {\n if ('expirationDateIOS' in purchase && purchase.expirationDateIOS) {\n subscription.expirationDateIOS = purchase.expirationDateIOS;\n\n // Calculate days until expiration (round to nearest day)\n const daysUntilExpiration = Math.round(\n (purchase.expirationDateIOS - currentTime) / (1000 * 60 * 60 * 24),\n );\n subscription.daysUntilExpirationIOS = daysUntilExpiration;\n subscription.willExpireSoon = daysUntilExpiration <= 7;\n }\n\n if ('environmentIOS' in purchase) {\n subscription.environmentIOS = purchase.environmentIOS ?? undefined;\n }\n } else if (Platform.OS === 'android') {\n if ('autoRenewingAndroid' in purchase) {\n if (typeof purchase.autoRenewingAndroid !== 'undefined') {\n subscription.autoRenewingAndroid = purchase.autoRenewingAndroid;\n }\n // If auto-renewing is false, subscription will expire soon\n if (purchase.autoRenewingAndroid === false) {\n subscription.willExpireSoon = true;\n } else if (purchase.autoRenewingAndroid === true) {\n subscription.willExpireSoon = false;\n }\n }\n }\n\n activeSubscriptions.push(subscription);\n }\n\n return activeSubscriptions;\n } catch (error) {\n console.error('Error getting active subscriptions:', error);\n return [];\n }\n};\n\n/**\n * Check if user has any active subscriptions\n * @param subscriptionIds - Optional array of subscription product IDs to check. If not provided, checks all subscriptions.\n * @returns Promise<boolean> true if user has at least one active subscription\n */\nexport const hasActiveSubscriptions = async (\n subscriptionIds?: string[],\n): Promise<boolean> => {\n const subscriptions = await getActiveSubscriptions(subscriptionIds);\n return subscriptions.length > 0;\n};\n"]}
|
package/build/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { Product, Purchase,
|
|
2
|
-
|
|
1
|
+
import { Product, Purchase, RequestPurchaseProps, RequestPurchasePropsByPlatforms, RequestSubscriptionPropsByPlatforms, ProductSubscription, VoidResult, ReceiptValidationResult } from './types';
|
|
2
|
+
import { PurchaseError } from './purchase-error';
|
|
3
|
+
export * from './types';
|
|
4
|
+
export { ErrorCodeUtils, ErrorCodeMapping } from './purchase-error';
|
|
3
5
|
export * from './modules/android';
|
|
4
6
|
export * from './modules/ios';
|
|
5
|
-
export { getActiveSubscriptions, hasActiveSubscriptions,
|
|
7
|
+
export { getActiveSubscriptions, hasActiveSubscriptions, } from './helpers/subscription';
|
|
6
8
|
export declare const PI: any;
|
|
7
9
|
export declare enum OpenIapEvent {
|
|
8
10
|
PurchaseUpdated = "purchase-updated",
|
|
@@ -16,6 +18,27 @@ export declare const emitter: {
|
|
|
16
18
|
};
|
|
17
19
|
removeListener: (eventName: string, listener: (...args: any[]) => void) => void;
|
|
18
20
|
};
|
|
21
|
+
/**
|
|
22
|
+
* TODO(v3.1.0): Remove legacy 'inapp' alias once downstream apps migrate to 'in-app'.
|
|
23
|
+
*/
|
|
24
|
+
export type ProductTypeInput = 'inapp' | 'in-app' | 'subs';
|
|
25
|
+
export type InAppTypeInput = Exclude<ProductTypeInput, 'subs'>;
|
|
26
|
+
type PurchaseRequestInApp = {
|
|
27
|
+
request: RequestPurchasePropsByPlatforms;
|
|
28
|
+
type?: InAppTypeInput;
|
|
29
|
+
};
|
|
30
|
+
type PurchaseRequestSubscription = {
|
|
31
|
+
request: RequestSubscriptionPropsByPlatforms;
|
|
32
|
+
type: 'subs';
|
|
33
|
+
};
|
|
34
|
+
export type PurchaseRequestInput = PurchaseRequestInApp | PurchaseRequestSubscription;
|
|
35
|
+
export type PurchaseRequest = {
|
|
36
|
+
request: RequestPurchaseProps;
|
|
37
|
+
type?: InAppTypeInput;
|
|
38
|
+
} | {
|
|
39
|
+
request: RequestSubscriptionPropsByPlatforms;
|
|
40
|
+
type: 'subs';
|
|
41
|
+
};
|
|
19
42
|
export declare const purchaseUpdatedListener: (listener: (event: Purchase) => void) => {
|
|
20
43
|
remove: () => void;
|
|
21
44
|
};
|
|
@@ -52,14 +75,14 @@ export declare function endConnection(): Promise<boolean>;
|
|
|
52
75
|
*
|
|
53
76
|
* @param params - Product fetch configuration
|
|
54
77
|
* @param params.skus - Array of product SKUs to fetch
|
|
55
|
-
* @param params.type - Type of products: '
|
|
78
|
+
* @param params.type - Type of products: 'in-app' for regular products (default) or 'subs' for subscriptions
|
|
56
79
|
*
|
|
57
80
|
* @example
|
|
58
81
|
* ```typescript
|
|
59
82
|
* // Regular products
|
|
60
83
|
* const products = await fetchProducts({
|
|
61
84
|
* skus: ['product1', 'product2'],
|
|
62
|
-
* type: '
|
|
85
|
+
* type: 'in-app'
|
|
63
86
|
* });
|
|
64
87
|
*
|
|
65
88
|
* // Subscriptions
|
|
@@ -71,7 +94,7 @@ export declare function endConnection(): Promise<boolean>;
|
|
|
71
94
|
*/
|
|
72
95
|
export declare const fetchProducts: ({ skus, type, }: {
|
|
73
96
|
skus: string[];
|
|
74
|
-
type?:
|
|
97
|
+
type?: ProductTypeInput;
|
|
75
98
|
}) => Promise<Product[] | ProductSubscription[]>;
|
|
76
99
|
export declare const getAvailablePurchases: ({ alsoPublishToEventListenerIOS, onlyIncludeActiveItemsIOS, }?: {
|
|
77
100
|
alsoPublishToEventListenerIOS?: boolean;
|
|
@@ -94,19 +117,12 @@ export declare const restorePurchases: (options?: {
|
|
|
94
117
|
alsoPublishToEventListenerIOS?: boolean;
|
|
95
118
|
onlyIncludeActiveItemsIOS?: boolean;
|
|
96
119
|
}) => Promise<Purchase[]>;
|
|
97
|
-
type PurchaseRequest = {
|
|
98
|
-
request: RequestPurchaseProps;
|
|
99
|
-
type?: 'inapp';
|
|
100
|
-
} | {
|
|
101
|
-
request: RequestSubscriptionProps;
|
|
102
|
-
type: 'subs';
|
|
103
|
-
};
|
|
104
120
|
/**
|
|
105
121
|
* Request a purchase for products or subscriptions.
|
|
106
122
|
*
|
|
107
123
|
* @param requestObj - Purchase request configuration
|
|
108
124
|
* @param requestObj.request - Platform-specific purchase parameters
|
|
109
|
-
* @param requestObj.type - Type of purchase: '
|
|
125
|
+
* @param requestObj.type - Type of purchase: 'in-app' for products (default) or 'subs' for subscriptions
|
|
110
126
|
*
|
|
111
127
|
* @example
|
|
112
128
|
* ```typescript
|
|
@@ -116,7 +132,7 @@ type PurchaseRequest = {
|
|
|
116
132
|
* ios: { sku: productId },
|
|
117
133
|
* android: { skus: [productId] }
|
|
118
134
|
* },
|
|
119
|
-
* type: '
|
|
135
|
+
* type: 'in-app'
|
|
120
136
|
* });
|
|
121
137
|
*
|
|
122
138
|
* // Subscription purchase
|
|
@@ -132,11 +148,11 @@ type PurchaseRequest = {
|
|
|
132
148
|
* });
|
|
133
149
|
* ```
|
|
134
150
|
*/
|
|
135
|
-
export declare const requestPurchase: (requestObj:
|
|
151
|
+
export declare const requestPurchase: (requestObj: PurchaseRequestInput) => Promise<Purchase | Purchase[] | void>;
|
|
136
152
|
export declare const finishTransaction: ({ purchase, isConsumable, }: {
|
|
137
153
|
purchase: Purchase;
|
|
138
154
|
isConsumable?: boolean;
|
|
139
|
-
}) => Promise<
|
|
155
|
+
}) => Promise<VoidResult | boolean>;
|
|
140
156
|
/**
|
|
141
157
|
* Retrieves the current storefront information from iOS App Store
|
|
142
158
|
*
|
|
@@ -173,7 +189,7 @@ export declare const validateReceipt: (sku: string, androidOptions?: {
|
|
|
173
189
|
productToken: string;
|
|
174
190
|
accessToken: string;
|
|
175
191
|
isSub?: boolean;
|
|
176
|
-
}) => Promise<
|
|
192
|
+
}) => Promise<ReceiptValidationResult>;
|
|
177
193
|
/**
|
|
178
194
|
* Deeplinks to native interface that allows users to manage their subscriptions
|
|
179
195
|
* @param options.skuAndroid - Required for Android to locate specific subscription (ignored on iOS)
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAmBA,OAAO,EACL,OAAO,EACP,QAAQ,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/build/index.js
CHANGED
|
@@ -6,9 +6,11 @@ import ExpoIapModule from './ExpoIapModule';
|
|
|
6
6
|
import { isProductIOS, validateReceiptIOS, deepLinkToSubscriptionsIOS, syncIOS, } from './modules/ios';
|
|
7
7
|
import { isProductAndroid, validateReceiptAndroid, deepLinkToSubscriptionsAndroid, } from './modules/android';
|
|
8
8
|
// Types
|
|
9
|
-
import {
|
|
9
|
+
import { ErrorCode, } from './types';
|
|
10
|
+
import { PurchaseError } from './purchase-error';
|
|
10
11
|
// Export all types
|
|
11
|
-
export * from './
|
|
12
|
+
export * from './types';
|
|
13
|
+
export { ErrorCodeUtils, ErrorCodeMapping } from './purchase-error';
|
|
12
14
|
export * from './modules/android';
|
|
13
15
|
export * from './modules/ios';
|
|
14
16
|
// Export subscription helpers
|
|
@@ -26,6 +28,24 @@ export function setValueAsync(value) {
|
|
|
26
28
|
}
|
|
27
29
|
// Ensure the emitter has proper EventEmitter interface
|
|
28
30
|
export const emitter = (ExpoIapModule || NativeModulesProxy.ExpoIap);
|
|
31
|
+
const normalizeProductType = (type) => {
|
|
32
|
+
if (type === 'inapp') {
|
|
33
|
+
console.warn("expo-iap: 'inapp' product type is deprecated and will be removed in v3.1.0. Use 'in-app' instead.");
|
|
34
|
+
}
|
|
35
|
+
if (!type || type === 'inapp' || type === 'in-app') {
|
|
36
|
+
return {
|
|
37
|
+
canonical: 'in-app',
|
|
38
|
+
native: 'inapp',
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (type === 'subs') {
|
|
42
|
+
return {
|
|
43
|
+
canonical: 'subs',
|
|
44
|
+
native: 'subs',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
throw new Error(`Unsupported product type: ${type}`);
|
|
48
|
+
};
|
|
29
49
|
export const purchaseUpdatedListener = (listener) => {
|
|
30
50
|
console.log('[JS] Registering purchaseUpdatedListener');
|
|
31
51
|
const wrappedListener = (event) => {
|
|
@@ -85,14 +105,14 @@ export async function endConnection() {
|
|
|
85
105
|
*
|
|
86
106
|
* @param params - Product fetch configuration
|
|
87
107
|
* @param params.skus - Array of product SKUs to fetch
|
|
88
|
-
* @param params.type - Type of products: '
|
|
108
|
+
* @param params.type - Type of products: 'in-app' for regular products (default) or 'subs' for subscriptions
|
|
89
109
|
*
|
|
90
110
|
* @example
|
|
91
111
|
* ```typescript
|
|
92
112
|
* // Regular products
|
|
93
113
|
* const products = await fetchProducts({
|
|
94
114
|
* skus: ['product1', 'product2'],
|
|
95
|
-
* type: '
|
|
115
|
+
* type: 'in-app'
|
|
96
116
|
* });
|
|
97
117
|
*
|
|
98
118
|
* // Subscriptions
|
|
@@ -102,15 +122,16 @@ export async function endConnection() {
|
|
|
102
122
|
* });
|
|
103
123
|
* ```
|
|
104
124
|
*/
|
|
105
|
-
export const fetchProducts = async ({ skus, type
|
|
125
|
+
export const fetchProducts = async ({ skus, type, }) => {
|
|
106
126
|
if (!skus?.length) {
|
|
107
127
|
throw new PurchaseError({
|
|
108
128
|
message: 'No SKUs provided',
|
|
109
129
|
code: ErrorCode.EmptySkuList,
|
|
110
130
|
});
|
|
111
131
|
}
|
|
132
|
+
const { canonical, native } = normalizeProductType(type);
|
|
112
133
|
if (Platform.OS === 'ios') {
|
|
113
|
-
const rawItems = await ExpoIapModule.fetchProducts({ skus, type });
|
|
134
|
+
const rawItems = await ExpoIapModule.fetchProducts({ skus, type: native });
|
|
114
135
|
const filteredItems = rawItems.filter((item) => {
|
|
115
136
|
if (!isProductIOS(item)) {
|
|
116
137
|
return false;
|
|
@@ -122,12 +143,12 @@ export const fetchProducts = async ({ skus, type = 'inapp', }) => {
|
|
|
122
143
|
skus.includes(item.id);
|
|
123
144
|
return isValid;
|
|
124
145
|
});
|
|
125
|
-
return
|
|
146
|
+
return canonical === 'in-app'
|
|
126
147
|
? filteredItems
|
|
127
148
|
: filteredItems;
|
|
128
149
|
}
|
|
129
150
|
if (Platform.OS === 'android') {
|
|
130
|
-
const items = await ExpoIapModule.fetchProducts(
|
|
151
|
+
const items = await ExpoIapModule.fetchProducts(native, skus);
|
|
131
152
|
const filteredItems = items.filter((item) => {
|
|
132
153
|
if (!isProductAndroid(item))
|
|
133
154
|
return false;
|
|
@@ -137,7 +158,7 @@ export const fetchProducts = async ({ skus, type = 'inapp', }) => {
|
|
|
137
158
|
typeof item.id === 'string' &&
|
|
138
159
|
skus.includes(item.id));
|
|
139
160
|
});
|
|
140
|
-
return
|
|
161
|
+
return canonical === 'in-app'
|
|
141
162
|
? filteredItems
|
|
142
163
|
: filteredItems;
|
|
143
164
|
}
|
|
@@ -195,7 +216,7 @@ const normalizeRequestProps = (request, platform) => {
|
|
|
195
216
|
*
|
|
196
217
|
* @param requestObj - Purchase request configuration
|
|
197
218
|
* @param requestObj.request - Platform-specific purchase parameters
|
|
198
|
-
* @param requestObj.type - Type of purchase: '
|
|
219
|
+
* @param requestObj.type - Type of purchase: 'in-app' for products (default) or 'subs' for subscriptions
|
|
199
220
|
*
|
|
200
221
|
* @example
|
|
201
222
|
* ```typescript
|
|
@@ -205,7 +226,7 @@ const normalizeRequestProps = (request, platform) => {
|
|
|
205
226
|
* ios: { sku: productId },
|
|
206
227
|
* android: { skus: [productId] }
|
|
207
228
|
* },
|
|
208
|
-
* type: '
|
|
229
|
+
* type: 'in-app'
|
|
209
230
|
* });
|
|
210
231
|
*
|
|
211
232
|
* // Subscription purchase
|
|
@@ -222,7 +243,9 @@ const normalizeRequestProps = (request, platform) => {
|
|
|
222
243
|
* ```
|
|
223
244
|
*/
|
|
224
245
|
export const requestPurchase = (requestObj) => {
|
|
225
|
-
const { request, type
|
|
246
|
+
const { request, type } = requestObj;
|
|
247
|
+
const { canonical, native } = normalizeProductType(type);
|
|
248
|
+
const isInAppPurchase = canonical === 'in-app';
|
|
226
249
|
if (Platform.OS === 'ios') {
|
|
227
250
|
const normalizedRequest = normalizeRequestProps(request, 'ios');
|
|
228
251
|
if (!normalizedRequest?.sku) {
|
|
@@ -238,7 +261,7 @@ export const requestPurchase = (requestObj) => {
|
|
|
238
261
|
quantity,
|
|
239
262
|
withOffer: offer,
|
|
240
263
|
});
|
|
241
|
-
return
|
|
264
|
+
return purchase;
|
|
242
265
|
})();
|
|
243
266
|
}
|
|
244
267
|
if (Platform.OS === 'android') {
|
|
@@ -246,11 +269,11 @@ export const requestPurchase = (requestObj) => {
|
|
|
246
269
|
if (!normalizedRequest?.skus?.length) {
|
|
247
270
|
throw new Error('Invalid request for Android. The `skus` property is required and must be a non-empty array.');
|
|
248
271
|
}
|
|
249
|
-
if (
|
|
272
|
+
if (isInAppPurchase) {
|
|
250
273
|
const { skus, obfuscatedAccountIdAndroid, obfuscatedProfileIdAndroid, isOfferPersonalized, } = normalizedRequest;
|
|
251
274
|
return (async () => {
|
|
252
275
|
return ExpoIapModule.requestPurchase({
|
|
253
|
-
type:
|
|
276
|
+
type: native,
|
|
254
277
|
skuArr: skus,
|
|
255
278
|
purchaseToken: undefined,
|
|
256
279
|
replacementMode: -1,
|
|
@@ -261,11 +284,11 @@ export const requestPurchase = (requestObj) => {
|
|
|
261
284
|
});
|
|
262
285
|
})();
|
|
263
286
|
}
|
|
264
|
-
if (
|
|
287
|
+
if (canonical === 'subs') {
|
|
265
288
|
const { skus, obfuscatedAccountIdAndroid, obfuscatedProfileIdAndroid, isOfferPersonalized, subscriptionOffers = [], replacementModeAndroid = -1, purchaseToken, } = normalizedRequest;
|
|
266
289
|
return (async () => {
|
|
267
290
|
return ExpoIapModule.requestPurchase({
|
|
268
|
-
type:
|
|
291
|
+
type: native,
|
|
269
292
|
skuArr: skus,
|
|
270
293
|
purchaseToken,
|
|
271
294
|
replacementMode: replacementModeAndroid,
|