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