expo-iap 2.4.4 → 2.4.5
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/README.md +52 -1
- package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +31 -6
- package/build/ExpoIap.types.d.ts +116 -2
- package/build/ExpoIap.types.d.ts.map +1 -1
- package/build/ExpoIap.types.js +142 -1
- package/build/ExpoIap.types.js.map +1 -1
- package/build/ExpoIapModule.d.ts +3 -2
- package/build/ExpoIapModule.d.ts.map +1 -1
- package/build/ExpoIapModule.js +4 -1
- package/build/ExpoIapModule.js.map +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/useIap.js.map +1 -1
- package/build/utils/errorMapping.d.ts +29 -0
- package/build/utils/errorMapping.d.ts.map +1 -0
- package/build/utils/errorMapping.js +79 -0
- package/build/utils/errorMapping.js.map +1 -0
- package/bun.lockb +0 -0
- package/docs/ERROR_CODES.md +172 -0
- package/{iap.md → docs/IAP.md} +191 -192
- package/docs/README.md +30 -0
- package/ios/ExpoIapModule.swift +68 -43
- package/ios/Types.swift +26 -18
- package/package.json +5 -2
- package/src/ExpoIap.types.ts +173 -0
- package/src/ExpoIapModule.ts +6 -1
- package/src/index.ts +1 -0
- package/src/useIap.ts +1 -1
- package/src/utils/errorMapping.ts +88 -0
package/ios/ExpoIapModule.swift
CHANGED
|
@@ -186,7 +186,32 @@ public class ExpoIapModule: Module {
|
|
|
186
186
|
Name("ExpoIap")
|
|
187
187
|
|
|
188
188
|
Constants([
|
|
189
|
-
"
|
|
189
|
+
"ERROR_CODES": [
|
|
190
|
+
"E_UNKNOWN": IapErrorCode.unknown,
|
|
191
|
+
"E_SERVICE_ERROR": IapErrorCode.serviceError,
|
|
192
|
+
"E_USER_CANCELLED": IapErrorCode.userCancelled,
|
|
193
|
+
"E_USER_ERROR": IapErrorCode.userError,
|
|
194
|
+
"E_ITEM_UNAVAILABLE": IapErrorCode.itemUnavailable,
|
|
195
|
+
"E_REMOTE_ERROR": IapErrorCode.remoteError,
|
|
196
|
+
"E_NETWORK_ERROR": IapErrorCode.networkError,
|
|
197
|
+
"E_RECEIPT_FAILED": IapErrorCode.receiptFailed,
|
|
198
|
+
"E_RECEIPT_FINISHED_FAILED": IapErrorCode.receiptFinishedFailed,
|
|
199
|
+
"E_NOT_PREPARED": IapErrorCode.notPrepared,
|
|
200
|
+
"E_NOT_ENDED": IapErrorCode.notEnded,
|
|
201
|
+
"E_ALREADY_OWNED": IapErrorCode.alreadyOwned,
|
|
202
|
+
"E_DEVELOPER_ERROR": IapErrorCode.developerError,
|
|
203
|
+
"E_PURCHASE_ERROR": IapErrorCode.purchaseError,
|
|
204
|
+
"E_SYNC_ERROR": IapErrorCode.syncError,
|
|
205
|
+
"E_DEFERRED_PAYMENT": IapErrorCode.deferredPayment,
|
|
206
|
+
"E_TRANSACTION_VALIDATION_FAILED": IapErrorCode.transactionValidationFailed,
|
|
207
|
+
"E_BILLING_RESPONSE_JSON_PARSE_ERROR": IapErrorCode.billingResponseJsonParseError,
|
|
208
|
+
"E_INTERRUPTED": IapErrorCode.interrupted,
|
|
209
|
+
"E_IAP_NOT_AVAILABLE": IapErrorCode.iapNotAvailable,
|
|
210
|
+
"E_ACTIVITY_UNAVAILABLE": IapErrorCode.activityUnavailable,
|
|
211
|
+
"E_ALREADY_PREPARED": IapErrorCode.alreadyPrepared,
|
|
212
|
+
"E_PENDING": IapErrorCode.pending,
|
|
213
|
+
"E_CONNECTION_CLOSED": IapErrorCode.connectionClosed,
|
|
214
|
+
]
|
|
190
215
|
])
|
|
191
216
|
|
|
192
217
|
Events(IapEvent.PurchaseUpdated, IapEvent.PurchaseError)
|
|
@@ -208,7 +233,7 @@ public class ExpoIapModule: Module {
|
|
|
208
233
|
|
|
209
234
|
AsyncFunction("getItems") { (skus: [String]) -> [[String: Any?]?] in
|
|
210
235
|
guard let productStore = self.productStore else {
|
|
211
|
-
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code:
|
|
236
|
+
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code: IapErrorCode.notPrepared)
|
|
212
237
|
}
|
|
213
238
|
|
|
214
239
|
do {
|
|
@@ -294,9 +319,9 @@ public class ExpoIapModule: Module {
|
|
|
294
319
|
}
|
|
295
320
|
} catch StoreError.failedVerification {
|
|
296
321
|
let err = [
|
|
297
|
-
"responseCode":
|
|
322
|
+
"responseCode": IapErrorCode.transactionValidationFailed,
|
|
298
323
|
"debugMessage": StoreError.failedVerification.localizedDescription,
|
|
299
|
-
"code":
|
|
324
|
+
"code": IapErrorCode.transactionValidationFailed,
|
|
300
325
|
"message": StoreError.failedVerification.localizedDescription,
|
|
301
326
|
"productId": "unknown",
|
|
302
327
|
]
|
|
@@ -305,9 +330,9 @@ public class ExpoIapModule: Module {
|
|
|
305
330
|
}
|
|
306
331
|
} catch {
|
|
307
332
|
let err = [
|
|
308
|
-
"responseCode":
|
|
333
|
+
"responseCode": IapErrorCode.unknown,
|
|
309
334
|
"debugMessage": error.localizedDescription,
|
|
310
|
-
"code":
|
|
335
|
+
"code": IapErrorCode.unknown,
|
|
311
336
|
"message": error.localizedDescription,
|
|
312
337
|
"productId": "unknown",
|
|
313
338
|
]
|
|
@@ -325,7 +350,7 @@ public class ExpoIapModule: Module {
|
|
|
325
350
|
appAccountToken: String?, quantity: Int, discountOffer: [String: String]?
|
|
326
351
|
) -> [String: Any?]? in
|
|
327
352
|
guard let productStore = self.productStore else {
|
|
328
|
-
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code:
|
|
353
|
+
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code: IapErrorCode.serviceError)
|
|
329
354
|
}
|
|
330
355
|
|
|
331
356
|
let product: Product? = await productStore.getProduct(productID: sku)
|
|
@@ -355,7 +380,7 @@ public class ExpoIapModule: Module {
|
|
|
355
380
|
options.insert(.appAccountToken(appAccountUUID))
|
|
356
381
|
}
|
|
357
382
|
guard let windowScene = await self.currentWindowScene() else {
|
|
358
|
-
throw Exception(name: "ExpoIapModule", description: "Could not find window scene", code:
|
|
383
|
+
throw Exception(name: "ExpoIapModule", description: "Could not find window scene", code: IapErrorCode.serviceError)
|
|
359
384
|
}
|
|
360
385
|
let result: Product.PurchaseResult
|
|
361
386
|
#if swift(>=5.9)
|
|
@@ -398,20 +423,20 @@ public class ExpoIapModule: Module {
|
|
|
398
423
|
return serialized
|
|
399
424
|
}
|
|
400
425
|
case .userCancelled:
|
|
401
|
-
throw Exception(name: "ExpoIapModule", description: "User cancelled the purchase", code:
|
|
426
|
+
throw Exception(name: "ExpoIapModule", description: "User cancelled the purchase", code: IapErrorCode.userCancelled)
|
|
402
427
|
case .pending:
|
|
403
|
-
throw Exception(name: "ExpoIapModule", description: "The payment was deferred", code:
|
|
428
|
+
throw Exception(name: "ExpoIapModule", description: "The payment was deferred", code: IapErrorCode.deferredPayment)
|
|
404
429
|
@unknown default:
|
|
405
|
-
throw Exception(name: "ExpoIapModule", description: "Unknown purchase result", code:
|
|
430
|
+
throw Exception(name: "ExpoIapModule", description: "Unknown purchase result", code: IapErrorCode.unknown)
|
|
406
431
|
}
|
|
407
432
|
} catch {
|
|
408
433
|
if error is Exception {
|
|
409
434
|
throw error
|
|
410
435
|
}
|
|
411
|
-
throw Exception(name: "ExpoIapModule", description: "Purchase failed: \(error.localizedDescription)", code:
|
|
436
|
+
throw Exception(name: "ExpoIapModule", description: "Purchase failed: \(error.localizedDescription)", code: IapErrorCode.purchaseError)
|
|
412
437
|
}
|
|
413
438
|
} else {
|
|
414
|
-
throw Exception(name: "ExpoIapModule", description: "Invalid product ID", code:
|
|
439
|
+
throw Exception(name: "ExpoIapModule", description: "Invalid product ID", code: IapErrorCode.itemUnavailable)
|
|
415
440
|
}
|
|
416
441
|
}
|
|
417
442
|
|
|
@@ -421,7 +446,7 @@ public class ExpoIapModule: Module {
|
|
|
421
446
|
|
|
422
447
|
AsyncFunction("subscriptionStatus") { (sku: String) -> [[String: Any?]?]? in
|
|
423
448
|
guard let productStore = self.productStore else {
|
|
424
|
-
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code:
|
|
449
|
+
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code: IapErrorCode.serviceError)
|
|
425
450
|
}
|
|
426
451
|
|
|
427
452
|
do {
|
|
@@ -436,13 +461,13 @@ public class ExpoIapModule: Module {
|
|
|
436
461
|
if error is Exception {
|
|
437
462
|
throw error
|
|
438
463
|
}
|
|
439
|
-
throw Exception(name: "ExpoIapModule", description: "Error getting subscription status: \(error.localizedDescription)", code:
|
|
464
|
+
throw Exception(name: "ExpoIapModule", description: "Error getting subscription status: \(error.localizedDescription)", code: IapErrorCode.serviceError)
|
|
440
465
|
}
|
|
441
466
|
}
|
|
442
467
|
|
|
443
468
|
AsyncFunction("currentEntitlement") { (sku: String) -> [String: Any?]? in
|
|
444
469
|
guard let productStore = self.productStore else {
|
|
445
|
-
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code:
|
|
470
|
+
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code: IapErrorCode.serviceError)
|
|
446
471
|
}
|
|
447
472
|
|
|
448
473
|
if let product = await productStore.getProduct(productID: sku) {
|
|
@@ -451,24 +476,24 @@ public class ExpoIapModule: Module {
|
|
|
451
476
|
let transaction = try self.checkVerified(result)
|
|
452
477
|
return serializeTransaction(transaction)
|
|
453
478
|
} catch StoreError.failedVerification {
|
|
454
|
-
throw Exception(name: "ExpoIapModule", description: "Failed to verify transaction for sku \(sku)", code:
|
|
479
|
+
throw Exception(name: "ExpoIapModule", description: "Failed to verify transaction for sku \(sku)", code: IapErrorCode.transactionValidationFailed)
|
|
455
480
|
} catch {
|
|
456
481
|
if error is Exception {
|
|
457
482
|
throw error
|
|
458
483
|
}
|
|
459
|
-
throw Exception(name: "ExpoIapModule", description: "Error fetching entitlement for sku \(sku): \(error.localizedDescription)", code:
|
|
484
|
+
throw Exception(name: "ExpoIapModule", description: "Error fetching entitlement for sku \(sku): \(error.localizedDescription)", code: IapErrorCode.serviceError)
|
|
460
485
|
}
|
|
461
486
|
} else {
|
|
462
|
-
throw Exception(name: "ExpoIapModule", description: "Can't find entitlement for sku \(sku)", code:
|
|
487
|
+
throw Exception(name: "ExpoIapModule", description: "Can't find entitlement for sku \(sku)", code: IapErrorCode.itemUnavailable)
|
|
463
488
|
}
|
|
464
489
|
} else {
|
|
465
|
-
throw Exception(name: "ExpoIapModule", description: "Can't find product for sku \(sku)", code:
|
|
490
|
+
throw Exception(name: "ExpoIapModule", description: "Can't find product for sku \(sku)", code: IapErrorCode.itemUnavailable)
|
|
466
491
|
}
|
|
467
492
|
}
|
|
468
493
|
|
|
469
494
|
AsyncFunction("latestTransaction") { (sku: String) -> [String: Any?]? in
|
|
470
495
|
guard let productStore = self.productStore else {
|
|
471
|
-
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code:
|
|
496
|
+
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code: IapErrorCode.serviceError)
|
|
472
497
|
}
|
|
473
498
|
|
|
474
499
|
if let product = await productStore.getProduct(productID: sku) {
|
|
@@ -477,18 +502,18 @@ public class ExpoIapModule: Module {
|
|
|
477
502
|
let transaction = try self.checkVerified(result)
|
|
478
503
|
return serializeTransaction(transaction)
|
|
479
504
|
} catch StoreError.failedVerification {
|
|
480
|
-
throw Exception(name: "ExpoIapModule", description: "Failed to verify transaction for sku \(sku)", code:
|
|
505
|
+
throw Exception(name: "ExpoIapModule", description: "Failed to verify transaction for sku \(sku)", code: IapErrorCode.transactionValidationFailed)
|
|
481
506
|
} catch {
|
|
482
507
|
if error is Exception {
|
|
483
508
|
throw error
|
|
484
509
|
}
|
|
485
|
-
throw Exception(name: "ExpoIapModule", description: "Error fetching latest transaction for sku \(sku): \(error.localizedDescription)", code:
|
|
510
|
+
throw Exception(name: "ExpoIapModule", description: "Error fetching latest transaction for sku \(sku): \(error.localizedDescription)", code: IapErrorCode.serviceError)
|
|
486
511
|
}
|
|
487
512
|
} else {
|
|
488
|
-
throw Exception(name: "ExpoIapModule", description: "Can't find latest transaction for sku \(sku)", code:
|
|
513
|
+
throw Exception(name: "ExpoIapModule", description: "Can't find latest transaction for sku \(sku)", code: IapErrorCode.itemUnavailable)
|
|
489
514
|
}
|
|
490
515
|
} else {
|
|
491
|
-
throw Exception(name: "ExpoIapModule", description: "Can't find product for sku \(sku)", code:
|
|
516
|
+
throw Exception(name: "ExpoIapModule", description: "Can't find product for sku \(sku)", code: IapErrorCode.itemUnavailable)
|
|
492
517
|
}
|
|
493
518
|
}
|
|
494
519
|
|
|
@@ -498,7 +523,7 @@ public class ExpoIapModule: Module {
|
|
|
498
523
|
self.transactions.removeValue(forKey: transactionIdentifier)
|
|
499
524
|
return true
|
|
500
525
|
} else {
|
|
501
|
-
throw Exception(name: "ExpoIapModule", description: "Invalid transaction ID", code:
|
|
526
|
+
throw Exception(name: "ExpoIapModule", description: "Invalid transaction ID", code: IapErrorCode.developerError)
|
|
502
527
|
}
|
|
503
528
|
}
|
|
504
529
|
|
|
@@ -514,7 +539,7 @@ public class ExpoIapModule: Module {
|
|
|
514
539
|
if error is Exception {
|
|
515
540
|
throw error
|
|
516
541
|
}
|
|
517
|
-
throw Exception(name: "ExpoIapModule", description: "Error synchronizing with the AppStore: \(error.localizedDescription)", code:
|
|
542
|
+
throw Exception(name: "ExpoIapModule", description: "Error synchronizing with the AppStore: \(error.localizedDescription)", code: IapErrorCode.syncError)
|
|
518
543
|
}
|
|
519
544
|
}
|
|
520
545
|
|
|
@@ -523,14 +548,14 @@ public class ExpoIapModule: Module {
|
|
|
523
548
|
SKPaymentQueue.default().presentCodeRedemptionSheet()
|
|
524
549
|
return true
|
|
525
550
|
#else
|
|
526
|
-
throw Exception(name: "ExpoIapModule", description: "This method is not available on tvOS", code:
|
|
551
|
+
throw Exception(name: "ExpoIapModule", description: "This method is not available on tvOS", code: IapErrorCode.serviceError)
|
|
527
552
|
#endif
|
|
528
553
|
}
|
|
529
554
|
|
|
530
555
|
AsyncFunction("showManageSubscriptions") { () -> Bool in
|
|
531
556
|
#if !os(tvOS)
|
|
532
557
|
guard let windowScene = await self.currentWindowScene() else {
|
|
533
|
-
throw Exception(name: "ExpoIapModule", description: "Cannot find window scene or not available on macOS", code:
|
|
558
|
+
throw Exception(name: "ExpoIapModule", description: "Cannot find window scene or not available on macOS", code: IapErrorCode.serviceError)
|
|
534
559
|
}
|
|
535
560
|
// Get all subscription products before showing the management UI
|
|
536
561
|
let subscriptionSkus = await self.getAllSubscriptionProductIds()
|
|
@@ -541,7 +566,7 @@ public class ExpoIapModule: Module {
|
|
|
541
566
|
self.pollForSubscriptionStatusChanges()
|
|
542
567
|
return true
|
|
543
568
|
#else
|
|
544
|
-
throw Exception(name: "ExpoIapModule", description: "This method is not available on tvOS", code:
|
|
569
|
+
throw Exception(name: "ExpoIapModule", description: "This method is not available on tvOS", code: IapErrorCode.serviceError)
|
|
545
570
|
#endif
|
|
546
571
|
}
|
|
547
572
|
|
|
@@ -567,26 +592,26 @@ public class ExpoIapModule: Module {
|
|
|
567
592
|
guard let product = await self.productStore?.getProduct(productID: sku),
|
|
568
593
|
let result = await product.latestTransaction
|
|
569
594
|
else {
|
|
570
|
-
throw Exception(name: "ExpoIapModule", description: "Can't find product or transaction for sku \(sku)", code:
|
|
595
|
+
throw Exception(name: "ExpoIapModule", description: "Can't find product or transaction for sku \(sku)", code: IapErrorCode.itemUnavailable)
|
|
571
596
|
}
|
|
572
597
|
|
|
573
598
|
do {
|
|
574
599
|
let transaction = try self.checkVerified(result)
|
|
575
600
|
guard let windowScene = await self.currentWindowScene() else {
|
|
576
|
-
throw Exception(name: "ExpoIapModule", description: "Cannot find window scene or not available on macOS", code:
|
|
601
|
+
throw Exception(name: "ExpoIapModule", description: "Cannot find window scene or not available on macOS", code: IapErrorCode.serviceError)
|
|
577
602
|
}
|
|
578
603
|
let refundStatus = try await transaction.beginRefundRequest(in: windowScene)
|
|
579
604
|
return serialize(refundStatus)
|
|
580
605
|
} catch StoreError.failedVerification {
|
|
581
|
-
throw Exception(name: "ExpoIapModule", description: "Failed to verify transaction for sku \(sku)", code:
|
|
606
|
+
throw Exception(name: "ExpoIapModule", description: "Failed to verify transaction for sku \(sku)", code: IapErrorCode.transactionValidationFailed)
|
|
582
607
|
} catch {
|
|
583
608
|
if error is Exception {
|
|
584
609
|
throw error
|
|
585
610
|
}
|
|
586
|
-
throw Exception(name: "ExpoIapModule", description: "Failed to refund purchase: \(error.localizedDescription)", code:
|
|
611
|
+
throw Exception(name: "ExpoIapModule", description: "Failed to refund purchase: \(error.localizedDescription)", code: IapErrorCode.serviceError)
|
|
587
612
|
}
|
|
588
613
|
#else
|
|
589
|
-
throw Exception(name: "ExpoIapModule", description: "This method is not available on tvOS", code:
|
|
614
|
+
throw Exception(name: "ExpoIapModule", description: "This method is not available on tvOS", code: IapErrorCode.serviceError)
|
|
590
615
|
#endif
|
|
591
616
|
}
|
|
592
617
|
|
|
@@ -601,7 +626,7 @@ public class ExpoIapModule: Module {
|
|
|
601
626
|
|
|
602
627
|
AsyncFunction("isTransactionVerified") { (sku: String) -> Bool in
|
|
603
628
|
guard let productStore = self.productStore else {
|
|
604
|
-
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code:
|
|
629
|
+
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code: IapErrorCode.serviceError)
|
|
605
630
|
}
|
|
606
631
|
|
|
607
632
|
if let product = await productStore.getProduct(productID: sku),
|
|
@@ -619,20 +644,20 @@ public class ExpoIapModule: Module {
|
|
|
619
644
|
|
|
620
645
|
AsyncFunction("getTransactionJws") { (sku: String) -> String? in
|
|
621
646
|
guard let productStore = self.productStore else {
|
|
622
|
-
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code:
|
|
647
|
+
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code: IapErrorCode.serviceError)
|
|
623
648
|
}
|
|
624
649
|
|
|
625
650
|
if let product = await productStore.getProduct(productID: sku),
|
|
626
651
|
let result = await product.latestTransaction {
|
|
627
652
|
return result.jwsRepresentation
|
|
628
653
|
} else {
|
|
629
|
-
throw Exception(name: "ExpoIapModule", description: "Can't find transaction for sku \(sku)", code:
|
|
654
|
+
throw Exception(name: "ExpoIapModule", description: "Can't find transaction for sku \(sku)", code: IapErrorCode.itemUnavailable)
|
|
630
655
|
}
|
|
631
656
|
}
|
|
632
657
|
|
|
633
658
|
AsyncFunction("validateReceiptIos") { (sku: String) -> [String: Any] in
|
|
634
659
|
guard let productStore = self.productStore else {
|
|
635
|
-
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code:
|
|
660
|
+
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code: IapErrorCode.serviceError)
|
|
636
661
|
}
|
|
637
662
|
|
|
638
663
|
// Get receipt data
|
|
@@ -697,9 +722,9 @@ public class ExpoIapModule: Module {
|
|
|
697
722
|
} catch {
|
|
698
723
|
if self.hasListeners {
|
|
699
724
|
let err = [
|
|
700
|
-
"responseCode":
|
|
725
|
+
"responseCode": IapErrorCode.transactionValidationFailed,
|
|
701
726
|
"debugMessage": error.localizedDescription,
|
|
702
|
-
"code":
|
|
727
|
+
"code": IapErrorCode.transactionValidationFailed,
|
|
703
728
|
"message": error.localizedDescription,
|
|
704
729
|
]
|
|
705
730
|
self.sendEvent(IapEvent.PurchaseError, err)
|
|
@@ -817,10 +842,10 @@ public class ExpoIapModule: Module {
|
|
|
817
842
|
let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
|
|
818
843
|
return receiptData.base64EncodedString(options: [])
|
|
819
844
|
} catch {
|
|
820
|
-
throw Exception(name: "ExpoIapModule", description: "Error reading receipt data: \(error.localizedDescription)", code:
|
|
845
|
+
throw Exception(name: "ExpoIapModule", description: "Error reading receipt data: \(error.localizedDescription)", code: IapErrorCode.receiptFailed)
|
|
821
846
|
}
|
|
822
847
|
} else {
|
|
823
|
-
throw Exception(name: "ExpoIapModule", description: "App Store receipt not found", code:
|
|
848
|
+
throw Exception(name: "ExpoIapModule", description: "App Store receipt not found", code: IapErrorCode.receiptFailed)
|
|
824
849
|
}
|
|
825
850
|
}
|
|
826
851
|
}
|
package/ios/Types.swift
CHANGED
|
@@ -12,24 +12,32 @@ public enum StoreError: Error {
|
|
|
12
12
|
case failedVerification
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
15
|
+
// Error codes for IAP operations - centralized error code management
|
|
16
|
+
struct IapErrorCode {
|
|
17
|
+
static let unknown = "E_UNKNOWN"
|
|
18
|
+
static let serviceError = "E_SERVICE_ERROR"
|
|
19
|
+
static let userCancelled = "E_USER_CANCELLED"
|
|
20
|
+
static let userError = "E_USER_ERROR"
|
|
21
|
+
static let itemUnavailable = "E_ITEM_UNAVAILABLE"
|
|
22
|
+
static let remoteError = "E_REMOTE_ERROR"
|
|
23
|
+
static let networkError = "E_NETWORK_ERROR"
|
|
24
|
+
static let receiptFailed = "E_RECEIPT_FAILED"
|
|
25
|
+
static let receiptFinishedFailed = "E_RECEIPT_FINISHED_FAILED"
|
|
26
|
+
static let notPrepared = "E_NOT_PREPARED"
|
|
27
|
+
static let notEnded = "E_NOT_ENDED"
|
|
28
|
+
static let alreadyOwned = "E_ALREADY_OWNED"
|
|
29
|
+
static let developerError = "E_DEVELOPER_ERROR"
|
|
30
|
+
static let purchaseError = "E_PURCHASE_ERROR"
|
|
31
|
+
static let syncError = "E_SYNC_ERROR"
|
|
32
|
+
static let deferredPayment = "E_DEFERRED_PAYMENT"
|
|
33
|
+
static let transactionValidationFailed = "E_TRANSACTION_VALIDATION_FAILED"
|
|
34
|
+
static let billingResponseJsonParseError = "E_BILLING_RESPONSE_JSON_PARSE_ERROR"
|
|
35
|
+
static let interrupted = "E_INTERRUPTED"
|
|
36
|
+
static let iapNotAvailable = "E_IAP_NOT_AVAILABLE"
|
|
37
|
+
static let activityUnavailable = "E_ACTIVITY_UNAVAILABLE"
|
|
38
|
+
static let alreadyPrepared = "E_ALREADY_PREPARED"
|
|
39
|
+
static let pending = "E_PENDING"
|
|
40
|
+
static let connectionClosed = "E_CONNECTION_CLOSED"
|
|
33
41
|
}
|
|
34
42
|
|
|
35
43
|
// Based on https://stackoverflow.com/a/40135192/570612
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-iap",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.5",
|
|
4
4
|
"description": "In App Purchase module in Expo",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -43,11 +43,14 @@
|
|
|
43
43
|
"eslint-config-prettier": "^10.1.5",
|
|
44
44
|
"eslint-plugin-prettier": "^5.4.1",
|
|
45
45
|
"expo-module-scripts": "^4.1.7",
|
|
46
|
-
"expo-modules-core": "
|
|
46
|
+
"expo-modules-core": "^2.4.0"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"expo": "*",
|
|
50
50
|
"react": "*",
|
|
51
51
|
"react-native": "*"
|
|
52
|
+
},
|
|
53
|
+
"expo": {
|
|
54
|
+
"plugin": "./app.plugin.js"
|
|
52
55
|
}
|
|
53
56
|
}
|
package/src/ExpoIap.types.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
RequestSubscriptionIosProps,
|
|
13
13
|
SubscriptionProductIos,
|
|
14
14
|
} from './types/ExpoIapIos.types';
|
|
15
|
+
import {NATIVE_ERROR_CODES} from './ExpoIapModule';
|
|
15
16
|
|
|
16
17
|
export type ChangeEventPayload = {
|
|
17
18
|
value: string;
|
|
@@ -81,6 +82,10 @@ export type PurchaseResult = {
|
|
|
81
82
|
purchaseTokenAndroid?: string;
|
|
82
83
|
};
|
|
83
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Centralized error codes for expo-iap
|
|
87
|
+
* These are mapped to platform-specific error codes and provide consistent error handling
|
|
88
|
+
*/
|
|
84
89
|
export enum ErrorCode {
|
|
85
90
|
E_UNKNOWN = 'E_UNKNOWN',
|
|
86
91
|
E_USER_CANCELLED = 'E_USER_CANCELLED',
|
|
@@ -99,8 +104,76 @@ export enum ErrorCode {
|
|
|
99
104
|
E_DEFERRED_PAYMENT = 'E_DEFERRED_PAYMENT',
|
|
100
105
|
E_INTERRUPTED = 'E_INTERRUPTED',
|
|
101
106
|
E_IAP_NOT_AVAILABLE = 'E_IAP_NOT_AVAILABLE',
|
|
107
|
+
E_PURCHASE_ERROR = 'E_PURCHASE_ERROR',
|
|
108
|
+
E_SYNC_ERROR = 'E_SYNC_ERROR',
|
|
109
|
+
E_TRANSACTION_VALIDATION_FAILED = 'E_TRANSACTION_VALIDATION_FAILED',
|
|
110
|
+
E_ACTIVITY_UNAVAILABLE = 'E_ACTIVITY_UNAVAILABLE',
|
|
111
|
+
E_ALREADY_PREPARED = 'E_ALREADY_PREPARED',
|
|
112
|
+
E_PENDING = 'E_PENDING',
|
|
113
|
+
E_CONNECTION_CLOSED = 'E_CONNECTION_CLOSED',
|
|
102
114
|
}
|
|
103
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Platform-specific error code mappings
|
|
118
|
+
* Maps ErrorCode enum values to platform-specific integer codes
|
|
119
|
+
*/
|
|
120
|
+
export const ErrorCodeMapping = {
|
|
121
|
+
ios: {
|
|
122
|
+
[ErrorCode.E_UNKNOWN]: 0,
|
|
123
|
+
[ErrorCode.E_SERVICE_ERROR]: 1,
|
|
124
|
+
[ErrorCode.E_USER_CANCELLED]: 2,
|
|
125
|
+
[ErrorCode.E_USER_ERROR]: 3,
|
|
126
|
+
[ErrorCode.E_ITEM_UNAVAILABLE]: 4,
|
|
127
|
+
[ErrorCode.E_REMOTE_ERROR]: 5,
|
|
128
|
+
[ErrorCode.E_NETWORK_ERROR]: 6,
|
|
129
|
+
[ErrorCode.E_RECEIPT_FAILED]: 7,
|
|
130
|
+
[ErrorCode.E_RECEIPT_FINISHED_FAILED]: 8,
|
|
131
|
+
[ErrorCode.E_DEVELOPER_ERROR]: 9,
|
|
132
|
+
[ErrorCode.E_PURCHASE_ERROR]: 10,
|
|
133
|
+
[ErrorCode.E_SYNC_ERROR]: 11,
|
|
134
|
+
[ErrorCode.E_DEFERRED_PAYMENT]: 12,
|
|
135
|
+
[ErrorCode.E_TRANSACTION_VALIDATION_FAILED]: 13,
|
|
136
|
+
[ErrorCode.E_NOT_PREPARED]: 14,
|
|
137
|
+
[ErrorCode.E_NOT_ENDED]: 15,
|
|
138
|
+
[ErrorCode.E_ALREADY_OWNED]: 16,
|
|
139
|
+
[ErrorCode.E_BILLING_RESPONSE_JSON_PARSE_ERROR]: 17,
|
|
140
|
+
[ErrorCode.E_INTERRUPTED]: 18,
|
|
141
|
+
[ErrorCode.E_IAP_NOT_AVAILABLE]: 19,
|
|
142
|
+
[ErrorCode.E_ACTIVITY_UNAVAILABLE]: 20,
|
|
143
|
+
[ErrorCode.E_ALREADY_PREPARED]: 21,
|
|
144
|
+
[ErrorCode.E_PENDING]: 22,
|
|
145
|
+
[ErrorCode.E_CONNECTION_CLOSED]: 23,
|
|
146
|
+
},
|
|
147
|
+
android: {
|
|
148
|
+
[ErrorCode.E_UNKNOWN]: 'E_UNKNOWN',
|
|
149
|
+
[ErrorCode.E_USER_CANCELLED]: 'E_USER_CANCELLED',
|
|
150
|
+
[ErrorCode.E_USER_ERROR]: 'E_USER_ERROR',
|
|
151
|
+
[ErrorCode.E_ITEM_UNAVAILABLE]: 'E_ITEM_UNAVAILABLE',
|
|
152
|
+
[ErrorCode.E_REMOTE_ERROR]: 'E_REMOTE_ERROR',
|
|
153
|
+
[ErrorCode.E_NETWORK_ERROR]: 'E_NETWORK_ERROR',
|
|
154
|
+
[ErrorCode.E_SERVICE_ERROR]: 'E_SERVICE_ERROR',
|
|
155
|
+
[ErrorCode.E_RECEIPT_FAILED]: 'E_RECEIPT_FAILED',
|
|
156
|
+
[ErrorCode.E_RECEIPT_FINISHED_FAILED]: 'E_RECEIPT_FINISHED_FAILED',
|
|
157
|
+
[ErrorCode.E_NOT_PREPARED]: 'E_NOT_PREPARED',
|
|
158
|
+
[ErrorCode.E_NOT_ENDED]: 'E_NOT_ENDED',
|
|
159
|
+
[ErrorCode.E_ALREADY_OWNED]: 'E_ALREADY_OWNED',
|
|
160
|
+
[ErrorCode.E_DEVELOPER_ERROR]: 'E_DEVELOPER_ERROR',
|
|
161
|
+
[ErrorCode.E_BILLING_RESPONSE_JSON_PARSE_ERROR]:
|
|
162
|
+
'E_BILLING_RESPONSE_JSON_PARSE_ERROR',
|
|
163
|
+
[ErrorCode.E_DEFERRED_PAYMENT]: 'E_DEFERRED_PAYMENT',
|
|
164
|
+
[ErrorCode.E_INTERRUPTED]: 'E_INTERRUPTED',
|
|
165
|
+
[ErrorCode.E_IAP_NOT_AVAILABLE]: 'E_IAP_NOT_AVAILABLE',
|
|
166
|
+
[ErrorCode.E_PURCHASE_ERROR]: 'E_PURCHASE_ERROR',
|
|
167
|
+
[ErrorCode.E_SYNC_ERROR]: 'E_SYNC_ERROR',
|
|
168
|
+
[ErrorCode.E_TRANSACTION_VALIDATION_FAILED]:
|
|
169
|
+
'E_TRANSACTION_VALIDATION_FAILED',
|
|
170
|
+
[ErrorCode.E_ACTIVITY_UNAVAILABLE]: 'E_ACTIVITY_UNAVAILABLE',
|
|
171
|
+
[ErrorCode.E_ALREADY_PREPARED]: 'E_ALREADY_PREPARED',
|
|
172
|
+
[ErrorCode.E_PENDING]: 'E_PENDING',
|
|
173
|
+
[ErrorCode.E_CONNECTION_CLOSED]: 'E_CONNECTION_CLOSED',
|
|
174
|
+
},
|
|
175
|
+
} as const;
|
|
176
|
+
|
|
104
177
|
export class PurchaseError implements Error {
|
|
105
178
|
constructor(
|
|
106
179
|
public name: string,
|
|
@@ -109,6 +182,7 @@ export class PurchaseError implements Error {
|
|
|
109
182
|
public debugMessage?: string,
|
|
110
183
|
public code?: ErrorCode,
|
|
111
184
|
public productId?: string,
|
|
185
|
+
public platform?: 'ios' | 'android',
|
|
112
186
|
) {
|
|
113
187
|
this.name = '[expo-iap]: PurchaseError';
|
|
114
188
|
this.message = message;
|
|
@@ -116,5 +190,104 @@ export class PurchaseError implements Error {
|
|
|
116
190
|
this.debugMessage = debugMessage;
|
|
117
191
|
this.code = code;
|
|
118
192
|
this.productId = productId;
|
|
193
|
+
this.platform = platform;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Creates a PurchaseError from platform-specific error data
|
|
198
|
+
* @param errorData Raw error data from native modules
|
|
199
|
+
* @param platform Platform where the error occurred
|
|
200
|
+
* @returns Properly typed PurchaseError instance
|
|
201
|
+
*/
|
|
202
|
+
static fromPlatformError(
|
|
203
|
+
errorData: any,
|
|
204
|
+
platform: 'ios' | 'android',
|
|
205
|
+
): PurchaseError {
|
|
206
|
+
const errorCode = errorData.code
|
|
207
|
+
? ErrorCodeUtils.fromPlatformCode(errorData.code, platform)
|
|
208
|
+
: ErrorCode.E_UNKNOWN;
|
|
209
|
+
|
|
210
|
+
return new PurchaseError(
|
|
211
|
+
'[expo-iap]: PurchaseError',
|
|
212
|
+
errorData.message || 'Unknown error occurred',
|
|
213
|
+
errorData.responseCode,
|
|
214
|
+
errorData.debugMessage,
|
|
215
|
+
errorCode,
|
|
216
|
+
errorData.productId,
|
|
217
|
+
platform,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Gets the platform-specific error code for this error
|
|
223
|
+
* @returns Platform-specific error code
|
|
224
|
+
*/
|
|
225
|
+
getPlatformCode(): string | number | undefined {
|
|
226
|
+
if (!this.code || !this.platform) return undefined;
|
|
227
|
+
return ErrorCodeUtils.toPlatformCode(this.code, this.platform);
|
|
119
228
|
}
|
|
120
229
|
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Utility functions for error code mapping and validation
|
|
233
|
+
*/
|
|
234
|
+
export const ErrorCodeUtils = {
|
|
235
|
+
/**
|
|
236
|
+
* Gets the native error code for the current platform
|
|
237
|
+
* @param errorCode ErrorCode enum value
|
|
238
|
+
* @returns Platform-specific error code from native constants
|
|
239
|
+
*/
|
|
240
|
+
getNativeErrorCode: (errorCode: ErrorCode): string => {
|
|
241
|
+
return NATIVE_ERROR_CODES[errorCode] || errorCode;
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Maps a platform-specific error code back to the standardized ErrorCode enum
|
|
246
|
+
* @param platformCode Platform-specific error code (string for Android, number for iOS)
|
|
247
|
+
* @param platform Target platform
|
|
248
|
+
* @returns Corresponding ErrorCode enum value or E_UNKNOWN if not found
|
|
249
|
+
*/
|
|
250
|
+
fromPlatformCode: (
|
|
251
|
+
platformCode: string | number,
|
|
252
|
+
platform: 'ios' | 'android',
|
|
253
|
+
): ErrorCode => {
|
|
254
|
+
const mapping = ErrorCodeMapping[platform];
|
|
255
|
+
|
|
256
|
+
for (const [errorCode, mappedCode] of Object.entries(mapping)) {
|
|
257
|
+
if (mappedCode === platformCode) {
|
|
258
|
+
return errorCode as ErrorCode;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return ErrorCode.E_UNKNOWN;
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Maps an ErrorCode enum to platform-specific code
|
|
267
|
+
* @param errorCode ErrorCode enum value
|
|
268
|
+
* @param platform Target platform
|
|
269
|
+
* @returns Platform-specific error code
|
|
270
|
+
*/
|
|
271
|
+
toPlatformCode: (
|
|
272
|
+
errorCode: ErrorCode,
|
|
273
|
+
platform: 'ios' | 'android',
|
|
274
|
+
): string | number => {
|
|
275
|
+
return (
|
|
276
|
+
ErrorCodeMapping[platform][errorCode] ??
|
|
277
|
+
(platform === 'ios' ? 0 : 'E_UNKNOWN')
|
|
278
|
+
);
|
|
279
|
+
},
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Checks if an error code is valid for the specified platform
|
|
283
|
+
* @param errorCode ErrorCode enum value
|
|
284
|
+
* @param platform Target platform
|
|
285
|
+
* @returns True if the error code is supported on the platform
|
|
286
|
+
*/
|
|
287
|
+
isValidForPlatform: (
|
|
288
|
+
errorCode: ErrorCode,
|
|
289
|
+
platform: 'ios' | 'android',
|
|
290
|
+
): boolean => {
|
|
291
|
+
return errorCode in ErrorCodeMapping[platform];
|
|
292
|
+
},
|
|
293
|
+
};
|
package/src/ExpoIapModule.ts
CHANGED
|
@@ -2,4 +2,9 @@ import {requireNativeModule} from 'expo-modules-core';
|
|
|
2
2
|
|
|
3
3
|
// It loads the native module object from the JSI or falls back to
|
|
4
4
|
// the bridge module (from NativeModulesProxy) if the remote debugger is on.
|
|
5
|
-
|
|
5
|
+
const ExpoIapModule = requireNativeModule('ExpoIap');
|
|
6
|
+
|
|
7
|
+
// Platform-specific error codes from native modules
|
|
8
|
+
export const NATIVE_ERROR_CODES = ExpoIapModule.ERROR_CODES || {};
|
|
9
|
+
|
|
10
|
+
export default ExpoIapModule;
|
package/src/index.ts
CHANGED
package/src/useIap.ts
CHANGED
|
@@ -23,8 +23,8 @@ import {
|
|
|
23
23
|
SubscriptionProduct,
|
|
24
24
|
SubscriptionPurchase,
|
|
25
25
|
} from './ExpoIap.types';
|
|
26
|
-
import {EventSubscription} from 'expo-modules-core';
|
|
27
26
|
import {Platform} from 'react-native';
|
|
27
|
+
import {EventSubscription} from 'expo-modules-core';
|
|
28
28
|
|
|
29
29
|
type UseIap = {
|
|
30
30
|
connected: boolean;
|