expo-iap 2.4.3 → 2.4.5-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/README.md +51 -1
- package/android/src/main/AndroidManifest.xml +2 -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 +164 -0
- package/{iap.md → docs/IAP.md} +191 -202
- package/docs/README.md +30 -0
- package/ios/ExpoIapModule.swift +69 -45
- package/ios/Types.swift +26 -18
- package/package.json +2 -2
- package/plugin/tsconfig.tsbuildinfo +1 -1
- 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
|
@@ -123,8 +123,7 @@ func serializeProduct(_ p: Product) -> [String: Any?] {
|
|
|
123
123
|
"id": p.id,
|
|
124
124
|
"title": p.displayName,
|
|
125
125
|
"isFamilyShareable": p.isFamilyShareable,
|
|
126
|
-
"jsonRepresentation":
|
|
127
|
-
String(data: p.jsonRepresentation, encoding: .utf8) ?? ""),
|
|
126
|
+
"jsonRepresentation": String(data: p.jsonRepresentation, encoding: .utf8),
|
|
128
127
|
"price": p.price,
|
|
129
128
|
"subscription": p.subscription,
|
|
130
129
|
"type": p.type,
|
|
@@ -187,7 +186,32 @@ public class ExpoIapModule: Module {
|
|
|
187
186
|
Name("ExpoIap")
|
|
188
187
|
|
|
189
188
|
Constants([
|
|
190
|
-
"
|
|
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
|
+
]
|
|
191
215
|
])
|
|
192
216
|
|
|
193
217
|
Events(IapEvent.PurchaseUpdated, IapEvent.PurchaseError)
|
|
@@ -209,7 +233,7 @@ public class ExpoIapModule: Module {
|
|
|
209
233
|
|
|
210
234
|
AsyncFunction("getItems") { (skus: [String]) -> [[String: Any?]?] in
|
|
211
235
|
guard let productStore = self.productStore else {
|
|
212
|
-
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code:
|
|
236
|
+
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code: IapErrorCode.notPrepared)
|
|
213
237
|
}
|
|
214
238
|
|
|
215
239
|
do {
|
|
@@ -295,9 +319,9 @@ public class ExpoIapModule: Module {
|
|
|
295
319
|
}
|
|
296
320
|
} catch StoreError.failedVerification {
|
|
297
321
|
let err = [
|
|
298
|
-
"responseCode":
|
|
322
|
+
"responseCode": IapErrorCode.transactionValidationFailed,
|
|
299
323
|
"debugMessage": StoreError.failedVerification.localizedDescription,
|
|
300
|
-
"code":
|
|
324
|
+
"code": IapErrorCode.transactionValidationFailed,
|
|
301
325
|
"message": StoreError.failedVerification.localizedDescription,
|
|
302
326
|
"productId": "unknown",
|
|
303
327
|
]
|
|
@@ -306,9 +330,9 @@ public class ExpoIapModule: Module {
|
|
|
306
330
|
}
|
|
307
331
|
} catch {
|
|
308
332
|
let err = [
|
|
309
|
-
"responseCode":
|
|
333
|
+
"responseCode": IapErrorCode.unknown,
|
|
310
334
|
"debugMessage": error.localizedDescription,
|
|
311
|
-
"code":
|
|
335
|
+
"code": IapErrorCode.unknown,
|
|
312
336
|
"message": error.localizedDescription,
|
|
313
337
|
"productId": "unknown",
|
|
314
338
|
]
|
|
@@ -326,7 +350,7 @@ public class ExpoIapModule: Module {
|
|
|
326
350
|
appAccountToken: String?, quantity: Int, discountOffer: [String: String]?
|
|
327
351
|
) -> [String: Any?]? in
|
|
328
352
|
guard let productStore = self.productStore else {
|
|
329
|
-
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code:
|
|
353
|
+
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code: IapErrorCode.serviceError)
|
|
330
354
|
}
|
|
331
355
|
|
|
332
356
|
let product: Product? = await productStore.getProduct(productID: sku)
|
|
@@ -356,7 +380,7 @@ public class ExpoIapModule: Module {
|
|
|
356
380
|
options.insert(.appAccountToken(appAccountUUID))
|
|
357
381
|
}
|
|
358
382
|
guard let windowScene = await self.currentWindowScene() else {
|
|
359
|
-
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)
|
|
360
384
|
}
|
|
361
385
|
let result: Product.PurchaseResult
|
|
362
386
|
#if swift(>=5.9)
|
|
@@ -399,20 +423,20 @@ public class ExpoIapModule: Module {
|
|
|
399
423
|
return serialized
|
|
400
424
|
}
|
|
401
425
|
case .userCancelled:
|
|
402
|
-
throw Exception(name: "ExpoIapModule", description: "User cancelled the purchase", code:
|
|
426
|
+
throw Exception(name: "ExpoIapModule", description: "User cancelled the purchase", code: IapErrorCode.userCancelled)
|
|
403
427
|
case .pending:
|
|
404
|
-
throw Exception(name: "ExpoIapModule", description: "The payment was deferred", code:
|
|
428
|
+
throw Exception(name: "ExpoIapModule", description: "The payment was deferred", code: IapErrorCode.deferredPayment)
|
|
405
429
|
@unknown default:
|
|
406
|
-
throw Exception(name: "ExpoIapModule", description: "Unknown purchase result", code:
|
|
430
|
+
throw Exception(name: "ExpoIapModule", description: "Unknown purchase result", code: IapErrorCode.unknown)
|
|
407
431
|
}
|
|
408
432
|
} catch {
|
|
409
433
|
if error is Exception {
|
|
410
434
|
throw error
|
|
411
435
|
}
|
|
412
|
-
throw Exception(name: "ExpoIapModule", description: "Purchase failed: \(error.localizedDescription)", code:
|
|
436
|
+
throw Exception(name: "ExpoIapModule", description: "Purchase failed: \(error.localizedDescription)", code: IapErrorCode.purchaseError)
|
|
413
437
|
}
|
|
414
438
|
} else {
|
|
415
|
-
throw Exception(name: "ExpoIapModule", description: "Invalid product ID", code:
|
|
439
|
+
throw Exception(name: "ExpoIapModule", description: "Invalid product ID", code: IapErrorCode.itemUnavailable)
|
|
416
440
|
}
|
|
417
441
|
}
|
|
418
442
|
|
|
@@ -422,7 +446,7 @@ public class ExpoIapModule: Module {
|
|
|
422
446
|
|
|
423
447
|
AsyncFunction("subscriptionStatus") { (sku: String) -> [[String: Any?]?]? in
|
|
424
448
|
guard let productStore = self.productStore else {
|
|
425
|
-
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code:
|
|
449
|
+
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code: IapErrorCode.serviceError)
|
|
426
450
|
}
|
|
427
451
|
|
|
428
452
|
do {
|
|
@@ -437,13 +461,13 @@ public class ExpoIapModule: Module {
|
|
|
437
461
|
if error is Exception {
|
|
438
462
|
throw error
|
|
439
463
|
}
|
|
440
|
-
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)
|
|
441
465
|
}
|
|
442
466
|
}
|
|
443
467
|
|
|
444
468
|
AsyncFunction("currentEntitlement") { (sku: String) -> [String: Any?]? in
|
|
445
469
|
guard let productStore = self.productStore else {
|
|
446
|
-
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code:
|
|
470
|
+
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code: IapErrorCode.serviceError)
|
|
447
471
|
}
|
|
448
472
|
|
|
449
473
|
if let product = await productStore.getProduct(productID: sku) {
|
|
@@ -452,24 +476,24 @@ public class ExpoIapModule: Module {
|
|
|
452
476
|
let transaction = try self.checkVerified(result)
|
|
453
477
|
return serializeTransaction(transaction)
|
|
454
478
|
} catch StoreError.failedVerification {
|
|
455
|
-
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)
|
|
456
480
|
} catch {
|
|
457
481
|
if error is Exception {
|
|
458
482
|
throw error
|
|
459
483
|
}
|
|
460
|
-
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)
|
|
461
485
|
}
|
|
462
486
|
} else {
|
|
463
|
-
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)
|
|
464
488
|
}
|
|
465
489
|
} else {
|
|
466
|
-
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)
|
|
467
491
|
}
|
|
468
492
|
}
|
|
469
493
|
|
|
470
494
|
AsyncFunction("latestTransaction") { (sku: String) -> [String: Any?]? in
|
|
471
495
|
guard let productStore = self.productStore else {
|
|
472
|
-
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code:
|
|
496
|
+
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code: IapErrorCode.serviceError)
|
|
473
497
|
}
|
|
474
498
|
|
|
475
499
|
if let product = await productStore.getProduct(productID: sku) {
|
|
@@ -478,18 +502,18 @@ public class ExpoIapModule: Module {
|
|
|
478
502
|
let transaction = try self.checkVerified(result)
|
|
479
503
|
return serializeTransaction(transaction)
|
|
480
504
|
} catch StoreError.failedVerification {
|
|
481
|
-
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)
|
|
482
506
|
} catch {
|
|
483
507
|
if error is Exception {
|
|
484
508
|
throw error
|
|
485
509
|
}
|
|
486
|
-
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)
|
|
487
511
|
}
|
|
488
512
|
} else {
|
|
489
|
-
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)
|
|
490
514
|
}
|
|
491
515
|
} else {
|
|
492
|
-
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)
|
|
493
517
|
}
|
|
494
518
|
}
|
|
495
519
|
|
|
@@ -499,7 +523,7 @@ public class ExpoIapModule: Module {
|
|
|
499
523
|
self.transactions.removeValue(forKey: transactionIdentifier)
|
|
500
524
|
return true
|
|
501
525
|
} else {
|
|
502
|
-
throw Exception(name: "ExpoIapModule", description: "Invalid transaction ID", code:
|
|
526
|
+
throw Exception(name: "ExpoIapModule", description: "Invalid transaction ID", code: IapErrorCode.developerError)
|
|
503
527
|
}
|
|
504
528
|
}
|
|
505
529
|
|
|
@@ -515,7 +539,7 @@ public class ExpoIapModule: Module {
|
|
|
515
539
|
if error is Exception {
|
|
516
540
|
throw error
|
|
517
541
|
}
|
|
518
|
-
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)
|
|
519
543
|
}
|
|
520
544
|
}
|
|
521
545
|
|
|
@@ -524,14 +548,14 @@ public class ExpoIapModule: Module {
|
|
|
524
548
|
SKPaymentQueue.default().presentCodeRedemptionSheet()
|
|
525
549
|
return true
|
|
526
550
|
#else
|
|
527
|
-
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)
|
|
528
552
|
#endif
|
|
529
553
|
}
|
|
530
554
|
|
|
531
555
|
AsyncFunction("showManageSubscriptions") { () -> Bool in
|
|
532
556
|
#if !os(tvOS)
|
|
533
557
|
guard let windowScene = await self.currentWindowScene() else {
|
|
534
|
-
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)
|
|
535
559
|
}
|
|
536
560
|
// Get all subscription products before showing the management UI
|
|
537
561
|
let subscriptionSkus = await self.getAllSubscriptionProductIds()
|
|
@@ -542,7 +566,7 @@ public class ExpoIapModule: Module {
|
|
|
542
566
|
self.pollForSubscriptionStatusChanges()
|
|
543
567
|
return true
|
|
544
568
|
#else
|
|
545
|
-
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)
|
|
546
570
|
#endif
|
|
547
571
|
}
|
|
548
572
|
|
|
@@ -568,26 +592,26 @@ public class ExpoIapModule: Module {
|
|
|
568
592
|
guard let product = await self.productStore?.getProduct(productID: sku),
|
|
569
593
|
let result = await product.latestTransaction
|
|
570
594
|
else {
|
|
571
|
-
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)
|
|
572
596
|
}
|
|
573
597
|
|
|
574
598
|
do {
|
|
575
599
|
let transaction = try self.checkVerified(result)
|
|
576
600
|
guard let windowScene = await self.currentWindowScene() else {
|
|
577
|
-
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)
|
|
578
602
|
}
|
|
579
603
|
let refundStatus = try await transaction.beginRefundRequest(in: windowScene)
|
|
580
604
|
return serialize(refundStatus)
|
|
581
605
|
} catch StoreError.failedVerification {
|
|
582
|
-
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)
|
|
583
607
|
} catch {
|
|
584
608
|
if error is Exception {
|
|
585
609
|
throw error
|
|
586
610
|
}
|
|
587
|
-
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)
|
|
588
612
|
}
|
|
589
613
|
#else
|
|
590
|
-
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)
|
|
591
615
|
#endif
|
|
592
616
|
}
|
|
593
617
|
|
|
@@ -602,7 +626,7 @@ public class ExpoIapModule: Module {
|
|
|
602
626
|
|
|
603
627
|
AsyncFunction("isTransactionVerified") { (sku: String) -> Bool in
|
|
604
628
|
guard let productStore = self.productStore else {
|
|
605
|
-
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code:
|
|
629
|
+
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code: IapErrorCode.serviceError)
|
|
606
630
|
}
|
|
607
631
|
|
|
608
632
|
if let product = await productStore.getProduct(productID: sku),
|
|
@@ -620,20 +644,20 @@ public class ExpoIapModule: Module {
|
|
|
620
644
|
|
|
621
645
|
AsyncFunction("getTransactionJws") { (sku: String) -> String? in
|
|
622
646
|
guard let productStore = self.productStore else {
|
|
623
|
-
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code:
|
|
647
|
+
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code: IapErrorCode.serviceError)
|
|
624
648
|
}
|
|
625
649
|
|
|
626
650
|
if let product = await productStore.getProduct(productID: sku),
|
|
627
651
|
let result = await product.latestTransaction {
|
|
628
652
|
return result.jwsRepresentation
|
|
629
653
|
} else {
|
|
630
|
-
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)
|
|
631
655
|
}
|
|
632
656
|
}
|
|
633
657
|
|
|
634
658
|
AsyncFunction("validateReceiptIos") { (sku: String) -> [String: Any] in
|
|
635
659
|
guard let productStore = self.productStore else {
|
|
636
|
-
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code:
|
|
660
|
+
throw Exception(name: "ExpoIapModule", description: "Connection not initialized", code: IapErrorCode.serviceError)
|
|
637
661
|
}
|
|
638
662
|
|
|
639
663
|
// Get receipt data
|
|
@@ -698,9 +722,9 @@ public class ExpoIapModule: Module {
|
|
|
698
722
|
} catch {
|
|
699
723
|
if self.hasListeners {
|
|
700
724
|
let err = [
|
|
701
|
-
"responseCode":
|
|
725
|
+
"responseCode": IapErrorCode.transactionValidationFailed,
|
|
702
726
|
"debugMessage": error.localizedDescription,
|
|
703
|
-
"code":
|
|
727
|
+
"code": IapErrorCode.transactionValidationFailed,
|
|
704
728
|
"message": error.localizedDescription,
|
|
705
729
|
]
|
|
706
730
|
self.sendEvent(IapEvent.PurchaseError, err)
|
|
@@ -818,10 +842,10 @@ public class ExpoIapModule: Module {
|
|
|
818
842
|
let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
|
|
819
843
|
return receiptData.base64EncodedString(options: [])
|
|
820
844
|
} catch {
|
|
821
|
-
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)
|
|
822
846
|
}
|
|
823
847
|
} else {
|
|
824
|
-
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)
|
|
825
849
|
}
|
|
826
850
|
}
|
|
827
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-rc.1",
|
|
4
4
|
"description": "In App Purchase module in Expo",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -43,7 +43,7 @@
|
|
|
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": "*",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/
|
|
1
|
+
{"root":["./src/withiap.ts"],"version":"5.8.3"}
|
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;
|