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.
Files changed (95) hide show
  1. package/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt +117 -114
  2. package/android/src/main/java/com/margelo/nitro/iap/ProductQueryHelpers.kt +42 -0
  3. package/android/src/test/java/com/margelo/nitro/iap/ProductQueryHelpersTest.kt +140 -0
  4. package/ios/HybridRnIap.swift +33 -0
  5. package/lib/module/hooks/useIAP.js.map +1 -1
  6. package/lib/module/hooks/useWebhookEvents.js +113 -0
  7. package/lib/module/hooks/useWebhookEvents.js.map +1 -0
  8. package/lib/module/index.js +331 -131
  9. package/lib/module/index.js.map +1 -1
  10. package/lib/module/kit-api.js +161 -0
  11. package/lib/module/kit-api.js.map +1 -0
  12. package/lib/module/types.js +16 -0
  13. package/lib/module/types.js.map +1 -1
  14. package/lib/module/utils/error.js.map +1 -1
  15. package/lib/module/utils/errorMapping.js +6 -0
  16. package/lib/module/utils/errorMapping.js.map +1 -1
  17. package/lib/module/webhook-client.js +164 -0
  18. package/lib/module/webhook-client.js.map +1 -0
  19. package/lib/typescript/plugin/src/withIAP.d.ts +1 -1
  20. package/lib/typescript/src/hooks/useIAP.d.ts +162 -2
  21. package/lib/typescript/src/hooks/useIAP.d.ts.map +1 -1
  22. package/lib/typescript/src/hooks/useWebhookEvents.d.ts +55 -0
  23. package/lib/typescript/src/hooks/useWebhookEvents.d.ts.map +1 -0
  24. package/lib/typescript/src/index.d.ts +282 -129
  25. package/lib/typescript/src/index.d.ts.map +1 -1
  26. package/lib/typescript/src/kit-api.d.ts +54 -0
  27. package/lib/typescript/src/kit-api.d.ts.map +1 -0
  28. package/lib/typescript/src/specs/RnIap.nitro.d.ts +7 -0
  29. package/lib/typescript/src/specs/RnIap.nitro.d.ts.map +1 -1
  30. package/lib/typescript/src/types.d.ts +304 -74
  31. package/lib/typescript/src/types.d.ts.map +1 -1
  32. package/lib/typescript/src/utils/error.d.ts +3 -0
  33. package/lib/typescript/src/utils/error.d.ts.map +1 -1
  34. package/lib/typescript/src/utils/errorMapping.d.ts +6 -0
  35. package/lib/typescript/src/utils/errorMapping.d.ts.map +1 -1
  36. package/lib/typescript/src/webhook-client.d.ts +82 -0
  37. package/lib/typescript/src/webhook-client.d.ts.map +1 -0
  38. package/nitrogen/generated/android/NitroIap+autolinking.cmake +3 -0
  39. package/nitrogen/generated/android/c++/JAdvancedCommerceInfoIOS.hpp +118 -0
  40. package/nitrogen/generated/android/c++/JAdvancedCommerceItemDetailsIOS.hpp +62 -0
  41. package/nitrogen/generated/android/c++/JAdvancedCommerceItemIOS.hpp +78 -0
  42. package/nitrogen/generated/android/c++/JAdvancedCommerceRefundIOS.hpp +62 -0
  43. package/nitrogen/generated/android/c++/JHybridRnIapSpec.cpp +44 -0
  44. package/nitrogen/generated/android/c++/JHybridRnIapSpec.hpp +1 -0
  45. package/nitrogen/generated/android/c++/JPurchase.hpp +11 -0
  46. package/nitrogen/generated/android/c++/JPurchaseIOS.hpp +16 -1
  47. package/nitrogen/generated/android/c++/JRequestPurchaseResult.hpp +11 -0
  48. package/nitrogen/generated/android/c++/JVariant_NullType_AdvancedCommerceInfoIOS.cpp +26 -0
  49. package/nitrogen/generated/android/c++/JVariant_NullType_AdvancedCommerceInfoIOS.hpp +84 -0
  50. package/nitrogen/generated/android/c++/JVariant_NullType_AdvancedCommerceItemDetailsIOS.cpp +26 -0
  51. package/nitrogen/generated/android/c++/JVariant_NullType_AdvancedCommerceItemDetailsIOS.hpp +74 -0
  52. package/nitrogen/generated/android/c++/JVariant_NullType_Array_AdvancedCommerceRefundIOS_.cpp +35 -0
  53. package/nitrogen/generated/android/c++/JVariant_NullType_Array_AdvancedCommerceRefundIOS_.hpp +84 -0
  54. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/AdvancedCommerceInfoIOS.kt +59 -0
  55. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/AdvancedCommerceItemDetailsIOS.kt +38 -0
  56. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/AdvancedCommerceItemIOS.kt +44 -0
  57. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/AdvancedCommerceRefundIOS.kt +38 -0
  58. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/HybridRnIapSpec.kt +4 -0
  59. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/PurchaseIOS.kt +5 -2
  60. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/Variant_NullType_AdvancedCommerceInfoIOS.kt +53 -0
  61. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/Variant_NullType_AdvancedCommerceItemDetailsIOS.kt +53 -0
  62. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/Variant_NullType_Array_AdvancedCommerceRefundIOS_.kt +53 -0
  63. package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Bridge.hpp +166 -0
  64. package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Umbrella.hpp +12 -0
  65. package/nitrogen/generated/ios/c++/HybridRnIapSpecSwift.hpp +20 -0
  66. package/nitrogen/generated/ios/swift/AdvancedCommerceInfoIOS.swift +294 -0
  67. package/nitrogen/generated/ios/swift/AdvancedCommerceItemDetailsIOS.swift +61 -0
  68. package/nitrogen/generated/ios/swift/AdvancedCommerceItemIOS.swift +141 -0
  69. package/nitrogen/generated/ios/swift/AdvancedCommerceRefundIOS.swift +61 -0
  70. package/nitrogen/generated/ios/swift/HybridRnIapSpec.swift +1 -0
  71. package/nitrogen/generated/ios/swift/HybridRnIapSpec_cxx.swift +25 -0
  72. package/nitrogen/generated/ios/swift/PurchaseIOS.swift +39 -2
  73. package/nitrogen/generated/ios/swift/Variant_NullType_AdvancedCommerceInfoIOS.swift +18 -0
  74. package/nitrogen/generated/ios/swift/Variant_NullType_AdvancedCommerceItemDetailsIOS.swift +18 -0
  75. package/nitrogen/generated/ios/swift/Variant_NullType__AdvancedCommerceRefundIOS_.swift +18 -0
  76. package/nitrogen/generated/shared/c++/AdvancedCommerceInfoIOS.hpp +117 -0
  77. package/nitrogen/generated/shared/c++/AdvancedCommerceItemDetailsIOS.hpp +86 -0
  78. package/nitrogen/generated/shared/c++/AdvancedCommerceItemIOS.hpp +99 -0
  79. package/nitrogen/generated/shared/c++/AdvancedCommerceRefundIOS.hpp +86 -0
  80. package/nitrogen/generated/shared/c++/HybridRnIapSpec.cpp +1 -0
  81. package/nitrogen/generated/shared/c++/HybridRnIapSpec.hpp +1 -0
  82. package/nitrogen/generated/shared/c++/PurchaseIOS.hpp +9 -2
  83. package/openiap-versions.json +3 -3
  84. package/package.json +1 -1
  85. package/plugin/build/withIAP.d.ts +1 -1
  86. package/plugin/src/withIAP.ts +1 -1
  87. package/src/hooks/useIAP.ts +162 -2
  88. package/src/hooks/useWebhookEvents.ts +180 -0
  89. package/src/index.ts +348 -130
  90. package/src/kit-api.ts +225 -0
  91. package/src/specs/RnIap.nitro.ts +8 -0
  92. package/src/types.ts +314 -74
  93. package/src/utils/error.ts +3 -0
  94. package/src/utils/errorMapping.ts +12 -0
  95. 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
+ }
@@ -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