expo-iap 5.3.0 → 5.3.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/{CLAUDE.md → AGENTS.md} +1 -3
- package/CONTRIBUTING.md +5 -12
- package/README.md +3 -3
- package/android/src/main/java/expo/modules/iap/ExpoIapHelper.kt +87 -39
- package/android/src/main/java/expo/modules/iap/ExpoIapLog.kt +4 -0
- package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +35 -27
- package/build/index.d.ts.map +1 -1
- package/build/index.js +65 -30
- package/build/index.js.map +1 -1
- package/build/index.kepler.d.ts.map +1 -1
- package/build/index.kepler.js +9 -1
- package/build/index.kepler.js.map +1 -1
- package/build/kit-api.d.ts +119 -2
- package/build/kit-api.d.ts.map +1 -1
- package/build/kit-api.js +3 -1
- package/build/kit-api.js.map +1 -1
- package/build/types.d.ts +33 -9
- package/build/types.d.ts.map +1 -1
- package/build/types.js.map +1 -1
- package/build/vega-adapter.d.ts.map +1 -1
- package/build/vega-adapter.js +5 -6
- package/build/vega-adapter.js.map +1 -1
- package/bun.lock +421 -1139
- package/ios/ExpoIapHelper.swift +80 -17
- package/ios/ExpoIapLog.swift +6 -6
- package/ios/ExpoIapModule.swift +20 -7
- package/ios/onside/OnsideIapModule.swift +28 -56
- package/openiap-versions.json +3 -3
- package/osv-scanner.toml +9 -0
- package/package.json +8 -2
- package/plugin/build/withVega.js +10 -0
- package/plugin/src/withVega.ts +10 -0
- package/src/index.kepler.ts +9 -1
- package/src/index.ts +54 -9
- package/src/kit-api.ts +125 -4
- package/src/types.ts +34 -9
- package/src/vega-adapter.ts +6 -11
- package/tests/ios/ExpoIapLogTests.swift +37 -0
package/src/kit-api.ts
CHANGED
|
@@ -31,7 +31,15 @@ export type KitSubscription = {
|
|
|
31
31
|
id: string;
|
|
32
32
|
productId: string;
|
|
33
33
|
platform: "IOS" | "Android";
|
|
34
|
-
state:
|
|
34
|
+
state:
|
|
35
|
+
| "Active"
|
|
36
|
+
| "InGracePeriod"
|
|
37
|
+
| "InBillingRetry"
|
|
38
|
+
| "Expired"
|
|
39
|
+
| "Revoked"
|
|
40
|
+
| "Refunded"
|
|
41
|
+
| "Paused"
|
|
42
|
+
| "Unknown";
|
|
35
43
|
expiresAt?: number;
|
|
36
44
|
renewsAt?: number;
|
|
37
45
|
willRenew?: boolean;
|
|
@@ -41,6 +49,7 @@ export type KitSubscription = {
|
|
|
41
49
|
startedAt: number;
|
|
42
50
|
updatedAt: number;
|
|
43
51
|
purchaseToken: string;
|
|
52
|
+
originalTransactionId?: string;
|
|
44
53
|
userId?: string;
|
|
45
54
|
};
|
|
46
55
|
|
|
@@ -58,7 +67,8 @@ export type StatusResponse = {
|
|
|
58
67
|
export type KitProductPlatform = "IOS" | "Android";
|
|
59
68
|
|
|
60
69
|
export type KitProductClientPayload = {
|
|
61
|
-
|
|
70
|
+
/** Current values are toml, json, and text; preserve unknown values. */
|
|
71
|
+
format: string;
|
|
62
72
|
body: string;
|
|
63
73
|
version: number;
|
|
64
74
|
updatedAt: number;
|
|
@@ -84,6 +94,13 @@ export type KitProduct = {
|
|
|
84
94
|
type: "Subscription" | "NonConsumable" | "Consumable";
|
|
85
95
|
title: string;
|
|
86
96
|
description?: string;
|
|
97
|
+
baseLocale?: string;
|
|
98
|
+
localizations?: Array<{
|
|
99
|
+
locale: string;
|
|
100
|
+
title: string;
|
|
101
|
+
description?: string;
|
|
102
|
+
}>;
|
|
103
|
+
regions?: "all" | string[];
|
|
87
104
|
priceAmountMicros?: number;
|
|
88
105
|
currency?: string;
|
|
89
106
|
state: "Draft" | "Ready" | "Active" | "Removed";
|
|
@@ -113,6 +130,108 @@ export type KitProductsResponse = {
|
|
|
113
130
|
nextCursor?: string;
|
|
114
131
|
};
|
|
115
132
|
|
|
133
|
+
export type KitSubscriptionsResponse = {
|
|
134
|
+
items: KitSubscription[];
|
|
135
|
+
total: number;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export type KitMrrCurrencyEntry = {
|
|
139
|
+
currency: string;
|
|
140
|
+
mrrMicros: number;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export type KitMetricsResponse = {
|
|
144
|
+
activeSubs: number;
|
|
145
|
+
inGracePeriod: number;
|
|
146
|
+
inBillingRetry: number;
|
|
147
|
+
refunded30d: number;
|
|
148
|
+
canceled30d: number;
|
|
149
|
+
mrrMicros: number;
|
|
150
|
+
currency: string;
|
|
151
|
+
reportingCurrency: string;
|
|
152
|
+
mrrByCurrency: KitMrrCurrencyEntry[];
|
|
153
|
+
excludedMrrByCurrency: KitMrrCurrencyEntry[];
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
export type KitRevenueMetricsResponse = {
|
|
157
|
+
days: Array<{
|
|
158
|
+
day: string;
|
|
159
|
+
currency: string;
|
|
160
|
+
productId: string;
|
|
161
|
+
platform: KitProductPlatform;
|
|
162
|
+
activeSubs: number;
|
|
163
|
+
newSubs: number;
|
|
164
|
+
renewals: number;
|
|
165
|
+
cancellations: number;
|
|
166
|
+
refunds: number;
|
|
167
|
+
revenueMicros: number;
|
|
168
|
+
}>;
|
|
169
|
+
currencies: string[];
|
|
170
|
+
productIds: string[];
|
|
171
|
+
platforms: KitProductPlatform[];
|
|
172
|
+
truncated: boolean;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export type KitProductUpsertResponse = { id: string; created: boolean };
|
|
176
|
+
export type KitProductStateResponse = {
|
|
177
|
+
id: string;
|
|
178
|
+
state: KitProduct["state"];
|
|
179
|
+
};
|
|
180
|
+
export type KitClientPayloadStateResponse = {
|
|
181
|
+
expectedVersion: number;
|
|
182
|
+
clientPayload?: KitProductClientPayload;
|
|
183
|
+
};
|
|
184
|
+
export type KitSetClientPayloadResponse = {
|
|
185
|
+
id: string;
|
|
186
|
+
created: boolean;
|
|
187
|
+
changed: boolean;
|
|
188
|
+
version: number;
|
|
189
|
+
updatedAt: number;
|
|
190
|
+
};
|
|
191
|
+
export type KitRemoveClientPayloadResponse = { ok: boolean };
|
|
192
|
+
export type KitProductSyncResponse = { jobId: string; deduped: boolean };
|
|
193
|
+
export type KitProductSyncJobResponse = {
|
|
194
|
+
_id: string;
|
|
195
|
+
_creationTime: number;
|
|
196
|
+
projectId: string;
|
|
197
|
+
platform: KitProductPlatform;
|
|
198
|
+
direction: "pull" | "push" | "both" | "purge-local";
|
|
199
|
+
dryRun: boolean;
|
|
200
|
+
status: "queued" | "running" | "succeeded" | "failed";
|
|
201
|
+
progress: {
|
|
202
|
+
phase: string;
|
|
203
|
+
current?: number;
|
|
204
|
+
total?: number;
|
|
205
|
+
failuresCount?: number;
|
|
206
|
+
};
|
|
207
|
+
result?: {
|
|
208
|
+
pulled: number;
|
|
209
|
+
pushed: number;
|
|
210
|
+
deleted?: number;
|
|
211
|
+
failures: Array<{ productId: string; reason: string }>;
|
|
212
|
+
failuresTruncated?: boolean;
|
|
213
|
+
plannedWrites?: Array<{
|
|
214
|
+
productId: string;
|
|
215
|
+
step: string;
|
|
216
|
+
detail?: string;
|
|
217
|
+
}>;
|
|
218
|
+
plannedWritesTruncated?: boolean;
|
|
219
|
+
manualActions?: Array<{
|
|
220
|
+
productId: string;
|
|
221
|
+
code: string;
|
|
222
|
+
message: string;
|
|
223
|
+
}>;
|
|
224
|
+
manualActionsTruncated?: boolean;
|
|
225
|
+
};
|
|
226
|
+
error?: string;
|
|
227
|
+
cancelRequested?: boolean;
|
|
228
|
+
expectedDeadline?: number;
|
|
229
|
+
createdBy?: string;
|
|
230
|
+
startedAt?: number;
|
|
231
|
+
completedAt?: number;
|
|
232
|
+
createdAt: number;
|
|
233
|
+
};
|
|
234
|
+
|
|
116
235
|
export type KitClientPayloadResponse = {
|
|
117
236
|
clientPayload: KitProductClientPayload;
|
|
118
237
|
};
|
|
@@ -135,7 +254,7 @@ const DEFAULT_BASE_URL = "https://kit.openiap.dev";
|
|
|
135
254
|
function mergeHeaders(
|
|
136
255
|
callerHeaders: Record<string, string> | undefined,
|
|
137
256
|
hasBody: boolean,
|
|
138
|
-
):
|
|
257
|
+
): RequestInit["headers"] {
|
|
139
258
|
if (typeof Headers === "function") {
|
|
140
259
|
const merged = new Headers(callerHeaders);
|
|
141
260
|
if (!merged.has("accept")) merged.set("accept", "application/json");
|
|
@@ -283,9 +402,11 @@ export function kitApi(options: KitApiOptions) {
|
|
|
283
402
|
if (!raw) return null;
|
|
284
403
|
const candidate = JSON.parse(raw) as Partial<CachedClientPayload>;
|
|
285
404
|
const payload = candidate.clientPayload;
|
|
405
|
+
// Only the invariants the cache depends on. `format` is opaque: evicting
|
|
406
|
+
// on an unknown one would kill ETag revalidation and offline reads.
|
|
286
407
|
if (
|
|
287
408
|
!payload ||
|
|
288
|
-
|
|
409
|
+
typeof payload.format !== "string" ||
|
|
289
410
|
typeof payload.body !== "string" ||
|
|
290
411
|
!Number.isSafeInteger(payload.version) ||
|
|
291
412
|
payload.version < 1 ||
|
package/src/types.ts
CHANGED
|
@@ -885,11 +885,11 @@ export interface Mutation {
|
|
|
885
885
|
*/
|
|
886
886
|
syncIOS: Promise<boolean>;
|
|
887
887
|
/**
|
|
888
|
-
* Verify a purchase against your own backend.
|
|
889
|
-
* variant
|
|
890
|
-
*
|
|
891
|
-
* receipt fields
|
|
892
|
-
*
|
|
888
|
+
* Verify a purchase against your own backend. Every VerifyPurchaseResult
|
|
889
|
+
* variant exposes isValid, so entitlement can be gated without inspecting the
|
|
890
|
+
* concrete type. Variants add their own metadata on top: IOS carries
|
|
891
|
+
* receipt/JWS fields, Android carries Play Store receipt fields, and Horizon
|
|
892
|
+
* carries grantTime.
|
|
893
893
|
* See: https://openiap.dev/docs/features/validation#verify-purchase
|
|
894
894
|
*/
|
|
895
895
|
verifyPurchase: Promise<VerifyPurchaseResult>;
|
|
@@ -1857,6 +1857,12 @@ export interface RequestVerifyPurchaseWithIapkitResult {
|
|
|
1857
1857
|
* Available in OpenIAP Spec 3.2.0 / openiap-apple 3.2.0 / openiap-google 3.3.0.
|
|
1858
1858
|
* Amazon RVS environment selected by IAPKit. Present as `Sandbox` or
|
|
1859
1859
|
* `Production` on handled Amazon verification results.
|
|
1860
|
+
*
|
|
1861
|
+
* Deliberately String, not an enum: the value space belongs to IAPKit and the
|
|
1862
|
+
* stores behind it, and Apple's App Store Server alone also names `Xcode` and
|
|
1863
|
+
* `LocalTesting`. SDKs must forward this value opaquely. Never reject a
|
|
1864
|
+
* verification because the environment is unrecognised — that fails a purchase
|
|
1865
|
+
* the store already confirmed.
|
|
1860
1866
|
*/
|
|
1861
1867
|
environment?: (string | null);
|
|
1862
1868
|
/**
|
|
@@ -2187,7 +2193,7 @@ export interface VerifyPurchaseProps {
|
|
|
2187
2193
|
|
|
2188
2194
|
export type VerifyPurchaseResult = VerifyPurchaseResultAndroid | VerifyPurchaseResultHorizon | VerifyPurchaseResultIOS;
|
|
2189
2195
|
|
|
2190
|
-
export interface VerifyPurchaseResultAndroid {
|
|
2196
|
+
export interface VerifyPurchaseResultAndroid extends VerifyPurchaseResultCommon {
|
|
2191
2197
|
autoRenewing: boolean;
|
|
2192
2198
|
betaProduct: boolean;
|
|
2193
2199
|
cancelDate?: (number | null);
|
|
@@ -2196,6 +2202,11 @@ export interface VerifyPurchaseResultAndroid {
|
|
|
2196
2202
|
deferredSku?: (string | null);
|
|
2197
2203
|
freeTrialEndDate: number;
|
|
2198
2204
|
gracePeriodEndDate: number;
|
|
2205
|
+
/**
|
|
2206
|
+
* Whether the purchase is valid. Uniform across every VerifyPurchaseResult
|
|
2207
|
+
* variant so callers can gate entitlement without inspecting the concrete type.
|
|
2208
|
+
*/
|
|
2209
|
+
isValid: boolean;
|
|
2199
2210
|
parentProductId: string;
|
|
2200
2211
|
productId: string;
|
|
2201
2212
|
productType: string;
|
|
@@ -2208,18 +2219,32 @@ export interface VerifyPurchaseResultAndroid {
|
|
|
2208
2219
|
testTransaction: boolean;
|
|
2209
2220
|
}
|
|
2210
2221
|
|
|
2222
|
+
/** Validity shared by every store-specific purchase verification result. */
|
|
2223
|
+
export interface VerifyPurchaseResultCommon {
|
|
2224
|
+
/** Whether the purchase is valid, without inspecting the concrete result variant. */
|
|
2225
|
+
isValid: boolean;
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2211
2228
|
/**
|
|
2212
2229
|
* Result from Meta Horizon verify_entitlement API.
|
|
2213
2230
|
* Returns verification status and grant time for the entitlement.
|
|
2214
2231
|
*/
|
|
2215
|
-
export interface VerifyPurchaseResultHorizon {
|
|
2232
|
+
export interface VerifyPurchaseResultHorizon extends VerifyPurchaseResultCommon {
|
|
2216
2233
|
/** Unix timestamp (seconds) when the entitlement was granted. */
|
|
2217
2234
|
grantTime?: (number | null);
|
|
2218
|
-
/**
|
|
2235
|
+
/**
|
|
2236
|
+
* Whether the purchase is valid. Uniform across every VerifyPurchaseResult
|
|
2237
|
+
* variant so callers can gate entitlement without inspecting the concrete type.
|
|
2238
|
+
*/
|
|
2239
|
+
isValid: boolean;
|
|
2240
|
+
/**
|
|
2241
|
+
* Whether the entitlement verification succeeded.
|
|
2242
|
+
* @deprecated Renamed to isValid so every VerifyPurchaseResult variant answers validity the same way. Scheduled for removal in OpenIAP 4.0.
|
|
2243
|
+
*/
|
|
2219
2244
|
success: boolean;
|
|
2220
2245
|
}
|
|
2221
2246
|
|
|
2222
|
-
export interface VerifyPurchaseResultIOS {
|
|
2247
|
+
export interface VerifyPurchaseResultIOS extends VerifyPurchaseResultCommon {
|
|
2223
2248
|
/** Whether the receipt is valid */
|
|
2224
2249
|
isValid: boolean;
|
|
2225
2250
|
/** JWS representation */
|
package/src/vega-adapter.ts
CHANGED
|
@@ -1177,17 +1177,12 @@ export function createExpoIapVegaModule(
|
|
|
1177
1177
|
`IAPKit returned malformed response (HTTP ${status}).`,
|
|
1178
1178
|
);
|
|
1179
1179
|
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
throw createVegaError(
|
|
1187
|
-
ErrorCode.PurchaseVerificationFailed,
|
|
1188
|
-
`IAPKit returned malformed response (HTTP ${status}).`,
|
|
1189
|
-
);
|
|
1190
|
-
}
|
|
1180
|
+
// Forwarded opaquely: `environment` is String in the spec.
|
|
1181
|
+
const rawEnvironment = json.environment;
|
|
1182
|
+
const environment =
|
|
1183
|
+
typeof rawEnvironment === 'string' && rawEnvironment.length > 0
|
|
1184
|
+
? rawEnvironment
|
|
1185
|
+
: undefined;
|
|
1191
1186
|
|
|
1192
1187
|
return {
|
|
1193
1188
|
...(environment == null ? {} : {environment}),
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
|
|
3
|
+
final class ExpoIapLogTests: XCTestCase {
|
|
4
|
+
override func tearDown() {
|
|
5
|
+
ExpoIapLog.setHandler(nil)
|
|
6
|
+
super.tearDown()
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
@objc func testNullSensitiveValuesAreOmittedAndPresentValuesAreMasked() {
|
|
10
|
+
var output = ""
|
|
11
|
+
ExpoIapLog.setEnabled(true)
|
|
12
|
+
ExpoIapLog.setHandler { _, message in output = message }
|
|
13
|
+
|
|
14
|
+
let payload: [String: Any?] = [
|
|
15
|
+
"purchaseToken": nil,
|
|
16
|
+
"userIdAmazon": NSNull(),
|
|
17
|
+
"apiKey": "known-api-key",
|
|
18
|
+
]
|
|
19
|
+
ExpoIapLog.payload("requestPurchase", payload: payload)
|
|
20
|
+
|
|
21
|
+
XCTAssertFalse(output.contains("purchaseToken"))
|
|
22
|
+
XCTAssertFalse(output.contains("userIdAmazon"))
|
|
23
|
+
XCTAssertTrue(output.contains("\"apiKey\":\"hidden\""))
|
|
24
|
+
XCTAssertFalse(output.contains("known-api-key"))
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@main
|
|
29
|
+
private enum ExpoIapLogTestRunner {
|
|
30
|
+
static func main() {
|
|
31
|
+
let test = ExpoIapLogTests(
|
|
32
|
+
selector: #selector(ExpoIapLogTests.testNullSensitiveValuesAreOmittedAndPresentValuesAreMasked)
|
|
33
|
+
)
|
|
34
|
+
test.run()
|
|
35
|
+
precondition(test.testRun?.hasSucceeded == true)
|
|
36
|
+
}
|
|
37
|
+
}
|