react-native-iap 15.2.0 → 15.2.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.
- package/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt +117 -114
- package/android/src/main/java/com/margelo/nitro/iap/ProductQueryHelpers.kt +42 -0
- package/android/src/test/java/com/margelo/nitro/iap/ProductQueryHelpersTest.kt +140 -0
- package/ios/HybridRnIap.swift +33 -0
- package/lib/module/hooks/useIAP.js.map +1 -1
- package/lib/module/hooks/useWebhookEvents.js +113 -0
- package/lib/module/hooks/useWebhookEvents.js.map +1 -0
- package/lib/module/index.js +331 -131
- package/lib/module/index.js.map +1 -1
- package/lib/module/kit-api.js +161 -0
- package/lib/module/kit-api.js.map +1 -0
- package/lib/module/types.js +16 -0
- package/lib/module/types.js.map +1 -1
- package/lib/module/utils/error.js.map +1 -1
- package/lib/module/utils/errorMapping.js +6 -0
- package/lib/module/utils/errorMapping.js.map +1 -1
- package/lib/module/webhook-client.js +164 -0
- package/lib/module/webhook-client.js.map +1 -0
- package/lib/typescript/plugin/src/withIAP.d.ts +1 -1
- package/lib/typescript/src/hooks/useIAP.d.ts +162 -2
- package/lib/typescript/src/hooks/useIAP.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useWebhookEvents.d.ts +55 -0
- package/lib/typescript/src/hooks/useWebhookEvents.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +282 -129
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/kit-api.d.ts +54 -0
- package/lib/typescript/src/kit-api.d.ts.map +1 -0
- package/lib/typescript/src/specs/RnIap.nitro.d.ts +7 -0
- package/lib/typescript/src/specs/RnIap.nitro.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +304 -74
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/utils/error.d.ts +3 -0
- package/lib/typescript/src/utils/error.d.ts.map +1 -1
- package/lib/typescript/src/utils/errorMapping.d.ts +6 -0
- package/lib/typescript/src/utils/errorMapping.d.ts.map +1 -1
- package/lib/typescript/src/webhook-client.d.ts +82 -0
- package/lib/typescript/src/webhook-client.d.ts.map +1 -0
- package/nitrogen/generated/android/NitroIap+autolinking.cmake +3 -0
- package/nitrogen/generated/android/c++/JAdvancedCommerceInfoIOS.hpp +118 -0
- package/nitrogen/generated/android/c++/JAdvancedCommerceItemDetailsIOS.hpp +62 -0
- package/nitrogen/generated/android/c++/JAdvancedCommerceItemIOS.hpp +78 -0
- package/nitrogen/generated/android/c++/JAdvancedCommerceRefundIOS.hpp +62 -0
- package/nitrogen/generated/android/c++/JHybridRnIapSpec.cpp +44 -0
- package/nitrogen/generated/android/c++/JHybridRnIapSpec.hpp +1 -0
- package/nitrogen/generated/android/c++/JPurchase.hpp +11 -0
- package/nitrogen/generated/android/c++/JPurchaseIOS.hpp +16 -1
- package/nitrogen/generated/android/c++/JRequestPurchaseResult.hpp +11 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_AdvancedCommerceInfoIOS.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_AdvancedCommerceInfoIOS.hpp +84 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_AdvancedCommerceItemDetailsIOS.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_AdvancedCommerceItemDetailsIOS.hpp +74 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_Array_AdvancedCommerceRefundIOS_.cpp +35 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_Array_AdvancedCommerceRefundIOS_.hpp +84 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/AdvancedCommerceInfoIOS.kt +59 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/AdvancedCommerceItemDetailsIOS.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/AdvancedCommerceItemIOS.kt +44 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/AdvancedCommerceRefundIOS.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/HybridRnIapSpec.kt +4 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/PurchaseIOS.kt +5 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/Variant_NullType_AdvancedCommerceInfoIOS.kt +53 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/Variant_NullType_AdvancedCommerceItemDetailsIOS.kt +53 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/Variant_NullType_Array_AdvancedCommerceRefundIOS_.kt +53 -0
- package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Bridge.hpp +166 -0
- package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Umbrella.hpp +12 -0
- package/nitrogen/generated/ios/c++/HybridRnIapSpecSwift.hpp +20 -0
- package/nitrogen/generated/ios/swift/AdvancedCommerceInfoIOS.swift +294 -0
- package/nitrogen/generated/ios/swift/AdvancedCommerceItemDetailsIOS.swift +61 -0
- package/nitrogen/generated/ios/swift/AdvancedCommerceItemIOS.swift +141 -0
- package/nitrogen/generated/ios/swift/AdvancedCommerceRefundIOS.swift +61 -0
- package/nitrogen/generated/ios/swift/HybridRnIapSpec.swift +1 -0
- package/nitrogen/generated/ios/swift/HybridRnIapSpec_cxx.swift +25 -0
- package/nitrogen/generated/ios/swift/PurchaseIOS.swift +39 -2
- package/nitrogen/generated/ios/swift/Variant_NullType_AdvancedCommerceInfoIOS.swift +18 -0
- package/nitrogen/generated/ios/swift/Variant_NullType_AdvancedCommerceItemDetailsIOS.swift +18 -0
- package/nitrogen/generated/ios/swift/Variant_NullType__AdvancedCommerceRefundIOS_.swift +18 -0
- package/nitrogen/generated/shared/c++/AdvancedCommerceInfoIOS.hpp +117 -0
- package/nitrogen/generated/shared/c++/AdvancedCommerceItemDetailsIOS.hpp +86 -0
- package/nitrogen/generated/shared/c++/AdvancedCommerceItemIOS.hpp +99 -0
- package/nitrogen/generated/shared/c++/AdvancedCommerceRefundIOS.hpp +86 -0
- package/nitrogen/generated/shared/c++/HybridRnIapSpec.cpp +1 -0
- package/nitrogen/generated/shared/c++/HybridRnIapSpec.hpp +1 -0
- package/nitrogen/generated/shared/c++/PurchaseIOS.hpp +9 -2
- package/openiap-versions.json +3 -3
- package/package.json +1 -1
- package/plugin/build/withIAP.d.ts +1 -1
- package/plugin/src/withIAP.ts +1 -1
- package/src/hooks/useIAP.ts +162 -2
- package/src/hooks/useWebhookEvents.ts +180 -0
- package/src/index.ts +348 -130
- package/src/kit-api.ts +225 -0
- package/src/specs/RnIap.nitro.ts +8 -0
- package/src/types.ts +314 -74
- package/src/utils/error.ts +3 -0
- package/src/utils/errorMapping.ts +12 -0
- package/src/webhook-client.ts +312 -0
package/src/kit-api.ts
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
// Tiny fetch wrapper around kit's `/v1` HTTP surface for use by the JS
|
|
2
|
+
// SDK consumers (react-native-iap + expo-iap). Mirrors the shape of
|
|
3
|
+
// `packages/mcp-server/src/kit-client.ts` so the same operations are
|
|
4
|
+
// reachable from both LLM tools and end-user apps without each
|
|
5
|
+
// duplicating the URL layout.
|
|
6
|
+
|
|
7
|
+
export type KitApiOptions = {
|
|
8
|
+
apiKey: string;
|
|
9
|
+
baseUrl?: string;
|
|
10
|
+
// Optional fetch override for runtimes without a global (older RN
|
|
11
|
+
// builds) or for injection in tests.
|
|
12
|
+
fetchImpl?: (input: string, init?: RequestInit) => Promise<Response>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type KitSubscription = {
|
|
16
|
+
id: string;
|
|
17
|
+
productId: string;
|
|
18
|
+
platform: "IOS" | "Android";
|
|
19
|
+
state: string;
|
|
20
|
+
expiresAt?: number;
|
|
21
|
+
renewsAt?: number;
|
|
22
|
+
willRenew?: boolean;
|
|
23
|
+
cancellationReason?: string;
|
|
24
|
+
currency?: string;
|
|
25
|
+
priceAmountMicros?: number;
|
|
26
|
+
startedAt: number;
|
|
27
|
+
updatedAt: number;
|
|
28
|
+
purchaseToken: string;
|
|
29
|
+
userId?: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type EntitlementsResponse = {
|
|
33
|
+
userId: string;
|
|
34
|
+
productIds: string[];
|
|
35
|
+
subscriptions: KitSubscription[];
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type StatusResponse = {
|
|
39
|
+
active: boolean;
|
|
40
|
+
subscription: KitSubscription | null;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const DEFAULT_BASE_URL = "https://kit.openiap.dev";
|
|
44
|
+
|
|
45
|
+
// Merge caller-supplied headers with kit defaults (`accept`,
|
|
46
|
+
// optionally `content-type`). When the runtime exposes a global
|
|
47
|
+
// `Headers` constructor we use it directly so callers passing a
|
|
48
|
+
// `Headers` instance (a `HeadersInit`) keep that exact instance's
|
|
49
|
+
// values. When `Headers` is missing — older React Native builds where
|
|
50
|
+
// the operator wires up `fetchImpl` without a `Headers` polyfill —
|
|
51
|
+
// we fall back to a case-insensitive merge into a plain record so
|
|
52
|
+
// the request still goes through. Either way, caller-set values take
|
|
53
|
+
// precedence over kit defaults.
|
|
54
|
+
function mergeHeaders(
|
|
55
|
+
callerHeaders: HeadersInit | undefined,
|
|
56
|
+
hasBody: boolean,
|
|
57
|
+
): HeadersInit {
|
|
58
|
+
if (typeof Headers === "function") {
|
|
59
|
+
const merged = new Headers(callerHeaders);
|
|
60
|
+
if (!merged.has("accept")) merged.set("accept", "application/json");
|
|
61
|
+
if (hasBody && !merged.has("content-type")) {
|
|
62
|
+
merged.set("content-type", "application/json");
|
|
63
|
+
}
|
|
64
|
+
return merged;
|
|
65
|
+
}
|
|
66
|
+
// Plain-object fallback path. Build a case-insensitive name map
|
|
67
|
+
// from whatever the caller passed (Headers-shaped, array-of-pairs,
|
|
68
|
+
// or plain record) and re-emit as a record `fetchImpl` accepts.
|
|
69
|
+
const lower = new Map<string, { name: string; value: string }>();
|
|
70
|
+
const setIfAbsent = (name: string, value: string) => {
|
|
71
|
+
const key = name.toLowerCase();
|
|
72
|
+
if (!lower.has(key)) lower.set(key, { name, value });
|
|
73
|
+
};
|
|
74
|
+
const setForce = (name: string, value: string) => {
|
|
75
|
+
const key = name.toLowerCase();
|
|
76
|
+
lower.set(key, { name, value });
|
|
77
|
+
};
|
|
78
|
+
if (callerHeaders) {
|
|
79
|
+
if (Array.isArray(callerHeaders)) {
|
|
80
|
+
for (const [name, value] of callerHeaders) setForce(name, value);
|
|
81
|
+
} else if (
|
|
82
|
+
typeof (callerHeaders as { forEach?: unknown }).forEach === "function"
|
|
83
|
+
) {
|
|
84
|
+
// `Headers`-like (without being our `typeof Headers === "function"`
|
|
85
|
+
// global). RN polyfills sometimes attach `Headers` only to
|
|
86
|
+
// request/response instances rather than the global scope.
|
|
87
|
+
// Standard signature is `forEach((value, key, parent))`; we
|
|
88
|
+
// bind the first two positionally so a polyfill that omits
|
|
89
|
+
// the third argument still works. `key` is the header name.
|
|
90
|
+
(
|
|
91
|
+
callerHeaders as {
|
|
92
|
+
forEach: (cb: (value: string, key: string) => void) => void;
|
|
93
|
+
}
|
|
94
|
+
).forEach((value, key) => setForce(key, value));
|
|
95
|
+
} else {
|
|
96
|
+
for (const [name, value] of Object.entries(
|
|
97
|
+
callerHeaders as Record<string, string>,
|
|
98
|
+
)) {
|
|
99
|
+
setForce(name, value);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
setIfAbsent("accept", "application/json");
|
|
104
|
+
if (hasBody) setIfAbsent("content-type", "application/json");
|
|
105
|
+
const out: Record<string, string> = {};
|
|
106
|
+
for (const { name, value } of lower.values()) out[name] = value;
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export class KitApiError extends Error {
|
|
111
|
+
constructor(
|
|
112
|
+
readonly status: number,
|
|
113
|
+
readonly body: unknown,
|
|
114
|
+
message: string,
|
|
115
|
+
) {
|
|
116
|
+
super(message);
|
|
117
|
+
this.name = "KitApiError";
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function kitApi(options: KitApiOptions) {
|
|
122
|
+
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
123
|
+
const fetchImpl: (input: string, init?: RequestInit) => Promise<Response> =
|
|
124
|
+
(() => {
|
|
125
|
+
if (options.fetchImpl) return options.fetchImpl;
|
|
126
|
+
if (typeof fetch === "function") {
|
|
127
|
+
return (input: string, init?: RequestInit) => fetch(input, init);
|
|
128
|
+
}
|
|
129
|
+
throw new Error(
|
|
130
|
+
"kitApi requires a fetch implementation. Pass `fetchImpl` for runtimes without a global fetch.",
|
|
131
|
+
);
|
|
132
|
+
})();
|
|
133
|
+
|
|
134
|
+
async function call<T>(path: string, init?: RequestInit): Promise<T> {
|
|
135
|
+
// Normalize headers without depending on a global `Headers`
|
|
136
|
+
// constructor: older React Native runtimes ship `fetch` (or a
|
|
137
|
+
// polyfill via `fetchImpl`) without exposing `Headers` globally.
|
|
138
|
+
// The prior implementation crashed before the first request on
|
|
139
|
+
// those runtimes. We use `new Headers()` when available (preserves
|
|
140
|
+
// caller-supplied `Headers` instances exactly), and otherwise fall
|
|
141
|
+
// back to a small case-insensitive merge into a plain record.
|
|
142
|
+
// Either way, kit defaults only apply when the caller hasn't set
|
|
143
|
+
// the same name.
|
|
144
|
+
const headers = mergeHeaders(init?.headers, init?.body != null);
|
|
145
|
+
// Prepend a leading slash if `path` is missing one. Today's
|
|
146
|
+
// call sites all hard-code the leading "/", but normalizing here
|
|
147
|
+
// makes the helper safe for future additions and matches the
|
|
148
|
+
// already-stripped `baseUrl` (PR #124
|
|
149
|
+
// (https://github.com/hyodotdev/openiap/pull/124) review).
|
|
150
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
151
|
+
const response = await fetchImpl(`${baseUrl}${normalizedPath}`, {
|
|
152
|
+
...init,
|
|
153
|
+
headers,
|
|
154
|
+
});
|
|
155
|
+
const text = await response.text();
|
|
156
|
+
// Empty body normalizes to null so callers expecting JSON
|
|
157
|
+
// (status / entitlements / list*) don't get a truthy ""
|
|
158
|
+
// and crash on property access.
|
|
159
|
+
let parsed: unknown = null;
|
|
160
|
+
let parseError: unknown = null;
|
|
161
|
+
if (text) {
|
|
162
|
+
try {
|
|
163
|
+
parsed = JSON.parse(text);
|
|
164
|
+
} catch (error) {
|
|
165
|
+
// Non-JSON body (a misconfigured proxy returning HTML, a
|
|
166
|
+
// CDN-injected error page, etc.) on a 2xx response would
|
|
167
|
+
// otherwise reach the caller as `parsed = text` and crash
|
|
168
|
+
// on property access via `parsed as T`. Throw a structured
|
|
169
|
+
// KitApiError instead so callers see a typed failure.
|
|
170
|
+
parseError = error;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (!response.ok) {
|
|
174
|
+
// Surface the raw body (text or parsed) on the error path so
|
|
175
|
+
// operators can read the upstream error message verbatim.
|
|
176
|
+
throw new KitApiError(
|
|
177
|
+
response.status,
|
|
178
|
+
parsed ?? text,
|
|
179
|
+
`kit ${path} returned ${response.status}`,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
if (parseError) {
|
|
183
|
+
throw new KitApiError(
|
|
184
|
+
response.status,
|
|
185
|
+
text,
|
|
186
|
+
`kit ${path} returned a non-JSON ${response.status} body (${
|
|
187
|
+
parseError instanceof Error ? parseError.message : String(parseError)
|
|
188
|
+
})`,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
return parsed as T;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
apiKey: options.apiKey,
|
|
196
|
+
baseUrl,
|
|
197
|
+
|
|
198
|
+
/** GET /v1/subscriptions/status — the `active` boolean is the
|
|
199
|
+
* fastest gate for "is this user paying?". */
|
|
200
|
+
status: (userId: string) =>
|
|
201
|
+
call<StatusResponse>(
|
|
202
|
+
`/v1/subscriptions/status/${encodeURIComponent(options.apiKey)}?userId=${encodeURIComponent(userId)}`,
|
|
203
|
+
),
|
|
204
|
+
|
|
205
|
+
/** GET /v1/subscriptions/entitlements — every productId the user
|
|
206
|
+
* is entitled to. Use this when feature gating depends on which
|
|
207
|
+
* specific tier the user owns. */
|
|
208
|
+
entitlements: (userId: string) =>
|
|
209
|
+
call<EntitlementsResponse>(
|
|
210
|
+
`/v1/subscriptions/entitlements/${encodeURIComponent(options.apiKey)}?userId=${encodeURIComponent(userId)}`,
|
|
211
|
+
),
|
|
212
|
+
|
|
213
|
+
/** POST /v1/subscriptions/bind-user — call after a successful
|
|
214
|
+
* verifyReceipt so kit knows which userId owns the verified
|
|
215
|
+
* `purchaseToken`. Idempotent. */
|
|
216
|
+
bindUser: (purchaseToken: string, userId: string) =>
|
|
217
|
+
call<{ ok: boolean; bound: boolean }>(
|
|
218
|
+
`/v1/subscriptions/bind-user/${encodeURIComponent(options.apiKey)}`,
|
|
219
|
+
{
|
|
220
|
+
method: "POST",
|
|
221
|
+
body: JSON.stringify({ purchaseToken, userId }),
|
|
222
|
+
},
|
|
223
|
+
),
|
|
224
|
+
};
|
|
225
|
+
}
|
package/src/specs/RnIap.nitro.ts
CHANGED
|
@@ -857,6 +857,14 @@ export interface RnIap extends HybridObject<{ios: 'swift'; android: 'kotlin'}> {
|
|
|
857
857
|
*/
|
|
858
858
|
getPendingTransactionsIOS(): Promise<NitroPurchase[]>;
|
|
859
859
|
|
|
860
|
+
/**
|
|
861
|
+
* Get the full StoreKit 2 transaction history as PurchaseIOS values.
|
|
862
|
+
* Requires SK2ConsumableTransactionHistory Info.plist key for finished consumables (iOS 18+).
|
|
863
|
+
* @returns Promise<NitroPurchase[]> - Array of all transactions
|
|
864
|
+
* @platform iOS
|
|
865
|
+
*/
|
|
866
|
+
getAllTransactionsIOS(): Promise<NitroPurchase[]>;
|
|
867
|
+
|
|
860
868
|
/**
|
|
861
869
|
* Sync with the App Store (iOS only)
|
|
862
870
|
* @returns Promise<boolean> - Success flag
|