expo-iap 3.1.17 → 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.
- package/CONTRIBUTING.md +1 -1
- package/README.md +1 -1
- package/android/build.gradle +5 -0
- 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/openiap-versions.json +2 -2
- 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/scripts/update-types.mjs +1 -1
package/CONTRIBUTING.md
CHANGED
|
@@ -216,7 +216,7 @@ For detailed code conventions, naming standards, and implementation guidelines,
|
|
|
216
216
|
|
|
217
217
|
### Updating OpenIAP Types
|
|
218
218
|
|
|
219
|
-
The generated TypeScript definitions in `src/types.ts` come from the [
|
|
219
|
+
The generated TypeScript definitions in `src/types.ts` come from the [OpenIAP](https://github.com/hyodotdev/openiap) release artifacts. Never edit this file by hand. When the schema changes or you need to pull newer types:
|
|
220
220
|
|
|
221
221
|
- Run `bun run generate:types` to download the latest pinned release and overwrite `src/types.ts`.
|
|
222
222
|
- To target a specific release, pass the tag: `bun run generate:types --tag <version>`.
|
package/README.md
CHANGED
|
@@ -76,7 +76,7 @@ For detailed usage examples and error handling, see the [documentation](https://
|
|
|
76
76
|
|
|
77
77
|
💼 **[View Our Sponsors](https://openiap.dev/sponsors)**
|
|
78
78
|
|
|
79
|
-
We're building the OpenIAP ecosystem—defining the spec at [openiap.dev](https://www.openiap.dev), maintaining [
|
|
79
|
+
We're building the OpenIAP ecosystem—defining the spec at [openiap.dev](https://www.openiap.dev), maintaining [OpenIAP](https://github.com/hyodotdev/openiap) for the shared type system, and shipping platform SDKs like [openiap-apple](https://github.com/hyodotdev/openiap-apple) and [openiap-google](https://github.com/hyodotdev/openiap-google) that power [expo-iap](https://github.com/hyochan/expo-iap), [flutter_inapp_purchase](https://github.com/hyochan/flutter_inapp_purchase), React Native, and [kmp-iap](https://github.com/hyochan/kmp-iap). The work so far has focused on untangling fragmented APIs; the next milestone is a streamlined purchase flow: `initConnection → fetchProducts → requestPurchase → (server receipt validation) → finishTransaction`.
|
|
80
80
|
|
|
81
81
|
Your sponsorship helps ensure developers across platforms, OS, and frameworks can implement in-app purchases without headaches. It also fuels new plugins, payment systems, and partner integrations already being explored in the OpenIAP community. Sponsors receive shout-outs in every release and can request tailored support depending on tier. If you’re interested—or have rollout feedback to share—you can view sponsorship options at [openiap.dev/sponsors](https://openiap.dev/sponsors).
|
|
82
82
|
|
package/android/build.gradle
CHANGED
|
@@ -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
|
package/coverage/clover.xml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<coverage generated="
|
|
3
|
-
<project timestamp="
|
|
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-
|
|
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-
|
|
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-
|
|
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<
|
|
|
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-
|
|
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-
|
|
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<
|
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
119
|
+
at 2025-10-19T23:17:12.601Z
|
|
120
120
|
</div>
|
|
121
121
|
<script src="../../prettify.js"></script>
|
|
122
122
|
<script>
|
package/openiap-versions.json
CHANGED
package/package.json
CHANGED
|
@@ -1,22 +1,5 @@
|
|
|
1
1
|
import { ConfigPlugin } from 'expo/config-plugins';
|
|
2
|
-
|
|
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
|
-
*
|
|
30
|
-
|
|
31
|
-
|
|
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>;
|
package/plugin/build/withIAP.js
CHANGED
|
@@ -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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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, {
|
|
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
|
|
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,
|
|
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
|
|
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;
|