expo-iap 2.2.11 → 2.3.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.
@@ -41,8 +41,3 @@ android {
41
41
  abortOnError false
42
42
  }
43
43
  }
44
-
45
- dependencies {
46
- implementation "com.android.billingclient:billing-ktx:7.0.0"
47
- implementation "com.google.android.gms:play-services-base:18.1.0"
48
- }
package/bun.lockb CHANGED
Binary file
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
- 'cpk.points.1000',
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-iap",
3
- "version": "2.2.11",
3
+ "version": "2.3.1-rc.1",
4
4
  "description": "In App Purchase module in Expo",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -1,6 +1,3 @@
1
1
  import { ConfigPlugin } from 'expo/config-plugins';
2
- export declare const modifyProjectBuildGradle: (buildGradle: string) => string;
3
- interface Props {
4
- }
5
- declare const _default: ConfigPlugin<Props | undefined>;
2
+ declare const _default: ConfigPlugin<void>;
6
3
  export default _default;
@@ -1,65 +1,66 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.modifyProjectBuildGradle = void 0;
4
3
  const config_plugins_1 = require("expo/config-plugins");
5
- const config_plugins_2 = require("expo/config-plugins");
6
4
  const pkg = require('../../package.json');
7
- const addToBuildGradle = (newLine, anchor, offset, buildGradle) => {
5
+ const BILLING_PERMISSION = 'com.android.vending.BILLING';
6
+ const BILLING_DEP = ` implementation "com.android.billingclient:billing-ktx:7.0.0"`;
7
+ const GMS_DEP = ` implementation "com.google.android.gms:play-services-base:18.1.0"`;
8
+ /**
9
+ * Injects implementation lines into app/build.gradle
10
+ */
11
+ function modifyAppBuildGradle(buildGradle) {
8
12
  const lines = buildGradle.split('\n');
9
- const lineIndex = lines.findIndex((line) => line.match(anchor));
10
- if (lineIndex === -1) {
11
- console.warn('Anchor "ext" not found in build.gradle, appending to end');
12
- lines.push(newLine);
13
+ const anchor = /dependencies\s*{/;
14
+ const index = lines.findIndex((line) => anchor.test(line));
15
+ if (index !== -1) {
16
+ if (!buildGradle.includes(BILLING_DEP)) {
17
+ lines.splice(index + 1, 0, BILLING_DEP);
18
+ }
19
+ if (!buildGradle.includes(GMS_DEP)) {
20
+ lines.splice(index + 2, 0, GMS_DEP);
21
+ }
13
22
  }
14
23
  else {
15
- lines.splice(lineIndex + offset, 0, newLine);
24
+ console.warn('Could not find dependencies block in app/build.gradle');
16
25
  }
17
26
  return lines.join('\n');
18
- };
19
- const modifyProjectBuildGradle = (buildGradle) => {
20
- const supportLibVersion = `supportLibVersion = "28.0.0"`;
21
- if (buildGradle.includes(supportLibVersion)) {
22
- return buildGradle;
23
- }
24
- return addToBuildGradle(supportLibVersion, 'ext', 1, buildGradle);
25
- };
26
- exports.modifyProjectBuildGradle = modifyProjectBuildGradle;
27
+ }
28
+ /**
29
+ * Adds BILLING permission to AndroidManifest.xml
30
+ */
27
31
  const withIAPAndroid = (config) => {
28
- config = (0, config_plugins_1.withProjectBuildGradle)(config, (config) => {
29
- config.modResults.contents = (0, exports.modifyProjectBuildGradle)(config.modResults.contents);
32
+ config = (0, config_plugins_1.withAppBuildGradle)(config, (config) => {
33
+ config.modResults.contents = modifyAppBuildGradle(config.modResults.contents);
30
34
  return config;
31
35
  });
32
- // Adding BILLING permission to AndroidManifest.xml
33
36
  config = (0, config_plugins_1.withAndroidManifest)(config, (config) => {
34
- console.log('Modifying AndroidManifest.xml...');
35
37
  const manifest = config.modResults;
36
- if (!manifest.manifest['uses-permission']) {
37
- manifest.manifest['uses-permission'] = [];
38
- }
39
- const permissions = manifest.manifest['uses-permission'];
40
- const billingPermission = {
41
- $: { 'android:name': 'com.android.vending.BILLING' },
42
- };
43
- if (!permissions.some((perm) => perm.$['android:name'] === 'com.android.vending.BILLING')) {
44
- permissions.push(billingPermission);
45
- console.log('Added com.android.vending.BILLING to permissions');
38
+ const permissions = manifest.manifest['uses-permission'] ?? [];
39
+ const alreadyExists = permissions.some((p) => p.$['android:name'] === BILLING_PERMISSION);
40
+ if (!alreadyExists) {
41
+ permissions.push({ $: { 'android:name': BILLING_PERMISSION } });
42
+ manifest.manifest['uses-permission'] = permissions;
43
+ console.log(`✅ Added ${BILLING_PERMISSION} to AndroidManifest.xml`);
46
44
  }
47
45
  else {
48
- console.log('com.android.vending.BILLING already exists in manifest');
46
+ console.log(`â„šī¸ ${BILLING_PERMISSION} already exists in AndroidManifest.xml`);
49
47
  }
50
48
  return config;
51
49
  });
52
50
  return config;
53
51
  };
54
- const withIAP = (config, props) => {
52
+ /**
53
+ * Main plugin entry
54
+ */
55
+ const withIAP = (config, _props) => {
55
56
  try {
56
- console.log('Applying expo-iap plugin...');
57
- config = withIAPAndroid(config);
57
+ console.log('đŸ› ī¸ Applying expo-iap plugin...');
58
+ return withIAPAndroid(config);
58
59
  }
59
60
  catch (error) {
60
- config_plugins_1.WarningAggregator.addWarningAndroid('expo-iap', `There was a problem configuring expo-iap in your native Android project: ${error}`);
61
- console.error('Error in expo-iap plugin:', error);
61
+ config_plugins_1.WarningAggregator.addWarningAndroid('expo-iap', `expo-iap plugin encountered an error: ${error}`);
62
+ console.error('expo-iap plugin error:', error);
63
+ return config;
62
64
  }
63
- return config;
64
65
  };
65
- exports.default = (0, config_plugins_2.createRunOncePlugin)(withIAP, pkg.name, pkg.version);
66
+ exports.default = (0, config_plugins_1.createRunOncePlugin)(withIAP, pkg.name, pkg.version);
@@ -1,67 +1,66 @@
1
1
  import {
2
+ ConfigPlugin,
2
3
  WarningAggregator,
3
4
  withAndroidManifest,
4
- withProjectBuildGradle,
5
+ withAppBuildGradle,
6
+ createRunOncePlugin,
5
7
  } from 'expo/config-plugins';
6
- import {ConfigPlugin, createRunOncePlugin} from 'expo/config-plugins';
7
8
 
8
9
  const pkg = require('../../package.json');
9
10
 
10
- const addToBuildGradle = (
11
- newLine: string,
12
- anchor: RegExp | string,
13
- offset: number,
14
- buildGradle: string,
15
- ) => {
11
+ const BILLING_PERMISSION = 'com.android.vending.BILLING';
12
+ const BILLING_DEP = ` implementation "com.android.billingclient:billing-ktx:7.0.0"`;
13
+ const GMS_DEP = ` implementation "com.google.android.gms:play-services-base:18.1.0"`;
14
+
15
+ /**
16
+ * Injects implementation lines into app/build.gradle
17
+ */
18
+ function modifyAppBuildGradle(buildGradle: string): string {
16
19
  const lines = buildGradle.split('\n');
17
- const lineIndex = lines.findIndex((line) => line.match(anchor));
18
- if (lineIndex === -1) {
19
- console.warn('Anchor "ext" not found in build.gradle, appending to end');
20
- lines.push(newLine);
20
+ const anchor = /dependencies\s*{/;
21
+ const index = lines.findIndex((line) => anchor.test(line));
22
+
23
+ if (index !== -1) {
24
+ if (!buildGradle.includes(BILLING_DEP)) {
25
+ lines.splice(index + 1, 0, BILLING_DEP);
26
+ }
27
+ if (!buildGradle.includes(GMS_DEP)) {
28
+ lines.splice(index + 2, 0, GMS_DEP);
29
+ }
21
30
  } else {
22
- lines.splice(lineIndex + offset, 0, newLine);
31
+ console.warn('Could not find dependencies block in app/build.gradle');
23
32
  }
24
- return lines.join('\n');
25
- };
26
33
 
27
- export const modifyProjectBuildGradle = (buildGradle: string) => {
28
- const supportLibVersion = `supportLibVersion = "28.0.0"`;
29
- if (buildGradle.includes(supportLibVersion)) {
30
- return buildGradle;
31
- }
32
- return addToBuildGradle(supportLibVersion, 'ext', 1, buildGradle);
33
- };
34
+ return lines.join('\n');
35
+ }
34
36
 
37
+ /**
38
+ * Adds BILLING permission to AndroidManifest.xml
39
+ */
35
40
  const withIAPAndroid: ConfigPlugin = (config) => {
36
- config = withProjectBuildGradle(config, (config) => {
37
- config.modResults.contents = modifyProjectBuildGradle(
41
+ config = withAppBuildGradle(config, (config) => {
42
+ config.modResults.contents = modifyAppBuildGradle(
38
43
  config.modResults.contents,
39
44
  );
40
45
  return config;
41
46
  });
42
47
 
43
- // Adding BILLING permission to AndroidManifest.xml
44
48
  config = withAndroidManifest(config, (config) => {
45
- console.log('Modifying AndroidManifest.xml...');
46
49
  const manifest = config.modResults;
50
+ const permissions = manifest.manifest['uses-permission'] ?? [];
47
51
 
48
- if (!manifest.manifest['uses-permission']) {
49
- manifest.manifest['uses-permission'] = [];
50
- }
52
+ const alreadyExists = permissions.some(
53
+ (p) => p.$['android:name'] === BILLING_PERMISSION,
54
+ );
51
55
 
52
- const permissions = manifest.manifest['uses-permission'];
53
- const billingPermission = {
54
- $: {'android:name': 'com.android.vending.BILLING'},
55
- };
56
- if (
57
- !permissions.some(
58
- (perm: any) => perm.$['android:name'] === 'com.android.vending.BILLING',
59
- )
60
- ) {
61
- permissions.push(billingPermission);
62
- console.log('Added com.android.vending.BILLING to permissions');
56
+ if (!alreadyExists) {
57
+ permissions.push({$: {'android:name': BILLING_PERMISSION}});
58
+ manifest.manifest['uses-permission'] = permissions;
59
+ console.log(`✅ Added ${BILLING_PERMISSION} to AndroidManifest.xml`);
63
60
  } else {
64
- console.log('com.android.vending.BILLING already exists in manifest');
61
+ console.log(
62
+ `â„šī¸ ${BILLING_PERMISSION} already exists in AndroidManifest.xml`,
63
+ );
65
64
  }
66
65
 
67
66
  return config;
@@ -70,20 +69,21 @@ const withIAPAndroid: ConfigPlugin = (config) => {
70
69
  return config;
71
70
  };
72
71
 
73
- interface Props {}
74
-
75
- const withIAP: ConfigPlugin<Props | undefined> = (config, props) => {
72
+ /**
73
+ * Main plugin entry
74
+ */
75
+ const withIAP: ConfigPlugin = (config, _props) => {
76
76
  try {
77
- console.log('Applying expo-iap plugin...');
78
- config = withIAPAndroid(config);
77
+ console.log('đŸ› ī¸ Applying expo-iap plugin...');
78
+ return withIAPAndroid(config);
79
79
  } catch (error) {
80
80
  WarningAggregator.addWarningAndroid(
81
81
  'expo-iap',
82
- `There was a problem configuring expo-iap in your native Android project: ${error}`,
82
+ `expo-iap plugin encountered an error: ${error}`,
83
83
  );
84
- console.error('Error in expo-iap plugin:', error);
84
+ console.error('expo-iap plugin error:', error);
85
+ return config;
85
86
  }
86
- return config;
87
87
  };
88
88
 
89
89
  export default createRunOncePlugin(withIAP, pkg.name, pkg.version);