expo-iap 3.1.18 → 3.1.19

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.
@@ -60,6 +60,11 @@ android {
60
60
  defaultConfig {
61
61
  versionCode 1
62
62
  versionName "0.1.0"
63
+ // When using local openiap-google with flavors, select the appropriate flavor
64
+ // Read horizonEnabled from gradle.properties, default to play
65
+ def horizonEnabled = project.findProperty('horizonEnabled')?.toBoolean() ?: false
66
+ def flavor = horizonEnabled ? 'horizon' : 'play'
67
+ missingDimensionStrategy "platform", flavor
63
68
  }
64
69
  lintOptions {
65
70
  abortOnError false
package/bun.lockb CHANGED
Binary file
@@ -1,6 +1,6 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
- <coverage generated="1760676677448" clover="3.2.0">
3
- <project timestamp="1760676677448" name="All files">
2
+ <coverage generated="1760915832618" clover="3.2.0">
3
+ <project timestamp="1760915832618" name="All files">
4
4
  <metrics statements="457" coveredstatements="429" conditionals="251" coveredconditionals="217" methods="95" coveredmethods="75" elements="803" coveredelements="721" complexity="0" loc="457" ncloc="457" packages="3" files="5" classes="5"/>
5
5
  <package name="src">
6
6
  <metrics statements="196" coveredstatements="190" conditionals="99" coveredconditionals="89" methods="41" coveredmethods="32"/>
@@ -131,7 +131,7 @@
131
131
  <div class='footer quiet pad2 space-top1 center small'>
132
132
  Code coverage generated by
133
133
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
134
- at 2025-10-17T04:51:17.432Z
134
+ at 2025-10-19T23:17:12.601Z
135
135
  </div>
136
136
  <script src="prettify.js"></script>
137
137
  <script>
@@ -101,7 +101,7 @@
101
101
  <div class='footer quiet pad2 space-top1 center small'>
102
102
  Code coverage generated by
103
103
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
104
- at 2025-10-17T04:51:17.432Z
104
+ at 2025-10-19T23:17:12.601Z
105
105
  </div>
106
106
  <script src="../prettify.js"></script>
107
107
  <script>
@@ -2239,7 +2239,7 @@ export {<span class="fstat-no" title="function not covered" >ExpoIapConsole}</sp
2239
2239
  <div class='footer quiet pad2 space-top1 center small'>
2240
2240
  Code coverage generated by
2241
2241
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
2242
- at 2025-10-17T04:51:17.432Z
2242
+ at 2025-10-19T23:17:12.601Z
2243
2243
  </div>
2244
2244
  <script src="../prettify.js"></script>
2245
2245
  <script>
@@ -814,7 +814,7 @@ export const createAlternativeBillingTokenAndroid: MutationField&lt;
814
814
  <div class='footer quiet pad2 space-top1 center small'>
815
815
  Code coverage generated by
816
816
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
817
- at 2025-10-17T04:51:17.432Z
817
+ at 2025-10-19T23:17:12.601Z
818
818
  </div>
819
819
  <script src="../../prettify.js"></script>
820
820
  <script>
@@ -116,7 +116,7 @@
116
116
  <div class='footer quiet pad2 space-top1 center small'>
117
117
  Code coverage generated by
118
118
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
119
- at 2025-10-17T04:51:17.432Z
119
+ at 2025-10-19T23:17:12.601Z
120
120
  </div>
121
121
  <script src="../../prettify.js"></script>
122
122
  <script>
@@ -1267,7 +1267,7 @@ export const presentExternalPurchaseLinkIOS: MutationField&lt;
1267
1267
  <div class='footer quiet pad2 space-top1 center small'>
1268
1268
  Code coverage generated by
1269
1269
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
1270
- at 2025-10-17T04:51:17.432Z
1270
+ at 2025-10-19T23:17:12.601Z
1271
1271
  </div>
1272
1272
  <script src="../../prettify.js"></script>
1273
1273
  <script>
@@ -268,7 +268,7 @@ export const ExpoIapConsole = createConsole();
268
268
  <div class='footer quiet pad2 space-top1 center small'>
269
269
  Code coverage generated by
270
270
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
271
- at 2025-10-17T04:51:17.432Z
271
+ at 2025-10-19T23:17:12.601Z
272
272
  </div>
273
273
  <script src="../../prettify.js"></script>
274
274
  <script>
@@ -1111,7 +1111,7 @@ export function getUserFriendlyErrorMessage(error: ErrorLike): string {
1111
1111
  <div class='footer quiet pad2 space-top1 center small'>
1112
1112
  Code coverage generated by
1113
1113
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
1114
- at 2025-10-17T04:51:17.432Z
1114
+ at 2025-10-19T23:17:12.601Z
1115
1115
  </div>
1116
1116
  <script src="../../prettify.js"></script>
1117
1117
  <script>
@@ -116,7 +116,7 @@
116
116
  <div class='footer quiet pad2 space-top1 center small'>
117
117
  Code coverage generated by
118
118
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
119
- at 2025-10-17T04:51:17.432Z
119
+ at 2025-10-19T23:17:12.601Z
120
120
  </div>
121
121
  <script src="../../prettify.js"></script>
122
122
  <script>
@@ -1,5 +1,5 @@
1
1
  {
2
- "apple": "1.2.24",
3
- "google": "1.2.13",
2
+ "apple": "1.2.25",
3
+ "google": "1.3.1",
4
4
  "gql": "1.2.2"
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-iap",
3
- "version": "3.1.18",
3
+ "version": "3.1.19",
4
4
  "description": "In App Purchase module in Expo",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -1,22 +1,5 @@
1
1
  import { ConfigPlugin } from 'expo/config-plugins';
2
- export interface IOSAlternativeBillingConfig {
3
- /** Country codes where external purchases are supported (ISO 3166-1 alpha-2) */
4
- countries?: string[];
5
- /** External purchase URLs per country (iOS 15.4+) */
6
- links?: Record<string, string>;
7
- /** Multiple external purchase URLs per country (iOS 17.5+, up to 5 per country) */
8
- multiLinks?: Record<string, string[]>;
9
- /** Custom link regions (iOS 18.1+) */
10
- customLinkRegions?: string[];
11
- /** Streaming link regions for music apps (iOS 18.2+) */
12
- streamingLinkRegions?: string[];
13
- /** Enable external purchase link entitlement */
14
- enableExternalPurchaseLink?: boolean;
15
- /** Enable external purchase link streaming entitlement (music apps only) */
16
- enableExternalPurchaseLinkStreaming?: boolean;
17
- }
18
- /** Add external purchase entitlements and Info.plist configuration */
19
- declare const withIosAlternativeBilling: ConfigPlugin<IOSAlternativeBillingConfig | undefined>;
2
+ import { withIosAlternativeBilling, type IOSAlternativeBillingConfig } from './withIosAlternativeBilling';
20
3
  export interface ExpoIapPluginOptions {
21
4
  /** Local development path for OpenIAP library */
22
5
  localPath?: string | {
@@ -26,12 +9,47 @@ export interface ExpoIapPluginOptions {
26
9
  /** Enable local development mode */
27
10
  enableLocalDev?: boolean;
28
11
  /**
29
- * iOS Alternative Billing configuration.
30
- * Configure external purchase countries, links, and entitlements.
31
- * Requires approval from Apple.
12
+ * Optional modules configuration
13
+ */
14
+ modules?: {
15
+ /**
16
+ * Onside module for iOS alternative billing (Korea market)
17
+ * @platform ios
18
+ */
19
+ onside?: boolean;
20
+ /**
21
+ * Horizon module for Meta Quest/VR devices
22
+ * @platform android
23
+ */
24
+ horizon?: boolean;
25
+ };
26
+ /**
27
+ * iOS-specific configuration
32
28
  * @platform ios
33
29
  */
30
+ ios?: {
31
+ /**
32
+ * iOS Alternative Billing configuration.
33
+ * Configure external purchase countries, links, and entitlements.
34
+ * Requires approval from Apple.
35
+ */
36
+ alternativeBilling?: IOSAlternativeBillingConfig;
37
+ };
38
+ /**
39
+ * Android-specific configuration
40
+ * @platform android
41
+ */
42
+ android?: {
43
+ /**
44
+ * Meta Horizon App ID for Quest/VR devices.
45
+ * Required when modules.horizon is true.
46
+ */
47
+ horizonAppId?: string;
48
+ };
49
+ /** @deprecated Use ios.alternativeBilling instead */
34
50
  iosAlternativeBilling?: IOSAlternativeBillingConfig;
51
+ /** @deprecated Use android.horizonAppId instead */
52
+ horizonAppId?: string;
35
53
  }
36
54
  export { withIosAlternativeBilling };
37
55
  declare const _default: ConfigPlugin<void | ExpoIapPluginOptions>;
@@ -41,6 +41,8 @@ const config_plugins_1 = require("expo/config-plugins");
41
41
  const fs = __importStar(require("fs"));
42
42
  const path = __importStar(require("path"));
43
43
  const withLocalOpenIAP_1 = __importDefault(require("./withLocalOpenIAP"));
44
+ const withIosAlternativeBilling_1 = require("./withIosAlternativeBilling");
45
+ Object.defineProperty(exports, "withIosAlternativeBilling", { enumerable: true, get: function () { return withIosAlternativeBilling_1.withIosAlternativeBilling; } });
44
46
  const pkg = require('../../package.json');
45
47
  const openiapVersions = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../../openiap-versions.json'), 'utf8'));
46
48
  const OPENIAP_ANDROID_VERSION = openiapVersions.google;
@@ -66,8 +68,10 @@ const addLineToGradle = (content, anchor, lineToAdd, offset = 1) => {
66
68
  }
67
69
  return lines.join('\n');
68
70
  };
69
- const modifyAppBuildGradle = (gradle, language) => {
71
+ const modifyAppBuildGradle = (gradle, language, isHorizonEnabled) => {
70
72
  let modified = gradle;
73
+ // Determine which flavor to use based on isHorizonEnabled
74
+ const flavor = isHorizonEnabled ? 'horizon' : 'play';
71
75
  // Ensure OpenIAP dependency exists at desired version in app-level build.gradle(.kts)
72
76
  const impl = (ga, v) => language === 'kotlin'
73
77
  ? ` implementation("${ga}:${v}")`
@@ -87,18 +91,40 @@ const modifyAppBuildGradle = (gradle, language) => {
87
91
  ? `🛠️ expo-iap: Replaced OpenIAP dependency with ${OPENIAP_ANDROID_VERSION}`
88
92
  : `🛠️ expo-iap: Added OpenIAP dependency (${OPENIAP_ANDROID_VERSION}) to build.gradle`);
89
93
  }
94
+ // Add flavor dimension and default config for OpenIAP if horizon is enabled
95
+ if (isHorizonEnabled) {
96
+ // Add missingDimensionStrategy to select horizon flavor
97
+ const defaultConfigRegex = /defaultConfig\s*{/;
98
+ if (defaultConfigRegex.test(modified)) {
99
+ const strategyLine = language === 'kotlin'
100
+ ? ` missingDimensionStrategy("platform", "${flavor}")`
101
+ : ` missingDimensionStrategy "platform", "${flavor}"`;
102
+ // Remove any existing platform strategies first to avoid duplicates
103
+ const strategyPattern = /^\s*missingDimensionStrategy\s*\(?\s*["']platform["']\s*,\s*["'](play|horizon)["']\s*\)?\s*$/gm;
104
+ if (strategyPattern.test(modified)) {
105
+ modified = modified.replace(strategyPattern, '');
106
+ logOnce('🧹 Removed existing missingDimensionStrategy for platform');
107
+ }
108
+ // Add the new strategy
109
+ if (!/missingDimensionStrategy.*platform/.test(modified)) {
110
+ modified = addLineToGradle(modified, defaultConfigRegex, strategyLine, 1);
111
+ logOnce(`🛠️ expo-iap: Added missingDimensionStrategy for ${flavor} flavor`);
112
+ }
113
+ }
114
+ }
90
115
  return modified;
91
116
  };
92
117
  const withIapAndroid = (config, props) => {
93
118
  const addDeps = props?.addDeps ?? true;
119
+ // Add dependencies if needed (only when not using local module)
94
120
  if (addDeps) {
95
121
  config = (0, config_plugins_1.withAppBuildGradle)(config, (config) => {
96
- // language provided by config-plugins: 'groovy' | 'kotlin'
97
122
  const language = config.modResults.language || 'groovy';
98
- config.modResults.contents = modifyAppBuildGradle(config.modResults.contents, language);
123
+ config.modResults.contents = modifyAppBuildGradle(config.modResults.contents, language, props?.isHorizonEnabled);
99
124
  return config;
100
125
  });
101
126
  }
127
+ // Note: missingDimensionStrategy for local dev is handled in withLocalOpenIAP
102
128
  config = (0, config_plugins_1.withAndroidManifest)(config, (config) => {
103
129
  const manifest = config.modResults;
104
130
  if (!manifest.manifest['uses-permission']) {
@@ -114,71 +140,44 @@ const withIapAndroid = (config, props) => {
114
140
  else {
115
141
  logOnce('ℹ️ com.android.vending.BILLING already exists in AndroidManifest.xml');
116
142
  }
117
- return config;
118
- });
119
- return config;
120
- };
121
- /** Add external purchase entitlements and Info.plist configuration */
122
- const withIosAlternativeBilling = (config, options) => {
123
- if (!options || !options.countries || options.countries.length === 0) {
124
- return config;
125
- }
126
- // Add entitlements
127
- config = (0, config_plugins_1.withEntitlementsPlist)(config, (config) => {
128
- // Always add basic external purchase entitlement when countries are specified
129
- config.modResults['com.apple.developer.storekit.external-purchase'] = true;
130
- logOnce('✅ Added com.apple.developer.storekit.external-purchase to entitlements');
131
- // Add external purchase link entitlement if enabled
132
- if (options.enableExternalPurchaseLink) {
133
- config.modResults['com.apple.developer.storekit.external-purchase-link'] =
134
- true;
135
- logOnce('✅ Added com.apple.developer.storekit.external-purchase-link to entitlements');
136
- }
137
- // Add streaming entitlement if enabled
138
- if (options.enableExternalPurchaseLinkStreaming) {
139
- config.modResults['com.apple.developer.storekit.external-purchase-link-streaming'] = true;
140
- logOnce('✅ Added com.apple.developer.storekit.external-purchase-link-streaming to entitlements');
141
- }
142
- return config;
143
- });
144
- // Add Info.plist configuration
145
- config = (0, config_plugins_1.withInfoPlist)(config, (config) => {
146
- const plist = config.modResults;
147
- // 1. SKExternalPurchase (Required)
148
- plist.SKExternalPurchase = options.countries;
149
- logOnce(`✅ Added SKExternalPurchase with countries: ${options.countries?.join(', ')}`);
150
- // 2. SKExternalPurchaseLink (Optional - iOS 15.4+)
151
- if (options.links && Object.keys(options.links).length > 0) {
152
- plist.SKExternalPurchaseLink = options.links;
153
- logOnce(`✅ Added SKExternalPurchaseLink for ${Object.keys(options.links).length} countries`);
154
- }
155
- // 3. SKExternalPurchaseMultiLink (iOS 17.5+)
156
- if (options.multiLinks && Object.keys(options.multiLinks).length > 0) {
157
- plist.SKExternalPurchaseMultiLink = options.multiLinks;
158
- logOnce(`✅ Added SKExternalPurchaseMultiLink for ${Object.keys(options.multiLinks).length} countries`);
159
- }
160
- // 4. SKExternalPurchaseCustomLinkRegions (iOS 18.1+)
161
- if (options.customLinkRegions && options.customLinkRegions.length > 0) {
162
- plist.SKExternalPurchaseCustomLinkRegions = options.customLinkRegions;
163
- logOnce(`✅ Added SKExternalPurchaseCustomLinkRegions: ${options.customLinkRegions.join(', ')}`);
164
- }
165
- // 5. SKExternalPurchaseLinkStreamingRegions (iOS 18.2+)
166
- if (options.streamingLinkRegions &&
167
- options.streamingLinkRegions.length > 0) {
168
- plist.SKExternalPurchaseLinkStreamingRegions =
169
- options.streamingLinkRegions;
170
- logOnce(`✅ Added SKExternalPurchaseLinkStreamingRegions: ${options.streamingLinkRegions.join(', ')}`);
143
+ // Add Meta Horizon App ID if provided
144
+ if (props?.horizonAppId) {
145
+ if (!manifest.manifest.application ||
146
+ manifest.manifest.application.length === 0) {
147
+ manifest.manifest.application = [
148
+ { $: { 'android:name': '.MainApplication' } },
149
+ ];
150
+ }
151
+ const application = manifest.manifest.application[0];
152
+ if (!application['meta-data']) {
153
+ application['meta-data'] = [];
154
+ }
155
+ const metaData = application['meta-data'];
156
+ const horizonAppIdMeta = {
157
+ $: {
158
+ 'android:name': 'com.oculus.vr.APP_ID',
159
+ 'android:value': props.horizonAppId,
160
+ },
161
+ };
162
+ const existingIndex = metaData.findIndex((m) => m.$['android:name'] === 'com.oculus.vr.APP_ID');
163
+ if (existingIndex !== -1) {
164
+ metaData[existingIndex] = horizonAppIdMeta;
165
+ logOnce(`✅ Updated com.oculus.vr.APP_ID to ${props.horizonAppId} in AndroidManifest.xml`);
166
+ }
167
+ else {
168
+ metaData.push(horizonAppIdMeta);
169
+ logOnce(`✅ Added com.oculus.vr.APP_ID: ${props.horizonAppId} to AndroidManifest.xml`);
170
+ }
171
171
  }
172
172
  return config;
173
173
  });
174
174
  return config;
175
175
  };
176
- exports.withIosAlternativeBilling = withIosAlternativeBilling;
177
176
  /** Ensure Podfile uses CocoaPods CDN and no stale local OpenIAP entry remains. */
178
177
  const withIapIOS = (config, options) => {
179
178
  // Add iOS alternative billing configuration if provided
180
179
  if (options) {
181
- config = withIosAlternativeBilling(config, options);
180
+ config = (0, withIosAlternativeBilling_1.withIosAlternativeBilling)(config, options);
182
181
  }
183
182
  return (0, config_plugins_1.withDangerousMod)(config, [
184
183
  'ios',
@@ -208,10 +207,19 @@ const withIapIOS = (config, options) => {
208
207
  };
209
208
  const withIap = (config, options) => {
210
209
  try {
210
+ // Read Horizon configuration from modules
211
+ const isHorizonEnabled = options?.modules?.horizon ?? false;
212
+ const horizonAppId = options?.android?.horizonAppId ?? options?.horizonAppId;
213
+ const iosAlternativeBilling = options?.ios?.alternativeBilling ?? options?.iosAlternativeBilling;
214
+ logOnce(`🔍 [expo-iap] Config values: horizonAppId=${horizonAppId}, isHorizonEnabled=${isHorizonEnabled}`);
211
215
  // Respect explicit flag; fall back to presence of localPath only when flag is unset
212
216
  const isLocalDev = options?.enableLocalDev ?? !!options?.localPath;
213
217
  // Apply Android modifications (skip adding deps when linking local module)
214
- let result = withIapAndroid(config, { addDeps: !isLocalDev });
218
+ let result = withIapAndroid(config, {
219
+ addDeps: !isLocalDev,
220
+ horizonAppId,
221
+ isHorizonEnabled,
222
+ });
215
223
  // iOS: choose one path to avoid overlap
216
224
  if (isLocalDev) {
217
225
  if (!options?.localPath) {
@@ -231,13 +239,15 @@ const withIap = (config, options) => {
231
239
  logOnce(`🔧 [expo-iap] Enabling local OpenIAP: ${preview}`);
232
240
  result = (0, withLocalOpenIAP_1.default)(result, {
233
241
  localPath: resolved,
234
- iosAlternativeBilling: options?.iosAlternativeBilling,
242
+ iosAlternativeBilling,
243
+ horizonAppId,
244
+ isHorizonEnabled, // Resolved from modules.horizon (line 467)
235
245
  });
236
246
  }
237
247
  }
238
248
  else {
239
249
  // Ensure iOS Podfile is set up to resolve public CocoaPods specs
240
- result = withIapIOS(result, options?.iosAlternativeBilling);
250
+ result = withIapIOS(result, iosAlternativeBilling);
241
251
  logOnce('📦 [expo-iap] Using OpenIAP from CocoaPods');
242
252
  }
243
253
  return result;
@@ -0,0 +1,19 @@
1
+ import { ConfigPlugin } from 'expo/config-plugins';
2
+ export interface IOSAlternativeBillingConfig {
3
+ /** Country codes where external purchases are supported (ISO 3166-1 alpha-2) */
4
+ countries?: string[];
5
+ /** External purchase URLs per country (iOS 15.4+) */
6
+ links?: Record<string, string>;
7
+ /** Multiple external purchase URLs per country (iOS 17.5+, up to 5 per country) */
8
+ multiLinks?: Record<string, string[]>;
9
+ /** Custom link regions (iOS 18.1+) */
10
+ customLinkRegions?: string[];
11
+ /** Streaming link regions for music apps (iOS 18.2+) */
12
+ streamingLinkRegions?: string[];
13
+ /** Enable external purchase link entitlement */
14
+ enableExternalPurchaseLink?: boolean;
15
+ /** Enable external purchase link streaming entitlement (music apps only) */
16
+ enableExternalPurchaseLinkStreaming?: boolean;
17
+ }
18
+ /** Add external purchase entitlements and Info.plist configuration */
19
+ export declare const withIosAlternativeBilling: ConfigPlugin<IOSAlternativeBillingConfig | undefined>;
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withIosAlternativeBilling = void 0;
4
+ const config_plugins_1 = require("expo/config-plugins");
5
+ // Log a message only once per Node process
6
+ const logOnce = (() => {
7
+ const printed = new Set();
8
+ return (msg) => {
9
+ if (!printed.has(msg)) {
10
+ console.log(msg);
11
+ printed.add(msg);
12
+ }
13
+ };
14
+ })();
15
+ /** Add external purchase entitlements and Info.plist configuration */
16
+ const withIosAlternativeBilling = (config, options) => {
17
+ if (!options || !options.countries || options.countries.length === 0) {
18
+ return config;
19
+ }
20
+ // Add entitlements
21
+ config = (0, config_plugins_1.withEntitlementsPlist)(config, (config) => {
22
+ // Always add basic external purchase entitlement when countries are specified
23
+ config.modResults['com.apple.developer.storekit.external-purchase'] = true;
24
+ logOnce('✅ Added com.apple.developer.storekit.external-purchase to entitlements');
25
+ // Add external purchase link entitlement if enabled
26
+ if (options.enableExternalPurchaseLink) {
27
+ config.modResults['com.apple.developer.storekit.external-purchase-link'] =
28
+ true;
29
+ logOnce('✅ Added com.apple.developer.storekit.external-purchase-link to entitlements');
30
+ }
31
+ // Add streaming entitlement if enabled
32
+ if (options.enableExternalPurchaseLinkStreaming) {
33
+ config.modResults['com.apple.developer.storekit.external-purchase-link-streaming'] = true;
34
+ logOnce('✅ Added com.apple.developer.storekit.external-purchase-link-streaming to entitlements');
35
+ }
36
+ return config;
37
+ });
38
+ // Add Info.plist configuration
39
+ config = (0, config_plugins_1.withInfoPlist)(config, (config) => {
40
+ const plist = config.modResults;
41
+ // 1. SKExternalPurchase (Required)
42
+ plist.SKExternalPurchase = options.countries;
43
+ logOnce(`✅ Added SKExternalPurchase with countries: ${options.countries?.join(', ')}`);
44
+ // 2. SKExternalPurchaseLink (Optional - iOS 15.4+)
45
+ if (options.links && Object.keys(options.links).length > 0) {
46
+ plist.SKExternalPurchaseLink = options.links;
47
+ logOnce(`✅ Added SKExternalPurchaseLink for ${Object.keys(options.links).length} countries`);
48
+ }
49
+ // 3. SKExternalPurchaseMultiLink (iOS 17.5+)
50
+ if (options.multiLinks && Object.keys(options.multiLinks).length > 0) {
51
+ plist.SKExternalPurchaseMultiLink = options.multiLinks;
52
+ logOnce(`✅ Added SKExternalPurchaseMultiLink for ${Object.keys(options.multiLinks).length} countries`);
53
+ }
54
+ // 4. SKExternalPurchaseCustomLinkRegions (iOS 18.1+)
55
+ if (options.customLinkRegions && options.customLinkRegions.length > 0) {
56
+ plist.SKExternalPurchaseCustomLinkRegions = options.customLinkRegions;
57
+ logOnce(`✅ Added SKExternalPurchaseCustomLinkRegions: ${options.customLinkRegions.join(', ')}`);
58
+ }
59
+ // 5. SKExternalPurchaseLinkStreamingRegions (iOS 18.2+)
60
+ if (options.streamingLinkRegions &&
61
+ options.streamingLinkRegions.length > 0) {
62
+ plist.SKExternalPurchaseLinkStreamingRegions =
63
+ options.streamingLinkRegions;
64
+ logOnce(`✅ Added SKExternalPurchaseLinkStreamingRegions: ${options.streamingLinkRegions.join(', ')}`);
65
+ }
66
+ return config;
67
+ });
68
+ return config;
69
+ };
70
+ exports.withIosAlternativeBilling = withIosAlternativeBilling;
@@ -1,5 +1,5 @@
1
1
  import { ConfigPlugin } from 'expo/config-plugins';
2
- import type { IOSAlternativeBillingConfig } from './withIAP';
2
+ import { type IOSAlternativeBillingConfig } from './withIosAlternativeBilling';
3
3
  /**
4
4
  * Plugin to add local OpenIAP pod dependency for development
5
5
  * This is only for local development with openiap-apple library
@@ -11,5 +11,8 @@ type LocalPathOption = string | {
11
11
  declare const withLocalOpenIAP: ConfigPlugin<{
12
12
  localPath?: LocalPathOption;
13
13
  iosAlternativeBilling?: IOSAlternativeBillingConfig;
14
+ horizonAppId?: string;
15
+ /** Resolved from modules.horizon by withIAP */
16
+ isHorizonEnabled?: boolean;
14
17
  } | void>;
15
18
  export default withLocalOpenIAP;