Skip to content

fix(apple): map StoreKitError.userCancelled to correct err code#40

Merged
hyochan merged 1 commit into
mainfrom
fix/ios-error-mapping
Nov 14, 2025
Merged

fix(apple): map StoreKitError.userCancelled to correct err code#40
hyochan merged 1 commit into
mainfrom
fix/ios-error-mapping

Conversation

@hyochan
Copy link
Copy Markdown
Member

@hyochan hyochan commented Nov 14, 2025

Map StoreKitError to PurchaseError in wrap() method to ensure user cancellation returns 'user-cancelled' instead of 'purchase-error'.

Fixes hyochan/expo-iap#261

Summary by CodeRabbit

  • Bug Fixes
    • Improved error handling for purchase operations by providing more accurate error categorization for network failures, user cancellations, and store availability issues, enabling better error reporting and recovery experiences.

Map StoreKitError to PurchaseError in wrap() method to ensure
user cancellation returns 'user-cancelled' instead of 'purchase-error'.

Fixes hyochan/expo-iap#261
@hyochan hyochan added the 🛠 bugfix All kinds of bug fixes label Nov 14, 2025
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Nov 14, 2025

Walkthrough

The PR adds StoreKit error mapping to the PurchaseError.wrap() function and applies it during purchase error handling, enabling conversion of StoreKitError.userCancelled and related errors to their corresponding PurchaseError codes instead of defaulting to generic unexpected errors.

Changes

Cohort / File(s) Summary
Error mapping enhancement
packages/apple/Sources/Models/OpenIapError.swift
Added StoreKit import and extended wrap() function to map StoreKitError cases (userCancelled, networkError, notAvailableInStorefront, notEntitled, systemError) to corresponding PurchaseError codes with fallback behavior.
Error handling application
packages/apple/Sources/OpenIapModule.swift
Modified non-promotional purchase error handling to wrap errors via PurchaseError.wrap() before emitting, ensuring StoreKitError.userCancelled is correctly mapped to PurchaseError.userCancelled.

Sequence Diagram

sequenceDiagram
    participant User
    participant Module as OpenIapModule
    participant StoreKit
    participant ErrorWrapper as PurchaseError.wrap()
    participant Handler as Error Handler

    User->>Module: requestPurchase()
    Module->>StoreKit: initiate purchase
    StoreKit-->>Module: StoreKitError.userCancelled
    
    rect rgba(255, 182, 193, 0.3)
        Note over Module,ErrorWrapper: NEW: Error Mapping
        Module->>ErrorWrapper: wrap(StoreKitError.userCancelled, fallback: .purchaseError)
        ErrorWrapper-->>Module: PurchaseError.userCancelled
    end
    
    Module->>Handler: emit mapped error
    Handler->>User: throw PurchaseError.userCancelled
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

  • Error mapping logic is straightforward with clear case handling
  • Application is consistent and localized to one error path
  • Changes maintain existing behavior for already-present PurchaseError instances

Possibly related issues

  • #261: Directly addressed—the PR maps StoreKitError.userCancelled to PurchaseError.userCancelled, fixing the wrong error code on iOS purchase cancellation.

Possibly related PRs

Suggested labels

📱 iOS, 🛠 bugfix

Poem

🐰 A hop and a wrap, errors now map!
StoreKit's .userCancelled no longer a trap,
The code flows just right, no more ERR_UNEXPECTED plight,
iOS cancels with grace—correct codes light the trace! ✨

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: mapping StoreKitError.userCancelled to the correct error code.
Linked Issues check ✅ Passed The PR implements the required fix by extending PurchaseError.wrap() to map StoreKitError.userCancelled to user-cancelled, directly addressing issue #261's requirement.
Out of Scope Changes check ✅ Passed All changes are focused on error mapping for StoreKitError handling; no unrelated modifications detected outside the scope of issue #261.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/ios-error-mapping

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 04de000 and 516de85.

📒 Files selected for processing (2)
  • packages/apple/Sources/Models/OpenIapError.swift (2 hunks)
  • packages/apple/Sources/OpenIapModule.swift (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
packages/apple/Sources/**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

packages/apple/Sources/**/*.swift: iOS-specific functions must have IOS suffix; cross-platform functions have no suffix
Swift acronym naming: acronyms are ALL CAPS only as a suffix; when at beginning/middle, use Pascal case (e.g., IapManager, ProductIAP)

Files:

  • packages/apple/Sources/OpenIapModule.swift
  • packages/apple/Sources/Models/OpenIapError.swift
packages/apple/Sources/Models/**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Keep official OpenIAP types in Sources/Models/ matching openiap.dev/docs/types naming (e.g., Product.swift, Purchase.swift, ActiveSubscription.swift)

Files:

  • packages/apple/Sources/Models/OpenIapError.swift
🧠 Learnings (3)
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/apple/Sources/Models/**/*.swift : Keep official OpenIAP types in Sources/Models/ matching openiap.dev/docs/types naming (e.g., Product.swift, Purchase.swift, ActiveSubscription.swift)

Applied to files:

  • packages/apple/Sources/OpenIapModule.swift
  • packages/apple/Sources/Models/OpenIapError.swift
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/apple/Sources/Helpers/**/*.swift : Place internal helper classes (not official OpenIAP types) under Sources/Helpers/ (e.g., ProductManager.swift, IapStatus.swift)

Applied to files:

  • packages/apple/Sources/OpenIapModule.swift
  • packages/apple/Sources/Models/OpenIapError.swift
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/apple/Sources/**/*.swift : Swift acronym naming: acronyms are ALL CAPS only as a suffix; when at beginning/middle, use Pascal case (e.g., IapManager, ProductIAP)

Applied to files:

  • packages/apple/Sources/OpenIapModule.swift
  • packages/apple/Sources/Models/OpenIapError.swift
🧬 Code graph analysis (1)
packages/apple/Sources/OpenIapModule.swift (2)
packages/apple/Sources/Models/OpenIapError.swift (2)
  • purchaseError (80-82)
  • wrap (93-125)
packages/gql/src/generated/types.ts (1)
  • PurchaseError (466-470)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test Android
🔇 Additional comments (3)
packages/apple/Sources/Models/OpenIapError.swift (2)

2-2: LGTM: StoreKit import added for error mapping.

The import is necessary to access the StoreKitError type used in the extended wrap() method.


91-125: Approved: StoreKitError mapping correctly covers all critical iOS 15.0+ cases.

The implementation properly maps StoreKitError cases including userCancelled, networkError, notAvailableInStorefront, notEntitled, and systemError, with a safe default case for unmapped errors. All target ErrorCode values (.userCancelled, .networkError, .itemUnavailable, .itemNotOwned, .serviceError) are defined in the schema. The default fallback strategy appropriately handles iOS 17+ additions (invalidOfferIdentifier, invalidOfferPrice, etc.) without requiring explicit mappings.

packages/apple/Sources/OpenIapModule.swift (1)

327-331: Correct application of StoreKitError mapping.

The use of PurchaseError.wrap() ensures that StoreKitError.userCancelled (if thrown during purchase) is now correctly mapped to ErrorCode.userCancelled, addressing the reported issue. This complements the explicit handling of Product.PurchaseResult.userCancelled at lines 367-370, providing comprehensive coverage for both error paths.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@hyochan hyochan merged commit 17a25c8 into main Nov 14, 2025
5 checks passed
@hyochan hyochan deleted the fix/ios-error-mapping branch November 14, 2025 08:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🛠 bugfix All kinds of bug fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Wrong error code when cancelling a purchase on iOS

1 participant