react-native-iap 16.3.0 → 16.3.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 (55) hide show
  1. package/README.md +2 -2
  2. package/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt +111 -15
  3. package/ios/HybridRnIap.swift +1 -1
  4. package/lib/module/index.js +51 -6
  5. package/lib/module/index.js.map +1 -1
  6. package/lib/module/index.kepler.js +9 -0
  7. package/lib/module/index.kepler.js.map +1 -1
  8. package/lib/module/kit-api.js +3 -1
  9. package/lib/module/kit-api.js.map +1 -1
  10. package/lib/module/types.js +2 -0
  11. package/lib/module/types.js.map +1 -1
  12. package/lib/module/vega-adapter.js +3 -4
  13. package/lib/module/vega-adapter.js.map +1 -1
  14. package/lib/typescript/src/index.d.ts.map +1 -1
  15. package/lib/typescript/src/index.kepler.d.ts.map +1 -1
  16. package/lib/typescript/src/kit-api.d.ts +119 -2
  17. package/lib/typescript/src/kit-api.d.ts.map +1 -1
  18. package/lib/typescript/src/specs/RnIap.nitro.d.ts +9 -3
  19. package/lib/typescript/src/specs/RnIap.nitro.d.ts.map +1 -1
  20. package/lib/typescript/src/types.d.ts +33 -9
  21. package/lib/typescript/src/types.d.ts.map +1 -1
  22. package/lib/typescript/src/vega-adapter.d.ts.map +1 -1
  23. package/nitrogen/generated/android/NitroIap+autolinking.cmake +1 -1
  24. package/nitrogen/generated/android/c++/JHybridRnIapSpec.cpp +8 -4
  25. package/nitrogen/generated/android/c++/JHybridRnIapSpec.hpp +1 -1
  26. package/nitrogen/generated/android/c++/JNitroPurchaseVerificationResultAndroid.hpp +5 -1
  27. package/nitrogen/generated/android/c++/JNitroPurchaseVerificationResultHorizon.hpp +69 -0
  28. package/nitrogen/generated/android/c++/JVariant_NitroPurchaseVerificationResultIOS_NitroPurchaseVerificationResultAndroid_NitroPurchaseVerificationResultHorizon.cpp +30 -0
  29. package/nitrogen/generated/android/c++/{JVariant_NitroPurchaseVerificationResultIOS_NitroPurchaseVerificationResultAndroid.hpp → JVariant_NitroPurchaseVerificationResultIOS_NitroPurchaseVerificationResultAndroid_NitroPurchaseVerificationResultHorizon.hpp} +33 -16
  30. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/HybridRnIapSpec.kt +1 -1
  31. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroPurchaseVerificationResultAndroid.kt +8 -3
  32. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroPurchaseVerificationResultHorizon.kt +61 -0
  33. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/{Variant_NitroPurchaseVerificationResultIOS_NitroPurchaseVerificationResultAndroid.kt → Variant_NitroPurchaseVerificationResultIOS_NitroPurchaseVerificationResultAndroid_NitroPurchaseVerificationResultHorizon.kt} +21 -8
  34. package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Bridge.cpp +4 -4
  35. package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Bridge.hpp +43 -34
  36. package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Umbrella.hpp +3 -0
  37. package/nitrogen/generated/ios/c++/HybridRnIapSpecSwift.hpp +4 -1
  38. package/nitrogen/generated/ios/swift/{Func_void_std__variant_NitroPurchaseVerificationResultIOS__NitroPurchaseVerificationResultAndroid_.swift → Func_void_std__variant_NitroPurchaseVerificationResultIOS__NitroPurchaseVerificationResultAndroid__NitroPurchaseVerificationResultHorizon_.swift} +14 -11
  39. package/nitrogen/generated/ios/swift/HybridRnIapSpec.swift +1 -1
  40. package/nitrogen/generated/ios/swift/HybridRnIapSpec_cxx.swift +11 -9
  41. package/nitrogen/generated/ios/swift/NitroPurchaseVerificationResultAndroid.swift +7 -2
  42. package/nitrogen/generated/ios/swift/NitroPurchaseVerificationResultHorizon.swift +71 -0
  43. package/nitrogen/generated/ios/swift/{Variant_NitroPurchaseVerificationResultIOS_NitroPurchaseVerificationResultAndroid.swift → Variant_NitroPurchaseVerificationResultIOS_NitroPurchaseVerificationResultAndroid_NitroPurchaseVerificationResultHorizon.swift} +6 -4
  44. package/nitrogen/generated/shared/c++/HybridRnIapSpec.hpp +4 -1
  45. package/nitrogen/generated/shared/c++/NitroPurchaseVerificationResultAndroid.hpp +5 -1
  46. package/nitrogen/generated/shared/c++/NitroPurchaseVerificationResultHorizon.hpp +93 -0
  47. package/openiap-versions.json +3 -3
  48. package/package.json +1 -1
  49. package/src/index.kepler.ts +10 -2
  50. package/src/index.ts +88 -18
  51. package/src/kit-api.ts +125 -4
  52. package/src/specs/RnIap.nitro.ts +12 -2
  53. package/src/types.ts +34 -9
  54. package/src/vega-adapter.ts +6 -11
  55. package/nitrogen/generated/android/c++/JVariant_NitroPurchaseVerificationResultIOS_NitroPurchaseVerificationResultAndroid.cpp +0 -26
@@ -1,4 +1,5 @@
1
1
  import {getVegaIapModule} from './vega';
2
+ import {ErrorCode} from './types';
2
3
  import type {
3
4
  MutationField,
4
5
  Product,
@@ -27,6 +28,7 @@ import {
27
28
  validateNitroPurchase,
28
29
  } from './utils/type-bridge';
29
30
  import {convertAndroidPurchasesOrThrow} from './utils/available-purchases';
31
+ import {createPurchaseError} from './utils/errorMapping';
30
32
 
31
33
  export * from './types';
32
34
  export * from './utils/error';
@@ -129,13 +131,19 @@ export const fetchProducts = async (
129
131
  const normalizedType = normalizeProductQueryType(type);
130
132
  const nitroProducts = await getModule().fetchProducts(skus, normalizedType);
131
133
  return mapProducts(nitroProducts, normalizedType) as
132
- | Product[]
133
- | ProductSubscription[];
134
+ Product[] | ProductSubscription[];
134
135
  };
135
136
 
136
137
  export const requestPurchase: MutationField<'requestPurchase'> = async (
137
138
  request,
138
139
  ) => {
140
+ const type = normalizeProductQueryType(request.type);
141
+ if (type === 'all') {
142
+ throw createPurchaseError({
143
+ code: ErrorCode.DeveloperError,
144
+ message: 'Product type all is only supported for product queries.',
145
+ });
146
+ }
139
147
  const perPlatformRequest = request.request as
140
148
  | RequestPurchasePropsByPlatforms
141
149
  | RequestSubscriptionPropsByPlatforms
package/src/index.ts CHANGED
@@ -9,6 +9,7 @@ import type {
9
9
  NitroPurchaseVerificationParams,
10
10
  NitroPurchaseVerificationResultIOS,
11
11
  NitroPurchaseVerificationResultAndroid,
12
+ NitroPurchaseVerificationResultHorizon,
12
13
  NitroPurchaseUpdatedListenerOptions,
13
14
  NitroSubscriptionStatus,
14
15
  RnIap,
@@ -41,6 +42,7 @@ import type {
41
42
  PurchaseIOS,
42
43
  QueryField,
43
44
  VerifyPurchaseResultAndroid,
45
+ VerifyPurchaseResultHorizon,
44
46
  VerifyPurchaseResultIOS,
45
47
  RequestPurchaseAndroidProps,
46
48
  RequestPurchaseIosProps,
@@ -884,7 +886,10 @@ export const fetchProducts: QueryField<'fetchProducts'> = async (request) => {
884
886
 
885
887
  try {
886
888
  if (!skus?.length) {
887
- throw new Error('No SKUs provided');
889
+ throw createPurchaseError({
890
+ message: 'No SKUs provided',
891
+ code: ErrorCode.EmptySkuList,
892
+ });
888
893
  }
889
894
 
890
895
  const normalizedType = normalizeProductQueryType(type);
@@ -1673,6 +1678,12 @@ export const requestPurchase: MutationField<'requestPurchase'> = async (
1673
1678
  try {
1674
1679
  const {request: platformRequest, type} = request;
1675
1680
  const normalizedType = normalizeProductQueryType(type ?? 'in-app');
1681
+ if (normalizedType === 'all') {
1682
+ throw createPurchaseError({
1683
+ code: ErrorCode.DeveloperError,
1684
+ message: 'Product type all is only supported for product queries.',
1685
+ });
1686
+ }
1676
1687
  const isSubs = isSubscriptionQuery(normalizedType);
1677
1688
  const perPlatformRequest = platformRequest as
1678
1689
  | RequestPurchasePropsByPlatforms
@@ -1691,15 +1702,23 @@ export const requestPurchase: MutationField<'requestPurchase'> = async (
1691
1702
 
1692
1703
  if (Platform.OS === 'ios') {
1693
1704
  if (!iosRequestSource?.sku) {
1694
- throw new Error(
1695
- 'Invalid request for iOS. The `sku` property is required.',
1696
- );
1705
+ throw createPurchaseError({
1706
+ message: 'Invalid request for iOS. The `sku` property is required.',
1707
+ code: ErrorCode.EmptySkuList,
1708
+ });
1697
1709
  }
1698
1710
  } else if (isAndroidStoreRuntime()) {
1699
- if (!androidRequestSource?.skus?.length) {
1700
- throw new Error(
1701
- 'Invalid request for Android. The `skus` property is required and must be a non-empty array.',
1702
- );
1711
+ const skus = androidRequestSource?.skus as unknown;
1712
+ if (
1713
+ !Array.isArray(skus) ||
1714
+ skus.length === 0 ||
1715
+ skus.some((sku) => typeof sku !== 'string' || sku.trim() === '')
1716
+ ) {
1717
+ throw createPurchaseError({
1718
+ message:
1719
+ 'Invalid request for Android. The `skus` property must contain only non-empty strings.',
1720
+ code: ErrorCode.EmptySkuList,
1721
+ });
1703
1722
  }
1704
1723
  } else {
1705
1724
  throw unsupportedPlatformError();
@@ -1756,6 +1775,29 @@ export const requestPurchase: MutationField<'requestPurchase'> = async (
1756
1775
  }
1757
1776
 
1758
1777
  if (isAndroidStoreRuntime() && androidRequestSource) {
1778
+ const rawAndroidRequest = androidRequestSource as unknown as Record<
1779
+ string,
1780
+ unknown
1781
+ >;
1782
+ const subscriptionOnlyFields = [
1783
+ 'subscriptionOffers',
1784
+ 'subscriptionProductReplacementParams',
1785
+ 'purchaseToken',
1786
+ 'originalExternalTransactionId',
1787
+ ];
1788
+ if (
1789
+ (!isSubs &&
1790
+ subscriptionOnlyFields.some(
1791
+ (field) => rawAndroidRequest[field] != null,
1792
+ )) ||
1793
+ (isSubs && rawAndroidRequest.offerToken != null)
1794
+ ) {
1795
+ throw createPurchaseError({
1796
+ message:
1797
+ 'Invalid request for Android. Purchase options must match the selected product type.',
1798
+ code: ErrorCode.DeveloperError,
1799
+ });
1800
+ }
1759
1801
  const androidRequest = isSubs
1760
1802
  ? (androidRequestSource as RequestSubscriptionAndroidProps)
1761
1803
  : (androidRequestSource as RequestPurchaseAndroidProps);
@@ -1788,6 +1830,29 @@ export const requestPurchase: MutationField<'requestPurchase'> = async (
1788
1830
 
1789
1831
  if (isSubs) {
1790
1832
  const subsRequest = androidRequest as RequestSubscriptionAndroidProps;
1833
+ const subscriptionOffers = subsRequest.subscriptionOffers as unknown;
1834
+ if (
1835
+ subscriptionOffers != null &&
1836
+ (!Array.isArray(subscriptionOffers) ||
1837
+ subscriptionOffers.some((offer) => {
1838
+ if (offer == null || typeof offer !== 'object') {
1839
+ return true;
1840
+ }
1841
+ const candidate = offer as Record<string, unknown>;
1842
+ return (
1843
+ typeof candidate.sku !== 'string' ||
1844
+ candidate.sku.trim() === '' ||
1845
+ typeof candidate.offerToken !== 'string' ||
1846
+ candidate.offerToken.trim() === ''
1847
+ );
1848
+ }))
1849
+ ) {
1850
+ throw createPurchaseError({
1851
+ message:
1852
+ 'Invalid request for Android. Every subscription offer must include non-empty `sku` and `offerToken` strings.',
1853
+ code: ErrorCode.DeveloperError,
1854
+ });
1855
+ }
1791
1856
  if (subsRequest.purchaseToken) {
1792
1857
  androidPayload.purchaseToken = subsRequest.purchaseToken;
1793
1858
  }
@@ -1800,15 +1865,11 @@ export const requestPurchase: MutationField<'requestPurchase'> = async (
1800
1865
  subsRequest.subscriptionProductReplacementParams;
1801
1866
  }
1802
1867
  androidPayload.subscriptionOffers = (
1803
- subsRequest.subscriptionOffers ?? []
1804
- )
1805
- .filter(
1806
- (offer): offer is AndroidSubscriptionOfferInput => offer != null,
1807
- )
1808
- .map((offer) => ({
1809
- sku: offer.sku,
1810
- offerToken: offer.offerToken,
1811
- }));
1868
+ (subscriptionOffers ?? []) as AndroidSubscriptionOfferInput[]
1869
+ ).map((offer) => ({
1870
+ sku: offer.sku,
1871
+ offerToken: offer.offerToken,
1872
+ }));
1812
1873
  }
1813
1874
 
1814
1875
  unifiedRequest.google = androidPayload;
@@ -2135,11 +2196,20 @@ export const verifyPurchase: MutationField<'verifyPurchase'> = async (
2135
2196
  : undefined,
2136
2197
  };
2137
2198
  return result;
2199
+ } else if (params.horizon !== null) {
2200
+ const horizonResult =
2201
+ nitroResult as NitroPurchaseVerificationResultHorizon;
2202
+ const result: VerifyPurchaseResultHorizon = {
2203
+ isValid: horizonResult.isValid,
2204
+ grantTime: horizonResult.grantTime,
2205
+ success: horizonResult.success,
2206
+ };
2207
+ return result;
2138
2208
  } else {
2139
- // Android
2140
2209
  const androidResult =
2141
2210
  nitroResult as NitroPurchaseVerificationResultAndroid;
2142
2211
  const result: VerifyPurchaseResultAndroid = {
2212
+ isValid: androidResult.isValid,
2143
2213
  autoRenewing: androidResult.autoRenewing,
2144
2214
  betaProduct: androidResult.betaProduct,
2145
2215
  cancelDate: androidResult.cancelDate,
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: string;
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
- format: "toml" | "json" | "text";
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
- ): HeadersInit {
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
- !["toml", "json", "text"].includes(payload.format) ||
409
+ typeof payload.format !== "string" ||
289
410
  typeof payload.body !== "string" ||
290
411
  !Number.isSafeInteger(payload.version) ||
291
412
  payload.version < 1 ||
@@ -55,6 +55,7 @@ import type {
55
55
  VerifyPurchaseGoogleOptions,
56
56
  VerifyPurchaseHorizonOptions,
57
57
  VerifyPurchaseResultAndroid,
58
+ VerifyPurchaseResultHorizon,
58
59
  RequestPurchaseIosProps,
59
60
  RequestPurchaseResult,
60
61
  RequestSubscriptionAndroidProps,
@@ -408,6 +409,7 @@ export interface NitroPurchaseVerificationResultIOS {
408
409
  }
409
410
 
410
411
  export interface NitroPurchaseVerificationResultAndroid {
412
+ isValid: VerifyPurchaseResultAndroid['isValid'];
411
413
  autoRenewing: VerifyPurchaseResultAndroid['autoRenewing'];
412
414
  betaProduct: VerifyPurchaseResultAndroid['betaProduct'];
413
415
  cancelDate: VerifyPurchaseResultAndroid['cancelDate'];
@@ -428,6 +430,12 @@ export interface NitroPurchaseVerificationResultAndroid {
428
430
  testTransaction: VerifyPurchaseResultAndroid['testTransaction'];
429
431
  }
430
432
 
433
+ export interface NitroPurchaseVerificationResultHorizon {
434
+ isValid: VerifyPurchaseResultHorizon['isValid'];
435
+ grantTime?: VerifyPurchaseResultHorizon['grantTime'];
436
+ success: VerifyPurchaseResultHorizon['success'];
437
+ }
438
+
431
439
  // VerifyPurchaseWithProvider types
432
440
 
433
441
  export interface NitroVerifyPurchaseWithIapkitAppleProps {
@@ -994,12 +1002,14 @@ export interface RnIap extends HybridObject<{ios: 'swift'; android: 'kotlin'}> {
994
1002
  /**
995
1003
  * Verify a purchase on the appropriate platform.
996
1004
  * @param params - Purchase verification parameters including platform-specific options
997
- * @returns Promise<NitroPurchaseVerificationResultIOS | NitroPurchaseVerificationResultAndroid>
1005
+ * @returns Promise with the platform-specific verification result
998
1006
  */
999
1007
  verifyPurchase(
1000
1008
  params: NitroPurchaseVerificationParams,
1001
1009
  ): Promise<
1002
- NitroPurchaseVerificationResultIOS | NitroPurchaseVerificationResultAndroid
1010
+ | NitroPurchaseVerificationResultIOS
1011
+ | NitroPurchaseVerificationResultAndroid
1012
+ | NitroPurchaseVerificationResultHorizon
1003
1013
  >;
1004
1014
 
1005
1015
  /**
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. Returns a platform-specific
889
- * variant of VerifyPurchaseResult VerifyPurchaseResultIOS exposes isValid
890
- * + receipt/JWS metadata, VerifyPurchaseResultAndroid carries Play Store
891
- * receipt fields (no isValid), and VerifyPurchaseResultHorizon uses success.
892
- * Inspect the concrete variant before reading fields.
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
- /** Whether the entitlement verification succeeded. */
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 */
@@ -1271,17 +1271,12 @@ export function createVegaIapModule(service: VegaPurchasingService): RnIap {
1271
1271
  `IAPKit returned malformed response (HTTP ${status}).`,
1272
1272
  );
1273
1273
  }
1274
- const environment = json.environment;
1275
- if (
1276
- environment != null &&
1277
- (typeof environment !== 'string' ||
1278
- (environment !== 'Sandbox' && environment !== 'Production'))
1279
- ) {
1280
- throw createVegaError(
1281
- ErrorCode.PurchaseVerificationFailed,
1282
- `IAPKit returned malformed response (HTTP ${status}).`,
1283
- );
1284
- }
1274
+ // Forwarded opaquely: `environment` is String in the spec.
1275
+ const rawEnvironment = json.environment;
1276
+ const environment =
1277
+ typeof rawEnvironment === 'string' && rawEnvironment.length > 0
1278
+ ? rawEnvironment
1279
+ : undefined;
1285
1280
 
1286
1281
  return {
1287
1282
  ...(environment == null ? {} : {environment}),
@@ -1,26 +0,0 @@
1
- ///
2
- /// JVariant_NitroPurchaseVerificationResultIOS_NitroPurchaseVerificationResultAndroid.cpp
3
- /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
4
- /// https://github.com/mrousavy/nitro
5
- /// Copyright © Marc Rousavy @ Margelo
6
- ///
7
-
8
- #include "JVariant_NitroPurchaseVerificationResultIOS_NitroPurchaseVerificationResultAndroid.hpp"
9
-
10
- namespace margelo::nitro::iap {
11
- /**
12
- * Converts JVariant_NitroPurchaseVerificationResultIOS_NitroPurchaseVerificationResultAndroid to std::variant<NitroPurchaseVerificationResultIOS, NitroPurchaseVerificationResultAndroid>
13
- */
14
- std::variant<NitroPurchaseVerificationResultIOS, NitroPurchaseVerificationResultAndroid> JVariant_NitroPurchaseVerificationResultIOS_NitroPurchaseVerificationResultAndroid::toCpp() const {
15
- if (isInstanceOf(JVariant_NitroPurchaseVerificationResultIOS_NitroPurchaseVerificationResultAndroid_impl::First::javaClassStatic())) {
16
- // It's a `NitroPurchaseVerificationResultIOS`
17
- auto jniValue = static_cast<const JVariant_NitroPurchaseVerificationResultIOS_NitroPurchaseVerificationResultAndroid_impl::First*>(this)->getValue();
18
- return jniValue->toCpp();
19
- } else if (isInstanceOf(JVariant_NitroPurchaseVerificationResultIOS_NitroPurchaseVerificationResultAndroid_impl::Second::javaClassStatic())) {
20
- // It's a `NitroPurchaseVerificationResultAndroid`
21
- auto jniValue = static_cast<const JVariant_NitroPurchaseVerificationResultIOS_NitroPurchaseVerificationResultAndroid_impl::Second*>(this)->getValue();
22
- return jniValue->toCpp();
23
- }
24
- throw std::invalid_argument("Variant is unknown Kotlin instance!");
25
- }
26
- } // namespace margelo::nitro::iap