expo-iap 2.2.7 → 2.2.8-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/ExpoIap.types.d.ts +1 -4
- package/build/ExpoIap.types.d.ts.map +1 -1
- package/build/ExpoIap.types.js +0 -5
- package/build/ExpoIap.types.js.map +1 -1
- package/build/index.d.ts +13 -3
- package/build/index.d.ts.map +1 -1
- package/build/index.js +72 -60
- package/build/index.js.map +1 -1
- package/build/modules/android.d.ts +0 -5
- package/build/modules/android.d.ts.map +1 -1
- package/build/modules/android.js +0 -3
- package/build/modules/android.js.map +1 -1
- package/build/modules/ios.d.ts +0 -5
- package/build/modules/ios.d.ts.map +1 -1
- package/build/modules/ios.js +0 -3
- package/build/modules/ios.js.map +1 -1
- package/build/useIap.d.ts +4 -2
- package/build/useIap.d.ts.map +1 -1
- package/build/useIap.js +10 -10
- package/build/useIap.js.map +1 -1
- package/iap.md +145 -161
- package/package.json +1 -1
- package/src/ExpoIap.types.ts +1 -5
- package/src/index.ts +139 -111
- package/src/modules/android.ts +0 -6
- package/src/modules/ios.ts +0 -6
- package/src/useIap.ts +18 -13
package/src/index.ts
CHANGED
|
@@ -5,7 +5,6 @@ import {Platform} from 'react-native';
|
|
|
5
5
|
import {
|
|
6
6
|
Product,
|
|
7
7
|
ProductPurchase,
|
|
8
|
-
ProductType,
|
|
9
8
|
Purchase,
|
|
10
9
|
PurchaseError,
|
|
11
10
|
PurchaseResult,
|
|
@@ -24,16 +23,13 @@ import {
|
|
|
24
23
|
RequestPurchaseIosProps,
|
|
25
24
|
RequestSubscriptionIosProps,
|
|
26
25
|
} from './types/ExpoIapIos.types';
|
|
27
|
-
import {isProductIos
|
|
28
|
-
import {
|
|
29
|
-
isProductAndroid,
|
|
30
|
-
isSubscriptionProductAndroid,
|
|
31
|
-
} from './modules/android';
|
|
26
|
+
import {isProductIos} from './modules/ios';
|
|
27
|
+
import {isProductAndroid} from './modules/android';
|
|
32
28
|
|
|
33
29
|
export * from './modules/android';
|
|
34
30
|
export * from './modules/ios';
|
|
35
31
|
|
|
36
|
-
// Get the native constant value
|
|
32
|
+
// Get the native constant value
|
|
37
33
|
export const PI = ExpoIapModule.PI;
|
|
38
34
|
|
|
39
35
|
export enum IapEvent {
|
|
@@ -81,11 +77,7 @@ export const getProducts = async (skus: string[]): Promise<Product[]> => {
|
|
|
81
77
|
return items.filter((item: unknown) => isProductIos<Product>(item));
|
|
82
78
|
},
|
|
83
79
|
android: async () => {
|
|
84
|
-
const products = await ExpoIapModule.getItemsByType(
|
|
85
|
-
ProductType.InAppPurchase,
|
|
86
|
-
skus,
|
|
87
|
-
);
|
|
88
|
-
|
|
80
|
+
const products = await ExpoIapModule.getItemsByType('inapp', skus);
|
|
89
81
|
return products.filter((product: unknown) =>
|
|
90
82
|
isProductAndroid<Product>(product),
|
|
91
83
|
);
|
|
@@ -105,7 +97,7 @@ export const getSubscriptions = async (
|
|
|
105
97
|
ios: async () => {
|
|
106
98
|
const rawItems = await ExpoIapModule.getItems(skus);
|
|
107
99
|
return rawItems.filter((item: unknown) => {
|
|
108
|
-
if (!
|
|
100
|
+
if (!isProductIos(item)) return false;
|
|
109
101
|
return (
|
|
110
102
|
typeof item === 'object' &&
|
|
111
103
|
item !== null &&
|
|
@@ -118,7 +110,7 @@ export const getSubscriptions = async (
|
|
|
118
110
|
android: async () => {
|
|
119
111
|
const rawItems = await ExpoIapModule.getItemsByType('subs', skus);
|
|
120
112
|
return rawItems.filter((item: unknown) => {
|
|
121
|
-
if (!
|
|
113
|
+
if (!isProductAndroid(item)) return false;
|
|
122
114
|
return (
|
|
123
115
|
typeof item === 'object' &&
|
|
124
116
|
item !== null &&
|
|
@@ -152,12 +144,9 @@ export const getPurchaseHistory = ({
|
|
|
152
144
|
);
|
|
153
145
|
},
|
|
154
146
|
android: async () => {
|
|
155
|
-
const products = await ExpoIapModule.getPurchaseHistoryByType(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const subscriptions = await ExpoIapModule.getPurchaseHistoryByType(
|
|
159
|
-
ProductType.Subscription,
|
|
160
|
-
);
|
|
147
|
+
const products = await ExpoIapModule.getPurchaseHistoryByType('inapp');
|
|
148
|
+
const subscriptions =
|
|
149
|
+
await ExpoIapModule.getPurchaseHistoryByType('subs');
|
|
161
150
|
return products.concat(subscriptions);
|
|
162
151
|
},
|
|
163
152
|
}) || (() => Promise.resolve([]))
|
|
@@ -178,12 +167,9 @@ export const getAvailablePurchases = ({
|
|
|
178
167
|
onlyIncludeActiveItems,
|
|
179
168
|
),
|
|
180
169
|
android: async () => {
|
|
181
|
-
const products = await ExpoIapModule.getAvailableItemsByType(
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const subscriptions = await ExpoIapModule.getAvailableItemsByType(
|
|
185
|
-
ProductType.Subscription,
|
|
186
|
-
);
|
|
170
|
+
const products = await ExpoIapModule.getAvailableItemsByType('inapp');
|
|
171
|
+
const subscriptions =
|
|
172
|
+
await ExpoIapModule.getAvailableItemsByType('subs');
|
|
187
173
|
return products.concat(subscriptions);
|
|
188
174
|
},
|
|
189
175
|
}) || (() => Promise.resolve([]))
|
|
@@ -192,9 +178,7 @@ export const getAvailablePurchases = ({
|
|
|
192
178
|
const offerToRecordIos = (
|
|
193
179
|
offer: PaymentDiscount | undefined,
|
|
194
180
|
): Record<keyof PaymentDiscount, string> | undefined => {
|
|
195
|
-
if (!offer)
|
|
196
|
-
return undefined;
|
|
197
|
-
}
|
|
181
|
+
if (!offer) return undefined;
|
|
198
182
|
return {
|
|
199
183
|
identifier: offer.identifier,
|
|
200
184
|
keyIdentifier: offer.keyIdentifier,
|
|
@@ -204,44 +188,90 @@ const offerToRecordIos = (
|
|
|
204
188
|
};
|
|
205
189
|
};
|
|
206
190
|
|
|
191
|
+
// Define discriminated union with explicit type parameter
|
|
192
|
+
type PurchaseRequest =
|
|
193
|
+
| {
|
|
194
|
+
request: RequestPurchaseIosProps | RequestPurchaseAndroidProps;
|
|
195
|
+
type?: 'inapp';
|
|
196
|
+
}
|
|
197
|
+
| {
|
|
198
|
+
request: RequestSubscriptionAndroidProps | RequestSubscriptionIosProps;
|
|
199
|
+
type: 'subs';
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// Type guards for request objects
|
|
203
|
+
const isIosRequest = (
|
|
204
|
+
request: any,
|
|
205
|
+
): request is RequestPurchaseIosProps | RequestSubscriptionIosProps =>
|
|
206
|
+
'sku' in request && typeof request.sku === 'string';
|
|
207
|
+
|
|
208
|
+
const isAndroidInAppRequest = (
|
|
209
|
+
request: any,
|
|
210
|
+
): request is RequestPurchaseAndroidProps =>
|
|
211
|
+
'skus' in request &&
|
|
212
|
+
Array.isArray(request.skus) &&
|
|
213
|
+
!('subscriptionOffers' in request);
|
|
214
|
+
|
|
215
|
+
const isAndroidSubscriptionRequest = (
|
|
216
|
+
request: any,
|
|
217
|
+
): request is RequestSubscriptionAndroidProps =>
|
|
218
|
+
'skus' in request &&
|
|
219
|
+
Array.isArray(request.skus) &&
|
|
220
|
+
'subscriptionOffers' in request;
|
|
221
|
+
|
|
207
222
|
export const requestPurchase = (
|
|
208
|
-
|
|
209
|
-
): Promise<
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
223
|
+
requestObj: PurchaseRequest,
|
|
224
|
+
): Promise<
|
|
225
|
+
| ProductPurchase
|
|
226
|
+
| SubscriptionPurchase
|
|
227
|
+
| ProductPurchase[]
|
|
228
|
+
| SubscriptionPurchase[]
|
|
229
|
+
| void
|
|
230
|
+
> => {
|
|
231
|
+
const {request, type = 'inapp'} = requestObj;
|
|
232
|
+
|
|
233
|
+
if (Platform.OS === 'ios') {
|
|
234
|
+
if (!isIosRequest(request)) {
|
|
235
|
+
throw new Error(
|
|
236
|
+
"Invalid request for iOS: Expected a 'RequestPurchaseIosProps' or 'RequestSubscriptionIosProps' object with a valid 'sku' property.",
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const {
|
|
241
|
+
sku,
|
|
242
|
+
andDangerouslyFinishTransactionAutomaticallyIOS = false,
|
|
243
|
+
appAccountToken,
|
|
244
|
+
quantity,
|
|
245
|
+
withOffer,
|
|
246
|
+
} = request;
|
|
247
|
+
|
|
248
|
+
return (async () => {
|
|
249
|
+
const offer = offerToRecordIos(withOffer);
|
|
250
|
+
const purchase = await ExpoIapModule.buyProduct(
|
|
251
|
+
sku,
|
|
252
|
+
andDangerouslyFinishTransactionAutomaticallyIOS,
|
|
253
|
+
appAccountToken,
|
|
254
|
+
quantity ?? -1,
|
|
255
|
+
offer,
|
|
256
|
+
);
|
|
257
|
+
return type === 'inapp'
|
|
258
|
+
? (purchase as ProductPurchase)
|
|
259
|
+
: (purchase as SubscriptionPurchase);
|
|
260
|
+
})();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (Platform.OS === 'android') {
|
|
264
|
+
if (isAndroidInAppRequest(request)) {
|
|
265
|
+
const {
|
|
266
|
+
skus,
|
|
267
|
+
obfuscatedAccountIdAndroid,
|
|
268
|
+
obfuscatedProfileIdAndroid,
|
|
269
|
+
isOfferPersonalized,
|
|
270
|
+
} = request;
|
|
271
|
+
|
|
272
|
+
return (async () => {
|
|
243
273
|
return ExpoIapModule.buyItemByType({
|
|
244
|
-
type:
|
|
274
|
+
type: 'inapp',
|
|
245
275
|
skuArr: skus,
|
|
246
276
|
purchaseToken: undefined,
|
|
247
277
|
replacementMode: -1,
|
|
@@ -249,60 +279,58 @@ export const requestPurchase = (
|
|
|
249
279
|
obfuscatedProfileId: obfuscatedProfileIdAndroid,
|
|
250
280
|
offerTokenArr: [],
|
|
251
281
|
isOfferPersonalized: isOfferPersonalized ?? false,
|
|
252
|
-
})
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
)();
|
|
282
|
+
}) as Promise<ProductPurchase[]>;
|
|
283
|
+
})();
|
|
284
|
+
}
|
|
256
285
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
appAccountToken,
|
|
270
|
-
quantity,
|
|
271
|
-
withOffer,
|
|
272
|
-
} = request as RequestSubscriptionIosProps;
|
|
273
|
-
const offer = offerToRecordIos(withOffer);
|
|
274
|
-
const purchase = await ExpoIapModule.buyProduct(
|
|
275
|
-
sku,
|
|
276
|
-
andDangerouslyFinishTransactionAutomaticallyIOS,
|
|
277
|
-
appAccountToken,
|
|
278
|
-
quantity ?? -1,
|
|
279
|
-
offer,
|
|
280
|
-
);
|
|
281
|
-
return Promise.resolve(purchase as SubscriptionPurchase);
|
|
282
|
-
},
|
|
283
|
-
android: async () => {
|
|
284
|
-
const {
|
|
285
|
-
skus,
|
|
286
|
-
isOfferPersonalized,
|
|
287
|
-
obfuscatedAccountIdAndroid,
|
|
288
|
-
obfuscatedProfileIdAndroid,
|
|
289
|
-
subscriptionOffers,
|
|
290
|
-
replacementModeAndroid,
|
|
291
|
-
purchaseTokenAndroid,
|
|
292
|
-
} = request as RequestSubscriptionAndroidProps;
|
|
286
|
+
if (isAndroidSubscriptionRequest(request)) {
|
|
287
|
+
const {
|
|
288
|
+
skus,
|
|
289
|
+
obfuscatedAccountIdAndroid,
|
|
290
|
+
obfuscatedProfileIdAndroid,
|
|
291
|
+
isOfferPersonalized,
|
|
292
|
+
subscriptionOffers = [],
|
|
293
|
+
replacementModeAndroid = -1,
|
|
294
|
+
purchaseTokenAndroid,
|
|
295
|
+
} = request;
|
|
296
|
+
|
|
297
|
+
return (async () => {
|
|
293
298
|
return ExpoIapModule.buyItemByType({
|
|
294
|
-
type:
|
|
295
|
-
skuArr: skus
|
|
299
|
+
type: 'subs',
|
|
300
|
+
skuArr: skus,
|
|
296
301
|
purchaseToken: purchaseTokenAndroid,
|
|
297
302
|
replacementMode: replacementModeAndroid,
|
|
298
303
|
obfuscatedAccountId: obfuscatedAccountIdAndroid,
|
|
299
304
|
obfuscatedProfileId: obfuscatedProfileIdAndroid,
|
|
300
305
|
offerTokenArr: subscriptionOffers.map((so) => so.offerToken),
|
|
301
306
|
isOfferPersonalized: isOfferPersonalized ?? false,
|
|
302
|
-
})
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
307
|
+
}) as Promise<SubscriptionPurchase[]>;
|
|
308
|
+
})();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
throw new Error(
|
|
312
|
+
"Invalid request for Android: Expected a 'RequestPurchaseAndroidProps' object with a valid 'skus' array or a 'RequestSubscriptionAndroidProps' object with 'skus' and 'subscriptionOffers'.",
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return Promise.resolve(); // Fallback for unsupported platforms
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* @deprecated Use `requestPurchase({ request, type: 'subs' })` instead. This method will be removed in version 3.0.0+.
|
|
321
|
+
*/
|
|
322
|
+
export const requestSubscription = async (
|
|
323
|
+
request: RequestSubscriptionProps,
|
|
324
|
+
): Promise<SubscriptionPurchase | SubscriptionPurchase[] | null | void> => {
|
|
325
|
+
console.warn(
|
|
326
|
+
"`requestSubscription` is deprecated. Use `requestPurchase({ request, type: 'subs' })` instead. This method will be removed in version 3.0.0+.",
|
|
327
|
+
);
|
|
328
|
+
return (await requestPurchase({request, type: 'subs'})) as
|
|
329
|
+
| SubscriptionPurchase
|
|
330
|
+
| SubscriptionPurchase[]
|
|
331
|
+
| null
|
|
332
|
+
| void;
|
|
333
|
+
};
|
|
306
334
|
|
|
307
335
|
export const finishTransaction = ({
|
|
308
336
|
purchase,
|
package/src/modules/android.ts
CHANGED
|
@@ -15,12 +15,6 @@ export function isProductAndroid<T extends {platform?: string}>(
|
|
|
15
15
|
);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
export function isSubscriptionProductAndroid<T extends {platform?: string}>(
|
|
19
|
-
item: unknown,
|
|
20
|
-
): item is T & {platform: 'android'} {
|
|
21
|
-
return isProductAndroid(item);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
18
|
/**
|
|
25
19
|
* Deep link to subscriptions screen on Android.
|
|
26
20
|
* @param {string} sku The product's SKU (on Android)
|
package/src/modules/ios.ts
CHANGED
|
@@ -32,12 +32,6 @@ export function isProductIos<T extends {platform?: string}>(
|
|
|
32
32
|
);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
export function isSubscriptionProductIos<T extends {platform?: string}>(
|
|
36
|
-
item: unknown,
|
|
37
|
-
): item is T & {platform: 'ios'} {
|
|
38
|
-
return isProductIos(item);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
35
|
// Functions
|
|
42
36
|
/**
|
|
43
37
|
* Sync state with Appstore (iOS only)
|
package/src/useIap.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
getPurchaseHistory,
|
|
10
10
|
finishTransaction as finishTransactionInternal,
|
|
11
11
|
getSubscriptions,
|
|
12
|
+
requestPurchase as requestPurchaseInternal,
|
|
12
13
|
} from './';
|
|
13
14
|
import {useCallback, useEffect, useState, useRef} from 'react';
|
|
14
15
|
import {
|
|
@@ -40,10 +41,11 @@ type IAP_STATUS = {
|
|
|
40
41
|
purchase: Purchase;
|
|
41
42
|
isConsumable?: boolean;
|
|
42
43
|
}) => Promise<string | boolean | PurchaseResult | void>;
|
|
43
|
-
getAvailablePurchases: () => Promise<void>;
|
|
44
|
-
getPurchaseHistories: () => Promise<void>;
|
|
44
|
+
getAvailablePurchases: (skus: string[]) => Promise<void>;
|
|
45
|
+
getPurchaseHistories: (skus: string[]) => Promise<void>;
|
|
45
46
|
getProducts: (skus: string[]) => Promise<void>;
|
|
46
47
|
getSubscriptions: (skus: string[]) => Promise<void>;
|
|
48
|
+
requestPurchase: typeof requestPurchaseInternal;
|
|
47
49
|
};
|
|
48
50
|
|
|
49
51
|
export function useIAP(): IAP_STATUS {
|
|
@@ -63,29 +65,31 @@ export function useIAP(): IAP_STATUS {
|
|
|
63
65
|
const [currentPurchaseError, setCurrentPurchaseError] =
|
|
64
66
|
useState<PurchaseError>();
|
|
65
67
|
|
|
66
|
-
// 구독을 훅 인스턴스별로 관리하기 위한 ref
|
|
67
68
|
const subscriptionsRef = useRef<{
|
|
68
69
|
purchaseUpdate?: Subscription;
|
|
69
70
|
purchaseError?: Subscription;
|
|
70
71
|
promotedProductsIos?: Subscription;
|
|
71
72
|
}>({});
|
|
72
73
|
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
const getProductsInternal = useCallback(
|
|
75
|
+
async (skus: string[]): Promise<void> => {
|
|
76
|
+
setProducts(await getProducts(skus));
|
|
77
|
+
},
|
|
78
|
+
[],
|
|
79
|
+
);
|
|
76
80
|
|
|
77
|
-
const
|
|
81
|
+
const getSubscriptionsInternal = useCallback(
|
|
78
82
|
async (skus: string[]): Promise<void> => {
|
|
79
83
|
setSubscriptions(await getSubscriptions(skus));
|
|
80
84
|
},
|
|
81
85
|
[],
|
|
82
86
|
);
|
|
83
87
|
|
|
84
|
-
const
|
|
88
|
+
const getAvailablePurchasesInternal = useCallback(async (): Promise<void> => {
|
|
85
89
|
setAvailablePurchases(await getAvailablePurchases());
|
|
86
90
|
}, []);
|
|
87
91
|
|
|
88
|
-
const
|
|
92
|
+
const getPurchaseHistoriesInternal = useCallback(async (): Promise<void> => {
|
|
89
93
|
setPurchaseHistories(await getPurchaseHistory());
|
|
90
94
|
}, []);
|
|
91
95
|
|
|
@@ -172,9 +176,10 @@ export function useIAP(): IAP_STATUS {
|
|
|
172
176
|
availablePurchases,
|
|
173
177
|
currentPurchase,
|
|
174
178
|
currentPurchaseError,
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
+
getAvailablePurchases: getAvailablePurchasesInternal,
|
|
180
|
+
getPurchaseHistories: getPurchaseHistoriesInternal,
|
|
181
|
+
getProducts: getProductsInternal,
|
|
182
|
+
getSubscriptions: getSubscriptionsInternal,
|
|
183
|
+
requestPurchase: requestPurchaseInternal,
|
|
179
184
|
};
|
|
180
185
|
}
|