expo-iap 2.3.0 → 2.3.1-rc.2
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/iap.md +20 -38
- package/package.json +1 -1
- package/plugin/build/withIAP.js +11 -1
- package/plugin/src/withIAP.ts +17 -1
package/iap.md
CHANGED
|
@@ -209,8 +209,8 @@ Transactions map to `Purchase` or `SubscriptionPurchase` with platform-specific
|
|
|
209
209
|
Below is a simple example of fetching products and making a purchase with `expo-iap` in a managed workflow, updated to use the new `requestPurchase` signature:
|
|
210
210
|
|
|
211
211
|
```tsx
|
|
212
|
-
import {useEffect, useState} from 'react';
|
|
213
|
-
import {Button, Text, View} from 'react-native';
|
|
212
|
+
import { useEffect, useState } from 'react';
|
|
213
|
+
import { Button, Text, View } from 'react-native';
|
|
214
214
|
import {
|
|
215
215
|
initConnection,
|
|
216
216
|
endConnection,
|
|
@@ -237,7 +237,7 @@ export default function SimpleIAP() {
|
|
|
237
237
|
|
|
238
238
|
const purchaseListener = purchaseUpdatedListener(async (purchase) => {
|
|
239
239
|
if (purchase) {
|
|
240
|
-
await finishTransaction({purchase, isConsumable: true});
|
|
240
|
+
await finishTransaction({ purchase, isConsumable: true });
|
|
241
241
|
alert('Purchase completed!');
|
|
242
242
|
}
|
|
243
243
|
});
|
|
@@ -252,12 +252,12 @@ export default function SimpleIAP() {
|
|
|
252
252
|
const buyItem = async () => {
|
|
253
253
|
if (!product) return;
|
|
254
254
|
await requestPurchase({
|
|
255
|
-
request: {skus: [product.id]}, // Android expects 'skus'; iOS would use 'sku'
|
|
255
|
+
request: { skus: [product.id] }, // Android expects 'skus'; iOS would use 'sku'
|
|
256
256
|
});
|
|
257
257
|
};
|
|
258
258
|
|
|
259
259
|
return (
|
|
260
|
-
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
|
|
260
|
+
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
|
261
261
|
<Text>{isConnected ? 'Connected' : 'Connecting...'}</Text>
|
|
262
262
|
{product ? (
|
|
263
263
|
<>
|
|
@@ -277,7 +277,7 @@ export default function SimpleIAP() {
|
|
|
277
277
|
The `useIAP` hook simplifies managing in-app purchases. Below is an example updated to use the new `requestPurchase` signature:
|
|
278
278
|
|
|
279
279
|
```tsx
|
|
280
|
-
import {useEffect, useState} from 'react';
|
|
280
|
+
import { useEffect, useState } from 'react';
|
|
281
281
|
import {
|
|
282
282
|
SafeAreaView,
|
|
283
283
|
ScrollView,
|
|
@@ -289,20 +289,12 @@ import {
|
|
|
289
289
|
InteractionManager,
|
|
290
290
|
Alert,
|
|
291
291
|
} from 'react-native';
|
|
292
|
-
import {useIAP} from 'expo-iap';
|
|
293
|
-
import type {ProductPurchase, SubscriptionProduct} from 'expo-iap';
|
|
292
|
+
import { useIAP } from 'expo-iap';
|
|
293
|
+
import type { ProductPurchase, SubscriptionProduct } from 'expo-iap';
|
|
294
294
|
|
|
295
295
|
// Define SKUs
|
|
296
|
-
const productSkus = [
|
|
297
|
-
|
|
298
|
-
'cpk.points.5000',
|
|
299
|
-
'cpk.points.10000',
|
|
300
|
-
'cpk.points.30000',
|
|
301
|
-
];
|
|
302
|
-
const subscriptionSkus = [
|
|
303
|
-
'cpk.membership.monthly.bronze',
|
|
304
|
-
'cpk.membership.monthly.silver',
|
|
305
|
-
];
|
|
296
|
+
const productSkus = ['cpk.points.1000', 'cpk.points.5000', 'cpk.points.10000', 'cpk.points.30000'];
|
|
297
|
+
const subscriptionSkus = ['cpk.membership.monthly.bronze', 'cpk.membership.monthly.silver'];
|
|
306
298
|
|
|
307
299
|
// Define operations
|
|
308
300
|
const operations = ['getProducts', 'getSubscriptions'] as const;
|
|
@@ -329,10 +321,7 @@ export default function IAPWithHook() {
|
|
|
329
321
|
|
|
330
322
|
const initializeIAP = async () => {
|
|
331
323
|
try {
|
|
332
|
-
await Promise.all([
|
|
333
|
-
getProducts(productSkus),
|
|
334
|
-
getSubscriptions(subscriptionSkus),
|
|
335
|
-
]);
|
|
324
|
+
await Promise.all([getProducts(productSkus), getSubscriptions(subscriptionSkus)]);
|
|
336
325
|
setIsReady(true);
|
|
337
326
|
} catch (error) {
|
|
338
327
|
console.error('Error initializing IAP:', error);
|
|
@@ -400,10 +389,7 @@ export default function IAPWithHook() {
|
|
|
400
389
|
<View style={styles.buttons}>
|
|
401
390
|
<ScrollView contentContainerStyle={styles.buttonsWrapper} horizontal>
|
|
402
391
|
{operations.map((operation) => (
|
|
403
|
-
<Pressable
|
|
404
|
-
key={operation}
|
|
405
|
-
onPress={() => handleOperation(operation)}
|
|
406
|
-
>
|
|
392
|
+
<Pressable key={operation} onPress={() => handleOperation(operation)}>
|
|
407
393
|
<View style={styles.buttonView}>
|
|
408
394
|
<Text>{operation}</Text>
|
|
409
395
|
</View>
|
|
@@ -415,10 +401,10 @@ export default function IAPWithHook() {
|
|
|
415
401
|
{!isReady ? (
|
|
416
402
|
<Text>Loading...</Text>
|
|
417
403
|
) : (
|
|
418
|
-
<View style={{gap: 12}}>
|
|
419
|
-
<Text style={{fontSize: 20}}>Products</Text>
|
|
404
|
+
<View style={{ gap: 12 }}>
|
|
405
|
+
<Text style={{ fontSize: 20 }}>Products</Text>
|
|
420
406
|
{products.map((item) => (
|
|
421
|
-
<View key={item.id} style={{gap: 12}}>
|
|
407
|
+
<View key={item.id} style={{ gap: 12 }}>
|
|
422
408
|
<Text>
|
|
423
409
|
{item.title} -{' '}
|
|
424
410
|
{item.platform === 'android'
|
|
@@ -429,24 +415,20 @@ export default function IAPWithHook() {
|
|
|
429
415
|
title="Buy"
|
|
430
416
|
onPress={() =>
|
|
431
417
|
requestPurchase({
|
|
432
|
-
request:
|
|
433
|
-
item.platform === 'android'
|
|
434
|
-
? {skus: [item.id]}
|
|
435
|
-
: {sku: item.id},
|
|
418
|
+
request: item.platform === 'android' ? { skus: [item.id] } : { sku: item.id },
|
|
436
419
|
})
|
|
437
420
|
}
|
|
438
421
|
/>
|
|
439
422
|
</View>
|
|
440
423
|
))}
|
|
441
424
|
|
|
442
|
-
<Text style={{fontSize: 20}}>Subscriptions</Text>
|
|
425
|
+
<Text style={{ fontSize: 20 }}>Subscriptions</Text>
|
|
443
426
|
{subscriptions.map((item) => (
|
|
444
|
-
<View key={item.id} style={{gap: 12}}>
|
|
427
|
+
<View key={item.id} style={{ gap: 12 }}>
|
|
445
428
|
<Text>
|
|
446
429
|
{item.title || item.displayName} -{' '}
|
|
447
430
|
{item.platform === 'android' && item.subscriptionOfferDetails
|
|
448
|
-
? item.subscriptionOfferDetails[0]?.pricingPhases
|
|
449
|
-
.pricingPhaseList[0].formattedPrice
|
|
431
|
+
? item.subscriptionOfferDetails[0]?.pricingPhases.pricingPhaseList[0].formattedPrice
|
|
450
432
|
: item.displayPrice}
|
|
451
433
|
</Text>
|
|
452
434
|
<Button
|
|
@@ -463,7 +445,7 @@ export default function IAPWithHook() {
|
|
|
463
445
|
offerToken: offer.offerToken,
|
|
464
446
|
})) || [],
|
|
465
447
|
}
|
|
466
|
-
: {sku: item.id},
|
|
448
|
+
: { sku: item.id },
|
|
467
449
|
type: 'subs',
|
|
468
450
|
})
|
|
469
451
|
}
|
package/package.json
CHANGED
package/plugin/build/withIAP.js
CHANGED
|
@@ -21,7 +21,11 @@ const modifyProjectBuildGradle = (gradle) => {
|
|
|
21
21
|
if (!modified.includes(supportLib)) {
|
|
22
22
|
modified = addLineToGradle(modified, /ext\s*{/, supportLib);
|
|
23
23
|
}
|
|
24
|
-
|
|
24
|
+
return modified;
|
|
25
|
+
};
|
|
26
|
+
const modifyAppBuildGradle = (gradle) => {
|
|
27
|
+
let modified = gradle;
|
|
28
|
+
// Add billing library dependencies to app-level build.gradle
|
|
25
29
|
const billingDep = ` implementation "com.android.billingclient:billing-ktx:7.0.0"`;
|
|
26
30
|
const gmsDep = ` implementation "com.google.android.gms:play-services-base:18.1.0"`;
|
|
27
31
|
if (!modified.includes(billingDep)) {
|
|
@@ -33,10 +37,16 @@ const modifyProjectBuildGradle = (gradle) => {
|
|
|
33
37
|
return modified;
|
|
34
38
|
};
|
|
35
39
|
const withIAPAndroid = (config) => {
|
|
40
|
+
// Add supportLibVersion to project build.gradle
|
|
36
41
|
config = (0, config_plugins_1.withProjectBuildGradle)(config, (config) => {
|
|
37
42
|
config.modResults.contents = modifyProjectBuildGradle(config.modResults.contents);
|
|
38
43
|
return config;
|
|
39
44
|
});
|
|
45
|
+
// Add IAP dependencies to app build.gradle
|
|
46
|
+
config = (0, config_plugins_1.withAppBuildGradle)(config, (config) => {
|
|
47
|
+
config.modResults.contents = modifyAppBuildGradle(config.modResults.contents);
|
|
48
|
+
return config;
|
|
49
|
+
});
|
|
40
50
|
config = (0, config_plugins_1.withAndroidManifest)(config, (config) => {
|
|
41
51
|
const manifest = config.modResults;
|
|
42
52
|
if (!manifest.manifest['uses-permission']) {
|
package/plugin/src/withIAP.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
WarningAggregator,
|
|
3
3
|
withAndroidManifest,
|
|
4
4
|
withProjectBuildGradle,
|
|
5
|
+
withAppBuildGradle,
|
|
5
6
|
ConfigPlugin,
|
|
6
7
|
createRunOncePlugin,
|
|
7
8
|
} from 'expo/config-plugins';
|
|
@@ -36,7 +37,13 @@ const modifyProjectBuildGradle = (gradle: string): string => {
|
|
|
36
37
|
modified = addLineToGradle(modified, /ext\s*{/, supportLib);
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
return modified;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const modifyAppBuildGradle = (gradle: string): string => {
|
|
44
|
+
let modified = gradle;
|
|
45
|
+
|
|
46
|
+
// Add billing library dependencies to app-level build.gradle
|
|
40
47
|
const billingDep = ` implementation "com.android.billingclient:billing-ktx:7.0.0"`;
|
|
41
48
|
const gmsDep = ` implementation "com.google.android.gms:play-services-base:18.1.0"`;
|
|
42
49
|
if (!modified.includes(billingDep)) {
|
|
@@ -50,6 +57,7 @@ const modifyProjectBuildGradle = (gradle: string): string => {
|
|
|
50
57
|
};
|
|
51
58
|
|
|
52
59
|
const withIAPAndroid: ConfigPlugin = (config) => {
|
|
60
|
+
// Add supportLibVersion to project build.gradle
|
|
53
61
|
config = withProjectBuildGradle(config, (config) => {
|
|
54
62
|
config.modResults.contents = modifyProjectBuildGradle(
|
|
55
63
|
config.modResults.contents,
|
|
@@ -57,6 +65,14 @@ const withIAPAndroid: ConfigPlugin = (config) => {
|
|
|
57
65
|
return config;
|
|
58
66
|
});
|
|
59
67
|
|
|
68
|
+
// Add IAP dependencies to app build.gradle
|
|
69
|
+
config = withAppBuildGradle(config, (config) => {
|
|
70
|
+
config.modResults.contents = modifyAppBuildGradle(
|
|
71
|
+
config.modResults.contents,
|
|
72
|
+
);
|
|
73
|
+
return config;
|
|
74
|
+
});
|
|
75
|
+
|
|
60
76
|
config = withAndroidManifest(config, (config) => {
|
|
61
77
|
const manifest = config.modResults;
|
|
62
78
|
if (!manifest.manifest['uses-permission']) {
|