expo-iap 3.1.18 โ 3.1.20
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/README.md +1 -1
- package/android/build.gradle +12 -0
- package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +1 -1
- package/build/types.d.ts +1 -1
- package/build/types.d.ts.map +1 -1
- package/build/types.js.map +1 -1
- package/bun.lockb +0 -0
- package/coverage/clover.xml +2 -2
- package/coverage/lcov-report/index.html +1 -1
- package/coverage/lcov-report/src/index.html +1 -1
- package/coverage/lcov-report/src/index.ts.html +1 -1
- package/coverage/lcov-report/src/modules/android.ts.html +1 -1
- package/coverage/lcov-report/src/modules/index.html +1 -1
- package/coverage/lcov-report/src/modules/ios.ts.html +1 -1
- package/coverage/lcov-report/src/utils/debug.ts.html +1 -1
- package/coverage/lcov-report/src/utils/errorMapping.ts.html +1 -1
- package/coverage/lcov-report/src/utils/index.html +1 -1
- package/ios/ExpoIap.podspec +2 -1
- package/openiap-versions.json +3 -3
- package/package.json +1 -1
- package/plugin/build/withIAP.d.ts +39 -21
- package/plugin/build/withIAP.js +72 -62
- package/plugin/build/withIosAlternativeBilling.d.ts +19 -0
- package/plugin/build/withIosAlternativeBilling.js +70 -0
- package/plugin/build/withLocalOpenIAP.d.ts +4 -1
- package/plugin/build/withLocalOpenIAP.js +58 -57
- package/plugin/src/withIAP.ts +145 -123
- package/plugin/src/withIosAlternativeBilling.ts +133 -0
- package/plugin/src/withLocalOpenIAP.ts +80 -67
- package/plugin/tsconfig.tsbuildinfo +1 -1
- package/src/types.ts +1 -1
package/plugin/src/withIAP.ts
CHANGED
|
@@ -5,12 +5,14 @@ import {
|
|
|
5
5
|
withAndroidManifest,
|
|
6
6
|
withAppBuildGradle,
|
|
7
7
|
withDangerousMod,
|
|
8
|
-
withEntitlementsPlist,
|
|
9
|
-
withInfoPlist,
|
|
10
8
|
} from 'expo/config-plugins';
|
|
11
9
|
import * as fs from 'fs';
|
|
12
10
|
import * as path from 'path';
|
|
13
11
|
import withLocalOpenIAP from './withLocalOpenIAP';
|
|
12
|
+
import {
|
|
13
|
+
withIosAlternativeBilling,
|
|
14
|
+
type IOSAlternativeBillingConfig,
|
|
15
|
+
} from './withIosAlternativeBilling';
|
|
14
16
|
|
|
15
17
|
const pkg = require('../../package.json');
|
|
16
18
|
const openiapVersions = JSON.parse(
|
|
@@ -55,9 +57,13 @@ const addLineToGradle = (
|
|
|
55
57
|
const modifyAppBuildGradle = (
|
|
56
58
|
gradle: string,
|
|
57
59
|
language: 'groovy' | 'kotlin',
|
|
60
|
+
isHorizonEnabled?: boolean,
|
|
58
61
|
): string => {
|
|
59
62
|
let modified = gradle;
|
|
60
63
|
|
|
64
|
+
// Determine which flavor to use based on isHorizonEnabled
|
|
65
|
+
const flavor = isHorizonEnabled ? 'horizon' : 'play';
|
|
66
|
+
|
|
61
67
|
// Ensure OpenIAP dependency exists at desired version in app-level build.gradle(.kts)
|
|
62
68
|
const impl = (ga: string, v: string) =>
|
|
63
69
|
language === 'kotlin'
|
|
@@ -91,27 +97,66 @@ const modifyAppBuildGradle = (
|
|
|
91
97
|
);
|
|
92
98
|
}
|
|
93
99
|
|
|
100
|
+
// Add flavor dimension and default config for OpenIAP if horizon is enabled
|
|
101
|
+
if (isHorizonEnabled) {
|
|
102
|
+
// Add missingDimensionStrategy to select horizon flavor
|
|
103
|
+
const defaultConfigRegex = /defaultConfig\s*{/;
|
|
104
|
+
if (defaultConfigRegex.test(modified)) {
|
|
105
|
+
const strategyLine =
|
|
106
|
+
language === 'kotlin'
|
|
107
|
+
? ` missingDimensionStrategy("platform", "${flavor}")`
|
|
108
|
+
: ` missingDimensionStrategy "platform", "${flavor}"`;
|
|
109
|
+
|
|
110
|
+
// Remove any existing platform strategies first to avoid duplicates
|
|
111
|
+
const strategyPattern =
|
|
112
|
+
/^\s*missingDimensionStrategy\s*\(?\s*["']platform["']\s*,\s*["'](play|horizon)["']\s*\)?\s*$/gm;
|
|
113
|
+
if (strategyPattern.test(modified)) {
|
|
114
|
+
modified = modified.replace(strategyPattern, '');
|
|
115
|
+
logOnce('๐งน Removed existing missingDimensionStrategy for platform');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Add the new strategy
|
|
119
|
+
if (!/missingDimensionStrategy.*platform/.test(modified)) {
|
|
120
|
+
modified = addLineToGradle(
|
|
121
|
+
modified,
|
|
122
|
+
defaultConfigRegex,
|
|
123
|
+
strategyLine,
|
|
124
|
+
1,
|
|
125
|
+
);
|
|
126
|
+
logOnce(
|
|
127
|
+
`๐ ๏ธ expo-iap: Added missingDimensionStrategy for ${flavor} flavor`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
94
133
|
return modified;
|
|
95
134
|
};
|
|
96
135
|
|
|
97
|
-
const withIapAndroid: ConfigPlugin<
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
136
|
+
const withIapAndroid: ConfigPlugin<
|
|
137
|
+
{
|
|
138
|
+
addDeps?: boolean;
|
|
139
|
+
horizonAppId?: string;
|
|
140
|
+
isHorizonEnabled?: boolean;
|
|
141
|
+
} | void
|
|
142
|
+
> = (config, props) => {
|
|
101
143
|
const addDeps = props?.addDeps ?? true;
|
|
102
144
|
|
|
145
|
+
// Add dependencies if needed (only when not using local module)
|
|
103
146
|
if (addDeps) {
|
|
104
147
|
config = withAppBuildGradle(config, (config) => {
|
|
105
|
-
// language provided by config-plugins: 'groovy' | 'kotlin'
|
|
106
148
|
const language = (config.modResults as any).language || 'groovy';
|
|
107
149
|
config.modResults.contents = modifyAppBuildGradle(
|
|
108
150
|
config.modResults.contents,
|
|
109
151
|
language,
|
|
152
|
+
props?.isHorizonEnabled,
|
|
110
153
|
);
|
|
111
154
|
return config;
|
|
112
155
|
});
|
|
113
156
|
}
|
|
114
157
|
|
|
158
|
+
// Note: missingDimensionStrategy for local dev is handled in withLocalOpenIAP
|
|
159
|
+
|
|
115
160
|
config = withAndroidManifest(config, (config) => {
|
|
116
161
|
const manifest = config.modResults;
|
|
117
162
|
if (!manifest.manifest['uses-permission']) {
|
|
@@ -133,121 +178,45 @@ const withIapAndroid: ConfigPlugin<{addDeps?: boolean} | void> = (
|
|
|
133
178
|
);
|
|
134
179
|
}
|
|
135
180
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
links?: Record<string, string>;
|
|
147
|
-
/** Multiple external purchase URLs per country (iOS 17.5+, up to 5 per country) */
|
|
148
|
-
multiLinks?: Record<string, string[]>;
|
|
149
|
-
/** Custom link regions (iOS 18.1+) */
|
|
150
|
-
customLinkRegions?: string[];
|
|
151
|
-
/** Streaming link regions for music apps (iOS 18.2+) */
|
|
152
|
-
streamingLinkRegions?: string[];
|
|
153
|
-
/** Enable external purchase link entitlement */
|
|
154
|
-
enableExternalPurchaseLink?: boolean;
|
|
155
|
-
/** Enable external purchase link streaming entitlement (music apps only) */
|
|
156
|
-
enableExternalPurchaseLinkStreaming?: boolean;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/** Add external purchase entitlements and Info.plist configuration */
|
|
160
|
-
const withIosAlternativeBilling: ConfigPlugin<
|
|
161
|
-
IOSAlternativeBillingConfig | undefined
|
|
162
|
-
> = (config, options) => {
|
|
163
|
-
if (!options || !options.countries || options.countries.length === 0) {
|
|
164
|
-
return config;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Add entitlements
|
|
168
|
-
config = withEntitlementsPlist(config, (config) => {
|
|
169
|
-
// Always add basic external purchase entitlement when countries are specified
|
|
170
|
-
config.modResults['com.apple.developer.storekit.external-purchase'] = true;
|
|
171
|
-
logOnce(
|
|
172
|
-
'โ
Added com.apple.developer.storekit.external-purchase to entitlements',
|
|
173
|
-
);
|
|
174
|
-
|
|
175
|
-
// Add external purchase link entitlement if enabled
|
|
176
|
-
if (options.enableExternalPurchaseLink) {
|
|
177
|
-
config.modResults['com.apple.developer.storekit.external-purchase-link'] =
|
|
178
|
-
true;
|
|
179
|
-
logOnce(
|
|
180
|
-
'โ
Added com.apple.developer.storekit.external-purchase-link to entitlements',
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Add streaming entitlement if enabled
|
|
185
|
-
if (options.enableExternalPurchaseLinkStreaming) {
|
|
186
|
-
config.modResults[
|
|
187
|
-
'com.apple.developer.storekit.external-purchase-link-streaming'
|
|
188
|
-
] = true;
|
|
189
|
-
logOnce(
|
|
190
|
-
'โ
Added com.apple.developer.storekit.external-purchase-link-streaming to entitlements',
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return config;
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
// Add Info.plist configuration
|
|
198
|
-
config = withInfoPlist(config, (config) => {
|
|
199
|
-
const plist = config.modResults;
|
|
200
|
-
|
|
201
|
-
// 1. SKExternalPurchase (Required)
|
|
202
|
-
plist.SKExternalPurchase = options.countries;
|
|
203
|
-
logOnce(
|
|
204
|
-
`โ
Added SKExternalPurchase with countries: ${options.countries?.join(
|
|
205
|
-
', ',
|
|
206
|
-
)}`,
|
|
207
|
-
);
|
|
181
|
+
// Add Meta Horizon App ID if provided
|
|
182
|
+
if (props?.horizonAppId) {
|
|
183
|
+
if (
|
|
184
|
+
!manifest.manifest.application ||
|
|
185
|
+
manifest.manifest.application.length === 0
|
|
186
|
+
) {
|
|
187
|
+
manifest.manifest.application = [
|
|
188
|
+
{$: {'android:name': '.MainApplication'}},
|
|
189
|
+
];
|
|
190
|
+
}
|
|
208
191
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
`โ
Added SKExternalPurchaseLink for ${
|
|
214
|
-
Object.keys(options.links).length
|
|
215
|
-
} countries`,
|
|
216
|
-
);
|
|
217
|
-
}
|
|
192
|
+
const application = manifest.manifest.application![0];
|
|
193
|
+
if (!application['meta-data']) {
|
|
194
|
+
application['meta-data'] = [];
|
|
195
|
+
}
|
|
218
196
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
);
|
|
227
|
-
}
|
|
197
|
+
const metaData = application['meta-data'];
|
|
198
|
+
const horizonAppIdMeta = {
|
|
199
|
+
$: {
|
|
200
|
+
'android:name': 'com.oculus.vr.APP_ID',
|
|
201
|
+
'android:value': props.horizonAppId,
|
|
202
|
+
},
|
|
203
|
+
};
|
|
228
204
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
plist.SKExternalPurchaseCustomLinkRegions = options.customLinkRegions;
|
|
232
|
-
logOnce(
|
|
233
|
-
`โ
Added SKExternalPurchaseCustomLinkRegions: ${options.customLinkRegions.join(
|
|
234
|
-
', ',
|
|
235
|
-
)}`,
|
|
205
|
+
const existingIndex = metaData.findIndex(
|
|
206
|
+
(m) => m.$['android:name'] === 'com.oculus.vr.APP_ID',
|
|
236
207
|
);
|
|
237
|
-
}
|
|
238
208
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
);
|
|
209
|
+
if (existingIndex !== -1) {
|
|
210
|
+
metaData[existingIndex] = horizonAppIdMeta;
|
|
211
|
+
logOnce(
|
|
212
|
+
`โ
Updated com.oculus.vr.APP_ID to ${props.horizonAppId} in AndroidManifest.xml`,
|
|
213
|
+
);
|
|
214
|
+
} else {
|
|
215
|
+
metaData.push(horizonAppIdMeta);
|
|
216
|
+
logOnce(
|
|
217
|
+
`โ
Added com.oculus.vr.APP_ID: ${props.horizonAppId} to AndroidManifest.xml`,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
251
220
|
}
|
|
252
221
|
|
|
253
222
|
return config;
|
|
@@ -310,12 +279,47 @@ export interface ExpoIapPluginOptions {
|
|
|
310
279
|
/** Enable local development mode */
|
|
311
280
|
enableLocalDev?: boolean;
|
|
312
281
|
/**
|
|
313
|
-
*
|
|
314
|
-
|
|
315
|
-
|
|
282
|
+
* Optional modules configuration
|
|
283
|
+
*/
|
|
284
|
+
modules?: {
|
|
285
|
+
/**
|
|
286
|
+
* Onside module for iOS alternative billing (Korea market)
|
|
287
|
+
* @platform ios
|
|
288
|
+
*/
|
|
289
|
+
onside?: boolean;
|
|
290
|
+
/**
|
|
291
|
+
* Horizon module for Meta Quest/VR devices
|
|
292
|
+
* @platform android
|
|
293
|
+
*/
|
|
294
|
+
horizon?: boolean;
|
|
295
|
+
};
|
|
296
|
+
/**
|
|
297
|
+
* iOS-specific configuration
|
|
316
298
|
* @platform ios
|
|
317
299
|
*/
|
|
300
|
+
ios?: {
|
|
301
|
+
/**
|
|
302
|
+
* iOS Alternative Billing configuration.
|
|
303
|
+
* Configure external purchase countries, links, and entitlements.
|
|
304
|
+
* Requires approval from Apple.
|
|
305
|
+
*/
|
|
306
|
+
alternativeBilling?: IOSAlternativeBillingConfig;
|
|
307
|
+
};
|
|
308
|
+
/**
|
|
309
|
+
* Android-specific configuration
|
|
310
|
+
* @platform android
|
|
311
|
+
*/
|
|
312
|
+
android?: {
|
|
313
|
+
/**
|
|
314
|
+
* Meta Horizon App ID for Quest/VR devices.
|
|
315
|
+
* Required when modules.horizon is true.
|
|
316
|
+
*/
|
|
317
|
+
horizonAppId?: string;
|
|
318
|
+
};
|
|
319
|
+
/** @deprecated Use ios.alternativeBilling instead */
|
|
318
320
|
iosAlternativeBilling?: IOSAlternativeBillingConfig;
|
|
321
|
+
/** @deprecated Use android.horizonAppId instead */
|
|
322
|
+
horizonAppId?: string;
|
|
319
323
|
}
|
|
320
324
|
|
|
321
325
|
const withIap: ConfigPlugin<ExpoIapPluginOptions | void> = (
|
|
@@ -323,10 +327,26 @@ const withIap: ConfigPlugin<ExpoIapPluginOptions | void> = (
|
|
|
323
327
|
options,
|
|
324
328
|
) => {
|
|
325
329
|
try {
|
|
330
|
+
// Read Horizon configuration from modules
|
|
331
|
+
const isHorizonEnabled = options?.modules?.horizon ?? false;
|
|
332
|
+
|
|
333
|
+
const horizonAppId =
|
|
334
|
+
options?.android?.horizonAppId ?? options?.horizonAppId;
|
|
335
|
+
const iosAlternativeBilling =
|
|
336
|
+
options?.ios?.alternativeBilling ?? options?.iosAlternativeBilling;
|
|
337
|
+
|
|
338
|
+
logOnce(
|
|
339
|
+
`๐ [expo-iap] Config values: horizonAppId=${horizonAppId}, isHorizonEnabled=${isHorizonEnabled}`,
|
|
340
|
+
);
|
|
341
|
+
|
|
326
342
|
// Respect explicit flag; fall back to presence of localPath only when flag is unset
|
|
327
343
|
const isLocalDev = options?.enableLocalDev ?? !!options?.localPath;
|
|
328
344
|
// Apply Android modifications (skip adding deps when linking local module)
|
|
329
|
-
let result = withIapAndroid(config, {
|
|
345
|
+
let result = withIapAndroid(config, {
|
|
346
|
+
addDeps: !isLocalDev,
|
|
347
|
+
horizonAppId,
|
|
348
|
+
isHorizonEnabled,
|
|
349
|
+
});
|
|
330
350
|
|
|
331
351
|
// iOS: choose one path to avoid overlap
|
|
332
352
|
if (isLocalDev) {
|
|
@@ -354,12 +374,14 @@ const withIap: ConfigPlugin<ExpoIapPluginOptions | void> = (
|
|
|
354
374
|
logOnce(`๐ง [expo-iap] Enabling local OpenIAP: ${preview}`);
|
|
355
375
|
result = withLocalOpenIAP(result, {
|
|
356
376
|
localPath: resolved,
|
|
357
|
-
iosAlternativeBilling
|
|
377
|
+
iosAlternativeBilling,
|
|
378
|
+
horizonAppId,
|
|
379
|
+
isHorizonEnabled, // Resolved from modules.horizon (line 467)
|
|
358
380
|
});
|
|
359
381
|
}
|
|
360
382
|
} else {
|
|
361
383
|
// Ensure iOS Podfile is set up to resolve public CocoaPods specs
|
|
362
|
-
result = withIapIOS(result,
|
|
384
|
+
result = withIapIOS(result, iosAlternativeBilling);
|
|
363
385
|
logOnce('๐ฆ [expo-iap] Using OpenIAP from CocoaPods');
|
|
364
386
|
}
|
|
365
387
|
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ConfigPlugin,
|
|
3
|
+
withEntitlementsPlist,
|
|
4
|
+
withInfoPlist,
|
|
5
|
+
} from 'expo/config-plugins';
|
|
6
|
+
|
|
7
|
+
// Log a message only once per Node process
|
|
8
|
+
const logOnce = (() => {
|
|
9
|
+
const printed = new Set<string>();
|
|
10
|
+
return (msg: string) => {
|
|
11
|
+
if (!printed.has(msg)) {
|
|
12
|
+
console.log(msg);
|
|
13
|
+
printed.add(msg);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
})();
|
|
17
|
+
|
|
18
|
+
export interface IOSAlternativeBillingConfig {
|
|
19
|
+
/** Country codes where external purchases are supported (ISO 3166-1 alpha-2) */
|
|
20
|
+
countries?: string[];
|
|
21
|
+
/** External purchase URLs per country (iOS 15.4+) */
|
|
22
|
+
links?: Record<string, string>;
|
|
23
|
+
/** Multiple external purchase URLs per country (iOS 17.5+, up to 5 per country) */
|
|
24
|
+
multiLinks?: Record<string, string[]>;
|
|
25
|
+
/** Custom link regions (iOS 18.1+) */
|
|
26
|
+
customLinkRegions?: string[];
|
|
27
|
+
/** Streaming link regions for music apps (iOS 18.2+) */
|
|
28
|
+
streamingLinkRegions?: string[];
|
|
29
|
+
/** Enable external purchase link entitlement */
|
|
30
|
+
enableExternalPurchaseLink?: boolean;
|
|
31
|
+
/** Enable external purchase link streaming entitlement (music apps only) */
|
|
32
|
+
enableExternalPurchaseLinkStreaming?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Add external purchase entitlements and Info.plist configuration */
|
|
36
|
+
export const withIosAlternativeBilling: ConfigPlugin<
|
|
37
|
+
IOSAlternativeBillingConfig | undefined
|
|
38
|
+
> = (config, options) => {
|
|
39
|
+
if (!options || !options.countries || options.countries.length === 0) {
|
|
40
|
+
return config;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Add entitlements
|
|
44
|
+
config = withEntitlementsPlist(config, (config) => {
|
|
45
|
+
// Always add basic external purchase entitlement when countries are specified
|
|
46
|
+
config.modResults['com.apple.developer.storekit.external-purchase'] = true;
|
|
47
|
+
logOnce(
|
|
48
|
+
'โ
Added com.apple.developer.storekit.external-purchase to entitlements',
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Add external purchase link entitlement if enabled
|
|
52
|
+
if (options.enableExternalPurchaseLink) {
|
|
53
|
+
config.modResults['com.apple.developer.storekit.external-purchase-link'] =
|
|
54
|
+
true;
|
|
55
|
+
logOnce(
|
|
56
|
+
'โ
Added com.apple.developer.storekit.external-purchase-link to entitlements',
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Add streaming entitlement if enabled
|
|
61
|
+
if (options.enableExternalPurchaseLinkStreaming) {
|
|
62
|
+
config.modResults[
|
|
63
|
+
'com.apple.developer.storekit.external-purchase-link-streaming'
|
|
64
|
+
] = true;
|
|
65
|
+
logOnce(
|
|
66
|
+
'โ
Added com.apple.developer.storekit.external-purchase-link-streaming to entitlements',
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return config;
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Add Info.plist configuration
|
|
74
|
+
config = withInfoPlist(config, (config) => {
|
|
75
|
+
const plist = config.modResults;
|
|
76
|
+
|
|
77
|
+
// 1. SKExternalPurchase (Required)
|
|
78
|
+
plist.SKExternalPurchase = options.countries;
|
|
79
|
+
logOnce(
|
|
80
|
+
`โ
Added SKExternalPurchase with countries: ${options.countries?.join(
|
|
81
|
+
', ',
|
|
82
|
+
)}`,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// 2. SKExternalPurchaseLink (Optional - iOS 15.4+)
|
|
86
|
+
if (options.links && Object.keys(options.links).length > 0) {
|
|
87
|
+
plist.SKExternalPurchaseLink = options.links;
|
|
88
|
+
logOnce(
|
|
89
|
+
`โ
Added SKExternalPurchaseLink for ${
|
|
90
|
+
Object.keys(options.links).length
|
|
91
|
+
} countries`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 3. SKExternalPurchaseMultiLink (iOS 17.5+)
|
|
96
|
+
if (options.multiLinks && Object.keys(options.multiLinks).length > 0) {
|
|
97
|
+
plist.SKExternalPurchaseMultiLink = options.multiLinks;
|
|
98
|
+
logOnce(
|
|
99
|
+
`โ
Added SKExternalPurchaseMultiLink for ${
|
|
100
|
+
Object.keys(options.multiLinks).length
|
|
101
|
+
} countries`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 4. SKExternalPurchaseCustomLinkRegions (iOS 18.1+)
|
|
106
|
+
if (options.customLinkRegions && options.customLinkRegions.length > 0) {
|
|
107
|
+
plist.SKExternalPurchaseCustomLinkRegions = options.customLinkRegions;
|
|
108
|
+
logOnce(
|
|
109
|
+
`โ
Added SKExternalPurchaseCustomLinkRegions: ${options.customLinkRegions.join(
|
|
110
|
+
', ',
|
|
111
|
+
)}`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 5. SKExternalPurchaseLinkStreamingRegions (iOS 18.2+)
|
|
116
|
+
if (
|
|
117
|
+
options.streamingLinkRegions &&
|
|
118
|
+
options.streamingLinkRegions.length > 0
|
|
119
|
+
) {
|
|
120
|
+
plist.SKExternalPurchaseLinkStreamingRegions =
|
|
121
|
+
options.streamingLinkRegions;
|
|
122
|
+
logOnce(
|
|
123
|
+
`โ
Added SKExternalPurchaseLinkStreamingRegions: ${options.streamingLinkRegions.join(
|
|
124
|
+
', ',
|
|
125
|
+
)}`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return config;
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
return config;
|
|
133
|
+
};
|