react-native-iap 14.3.0 → 14.3.2-rc.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/ios/HybridRnIap.swift +25 -19
- package/lib/index.d.ts +8 -0
- package/lib/index.js +36 -0
- package/lib/module/helpers/subscription.js +2 -2
- package/lib/module/helpers/subscription.js.map +1 -1
- package/lib/module/index.js +100 -105
- package/lib/module/index.js.map +1 -1
- package/lib/module/types.js.map +1 -1
- package/lib/module/utils/type-bridge.js +1 -2
- package/lib/module/utils/type-bridge.js.map +1 -1
- package/lib/specs/RnIap.nitro.d.ts +7 -0
- package/lib/specs/RnIap.nitro.js +1 -0
- package/lib/typescript/src/helpers/subscription.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +0 -2
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/plugin/tsconfig.tsbuildinfo +1 -1
- package/src/helpers/subscription.ts +2 -5
- package/src/index.ts +117 -155
- package/src/types.ts +0 -2
- package/src/utils/type-bridge.ts +1 -1
package/ios/HybridRnIap.swift
CHANGED
|
@@ -60,12 +60,12 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
60
60
|
#if DEBUG
|
|
61
61
|
print("[HybridRnIap] purchaseError event: code=\(error.code), productId=\(error.productId ?? "-")")
|
|
62
62
|
#endif
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
let nitroError = self.createPurchaseErrorResult(
|
|
64
|
+
code: error.code,
|
|
65
|
+
message: error.message,
|
|
66
|
+
productId: error.productId
|
|
67
|
+
)
|
|
68
|
+
self.sendPurchaseError(nitroError, productId: error.productId)
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
}
|
|
@@ -123,7 +123,7 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
123
123
|
message: error.message,
|
|
124
124
|
productId: error.productId
|
|
125
125
|
)
|
|
126
|
-
self.sendPurchaseError(nitroError)
|
|
126
|
+
self.sendPurchaseError(nitroError, productId: error.productId)
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
}
|
|
@@ -168,7 +168,7 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
168
168
|
message: error.localizedDescription,
|
|
169
169
|
productId: nil
|
|
170
170
|
)
|
|
171
|
-
self.sendPurchaseError(err)
|
|
171
|
+
self.sendPurchaseError(err, productId: nil)
|
|
172
172
|
self.isInitialized = false
|
|
173
173
|
self.isInitializing = false
|
|
174
174
|
return false
|
|
@@ -205,7 +205,7 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
205
205
|
code: OpenIapError.E_USER_ERROR,
|
|
206
206
|
message: "No iOS request provided"
|
|
207
207
|
)
|
|
208
|
-
self.sendPurchaseError(error)
|
|
208
|
+
self.sendPurchaseError(error, productId: nil)
|
|
209
209
|
return
|
|
210
210
|
}
|
|
211
211
|
do {
|
|
@@ -219,7 +219,7 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
219
219
|
message: "IAP store connection not initialized",
|
|
220
220
|
productId: iosRequest.sku
|
|
221
221
|
)
|
|
222
|
-
self.sendPurchaseError(err)
|
|
222
|
+
self.sendPurchaseError(err, productId: iosRequest.sku)
|
|
223
223
|
return
|
|
224
224
|
}
|
|
225
225
|
// Delegate purchase to OpenIAP. It emits success/error events which we bridge above.
|
|
@@ -246,7 +246,7 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
246
246
|
message: error.localizedDescription,
|
|
247
247
|
productId: iosRequest.sku
|
|
248
248
|
)
|
|
249
|
-
self.sendPurchaseErrorDedup(err)
|
|
249
|
+
self.sendPurchaseErrorDedup(err, productId: iosRequest.sku)
|
|
250
250
|
}
|
|
251
251
|
}
|
|
252
252
|
}
|
|
@@ -612,25 +612,30 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
612
612
|
}
|
|
613
613
|
}
|
|
614
614
|
|
|
615
|
-
private func sendPurchaseError(_ error: NitroPurchaseResult) {
|
|
616
|
-
// Update last error for deduplication
|
|
615
|
+
private func sendPurchaseError(_ error: NitroPurchaseResult, productId: String? = nil) {
|
|
616
|
+
// Update last error for deduplication using the associated product SKU (not token)
|
|
617
617
|
lastPurchaseErrorCode = error.code
|
|
618
|
-
lastPurchaseErrorProductId =
|
|
618
|
+
lastPurchaseErrorProductId = productId
|
|
619
619
|
lastPurchaseErrorTimestamp = Date().timeIntervalSince1970
|
|
620
|
+
// Ensure we never leak SKU via purchaseToken
|
|
621
|
+
var sanitized = error
|
|
622
|
+
if let pid = productId, sanitized.purchaseToken == pid {
|
|
623
|
+
sanitized.purchaseToken = nil
|
|
624
|
+
}
|
|
620
625
|
for listener in purchaseErrorListeners {
|
|
621
|
-
listener(
|
|
626
|
+
listener(sanitized)
|
|
622
627
|
}
|
|
623
628
|
}
|
|
624
629
|
|
|
625
|
-
private func sendPurchaseErrorDedup(_ error: NitroPurchaseResult) {
|
|
630
|
+
private func sendPurchaseErrorDedup(_ error: NitroPurchaseResult, productId: String? = nil) {
|
|
626
631
|
let now = Date().timeIntervalSince1970
|
|
627
632
|
let sameCode = (error.code == lastPurchaseErrorCode)
|
|
628
|
-
let sameProduct = (
|
|
633
|
+
let sameProduct = (productId == lastPurchaseErrorProductId)
|
|
629
634
|
let withinWindow = (now - lastPurchaseErrorTimestamp) < 0.3
|
|
630
635
|
if sameCode && sameProduct && withinWindow {
|
|
631
636
|
return
|
|
632
637
|
}
|
|
633
|
-
sendPurchaseError(error)
|
|
638
|
+
sendPurchaseError(error, productId: productId)
|
|
634
639
|
}
|
|
635
640
|
|
|
636
641
|
private func createPurchaseErrorResult(code: String, message: String, productId: String? = nil) -> NitroPurchaseResult {
|
|
@@ -638,7 +643,8 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
638
643
|
result.responseCode = 0
|
|
639
644
|
result.code = code
|
|
640
645
|
result.message = message
|
|
641
|
-
|
|
646
|
+
// Do not overload the token field with productId
|
|
647
|
+
result.purchaseToken = nil
|
|
642
648
|
return result
|
|
643
649
|
}
|
|
644
650
|
|
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { NitroModules } from 'react-native-nitro-modules';
|
|
2
|
+
export * from './specs/RnIap.nitro';
|
|
3
|
+
class RnIapImpl {
|
|
4
|
+
hybridObject = null;
|
|
5
|
+
getHybridObject() {
|
|
6
|
+
if (!this.hybridObject) {
|
|
7
|
+
try {
|
|
8
|
+
console.log('🔧 Creating RnIap HybridObject...');
|
|
9
|
+
this.hybridObject = NitroModules.createHybridObject('RnIap');
|
|
10
|
+
console.log('🔧 HybridObject created successfully:', !!this.hybridObject);
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
console.error('🔧 Failed to create HybridObject:', error);
|
|
14
|
+
throw new Error(`Failed to create RnIap HybridObject: ${error}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return this.hybridObject;
|
|
18
|
+
}
|
|
19
|
+
toString() {
|
|
20
|
+
try {
|
|
21
|
+
console.log('🔧 Getting HybridObject for toString...');
|
|
22
|
+
const hybridObject = this.getHybridObject();
|
|
23
|
+
console.log('🔧 HybridObject obtained, calling toString...');
|
|
24
|
+
const result = hybridObject.toString();
|
|
25
|
+
console.log('🔧 toString completed with result:', result);
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
console.error('🔧 toString failed:', error);
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Create singleton instance
|
|
35
|
+
const RnIap = new RnIapImpl();
|
|
36
|
+
export default RnIap;
|
|
@@ -26,8 +26,8 @@ export const getActiveSubscriptions = async subscriptionIds => {
|
|
|
26
26
|
isActive: true,
|
|
27
27
|
// If it's in availablePurchases, it's active
|
|
28
28
|
// Backend validation fields
|
|
29
|
-
transactionId: purchase.
|
|
30
|
-
purchaseToken:
|
|
29
|
+
transactionId: purchase.id,
|
|
30
|
+
purchaseToken: purchase.purchaseToken,
|
|
31
31
|
transactionDate: purchase.transactionDate,
|
|
32
32
|
// Platform-specific fields
|
|
33
33
|
expirationDateIOS: iosPurchase.expirationDateIOS ? new Date(iosPurchase.expirationDateIOS) : undefined,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["getAvailablePurchases","getActiveSubscriptions","subscriptionIds","purchases","subscriptions","filter","purchase","length","includes","productId","map","iosPurchase","androidPurchase","isActive","transactionId","id","purchaseToken","
|
|
1
|
+
{"version":3,"names":["getAvailablePurchases","getActiveSubscriptions","subscriptionIds","purchases","subscriptions","filter","purchase","length","includes","productId","map","iosPurchase","androidPurchase","isActive","transactionId","id","purchaseToken","transactionDate","expirationDateIOS","Date","undefined","autoRenewingAndroid","isAutoRenewing","environmentIOS","willExpireSoon","daysUntilExpirationIOS","Math","ceil","now","error","console","hasActiveSubscriptions","activeSubscriptions"],"sourceRoot":"../../../src","sources":["helpers/subscription.ts"],"mappings":";;AAAA,SAAQA,qBAAqB,QAAO,aAAK;AAGzC;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,sBAAsB,GAAG,MACpCC,eAA0B,IACQ;EAClC,IAAI;IACF;IACA,MAAMC,SAAS,GAAG,MAAMH,qBAAqB,CAAC,CAAC;;IAE/C;IACA,MAAMI,aAAa,GAAGD,SAAS,CAC5BE,MAAM,CAAEC,QAAQ,IAAK;MACpB;MACA,IAAIJ,eAAe,IAAIA,eAAe,CAACK,MAAM,GAAG,CAAC,EAAE;QACjD,OAAOL,eAAe,CAACM,QAAQ,CAACF,QAAQ,CAACG,SAAS,CAAC;MACrD;MACA,OAAO,IAAI;IACb,CAAC,CAAC,CACDC,GAAG,CAAEJ,QAAQ,IAAyB;MACrC,MAAMK,WAAW,GAAGL,QAAuB;MAC3C,MAAMM,eAAe,GAAGN,QAA2B;MACnD,OAAO;QACLG,SAAS,EAAEH,QAAQ,CAACG,SAAS;QAC7BI,QAAQ,EAAE,IAAI;QAAE;QAChB;QACAC,aAAa,EAAER,QAAQ,CAACS,EAAE;QAC1BC,aAAa,EAAEV,QAAQ,CAACU,aAAa;QACrCC,eAAe,EAAEX,QAAQ,CAACW,eAAe;QACzC;QACAC,iBAAiB,EAAEP,WAAW,CAACO,iBAAiB,GAC5C,IAAIC,IAAI,CAACR,WAAW,CAACO,iBAAiB,CAAC,GACvCE,SAAS;QACbC,mBAAmB,EACjBT,eAAe,CAACS,mBAAmB,IACnCT,eAAe,CAACU,cAAc;QAAE;QAClCC,cAAc,EAAEZ,WAAW,CAACY,cAAc;QAC1C;QACAC,cAAc,EAAE,KAAK;QAAE;QACvBC,sBAAsB,EAAEd,WAAW,CAACO,iBAAiB,GACjDQ,IAAI,CAACC,IAAI,CACP,CAAChB,WAAW,CAACO,iBAAiB,GAAGC,IAAI,CAACS,GAAG,CAAC,CAAC,KACxC,IAAI,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CACxB,CAAC,GACDR;MACN,CAAC;IACH,CAAC,CAAC;IAEJ,OAAOhB,aAAa;EACtB,CAAC,CAAC,OAAOyB,KAAK,EAAE;IACdC,OAAO,CAACD,KAAK,CAAC,qCAAqC,EAAEA,KAAK,CAAC;IAC3D,MAAMA,KAAK;EACb;AACF,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA,OAAO,MAAME,sBAAsB,GAAG,MACpC7B,eAA0B,IACL;EACrB,IAAI;IACF,MAAM8B,mBAAmB,GAAG,MAAM/B,sBAAsB,CAACC,eAAe,CAAC;IACzE,OAAO8B,mBAAmB,CAACzB,MAAM,GAAG,CAAC;EACvC,CAAC,CAAC,OAAOsB,KAAK,EAAE;IACdC,OAAO,CAACD,KAAK,CAAC,uCAAuC,EAAEA,KAAK,CAAC;IAC7D,OAAO,KAAK;EACd;AACF,CAAC","ignoreList":[]}
|
package/lib/module/index.js
CHANGED
|
@@ -38,15 +38,27 @@ export const restorePurchases = async (options = {
|
|
|
38
38
|
|
|
39
39
|
// Development utilities removed - use type bridge functions directly if needed
|
|
40
40
|
|
|
41
|
-
// Create the RnIap HybridObject instance
|
|
42
|
-
|
|
41
|
+
// Create the RnIap HybridObject instance lazily to avoid early JSI crashes
|
|
42
|
+
let iap = null;
|
|
43
|
+
const getIap = () => {
|
|
44
|
+
if (iap) return iap;
|
|
45
|
+
|
|
46
|
+
// Guard against accessing Nitro before it's installed into the JS runtime
|
|
47
|
+
const hasNitroDispatcher = typeof globalThis !== 'undefined' && globalThis?.__nitro?.dispatcher != null;
|
|
48
|
+
const isJestEnvironment = typeof globalThis.jest !== 'undefined' || typeof process !== 'undefined' && !!process.env && process.env.JEST_WORKER_ID != null;
|
|
49
|
+
if (!hasNitroDispatcher && !isJestEnvironment) {
|
|
50
|
+
throw new Error('Nitro runtime not installed yet. Ensure react-native-nitro-modules is initialized before calling IAP.');
|
|
51
|
+
}
|
|
52
|
+
iap = NitroModules.createHybridObject('RnIap');
|
|
53
|
+
return iap;
|
|
54
|
+
};
|
|
43
55
|
|
|
44
56
|
/**
|
|
45
57
|
* Initialize connection to the store
|
|
46
58
|
*/
|
|
47
59
|
export const initConnection = async () => {
|
|
48
60
|
try {
|
|
49
|
-
return await
|
|
61
|
+
return await getIap().initConnection();
|
|
50
62
|
} catch (error) {
|
|
51
63
|
console.error('Failed to initialize IAP connection:', error);
|
|
52
64
|
throw error;
|
|
@@ -58,7 +70,9 @@ export const initConnection = async () => {
|
|
|
58
70
|
*/
|
|
59
71
|
export const endConnection = async () => {
|
|
60
72
|
try {
|
|
61
|
-
|
|
73
|
+
// If never initialized, treat as ended
|
|
74
|
+
if (!iap) return true;
|
|
75
|
+
return await getIap().endConnection();
|
|
62
76
|
} catch (error) {
|
|
63
77
|
console.error('Failed to end IAP connection:', error);
|
|
64
78
|
throw error;
|
|
@@ -90,7 +104,7 @@ export const fetchProducts = async ({
|
|
|
90
104
|
throw new Error('No SKUs provided');
|
|
91
105
|
}
|
|
92
106
|
if (type === 'all') {
|
|
93
|
-
const [inappNitro, subsNitro] = await Promise.all([
|
|
107
|
+
const [inappNitro, subsNitro] = await Promise.all([getIap().fetchProducts(skus, 'inapp'), getIap().fetchProducts(skus, 'subs')]);
|
|
94
108
|
const allNitro = [...inappNitro, ...subsNitro];
|
|
95
109
|
const validAll = allNitro.filter(validateNitroProduct);
|
|
96
110
|
if (validAll.length !== allNitro.length) {
|
|
@@ -98,7 +112,7 @@ export const fetchProducts = async ({
|
|
|
98
112
|
}
|
|
99
113
|
return validAll.map(convertNitroProductToProduct);
|
|
100
114
|
}
|
|
101
|
-
const nitroProducts = await
|
|
115
|
+
const nitroProducts = await getIap().fetchProducts(skus, type);
|
|
102
116
|
|
|
103
117
|
// Validate and convert NitroProducts to TypeScript Products
|
|
104
118
|
const validProducts = nitroProducts.filter(validateNitroProduct);
|
|
@@ -197,7 +211,7 @@ export const requestPurchase = async ({
|
|
|
197
211
|
}
|
|
198
212
|
|
|
199
213
|
// Call unified method - returns void, listen for events instead
|
|
200
|
-
await
|
|
214
|
+
await getIap().requestPurchase(unifiedRequest);
|
|
201
215
|
} catch (error) {
|
|
202
216
|
console.error('Failed to request purchase:', error);
|
|
203
217
|
throw error;
|
|
@@ -234,12 +248,12 @@ export const getAvailablePurchases = async ({
|
|
|
234
248
|
};
|
|
235
249
|
} else if (Platform.OS === 'android') {
|
|
236
250
|
// For Android, we need to call twice for inapp and subs
|
|
237
|
-
const inappNitroPurchases = await
|
|
251
|
+
const inappNitroPurchases = await getIap().getAvailablePurchases({
|
|
238
252
|
android: {
|
|
239
253
|
type: 'inapp'
|
|
240
254
|
}
|
|
241
255
|
});
|
|
242
|
-
const subsNitroPurchases = await
|
|
256
|
+
const subsNitroPurchases = await getIap().getAvailablePurchases({
|
|
243
257
|
android: {
|
|
244
258
|
type: 'subs'
|
|
245
259
|
}
|
|
@@ -255,7 +269,7 @@ export const getAvailablePurchases = async ({
|
|
|
255
269
|
} else {
|
|
256
270
|
throw new Error('Unsupported platform');
|
|
257
271
|
}
|
|
258
|
-
const nitroPurchases = await
|
|
272
|
+
const nitroPurchases = await getIap().getAvailablePurchases(options);
|
|
259
273
|
|
|
260
274
|
// Validate and convert NitroPurchases to TypeScript Purchases
|
|
261
275
|
const validPurchases = nitroPurchases.filter(validateNitroPurchase);
|
|
@@ -299,7 +313,7 @@ export const finishTransaction = async ({
|
|
|
299
313
|
};
|
|
300
314
|
} else if (Platform.OS === 'android') {
|
|
301
315
|
const androidPurchase = purchase;
|
|
302
|
-
const token = androidPurchase.purchaseToken
|
|
316
|
+
const token = androidPurchase.purchaseToken;
|
|
303
317
|
if (!token) {
|
|
304
318
|
throw new Error('purchaseToken required to finish Android transaction');
|
|
305
319
|
}
|
|
@@ -310,7 +324,7 @@ export const finishTransaction = async ({
|
|
|
310
324
|
} else {
|
|
311
325
|
throw new Error('Unsupported platform');
|
|
312
326
|
}
|
|
313
|
-
const result = await
|
|
327
|
+
const result = await getIap().finishTransaction(params);
|
|
314
328
|
|
|
315
329
|
// Handle variant return type
|
|
316
330
|
if (typeof result === 'boolean') {
|
|
@@ -348,7 +362,7 @@ export const acknowledgePurchaseAndroid = async purchaseToken => {
|
|
|
348
362
|
if (Platform.OS !== 'android') {
|
|
349
363
|
throw new Error('acknowledgePurchaseAndroid is only available on Android');
|
|
350
364
|
}
|
|
351
|
-
const result = await
|
|
365
|
+
const result = await getIap().finishTransaction({
|
|
352
366
|
android: {
|
|
353
367
|
purchaseToken,
|
|
354
368
|
isConsumable: false
|
|
@@ -386,7 +400,7 @@ export const consumePurchaseAndroid = async purchaseToken => {
|
|
|
386
400
|
if (Platform.OS !== 'android') {
|
|
387
401
|
throw new Error('consumePurchaseAndroid is only available on Android');
|
|
388
402
|
}
|
|
389
|
-
const result = await
|
|
403
|
+
const result = await getIap().finishTransaction({
|
|
390
404
|
android: {
|
|
391
405
|
purchaseToken,
|
|
392
406
|
isConsumable: true
|
|
@@ -450,12 +464,27 @@ export const purchaseUpdatedListener = listener => {
|
|
|
450
464
|
|
|
451
465
|
// Store the wrapped listener for removal
|
|
452
466
|
listenerMap.set(listener, wrappedListener);
|
|
453
|
-
|
|
467
|
+
let attached = false;
|
|
468
|
+
try {
|
|
469
|
+
getIap().addPurchaseUpdatedListener(wrappedListener);
|
|
470
|
+
attached = true;
|
|
471
|
+
} catch (e) {
|
|
472
|
+
const msg = String(e ?? '');
|
|
473
|
+
if (msg.includes('Nitro runtime not installed')) {
|
|
474
|
+
console.warn('[purchaseUpdatedListener] Nitro not ready yet; listener inert until initConnection()');
|
|
475
|
+
} else {
|
|
476
|
+
throw e;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
454
479
|
return {
|
|
455
480
|
remove: () => {
|
|
456
481
|
const wrapped = listenerMap.get(listener);
|
|
457
482
|
if (wrapped) {
|
|
458
|
-
|
|
483
|
+
if (attached) {
|
|
484
|
+
try {
|
|
485
|
+
getIap().removePurchaseUpdatedListener(wrapped);
|
|
486
|
+
} catch {}
|
|
487
|
+
}
|
|
459
488
|
listenerMap.delete(listener);
|
|
460
489
|
}
|
|
461
490
|
}
|
|
@@ -492,10 +521,25 @@ export const purchaseUpdatedListener = listener => {
|
|
|
492
521
|
export const purchaseErrorListener = listener => {
|
|
493
522
|
// Store the listener for removal
|
|
494
523
|
listenerMap.set(listener, listener);
|
|
495
|
-
|
|
524
|
+
let attached = false;
|
|
525
|
+
try {
|
|
526
|
+
getIap().addPurchaseErrorListener(listener);
|
|
527
|
+
attached = true;
|
|
528
|
+
} catch (e) {
|
|
529
|
+
const msg = String(e ?? '');
|
|
530
|
+
if (msg.includes('Nitro runtime not installed')) {
|
|
531
|
+
console.warn('[purchaseErrorListener] Nitro not ready yet; listener inert until initConnection()');
|
|
532
|
+
} else {
|
|
533
|
+
throw e;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
496
536
|
return {
|
|
497
537
|
remove: () => {
|
|
498
|
-
|
|
538
|
+
if (attached) {
|
|
539
|
+
try {
|
|
540
|
+
getIap().removePurchaseErrorListener(listener);
|
|
541
|
+
} catch {}
|
|
542
|
+
}
|
|
499
543
|
listenerMap.delete(listener);
|
|
500
544
|
}
|
|
501
545
|
};
|
|
@@ -541,12 +585,27 @@ export const promotedProductListenerIOS = listener => {
|
|
|
541
585
|
|
|
542
586
|
// Store the wrapped listener for removal
|
|
543
587
|
listenerMap.set(listener, wrappedListener);
|
|
544
|
-
|
|
588
|
+
let attached = false;
|
|
589
|
+
try {
|
|
590
|
+
getIap().addPromotedProductListenerIOS(wrappedListener);
|
|
591
|
+
attached = true;
|
|
592
|
+
} catch (e) {
|
|
593
|
+
const msg = String(e ?? '');
|
|
594
|
+
if (msg.includes('Nitro runtime not installed')) {
|
|
595
|
+
console.warn('[promotedProductListenerIOS] Nitro not ready yet; listener inert until initConnection()');
|
|
596
|
+
} else {
|
|
597
|
+
throw e;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
545
600
|
return {
|
|
546
601
|
remove: () => {
|
|
547
602
|
const wrapped = listenerMap.get(listener);
|
|
548
603
|
if (wrapped) {
|
|
549
|
-
|
|
604
|
+
if (attached) {
|
|
605
|
+
try {
|
|
606
|
+
getIap().removePromotedProductListenerIOS(wrapped);
|
|
607
|
+
} catch {}
|
|
608
|
+
}
|
|
550
609
|
listenerMap.delete(listener);
|
|
551
610
|
}
|
|
552
611
|
}
|
|
@@ -564,16 +623,12 @@ export const promotedProductListenerIOS = listener => {
|
|
|
564
623
|
* @returns Promise<ReceiptValidationResultIOS | ReceiptValidationResultAndroid> - Platform-specific receipt validation result
|
|
565
624
|
*/
|
|
566
625
|
export const validateReceipt = async (sku, androidOptions) => {
|
|
567
|
-
if (!iap) {
|
|
568
|
-
const errorJson = parseErrorStringToJsonObj('RnIap: Service not initialized. Call initConnection() first.');
|
|
569
|
-
throw new Error(errorJson.message);
|
|
570
|
-
}
|
|
571
626
|
try {
|
|
572
627
|
const params = {
|
|
573
628
|
sku,
|
|
574
629
|
androidOptions
|
|
575
630
|
};
|
|
576
|
-
const nitroResult = await
|
|
631
|
+
const nitroResult = await getIap().validateReceipt(params);
|
|
577
632
|
|
|
578
633
|
// Convert Nitro result to public API result
|
|
579
634
|
if (Platform.OS === 'ios') {
|
|
@@ -626,12 +681,8 @@ export const syncIOS = async () => {
|
|
|
626
681
|
if (Platform.OS !== 'ios') {
|
|
627
682
|
throw new Error('syncIOS is only available on iOS');
|
|
628
683
|
}
|
|
629
|
-
if (!iap) {
|
|
630
|
-
const errorJson = parseErrorStringToJsonObj('RnIap: Service not initialized. Call initConnection() first.');
|
|
631
|
-
throw new Error(errorJson.message);
|
|
632
|
-
}
|
|
633
684
|
try {
|
|
634
|
-
return await
|
|
685
|
+
return await getIap().syncIOS();
|
|
635
686
|
} catch (error) {
|
|
636
687
|
console.error('[syncIOS] Failed:', error);
|
|
637
688
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
@@ -648,12 +699,8 @@ export const requestPromotedProductIOS = async () => {
|
|
|
648
699
|
if (Platform.OS !== 'ios') {
|
|
649
700
|
return null;
|
|
650
701
|
}
|
|
651
|
-
if (!iap) {
|
|
652
|
-
const errorJson = parseErrorStringToJsonObj('RnIap: Service not initialized. Call initConnection() first.');
|
|
653
|
-
throw new Error(errorJson.message);
|
|
654
|
-
}
|
|
655
702
|
try {
|
|
656
|
-
const nitroProduct = await
|
|
703
|
+
const nitroProduct = await getIap().requestPromotedProductIOS();
|
|
657
704
|
if (nitroProduct) {
|
|
658
705
|
return convertNitroProductToProduct(nitroProduct);
|
|
659
706
|
}
|
|
@@ -674,12 +721,8 @@ export const presentCodeRedemptionSheetIOS = async () => {
|
|
|
674
721
|
if (Platform.OS !== 'ios') {
|
|
675
722
|
return false;
|
|
676
723
|
}
|
|
677
|
-
if (!iap) {
|
|
678
|
-
const errorJson = parseErrorStringToJsonObj('RnIap: Service not initialized. Call initConnection() first.');
|
|
679
|
-
throw new Error(errorJson.message);
|
|
680
|
-
}
|
|
681
724
|
try {
|
|
682
|
-
return await
|
|
725
|
+
return await getIap().presentCodeRedemptionSheetIOS();
|
|
683
726
|
} catch (error) {
|
|
684
727
|
console.error('[presentCodeRedemptionSheetIOS] Failed:', error);
|
|
685
728
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
@@ -696,12 +739,8 @@ export const buyPromotedProductIOS = async () => {
|
|
|
696
739
|
if (Platform.OS !== 'ios') {
|
|
697
740
|
throw new Error('buyPromotedProductIOS is only available on iOS');
|
|
698
741
|
}
|
|
699
|
-
if (!iap) {
|
|
700
|
-
const errorJson = parseErrorStringToJsonObj('RnIap: Service not initialized. Call initConnection() first.');
|
|
701
|
-
throw new Error(errorJson.message);
|
|
702
|
-
}
|
|
703
742
|
try {
|
|
704
|
-
await
|
|
743
|
+
await getIap().buyPromotedProductIOS();
|
|
705
744
|
} catch (error) {
|
|
706
745
|
console.error('[buyPromotedProductIOS] Failed:', error);
|
|
707
746
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
@@ -718,12 +757,8 @@ export const clearTransactionIOS = async () => {
|
|
|
718
757
|
if (Platform.OS !== 'ios') {
|
|
719
758
|
return;
|
|
720
759
|
}
|
|
721
|
-
if (!iap) {
|
|
722
|
-
const errorJson = parseErrorStringToJsonObj('RnIap: Service not initialized. Call initConnection() first.');
|
|
723
|
-
throw new Error(errorJson.message);
|
|
724
|
-
}
|
|
725
760
|
try {
|
|
726
|
-
await
|
|
761
|
+
await getIap().clearTransactionIOS();
|
|
727
762
|
} catch (error) {
|
|
728
763
|
console.error('[clearTransactionIOS] Failed:', error);
|
|
729
764
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
@@ -741,12 +776,8 @@ export const beginRefundRequestIOS = async sku => {
|
|
|
741
776
|
if (Platform.OS !== 'ios') {
|
|
742
777
|
return null;
|
|
743
778
|
}
|
|
744
|
-
if (!iap) {
|
|
745
|
-
const errorJson = parseErrorStringToJsonObj('RnIap: Service not initialized. Call initConnection() first.');
|
|
746
|
-
throw new Error(errorJson.message);
|
|
747
|
-
}
|
|
748
779
|
try {
|
|
749
|
-
return await
|
|
780
|
+
return await getIap().beginRefundRequestIOS(sku);
|
|
750
781
|
} catch (error) {
|
|
751
782
|
console.error('[beginRefundRequestIOS] Failed:', error);
|
|
752
783
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
@@ -765,12 +796,8 @@ export const subscriptionStatusIOS = async sku => {
|
|
|
765
796
|
if (Platform.OS !== 'ios') {
|
|
766
797
|
throw new Error('subscriptionStatusIOS is only available on iOS');
|
|
767
798
|
}
|
|
768
|
-
if (!iap) {
|
|
769
|
-
const errorJson = parseErrorStringToJsonObj('RnIap: Service not initialized. Call initConnection() first.');
|
|
770
|
-
throw new Error(errorJson.message);
|
|
771
|
-
}
|
|
772
799
|
try {
|
|
773
|
-
const statuses = await
|
|
800
|
+
const statuses = await getIap().subscriptionStatusIOS(sku);
|
|
774
801
|
if (!statuses || !Array.isArray(statuses)) return [];
|
|
775
802
|
return statuses.map(s => convertNitroSubscriptionStatusToSubscriptionStatusIOS(s));
|
|
776
803
|
} catch (error) {
|
|
@@ -790,12 +817,8 @@ export const currentEntitlementIOS = async sku => {
|
|
|
790
817
|
if (Platform.OS !== 'ios') {
|
|
791
818
|
return null;
|
|
792
819
|
}
|
|
793
|
-
if (!iap) {
|
|
794
|
-
const errorJson = parseErrorStringToJsonObj('RnIap: Service not initialized. Call initConnection() first.');
|
|
795
|
-
throw new Error(errorJson.message);
|
|
796
|
-
}
|
|
797
820
|
try {
|
|
798
|
-
const nitroPurchase = await
|
|
821
|
+
const nitroPurchase = await getIap().currentEntitlementIOS(sku);
|
|
799
822
|
if (nitroPurchase) {
|
|
800
823
|
return convertNitroPurchaseToPurchase(nitroPurchase);
|
|
801
824
|
}
|
|
@@ -817,12 +840,8 @@ export const latestTransactionIOS = async sku => {
|
|
|
817
840
|
if (Platform.OS !== 'ios') {
|
|
818
841
|
return null;
|
|
819
842
|
}
|
|
820
|
-
if (!iap) {
|
|
821
|
-
const errorJson = parseErrorStringToJsonObj('RnIap: Service not initialized. Call initConnection() first.');
|
|
822
|
-
throw new Error(errorJson.message);
|
|
823
|
-
}
|
|
824
843
|
try {
|
|
825
|
-
const nitroPurchase = await
|
|
844
|
+
const nitroPurchase = await getIap().latestTransactionIOS(sku);
|
|
826
845
|
if (nitroPurchase) {
|
|
827
846
|
return convertNitroPurchaseToPurchase(nitroPurchase);
|
|
828
847
|
}
|
|
@@ -843,12 +862,8 @@ export const getPendingTransactionsIOS = async () => {
|
|
|
843
862
|
if (Platform.OS !== 'ios') {
|
|
844
863
|
return [];
|
|
845
864
|
}
|
|
846
|
-
if (!iap) {
|
|
847
|
-
const errorJson = parseErrorStringToJsonObj('RnIap: Service not initialized. Call initConnection() first.');
|
|
848
|
-
throw new Error(errorJson.message);
|
|
849
|
-
}
|
|
850
865
|
try {
|
|
851
|
-
const nitroPurchases = await
|
|
866
|
+
const nitroPurchases = await getIap().getPendingTransactionsIOS();
|
|
852
867
|
return nitroPurchases.map(convertNitroPurchaseToPurchase);
|
|
853
868
|
} catch (error) {
|
|
854
869
|
console.error('[getPendingTransactionsIOS] Failed:', error);
|
|
@@ -866,12 +881,8 @@ export const showManageSubscriptionsIOS = async () => {
|
|
|
866
881
|
if (Platform.OS !== 'ios') {
|
|
867
882
|
return [];
|
|
868
883
|
}
|
|
869
|
-
if (!iap) {
|
|
870
|
-
const errorJson = parseErrorStringToJsonObj('RnIap: Service not initialized. Call initConnection() first.');
|
|
871
|
-
throw new Error(errorJson.message);
|
|
872
|
-
}
|
|
873
884
|
try {
|
|
874
|
-
const nitroPurchases = await
|
|
885
|
+
const nitroPurchases = await getIap().showManageSubscriptionsIOS();
|
|
875
886
|
return nitroPurchases.map(convertNitroPurchaseToPurchase);
|
|
876
887
|
} catch (error) {
|
|
877
888
|
console.error('[showManageSubscriptionsIOS] Failed:', error);
|
|
@@ -890,12 +901,8 @@ export const isEligibleForIntroOfferIOS = async groupID => {
|
|
|
890
901
|
if (Platform.OS !== 'ios') {
|
|
891
902
|
return false;
|
|
892
903
|
}
|
|
893
|
-
if (!iap) {
|
|
894
|
-
const errorJson = parseErrorStringToJsonObj('RnIap: Service not initialized. Call initConnection() first.');
|
|
895
|
-
throw new Error(errorJson.message);
|
|
896
|
-
}
|
|
897
904
|
try {
|
|
898
|
-
return await
|
|
905
|
+
return await getIap().isEligibleForIntroOfferIOS(groupID);
|
|
899
906
|
} catch (error) {
|
|
900
907
|
console.error('[isEligibleForIntroOfferIOS] Failed:', error);
|
|
901
908
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
@@ -912,12 +919,8 @@ export const getReceiptDataIOS = async () => {
|
|
|
912
919
|
if (Platform.OS !== 'ios') {
|
|
913
920
|
throw new Error('getReceiptDataIOS is only available on iOS');
|
|
914
921
|
}
|
|
915
|
-
if (!iap) {
|
|
916
|
-
const errorJson = parseErrorStringToJsonObj('RnIap: Service not initialized. Call initConnection() first.');
|
|
917
|
-
throw new Error(errorJson.message);
|
|
918
|
-
}
|
|
919
922
|
try {
|
|
920
|
-
return await
|
|
923
|
+
return await getIap().getReceiptDataIOS();
|
|
921
924
|
} catch (error) {
|
|
922
925
|
console.error('[getReceiptDataIOS] Failed:', error);
|
|
923
926
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
@@ -935,12 +938,8 @@ export const isTransactionVerifiedIOS = async sku => {
|
|
|
935
938
|
if (Platform.OS !== 'ios') {
|
|
936
939
|
return false;
|
|
937
940
|
}
|
|
938
|
-
if (!iap) {
|
|
939
|
-
const errorJson = parseErrorStringToJsonObj('RnIap: Service not initialized. Call initConnection() first.');
|
|
940
|
-
throw new Error(errorJson.message);
|
|
941
|
-
}
|
|
942
941
|
try {
|
|
943
|
-
return await
|
|
942
|
+
return await getIap().isTransactionVerifiedIOS(sku);
|
|
944
943
|
} catch (error) {
|
|
945
944
|
console.error('[isTransactionVerifiedIOS] Failed:', error);
|
|
946
945
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
@@ -958,12 +957,8 @@ export const getTransactionJwsIOS = async sku => {
|
|
|
958
957
|
if (Platform.OS !== 'ios') {
|
|
959
958
|
return null;
|
|
960
959
|
}
|
|
961
|
-
if (!iap) {
|
|
962
|
-
const errorJson = parseErrorStringToJsonObj('RnIap: Service not initialized. Call initConnection() first.');
|
|
963
|
-
throw new Error(errorJson.message);
|
|
964
|
-
}
|
|
965
960
|
try {
|
|
966
|
-
return await
|
|
961
|
+
return await getIap().getTransactionJwsIOS(sku);
|
|
967
962
|
} catch (error) {
|
|
968
963
|
console.error('[getTransactionJwsIOS] Failed:', error);
|
|
969
964
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
@@ -988,7 +983,7 @@ export const getStorefrontIOS = async () => {
|
|
|
988
983
|
}
|
|
989
984
|
try {
|
|
990
985
|
// Call the native method to get storefront
|
|
991
|
-
const storefront = await
|
|
986
|
+
const storefront = await getIap().getStorefrontIOS();
|
|
992
987
|
return storefront;
|
|
993
988
|
} catch (error) {
|
|
994
989
|
console.error('Failed to get storefront:', error);
|
|
@@ -1006,7 +1001,7 @@ export const getStorefront = async () => {
|
|
|
1006
1001
|
if (Platform.OS === 'android') {
|
|
1007
1002
|
try {
|
|
1008
1003
|
// Optional since older builds may not have the method
|
|
1009
|
-
const result = await
|
|
1004
|
+
const result = await getIap().getStorefrontAndroid?.();
|
|
1010
1005
|
return result ?? '';
|
|
1011
1006
|
} catch {
|
|
1012
1007
|
return '';
|
|
@@ -1021,7 +1016,7 @@ export const getStorefront = async () => {
|
|
|
1021
1016
|
*/
|
|
1022
1017
|
export const deepLinkToSubscriptions = async (options = {}) => {
|
|
1023
1018
|
if (Platform.OS === 'android') {
|
|
1024
|
-
await
|
|
1019
|
+
await getIap().deepLinkToSubscriptionsAndroid?.({
|
|
1025
1020
|
skuAndroid: options.skuAndroid,
|
|
1026
1021
|
packageNameAndroid: options.packageNameAndroid
|
|
1027
1022
|
});
|
|
@@ -1030,7 +1025,7 @@ export const deepLinkToSubscriptions = async (options = {}) => {
|
|
|
1030
1025
|
// iOS: Use manage subscriptions sheet (ignore returned purchases for deeplink parity)
|
|
1031
1026
|
if (Platform.OS === 'ios') {
|
|
1032
1027
|
try {
|
|
1033
|
-
await
|
|
1028
|
+
await getIap().showManageSubscriptionsIOS();
|
|
1034
1029
|
} catch {
|
|
1035
1030
|
// no-op
|
|
1036
1031
|
}
|
|
@@ -1064,7 +1059,7 @@ export const getAppTransactionIOS = async () => {
|
|
|
1064
1059
|
}
|
|
1065
1060
|
try {
|
|
1066
1061
|
// Call the native method to get app transaction
|
|
1067
|
-
const appTransaction = await
|
|
1062
|
+
const appTransaction = await getIap().getAppTransactionIOS();
|
|
1068
1063
|
return appTransaction;
|
|
1069
1064
|
} catch (error) {
|
|
1070
1065
|
console.error('Failed to get app transaction:', error);
|