expo-iap 2.7.12 → 2.7.13

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.
@@ -408,10 +408,11 @@ class ExpoIapModule :
408
408
  if (billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
409
409
  val errorData = PlayUtils.getBillingResponseData(billingResult.responseCode)
410
410
  var errorMessage = billingResult.debugMessage ?: errorData.message
411
+ var subResponseCode: Int? = null
411
412
 
412
413
  // Check for sub-response codes (v8.0.0+)
413
414
  try {
414
- val subResponseCode = billingResult.javaClass.getMethod("getSubResponseCode").invoke(billingResult) as? Int
415
+ subResponseCode = billingResult.javaClass.getMethod("getSubResponseCode").invoke(billingResult) as? Int
415
416
  if (subResponseCode != null && subResponseCode != 0) {
416
417
  if (subResponseCode == 1) { // PAYMENT_DECLINED_DUE_TO_INSUFFICIENT_FUNDS
417
418
  errorMessage = "$errorMessage (Payment declined due to insufficient funds)"
@@ -423,6 +424,32 @@ class ExpoIapModule :
423
424
  // Method doesn't exist in older versions, ignore
424
425
  }
425
426
 
427
+ // Send error event to match iOS behavior
428
+ val errorMap = mutableMapOf<String, Any?>(
429
+ "responseCode" to billingResult.responseCode,
430
+ "debugMessage" to billingResult.debugMessage,
431
+ "code" to errorData.code,
432
+ "message" to errorMessage
433
+ )
434
+
435
+ // Add product ID if available
436
+ if (skuArr.isNotEmpty()) {
437
+ errorMap["productId"] = skuArr.first()
438
+ }
439
+
440
+ // Add sub-response code if available
441
+ subResponseCode?.let {
442
+ if (it != 0) {
443
+ errorMap["subResponseCode"] = it
444
+ }
445
+ }
446
+
447
+ try {
448
+ sendEvent(IapEvent.PURCHASE_ERROR, errorMap.toMap())
449
+ } catch (e: Exception) {
450
+ Log.e(TAG, "Failed to send PURCHASE_ERROR event: ${e.message}")
451
+ }
452
+
426
453
  promise.reject(errorData.code, errorMessage, null)
427
454
  return@ensureConnection
428
455
  }
@@ -241,7 +241,7 @@ public class ExpoIapModule: Module {
241
241
  "ERROR_CODES": IapErrorCode.toDictionary()
242
242
  ])
243
243
 
244
- Events(IapEvent.PurchaseUpdated, IapEvent.PurchaseError)
244
+ Events(IapEvent.PurchaseUpdated, IapEvent.PurchaseError, IapEvent.PromotedProductIOS)
245
245
 
246
246
  OnStartObserving {
247
247
  self.hasListeners = true
@@ -494,6 +494,14 @@ public class ExpoIapModule: Module {
494
494
  options.insert(.appAccountToken(appAccountUUID))
495
495
  }
496
496
  guard let windowScene = await self.currentWindowScene() else {
497
+ let errorData = [
498
+ "responseCode": IapErrorCode.serviceError,
499
+ "debugMessage": "Could not find window scene",
500
+ "code": IapErrorCode.serviceError,
501
+ "message": "Could not find window scene",
502
+ "productId": sku,
503
+ ]
504
+ self.sendEvent(IapEvent.PurchaseError, errorData)
497
505
  throw Exception(name: "ExpoIapModule", description: "Could not find window scene", code: IapErrorCode.serviceError)
498
506
  }
499
507
  let result: Product.PurchaseResult
@@ -537,19 +545,105 @@ public class ExpoIapModule: Module {
537
545
  return serialized
538
546
  }
539
547
  case .userCancelled:
548
+ let errorData = [
549
+ "responseCode": IapErrorCode.userCancelled,
550
+ "debugMessage": "User cancelled the purchase",
551
+ "code": IapErrorCode.userCancelled,
552
+ "message": "User cancelled the purchase",
553
+ "productId": sku,
554
+ ]
555
+ self.sendEvent(IapEvent.PurchaseError, errorData)
540
556
  throw Exception(name: "ExpoIapModule", description: "User cancelled the purchase", code: IapErrorCode.userCancelled)
541
557
  case .pending:
558
+ let errorData = [
559
+ "responseCode": IapErrorCode.deferredPayment,
560
+ "debugMessage": "The payment was deferred",
561
+ "code": IapErrorCode.deferredPayment,
562
+ "message": "The payment was deferred",
563
+ "productId": sku,
564
+ ]
565
+ self.sendEvent(IapEvent.PurchaseError, errorData)
542
566
  throw Exception(name: "ExpoIapModule", description: "The payment was deferred", code: IapErrorCode.deferredPayment)
543
567
  @unknown default:
568
+ let errorData = [
569
+ "responseCode": IapErrorCode.unknown,
570
+ "debugMessage": "Unknown purchase result",
571
+ "code": IapErrorCode.unknown,
572
+ "message": "Unknown purchase result",
573
+ "productId": sku,
574
+ ]
575
+ self.sendEvent(IapEvent.PurchaseError, errorData)
544
576
  throw Exception(name: "ExpoIapModule", description: "Unknown purchase result", code: IapErrorCode.unknown)
545
577
  }
546
578
  } catch {
547
579
  if error is Exception {
548
580
  throw error
549
581
  }
550
- throw Exception(name: "ExpoIapModule", description: "Purchase failed: \(error.localizedDescription)", code: IapErrorCode.purchaseError)
582
+
583
+ // Map StoreKit errors to proper error codes
584
+ var errorCode = IapErrorCode.purchaseError
585
+ var errorMessage = error.localizedDescription
586
+
587
+ // Check for specific StoreKit error types
588
+ if let nsError = error as NSError? {
589
+ switch nsError.domain {
590
+ case "SKErrorDomain":
591
+ // Handle SKError codes
592
+ switch nsError.code {
593
+ case 0: // SKError.unknown
594
+ errorCode = IapErrorCode.unknown
595
+ case 1: // SKError.clientInvalid
596
+ errorCode = IapErrorCode.serviceError
597
+ case 2: // SKError.paymentCancelled
598
+ errorCode = IapErrorCode.userCancelled
599
+ errorMessage = "User cancelled the purchase"
600
+ case 3: // SKError.paymentInvalid
601
+ errorCode = IapErrorCode.userError
602
+ case 4: // SKError.paymentNotAllowed
603
+ errorCode = IapErrorCode.userError
604
+ errorMessage = "Payment not allowed"
605
+ case 5: // SKError.storeProductNotAvailable
606
+ errorCode = IapErrorCode.itemUnavailable
607
+ case 6: // SKError.cloudServicePermissionDenied
608
+ errorCode = IapErrorCode.serviceError
609
+ case 7: // SKError.cloudServiceNetworkConnectionFailed
610
+ errorCode = IapErrorCode.networkError
611
+ case 8: // SKError.cloudServiceRevoked
612
+ errorCode = IapErrorCode.serviceError
613
+ default:
614
+ errorCode = IapErrorCode.purchaseError
615
+ }
616
+ case "NSURLErrorDomain":
617
+ errorCode = IapErrorCode.networkError
618
+ errorMessage = "Network error: \(error.localizedDescription)"
619
+ default:
620
+ errorCode = IapErrorCode.purchaseError
621
+ }
622
+ } else if error.localizedDescription.lowercased().contains("network") {
623
+ errorCode = IapErrorCode.networkError
624
+ } else if error.localizedDescription.lowercased().contains("cancelled") {
625
+ errorCode = IapErrorCode.userCancelled
626
+ }
627
+
628
+ let errorData = [
629
+ "responseCode": errorCode,
630
+ "debugMessage": "Purchase failed: \(error.localizedDescription)",
631
+ "code": errorCode,
632
+ "message": errorMessage,
633
+ "productId": sku,
634
+ ]
635
+ self.sendEvent(IapEvent.PurchaseError, errorData)
636
+ throw Exception(name: "ExpoIapModule", description: errorMessage, code: errorCode)
551
637
  }
552
638
  } else {
639
+ let errorData = [
640
+ "responseCode": IapErrorCode.itemUnavailable,
641
+ "debugMessage": "Invalid product ID",
642
+ "code": IapErrorCode.itemUnavailable,
643
+ "message": "Invalid product ID",
644
+ "productId": sku,
645
+ ]
646
+ self.sendEvent(IapEvent.PurchaseError, errorData)
553
647
  throw Exception(name: "ExpoIapModule", description: "Invalid product ID", code: IapErrorCode.itemUnavailable)
554
648
  }
555
649
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-iap",
3
- "version": "2.7.12",
3
+ "version": "2.7.13",
4
4
  "description": "In App Purchase module in Expo",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",