Skip to content

feat: verify purchase with provider#46

Merged
hyochan merged 16 commits into
mainfrom
feat/verify-receipt-with-provider
Dec 6, 2025
Merged

feat: verify purchase with provider#46
hyochan merged 16 commits into
mainfrom
feat/verify-receipt-with-provider

Conversation

@hyochan
Copy link
Copy Markdown
Member

@hyochan hyochan commented Nov 22, 2025

Summary by CodeRabbit

  • New Features

    • Add provider-based purchase verification (IAPKit) with a verifyPurchaseWithProvider mutation returning per-provider/store results.
  • Refactor

    • Rename receipt validation APIs/types to VerifyPurchase* and update error codes/messages; include deprecated compatibility aliases.
  • Tests

    • Add extensive unit tests covering IAPKit and platform verification flows.
  • Documentation

    • Update docs, examples and sample apps with verification guidance, configuration, UI for selecting verification methods and API key usage.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Nov 22, 2025

Warning

Rate limit exceeded

@hyochan has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 17 minutes and 29 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 58170c6 and 1b9f559.

📒 Files selected for processing (5)
  • .claude/guides/04-apple-package.md (1 hunks)
  • packages/apple/Sources/OpenIapProtocol.swift (2 hunks)
  • packages/docs/src/pages/docs/apis.tsx (5 hunks)
  • packages/google/Example/src/main/java/dev/hyo/martie/screens/PurchaseFlowScreen.kt (6 hunks)
  • packages/gql/scripts/generate-swift-types.mjs (2 hunks)

Walkthrough

Adds provider-based purchase verification (IAPKit) and renames receipt validation APIs to VerifyPurchase across platforms. Introduces new enums/props/results for IAPKit, implements verifyPurchaseWithProvider (with IAPKit HTTP payloads) on Apple and Google, updates GraphQL schema, preserves backward-compatible deprecated aliases, and updates tests and examples to exercise the new flows.

Changes

Cohort / File(s) Change Summary
Core types — Apple
packages/apple/Sources/Models/Types.swift, packages/apple/Sources/Models/ReceiptValidationCompat.swift, packages/apple/Sources/Models/OpenIapSerialization.swift
Add Iapkit enums and PurchaseVerificationProvider; introduce VerifyPurchase* props/results and IAPKit request/result types; replace ReceiptValidation* names with VerifyPurchase* and add deprecated aliases; rename serialization helper.
Core types — Google (Kotlin)
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt, packages/google/openiap/src/main/java/dev/hyo/openiap/compat/ReceiptValidationCompat.kt
Add Iapkit enums and VerifyPurchase* data classes; replace ReceiptValidation* with VerifyPurchase*; add Kotlin deprecated typealiases for compatibility.
Module impl — Apple
packages/apple/Sources/OpenIapModule.swift, packages/apple/Sources/OpenIapProtocol.swift, packages/apple/Sources/OpenIapStore.swift
Change APIs to use VerifyPurchaseProps/VerifyPurchaseResult; add public verifyPurchaseWithProvider and private verifyPurchaseWithIapkit + payload builders; add protocol compatibility wrappers and update store methods.
Module impl — Google
packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt, packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt, packages/google/openiap/src/main/java/dev/hyo/openiap/OpenIapProtocol.kt
Wire verifyPurchaseWithProvider into module/MutationHandlers; enforce IAPKit provider and iapkit options; swap ReceiptValidationProps → VerifyPurchaseProps in handlers.
Verification utils — Google
packages/google/openiap/src/main/java/dev/hyo/openiap/utils/PurchaseVerificationValidator.kt, (removed) packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt
Add verifyPurchaseWithGooglePlay and verifyPurchaseWithIapkit implementations (HTTP flows, payload builders, error mapping, JSON parsing); remove old Google Play ReceiptValidator implementation.
GraphQL schema & types
packages/gql/src/api.graphql, packages/gql/src/api-ios.graphql, packages/gql/src/type.graphql, packages/gql/src/type-android.graphql, packages/gql/src/type-ios.graphql, packages/gql/src/error.graphql
Rename inputs/results to VerifyPurchase*; add VerifyPurchaseResult union; add VerifyPurchaseWithProviderProps/Result and RequestVerifyPurchaseWithIapkit* types; add PurchaseVerificationProvider/IapkitStore/IapkitPurchaseState enums; add new error members and deprecation notes.
Tests — Apple
packages/apple/Tests/OpenIapTests/VerifyPurchaseTests.swift, packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift
Rename tests to VerifyPurchase*, update FakeOpenIapModule signatures to VerifyPurchase types, add provider verification tests and fake module returning provider results.
Tests — Google
packages/google/openiap/src/test/java/dev/hyo/openiap/PurchaseVerificationValidatorTest.kt, packages/google/openiap/src/test/java/dev/hyo/openiap/ReceiptValidatorTest.kt
Rework tests to use VerifyPurchase props/types; add tests for IAPKit flows, payload validation, headers, endpoint behavior, and error wrapping.
Examples & UI (Apple & Android)
packages/google/Example/.../PurchaseFlowScreen.kt, packages/google/Example/.../SubscriptionFlowScreen.kt, packages/apple/Example/OpenIapExample/Screens/PurchaseFlowScreen.swift, packages/apple/Example/OpenIapExample/Screens/SubscriptionFlowScreen.swift
Add verification method selection (None/Local/IAPKit), UI state and components, API key handling, verifyWithIapkit/verifyLocally helpers, and change purchase completion flow to require/reflect verification results; add deduplication for processed purchases.
Docs, config & misc
.vscode/settings.json, packages/docs/*, packages/gql/package.json, packages/docs/src/pages/docs/example.tsx, packages/docs/src/styles/pages.css, packages/google/Example/build.gradle.kts, packages/google/local.properties.example, packages/apple/Example/*, packages/docs/package.json
Add "Iapkit" to cSpell; update docs and examples to VerifyPurchase names and IAPKit usage; add example Info.plist and Xcode scheme; add BuildConfig IAPKIT_API_KEY and local.properties.example; update docs pages, styles, and some package versions.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant OpenIapStore
    participant OpenIapModule
    participant IAPKit
    Client->>OpenIapStore: verifyPurchaseWithProvider(props)
    OpenIapStore->>OpenIapModule: verifyPurchaseWithProvider(props)
    OpenIapModule->>OpenIapModule: validate provider & iapkit props
    OpenIapModule->>OpenIapModule: build IAPKit payload (Apple/Google)
    OpenIapModule->>IAPKit: POST payload (optional Bearer API key)
    IAPKit-->>OpenIapModule: 2xx + JSON (per-store results)
    OpenIapModule-->>OpenIapStore: VerifyPurchaseWithProviderResult(iapkitResults, provider)
    OpenIapStore-->>Client: [RequestVerifyPurchaseWithIapkitResult]
Loading
sequenceDiagram
    participant Client
    participant OpenIapStore
    participant OpenIapModule
    Client->>OpenIapStore: verifyPurchase(sku)
    OpenIapStore->>OpenIapModule: verifyPurchase(VerifyPurchaseProps)
    OpenIapModule->>OpenIapModule: choose verify path (GooglePlay / IAPKit / local)
    OpenIapModule->>OpenIapModule: perform platform/provider verification
    OpenIapModule-->>OpenIapStore: VerifyPurchaseResult (Android or iOS)
    OpenIapStore-->>Client: VerifyPurchaseResultIOS (extracted)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45–75 minutes

  • Cross-language surface area (Swift, Kotlin, GraphQL, JS, tests) increases review scope.
  • High-density logic and error handling in IAPKit payload builders, HTTP clients, and response parsing require careful scrutiny.
  • GraphQL schema renames and compatibility aliases require consistency checks.

Focus areas:

  • IAPKit payload builders and per-store validation logic (Apple vs Google branches).
  • HTTP request/response handling and error extraction for IAPKit and Google Play paths.
  • Consistency of VerifyPurchase* renames and deprecated ReceiptValidation* aliases across languages and GraphQL schema.
  • Mutation/Query handler wiring and backward-compatibility wrappers.

Possibly related PRs

Suggested labels

📱 iOS, 🤖 android, ⬡ gql, ❄️ types

Poem

🐰 I hopped through code with tiny paws,
I renamed receipts and fixed some laws.
IAPKit whispers to the cloud,
Verifications made us proud.
Deprecated crumbs — I munch and applause! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.53% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: adding a new capability to verify purchases with a specific provider (IAPKit). It directly reflects the primary feature addition across the codebase.

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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/gql/src/type.graphql (1)

221-288: IAPKit/provider input/result wiring is well-shaped; keep invariants in resolvers

The input/result types (RequestVerifyPurchaseWithIapkit*, VerifyPurchaseWithProviderProps/Result) match the platform models and docs clearly state when apple/google are required. Just ensure resolvers always set only the matching provider field on VerifyPurchaseWithProviderResult (currently iapkit) to preserve the intended union-like semantics.

packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt (1)

5-6: IAPKit verification implementation is solid; consider tiny logging/DRY tweaks

The new verifyPurchaseWithIapkit implementation looks correct:

  • Validates endpoint and store-specific fields (Apple receipt/appId, Google packageName/purchaseId/purchaseToken).
  • Runs on Dispatchers.IO, builds a JSON payload consistent with the new Iapkit types, and posts with optional Bearer apiKey.
  • Maps non‑2xx/JSON issues to OpenIapError.InvalidReceipt and IO problems to OpenIapError.NetworkError, matching existing validateReceiptWithGooglePlay patterns.

Two minor nits you may want to adjust:

  • The HTTP error log message says "verifyPurchaseWithProvider failed" from inside verifyPurchaseWithIapkit; renaming it for this function would make logs easier to follow.
  • The HTTP/JSON handling is nearly identical to validateReceiptWithGooglePlay; if this pattern grows further, a shared helper for “execute JSON HTTP request and parse” could reduce duplication.

Also applies to: 10-14, 85-175

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fe1dc88 and a754031.

⛔ Files ignored due to path filters (4)
  • packages/gql/src/generated/Types.kt is excluded by !**/generated/**
  • packages/gql/src/generated/Types.swift is excluded by !**/generated/**
  • packages/gql/src/generated/types.dart is excluded by !**/generated/**
  • packages/gql/src/generated/types.ts is excluded by !**/generated/**
📒 Files selected for processing (15)
  • .vscode/settings.json (1 hunks)
  • packages/apple/Sources/Models/Types.swift (10 hunks)
  • packages/apple/Sources/OpenIapModule.swift (1 hunks)
  • packages/apple/Sources/OpenIapProtocol.swift (1 hunks)
  • packages/apple/Sources/OpenIapStore.swift (1 hunks)
  • packages/apple/Tests/OpenIapTests/VerifyPurchaseTests.swift (3 hunks)
  • packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift (1 hunks)
  • packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt (3 hunks)
  • packages/google/openiap/src/main/java/dev/hyo/openiap/OpenIapProtocol.kt (1 hunks)
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (8 hunks)
  • packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt (2 hunks)
  • packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt (4 hunks)
  • packages/google/openiap/src/test/java/dev/hyo/openiap/ReceiptValidatorTest.kt (2 hunks)
  • packages/gql/src/api.graphql (1 hunks)
  • packages/gql/src/type.graphql (2 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 17
File: packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt:620-629
Timestamp: 2025-10-18T05:46:51.596Z
Learning: In packages/google/openiap/src/main/java/**/*.kt (Android-only package): DO NOT add Android suffix to function names, even for Android-specific APIs. Exception: Only use Android suffix for cross-platform API types (e.g., ProductAndroid, PurchaseAndroid) that contrast with iOS types. Examples of correct naming: isHorizonEnvironment(context: Context), buildModule(context: Context), acknowledgePurchase(), consumePurchase().
📚 Learning: 2025-10-18T05:46:51.596Z
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 17
File: packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt:620-629
Timestamp: 2025-10-18T05:46:51.596Z
Learning: In packages/google/openiap/src/main/java/**/*.kt (Android-only package): DO NOT add Android suffix to function names, even for Android-specific APIs. Exception: Only use Android suffix for cross-platform API types (e.g., ProductAndroid, PurchaseAndroid) that contrast with iOS types. Examples of correct naming: isHorizonEnvironment(context: Context), buildModule(context: Context), acknowledgePurchase(), consumePurchase().

Applied to files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/OpenIapProtocol.kt
  • packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt
  • packages/google/openiap/src/test/java/dev/hyo/openiap/ReceiptValidatorTest.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt
  • packages/gql/src/type.graphql
  • packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
  • packages/apple/Sources/Models/Types.swift
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
🧬 Code graph analysis (10)
packages/apple/Sources/OpenIapProtocol.swift (4)
packages/apple/Sources/OpenIapModule.swift (1)
  • verifyPurchaseWithProvider (594-603)
packages/apple/Sources/OpenIapStore.swift (1)
  • verifyPurchaseWithProvider (388-394)
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (1)
  • verifyPurchaseWithProvider (2537-2537)
packages/gql/src/generated/Types.kt (1)
  • verifyPurchaseWithProvider (2604-2604)
packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt (1)
packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt (1)
  • verifyPurchaseWithIapkit (85-175)
packages/google/openiap/src/test/java/dev/hyo/openiap/ReceiptValidatorTest.kt (2)
packages/gql/src/generated/types.ts (3)
  • RequestVerifyPurchaseWithIapkitProps (810-821)
  • RequestVerifyPurchaseWithIapkitAppleProps (792-799)
  • RequestVerifyPurchaseWithIapkitGoogleProps (801-808)
packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt (1)
  • verifyPurchaseWithIapkit (85-175)
packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt (1)
packages/google/openiap/src/test/java/dev/hyo/openiap/ReceiptValidatorTest.kt (1)
  • setRequestProperty (265-269)
packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift (2)
packages/gql/src/generated/types.ts (10)
  • RequestVerifyPurchaseWithIapkitResult (823-826)
  • ReceiptValidationResult (625-625)
  • ReceiptValidationResultIOS (648-657)
  • VerifyPurchaseWithProviderResult (890-892)
  • VerifyPurchaseWithProviderProps (885-888)
  • RequestVerifyPurchaseWithIapkitProps (810-821)
  • RequestVerifyPurchaseWithIapkitAppleProps (792-799)
  • IapkitStore (177-177)
  • PurchaseError (481-485)
  • Subscription (828-840)
packages/apple/Tests/OpenIapTests/VerifyPurchaseTests.swift (24)
  • initConnection (75-75)
  • endConnection (76-76)
  • fetchProducts (79-79)
  • getPromotedProductIOS (80-80)
  • requestPurchase (83-83)
  • requestPurchaseOnPromotedProductIOS (84-84)
  • restorePurchases (85-85)
  • getAvailablePurchases (86-86)
  • finishTransaction (89-89)
  • getPendingTransactionsIOS (90-90)
  • clearTransactionIOS (91-91)
  • isTransactionVerifiedIOS (92-92)
  • getTransactionJwsIOS (93-93)
  • currentEntitlementIOS (94-94)
  • latestTransactionIOS (95-95)
  • getReceiptDataIOS (98-98)
  • validateReceiptIOS (99-104)
  • validateReceipt (106-108)
  • verifyPurchase (110-112)
  • purchaseUpdatedListener (138-140)
  • purchaseErrorListener (142-144)
  • promotedProductListenerIOS (146-148)
  • removeListener (150-152)
  • removeAllListeners (154-154)
packages/apple/Sources/OpenIapModule.swift (3)
packages/apple/Sources/OpenIapStore.swift (1)
  • verifyPurchaseWithProvider (388-394)
packages/gql/src/generated/Types.kt (11)
  • verifyPurchaseWithProvider (2604-2604)
  • iapkit (2395-2412)
  • message (1537-1556)
  • store (1647-1666)
  • appId (608-660)
  • appId (2290-2319)
  • packageName (2321-2350)
  • request (2104-2155)
  • apiKey (2352-2393)
  • error (773-798)
  • error (803-828)
packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt (1)
  • verifyPurchaseWithIapkit (85-175)
packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt (1)
packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt (1)
  • verifyPurchaseWithIapkit (85-175)
packages/apple/Sources/OpenIapStore.swift (2)
packages/apple/Sources/OpenIapModule.swift (1)
  • verifyPurchaseWithProvider (594-603)
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (2)
  • verifyPurchaseWithProvider (2537-2537)
  • iapkit (2328-2345)
packages/apple/Sources/Models/Types.swift (4)
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (8)
  • iapkit (2328-2345)
  • store (1580-1599)
  • appId (541-593)
  • appId (2223-2252)
  • packageName (2254-2283)
  • apiKey (2285-2326)
  • verifyPurchaseWithProvider (2537-2537)
  • verifyPurchase (2533-2533)
packages/gql/src/generated/Types.kt (8)
  • iapkit (2395-2412)
  • store (1647-1666)
  • appId (608-660)
  • appId (2290-2319)
  • packageName (2321-2350)
  • apiKey (2352-2393)
  • verifyPurchaseWithProvider (2604-2604)
  • verifyPurchase (2600-2600)
packages/apple/Sources/OpenIapModule.swift (2)
  • verifyPurchaseWithProvider (594-603)
  • verifyPurchase (589-592)
packages/apple/Sources/OpenIapStore.swift (2)
  • verifyPurchaseWithProvider (388-394)
  • verifyPurchase (376-386)
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (1)
packages/gql/src/generated/types.ts (5)
  • RequestVerifyPurchaseWithIapkitResult (823-826)
  • RequestVerifyPurchaseWithIapkitAppleProps (792-799)
  • RequestVerifyPurchaseWithIapkitGoogleProps (801-808)
  • RequestVerifyPurchaseWithIapkitProps (810-821)
  • VerifyPurchaseWithProviderProps (885-888)
⏰ 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 (25)
.vscode/settings.json (1)

4-4: Verify the casing of the IAPKit provider name.

The word is added as "Iapkit" but the PR summary and broader context reference "IAPKit" (with capital A, P, K). Ensure the casing matches how the provider is actually referenced throughout the codebase to avoid spell-checker false positives.

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

594-603: LGTM! Clean provider validation and delegation.

The method correctly validates the provider type and required parameters before delegating to the helper. Error messages are clear and use appropriate error codes.


605-687: <!-- [scratchpad_start]
[task review_verification_1/1]
Verify cross-platform consistency claims in review comment for verifyPurchaseWithIapkit

[observations]
Script output shows significant differences between Swift and Kotlin implementations:

Swift (lines 605-687):

  • Endpoint validation: guard !endpoint.isEmpty AND URL(string: endpoint) in single check
  • Error handling: Uses custom makePurchaseError(code:, message:) with codes like .developerError, .networkError, .receiptFailed
  • Apple validation: Throws makePurchaseError consistently
  • Google validation: Throws makePurchaseError consistently
  • Network errors: Mapped via URLError catch to .networkError
  • Parse errors: Mapped to .receiptFailed

Kotlin (from script output):

  • Endpoint validation: takeIf { it.isNotBlank() } (does NOT validate URL parsing)
  • Error handling: MIXES IllegalArgumentException (for validation) and OpenIapError.InvalidReceipt / OpenIapError.NetworkError
  • Apple validation: Throws IllegalArgumentException (different from endpoint)
  • Network errors: Mapped via IOException catch to OpenIapError.NetworkError
  • Parse errors: Mapped to OpenIapError.InvalidReceipt

[analysis]
The review comment claims implementations "closely mirror" each other for consistency. However:

  1. Error handling strategies DIFFER: Swift uses unified PurchaseError codes; Kotlin uses mixed IllegalArgumentException + OpenIapError
  2. Endpoint validation differs: Swift validates URL parsing; Kotlin only checks blank
  3. These are intentional platform-specific conventions, NOT bugs in the Swift code
  4. The review comment's claim about mirroring consistency is overstated/inaccurate
  5. The review comment uses invalid categorization tags (, don't exist)

[analysis_conclusion]
The review comment makes an inaccurate claim about cross-platform consistency. The implementations intentionally differ for platform conventions. The review comment itself contains factual errors about the degree of mirroring between implementations and uses invalid categorization tags.

[done]
Ready to generate corrected review comment
[scratchpad_end]

Swift implementation is well-structured with comprehensive error handling; however, note that the Kotlin implementation uses a different error strategy.

The method properly validates inputs, constructs platform-specific payloads, and maps errors appropriately. However, the Swift and Kotlin implementations differ in their error handling approaches: Swift uses custom PurchaseError codes consistently, while Kotlin mixes IllegalArgumentException for validation with OpenIapError enums for other failures. Additionally, Swift validates URL parsing on the endpoint while Kotlin only checks for blank strings. These differences appear intentional for platform conventions, but verify they don't create unexpected behavior in cross-platform scenarios.

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

49-49: LGTM! Protocol extension aligns with implementation.

The method signature correctly matches the implementation in OpenIapModule.swift and is appropriately placed in the Validation section.

packages/google/openiap/src/main/java/dev/hyo/openiap/OpenIapProtocol.kt (1)

30-30: LGTM! Protocol property follows existing patterns.

The property is correctly defined using the mutation handler typealias and is appropriately placed alongside the existing verifyPurchase handler.

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

388-394: LGTM! Clean delegation to module with appropriate result unpacking.

The method follows the established pattern in this file (similar to verifyPurchase above) and correctly unpacks the .iapkit case from the provider result.

packages/gql/src/api.graphql (1)

77-83: LGTM! GraphQL mutation properly defined and documented.

The mutation field is well-documented and correctly placed alongside the existing verifyPurchase mutation. The use of non-nullable types for both input and output is appropriate for this verification operation.

packages/google/openiap/src/test/java/dev/hyo/openiap/ReceiptValidatorTest.kt (3)

103-169: Excellent validation test coverage for required parameters.

The three tests comprehensively cover the validation requirements:

  • Missing Apple props when store is Apple
  • Missing/blank endpoint
  • Production mode requiring appId

171-224: Thorough verification of HTTP request construction and response parsing.

The tests properly validate:

  • Request body structure (using Gson to parse captured body)
  • Authorization header inclusion (Bearer token)
  • Store-specific payload fields (Apple: store/receipt/environment/appId, Google: store/packageName/purchaseId/purchaseToken)
  • Response parsing to RequestVerifyPurchaseWithIapkitResult

256-278: Good test infrastructure enhancements for request verification.

The additions to FakeHttpURLConnection enable proper verification of outbound HTTP requests:

  • headers map captures setRequestProperty calls
  • writtenBody captures the request payload via a custom OutputStream
  • These enhancements are essential for the new test assertions
packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift (3)

7-42: Good test coverage for successful provider verification path.

The test properly validates:

  • Non-nil result is returned when provider returns iapkit result
  • Store field matches expected value (.apple)
  • Valid field reflects the verification outcome (true)

44-76: Proper test for nil result handling.

The test correctly validates that the store method returns nil when the provider result's iapkit case contains nil. This ensures the unwrapping logic in OpenIapStore.verifyPurchaseWithProvider works as expected.


79-170: Well-structured test double following established patterns.

The FakeVerifyPurchaseModule properly implements OpenIapModuleProtocol with:

  • Configurable providerResult for testing different scenarios
  • All required protocol methods stubbed appropriately
  • Clean initialization accepting test parameters

This follows the same pattern as the fake module in VerifyPurchaseTests.swift.

packages/apple/Tests/OpenIapTests/VerifyPurchaseTests.swift (1)

64-72: Good integration of provider path into existing test infrastructure.

The changes properly extend the fake module to support the new provider verification path:

  • providerResult property with sensible default (.iapkit(nil))
  • Updated initializer maintains backward compatibility
  • Simple verifyPurchaseWithProvider implementation returns configured result

This allows the existing tests to continue working while enabling new provider-based tests.

Also applies to: 114-116

packages/gql/src/type.graphql (1)

33-47: IAPKit/provider enums look consistent with generated Kotlin/Swift types

Enum names and values align with the new IapkitEnvironment, IapkitStore, and PurchaseVerificationProvider types in Kotlin/Swift, so schema ↔ client codegen should stay coherent. No issues here.

packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt (1)

39-42: Horizon IAPKit provider flow is correctly gated and wired

The Horizon module’s verifyPurchaseWithProvider mirrors the Swift implementation: it only accepts .Iapkit, throws FeatureNotSupported for others, requires iapkit options (else DeveloperError), and delegates to verifyPurchaseWithIapkit before wrapping in VerifyPurchaseWithProviderResultIapkit. MutationHandlers wiring looks correct.

Also applies to: 661-667, 696-697

packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt (1)

36-39: Play IAPKit provider verification matches Horizon/iOS behavior

The Play module’s verifyPurchaseWithProvider is symmetric with Horizon and the Swift implementation: it restricts to .Iapkit, validates presence of iapkit options, calls verifyPurchaseWithIapkit, and wraps the result in VerifyPurchaseWithProviderResultIapkit, then exposes it via mutationHandlers. This keeps the provider path consistent across platforms.

Also applies to: 66-67, 812-818, 847-848

packages/apple/Sources/Models/Types.swift (1)

153-161: Swift types/protocols cleanly mirror the new GraphQL/provider model

The added enums (IapkitEnvironment, IapkitStore, PurchaseVerificationProvider), IAPKit input/result structs, VerifyPurchaseWithProviderResult enum, and the verifyPurchaseWithProvider additions to MutationResolver, handler typealias, and MutationHandlers all line up with the GraphQL schema and Kotlin types. This keeps the Swift-side API surface in sync with Android and the schema, and should make the new provider-based verification path straightforward to consume.

Also applies to: 202-205, 592-595, 633-635, 1006-1082, 1447-1449, 1529-1530, 1553-1553, 1576-1577, 1599-1601

packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (7)

211-243: LGTM! Well-structured enum additions.

The new IapkitEnvironment and IapkitStore enums follow the established pattern with proper JSON serialization and case-insensitive deserialization.


375-387: LGTM! Extensible provider enum.

The PurchaseVerificationProvider enum correctly handles case-insensitive deserialization and is structured to support additional providers in the future.


1580-1599: LGTM! Clean result type.

The RequestVerifyPurchaseWithIapkitResult data class matches the TypeScript definition and follows the established serialization pattern.


1737-1739: LGTM! Consistent sealed interface pattern.

The VerifyPurchaseWithProviderResult sealed interface follows the same pattern as other result types in the file, enabling type-safe provider-specific results.


2223-2283: LGTM! Platform-specific verification props.

Both RequestVerifyPurchaseWithIapkitAppleProps and RequestVerifyPurchaseWithIapkitGoogleProps data classes correctly handle their respective platform parameters with proper null safety and clear documentation.


2328-2345: LGTM! Complete provider verification integration.

The VerifyPurchaseWithProviderProps data class and corresponding mutation infrastructure are correctly implemented and follow the established patterns. The new verifyPurchaseWithProvider mutation is properly integrated into the MutationResolver interface, typealias, and MutationHandlers data class.

Also applies to: 2534-2537, 2666-2666, 2689-2690


2285-2326: Validation is correctly implemented at runtime.

The conditional requirements (apple required when store is Apple, google required when store is Google) are properly enforced in the verifyPurchaseWithIapkit function (packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt). When props.store is Apple, the function throws IllegalArgumentException if props.apple is null (line 96); similarly for Google store and props.google (line 116). Tests confirm this validation works correctly.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/docs/src/pages/docs/types.tsx (1)

1733-1900: Align VerifyPurchase and IAPKit docs with generated types and backend behavior

The new docs sections generally mirror the schema, but there are a couple of mismatches to fix:

  • In VerifyPurchaseResultAndroid, deferredSku should be a string (SKU) and nullable, and cancelReason is nullable in the generated models. The current doc signature (deferredSku: number | null; cancelReason: string;) doesn’t reflect that.
  • The RequestVerifyPurchaseWithIapkitProps interface documents an endpoint?: string field, but the generated Kotlin/GraphQL types and verifyPurchaseWithIapkit implementation don’t expose or use this property.

To keep the docs authoritative, it’s worth updating these definitions to match the generated types (or extending the schema/implementation to support endpoint if that’s intentional).

Also applies to: 2193-2260

🧹 Nitpick comments (7)
packages/docs/src/pages/docs/apis.tsx (2)

489-512: Nice renewalInfoIOS callout; consider extracting shared styles

The new green callout and inline <code> styling look good and readable. If we expect more of these “feature highlight” boxes, consider moving this style into a reusable class or small component to keep docs theming consistent and easier to tweak later.


624-695: verifyPurchaseWithProvider docs are clear; maybe clarify provider payload expectations

The new verifyPurchaseWithProvider section reads well and matches the new provider types. Consider briefly stating whether iapkit.apple / iapkit.google are both optional and what minimum combination is valid (e.g., “at least one of apple or google must be provided”) so integrators know how to form partial requests.

packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift (1)

7-44: Provider tests exercise the store path correctly; consider aligning naming and cleanup.

The happy‑path and empty‑result tests correctly validate that OpenIapStore.verifyPurchaseWithProvider returns the wired iapkit array with the expected store/valid flags and that the empty provider result is surfaced as an empty array. The FakeVerifyPurchaseModule stub is also sufficient for isolating the store behavior.

Two small nits you may want to address:

  • testStoreReturnsNilWhenProviderResultIsNil now asserts result.count == 0 while verifyPurchaseWithProvider returns a non‑optional array; renaming the test (and/or its description) to reflect “empty result” rather than “nil” would avoid confusion.
  • SwiftLint’s redundant_void_return warnings on stub methods like restorePurchases, finishTransaction, and deepLinkToSubscriptions can be silenced by dropping the explicit -> Void return type and bare () bodies, if you care about a clean lint run in tests.

Also applies to: 47-80, 88-139

packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt (1)

39-42: Wrap verifyPurchaseWithIapkit store‑validation errors into OpenIapError for consistency.

The Horizon implementation of verifyPurchaseWithProvider correctly:

  • restricts providers to PurchaseVerificationProvider.Iapkit, and
  • requires props.iapkit, forwarding to verifyPurchaseWithIapkit and returning VerifyPurchaseWithProviderResult.

One behavioral gap vs the Apple side is that verifyPurchaseWithIapkit on Android throws an IllegalArgumentException when props.store is not IapkitStore.Google, whereas the Apple module maps equivalent misuse to a developer‑facing purchase error. To keep the public surface consistent and avoid leaking generic exceptions, consider mapping that case to OpenIapError.DeveloperError here:

-    override val verifyPurchaseWithProvider: MutationVerifyPurchaseWithProviderHandler = { props ->
-        if (props.provider != PurchaseVerificationProvider.Iapkit) {
-            throw OpenIapError.FeatureNotSupported
-        }
-        val options = props.iapkit ?: throw OpenIapError.DeveloperError
-        VerifyPurchaseWithProviderResult(
-            iapkit = verifyPurchaseWithIapkit(options, TAG),
-            provider = props.provider
-        )
-    }
+    override val verifyPurchaseWithProvider: MutationVerifyPurchaseWithProviderHandler = { props ->
+        if (props.provider != PurchaseVerificationProvider.Iapkit) {
+            throw OpenIapError.FeatureNotSupported
+        }
+        val options = props.iapkit ?: throw OpenIapError.DeveloperError
+        try {
+            VerifyPurchaseWithProviderResult(
+                iapkit = verifyPurchaseWithIapkit(options, TAG),
+                provider = props.provider
+            )
+        } catch (e: IllegalArgumentException) {
+            // E.g. non‑Google store for Android IAPKit verification
+            throw OpenIapError.DeveloperError
+        }
+    }

This keeps “bad props” scenarios in the same error family across Apple and Horizon while preserving the existing OpenIapError.InvalidReceipt / OpenIapError.NetworkError mapping from the validator.

Also applies to: 660-669, 687-699

packages/apple/Tests/OpenIapTests/VerifyPurchaseTests.swift (1)

5-58: VerifyPurchase tests cover both “iOS result” and “Android result on Apple” behaviors well.

The tests correctly assert that:

  • an iOS VerifyPurchaseResultIOS flows through OpenIapStore.verifyPurchase(sku:) unchanged, and
  • an Android variant triggers .featureNotSupported, matching the store’s platform guard.

FakeOpenIapModule’s use of the new VerifyPurchase types (including VerifyPurchaseWithProviderResult) keeps it aligned with the production protocol.

If you find yourself adding more tests around provider flows, you might consider reusing this fake in VerifyPurchaseWithProviderTests (or extracting a shared helper) to avoid duplicating stub implementations, but that’s purely for test maintenance.

Also applies to: 62-120

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

553-607: VerifyPurchase and IAPKit provider flow look solid; a couple of optional refinements to consider.

The new iOS verification path is well‑structured:

  • performVerifyPurchaseIOS computes VerifyPurchaseResultIOS from storeProduct(for:), product.latestTransaction, and the local receipt; callers get a single “isValid + enriched context” object instead of multiple APIs.
  • validateReceiptIOS, validateReceipt, and verifyPurchase now consistently front this logic via VerifyPurchaseProps / VerifyPurchaseResult, which matches the generated models and store API.
  • verifyPurchaseWithProvider/verifyPurchaseWithIapkit enforce provider == .iapkit, require appropriate Apple payload, and map HTTP/network/JSON failures into PurchaseError codes with useful logging.

Two design choices you may want to confirm or evolve over time:

  • performVerifyPurchaseIOS currently swallows all underlying errors (missing SKU, verification failure, etc.) and reports isValid = false with empty JWS/transaction. If some callers need to distinguish “invalid receipt” from “couldn’t verify due to a service/config issue”, you might later introduce an additional flag or error code in the result instead of treating everything as “not valid”.
  • The IAPKit endpoint is hard‑coded to https://iapkit.com/purchase/verify. If you expect staged environments or want easier unit testing of the HTTP client, threading this URL (or a base URL) via configuration/props would make the behavior more flexible without changing the public signature.

If the current semantics (best‑effort boolean validity and fixed production endpoint) are deliberate for v1, the implementation itself looks correct.

Also applies to: 608-718

packages/apple/Sources/Models/Types.swift (1)

153-162: Generated IAPKit/provider models align with schema; lint warnings are purely stylistic.

The newly generated enums (IapkitEnvironment, IapkitStore, PurchaseVerificationProvider), result structs (VerifyPurchaseResult*, VerifyPurchaseWithProviderResult, RequestVerifyPurchaseWithIapkitResult), input props (RequestVerifyPurchaseWithIapkit*Props, VerifyPurchaseProps, VerifyPurchaseWithProviderProps, VerifyPurchaseAndroidOptions), and the VerifyPurchaseResult union all match the GraphQL schema and the Kotlin/TypeScript models referenced elsewhere in the PR. They also line up with how the Apple and Horizon modules consume these types.

The SwiftLint warnings here (redundant string raw values, long type names like RequestVerifyPurchaseWithIapkitAppleProps and MutationVerifyPurchaseWithProviderHandler) are expected in an auto‑generated file. If they become noisy, it’s better to adjust the generator or lint configuration (e.g., exclude this file or relax those rules for generated code) rather than hand‑editing the output.

Also applies to: 202-205, 560-637, 974-1080, 1379-1382, 1525-1552, 1621-1681

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a754031 and 0f86d2b.

⛔ Files ignored due to path filters (4)
  • packages/gql/src/generated/Types.kt is excluded by !**/generated/**
  • packages/gql/src/generated/Types.swift is excluded by !**/generated/**
  • packages/gql/src/generated/types.dart is excluded by !**/generated/**
  • packages/gql/src/generated/types.ts is excluded by !**/generated/**
📒 Files selected for processing (24)
  • packages/apple/CONTRIBUTING.md (1 hunks)
  • packages/apple/Sources/Models/OpenIapSerialization.swift (1 hunks)
  • packages/apple/Sources/Models/Types.swift (13 hunks)
  • packages/apple/Sources/OpenIapModule.swift (3 hunks)
  • packages/apple/Sources/OpenIapProtocol.swift (1 hunks)
  • packages/apple/Sources/OpenIapStore.swift (2 hunks)
  • packages/apple/Tests/OpenIapTests/VerifyPurchaseTests.swift (5 hunks)
  • packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift (1 hunks)
  • packages/docs/src/components/SearchModal.tsx (2 hunks)
  • packages/docs/src/pages/docs/apis.tsx (5 hunks)
  • packages/docs/src/pages/docs/types.tsx (5 hunks)
  • packages/google/CONTRIBUTING.md (1 hunks)
  • packages/google/Example/src/main/java/dev/hyo/martie/screens/SubscriptionFlowScreen.kt (1 hunks)
  • packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt (3 hunks)
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (11 hunks)
  • packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt (4 hunks)
  • packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt (5 hunks)
  • packages/google/openiap/src/test/java/dev/hyo/openiap/ReceiptValidatorTest.kt (4 hunks)
  • packages/gql/CONVENTION.md (1 hunks)
  • packages/gql/src/api-ios.graphql (1 hunks)
  • packages/gql/src/api.graphql (1 hunks)
  • packages/gql/src/type-android.graphql (1 hunks)
  • packages/gql/src/type-ios.graphql (1 hunks)
  • packages/gql/src/type.graphql (2 hunks)
✅ Files skipped from review due to trivial changes (3)
  • packages/google/Example/src/main/java/dev/hyo/martie/screens/SubscriptionFlowScreen.kt
  • packages/gql/CONVENTION.md
  • packages/docs/src/components/SearchModal.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/gql/src/api.graphql
  • packages/google/openiap/src/test/java/dev/hyo/openiap/ReceiptValidatorTest.kt
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 17
File: packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt:620-629
Timestamp: 2025-10-18T05:46:51.596Z
Learning: In packages/google/openiap/src/main/java/**/*.kt (Android-only package): DO NOT add Android suffix to function names, even for Android-specific APIs. Exception: Only use Android suffix for cross-platform API types (e.g., ProductAndroid, PurchaseAndroid) that contrast with iOS types. Examples of correct naming: isHorizonEnvironment(context: Context), buildModule(context: Context), acknowledgePurchase(), consumePurchase().
📚 Learning: 2025-10-18T05:46:51.596Z
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 17
File: packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt:620-629
Timestamp: 2025-10-18T05:46:51.596Z
Learning: In packages/google/openiap/src/main/java/**/*.kt (Android-only package): DO NOT add Android suffix to function names, even for Android-specific APIs. Exception: Only use Android suffix for cross-platform API types (e.g., ProductAndroid, PurchaseAndroid) that contrast with iOS types. Examples of correct naming: isHorizonEnvironment(context: Context), buildModule(context: Context), acknowledgePurchase(), consumePurchase().

Applied to files:

  • packages/apple/CONTRIBUTING.md
  • packages/gql/src/type-android.graphql
  • packages/docs/src/pages/docs/types.tsx
  • packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
  • packages/google/CONTRIBUTING.md
  • packages/apple/Sources/OpenIapModule.swift
  • packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt
  • packages/apple/Tests/OpenIapTests/VerifyPurchaseTests.swift
  • packages/gql/src/type.graphql
  • packages/apple/Sources/Models/Types.swift
  • packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
🧬 Code graph analysis (11)
packages/apple/Sources/Models/OpenIapSerialization.swift (1)
packages/gql/src/generated/types.ts (1)
  • VerifyPurchaseProps (842-847)
packages/apple/Sources/OpenIapStore.swift (6)
packages/apple/Tests/OpenIapTests/VerifyPurchaseTests.swift (3)
  • validateReceipt (109-111)
  • verifyPurchase (113-115)
  • verifyPurchaseWithProvider (117-119)
packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift (3)
  • validateReceipt (125-127)
  • verifyPurchase (129-131)
  • verifyPurchaseWithProvider (133-135)
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (5)
  • validateReceipt (2543-2543)
  • verifyPurchase (2547-2547)
  • verifyPurchaseWithProvider (2551-2551)
  • iapkit (1737-1759)
  • iapkit (2342-2359)
packages/gql/src/generated/Types.kt (5)
  • validateReceipt (2610-2610)
  • verifyPurchase (2614-2614)
  • verifyPurchaseWithProvider (2618-2618)
  • iapkit (1804-1826)
  • iapkit (2409-2426)
packages/apple/Sources/OpenIapModule.swift (2)
  • verifyPurchase (589-592)
  • verifyPurchaseWithProvider (594-606)
packages/gql/src/generated/types.ts (1)
  • VerifyPurchaseProps (842-847)
packages/apple/Sources/OpenIapProtocol.swift (4)
packages/apple/Tests/OpenIapTests/VerifyPurchaseTests.swift (4)
  • validateReceiptIOS (102-107)
  • validateReceipt (109-111)
  • verifyPurchase (113-115)
  • verifyPurchaseWithProvider (117-119)
packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift (4)
  • validateReceiptIOS (118-123)
  • validateReceipt (125-127)
  • verifyPurchase (129-131)
  • verifyPurchaseWithProvider (133-135)
packages/apple/Sources/OpenIapModule.swift (4)
  • deprecated (553-556)
  • deprecated (584-587)
  • verifyPurchase (589-592)
  • verifyPurchaseWithProvider (594-606)
packages/apple/Sources/OpenIapStore.swift (3)
  • deprecated (371-374)
  • verifyPurchase (376-386)
  • verifyPurchaseWithProvider (388-391)
packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt (2)
packages/gql/src/generated/types.ts (1)
  • VerifyPurchaseWithProviderResult (888-892)
packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt (1)
  • verifyPurchaseWithIapkit (86-143)
packages/apple/Sources/OpenIapModule.swift (5)
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (29)
  • verifyPurchase (2547-2547)
  • verifyPurchaseWithProvider (2551-2551)
  • iapkit (1737-1759)
  • iapkit (2342-2359)
  • code (1200-1222)
  • message (1362-1381)
  • rawValue (15-44)
  • rawValue (46-157)
  • rawValue (162-185)
  • rawValue (187-209)
  • rawValue (211-226)
  • rawValue (228-243)
  • rawValue (245-261)
  • rawValue (263-284)
  • rawValue (286-304)
  • rawValue (306-321)
  • rawValue (323-344)
  • rawValue (346-373)
  • rawValue (375-387)
  • rawValue (389-404)
  • rawValue (406-430)
  • store (1472-1491)
  • request (2007-2058)
  • apiKey (2255-2290)
  • error (706-731)
  • error (736-761)
  • appId (541-593)
  • appId (2193-2222)
  • packageName (2224-2253)
packages/gql/src/generated/Types.kt (15)
  • verifyPurchase (2614-2614)
  • verifyPurchaseWithProvider (2618-2618)
  • iapkit (1804-1826)
  • iapkit (2409-2426)
  • code (1267-1289)
  • message (1429-1448)
  • rawValue (14-43)
  • store (1539-1558)
  • request (2074-2125)
  • apiKey (2322-2357)
  • error (773-798)
  • error (803-828)
  • appId (608-660)
  • appId (2260-2289)
  • packageName (2291-2320)
packages/gql/src/generated/types.ts (3)
  • VerifyPurchaseResultIOS (872-881)
  • VerifyPurchaseWithProviderResult (888-892)
  • RequestVerifyPurchaseWithIapkitResult (773-776)
packages/apple/Sources/OpenIapStore.swift (2)
  • verifyPurchase (376-386)
  • verifyPurchaseWithProvider (388-391)
packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt (1)
  • verifyPurchaseWithIapkit (86-143)
packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt (1)
packages/google/openiap/src/test/java/dev/hyo/openiap/ReceiptValidatorTest.kt (1)
  • setRequestProperty (256-260)
packages/apple/Tests/OpenIapTests/VerifyPurchaseTests.swift (6)
packages/gql/src/generated/types.ts (4)
  • VerifyPurchaseResultIOS (872-881)
  • VerifyPurchaseResultAndroid (851-870)
  • VerifyPurchaseWithProviderResult (888-892)
  • PurchaseError (481-485)
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (9)
  • isValid (1698-1735)
  • iapkit (1737-1759)
  • iapkit (2342-2359)
  • validateReceiptIOS (2629-2629)
  • code (1200-1222)
  • message (1362-1381)
  • validateReceipt (2543-2543)
  • verifyPurchase (2547-2547)
  • verifyPurchaseWithProvider (2551-2551)
packages/gql/src/generated/Types.kt (9)
  • isValid (1765-1802)
  • iapkit (1804-1826)
  • iapkit (2409-2426)
  • validateReceiptIOS (2696-2696)
  • code (1267-1289)
  • message (1429-1448)
  • validateReceipt (2610-2610)
  • verifyPurchase (2614-2614)
  • verifyPurchaseWithProvider (2618-2618)
packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift (4)
  • validateReceiptIOS (118-123)
  • validateReceipt (125-127)
  • verifyPurchase (129-131)
  • verifyPurchaseWithProvider (133-135)
packages/apple/Sources/OpenIapModule.swift (2)
  • verifyPurchase (589-592)
  • verifyPurchaseWithProvider (594-606)
packages/apple/Sources/OpenIapStore.swift (2)
  • verifyPurchase (376-386)
  • verifyPurchaseWithProvider (388-391)
packages/apple/Sources/Models/Types.swift (5)
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (15)
  • iapkit (1737-1759)
  • iapkit (2342-2359)
  • store (1472-1491)
  • autoRenewing (1629-1696)
  • isValid (1698-1735)
  • appId (541-593)
  • appId (2193-2222)
  • packageName (2224-2253)
  • apiKey (2255-2290)
  • accessToken (2292-2315)
  • androidOptions (2317-2340)
  • validateReceipt (2543-2543)
  • verifyPurchase (2547-2547)
  • verifyPurchaseWithProvider (2551-2551)
  • validateReceiptIOS (2629-2629)
packages/gql/src/generated/Types.kt (15)
  • iapkit (1804-1826)
  • iapkit (2409-2426)
  • store (1539-1558)
  • autoRenewing (1696-1763)
  • isValid (1765-1802)
  • appId (608-660)
  • appId (2260-2289)
  • packageName (2291-2320)
  • apiKey (2322-2357)
  • accessToken (2359-2382)
  • androidOptions (2384-2407)
  • validateReceipt (2610-2610)
  • verifyPurchase (2614-2614)
  • verifyPurchaseWithProvider (2618-2618)
  • validateReceiptIOS (2696-2696)
packages/apple/Tests/OpenIapTests/VerifyPurchaseTests.swift (4)
  • validateReceipt (109-111)
  • verifyPurchase (113-115)
  • verifyPurchaseWithProvider (117-119)
  • validateReceiptIOS (102-107)
packages/apple/Sources/OpenIapModule.swift (2)
  • verifyPurchase (589-592)
  • verifyPurchaseWithProvider (594-606)
packages/apple/Sources/OpenIapStore.swift (2)
  • verifyPurchase (376-386)
  • verifyPurchaseWithProvider (388-391)
packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt (3)
packages/gql/src/generated/types.ts (1)
  • VerifyPurchaseWithProviderResult (888-892)
packages/apple/Sources/OpenIapModule.swift (1)
  • verifyPurchaseWithIapkit (608-663)
packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt (1)
  • verifyPurchaseWithIapkit (86-143)
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (1)
packages/gql/src/generated/types.ts (10)
  • RequestVerifyPurchaseWithIapkitResult (773-776)
  • VerifyPurchaseResultAndroid (851-870)
  • VerifyPurchaseResultIOS (872-881)
  • VerifyPurchaseWithProviderResult (888-892)
  • RequestVerifyPurchaseWithIapkitAppleProps (744-751)
  • RequestVerifyPurchaseWithIapkitGoogleProps (753-760)
  • RequestVerifyPurchaseWithIapkitProps (762-771)
  • VerifyPurchaseAndroidOptions (835-840)
  • VerifyPurchaseProps (842-847)
  • VerifyPurchaseWithProviderProps (883-886)
packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift (3)
packages/apple/Sources/OpenIapModule.swift (2)
  • verifyPurchaseWithProvider (594-606)
  • verifyPurchase (589-592)
packages/apple/Sources/OpenIapStore.swift (2)
  • verifyPurchaseWithProvider (388-391)
  • verifyPurchase (376-386)
packages/gql/src/generated/types.ts (5)
  • RequestVerifyPurchaseWithIapkitResult (773-776)
  • VerifyPurchaseWithProviderResult (888-892)
  • VerifyPurchaseWithProviderProps (883-886)
  • IapkitStore (177-177)
  • PurchaseError (481-485)
🪛 SwiftLint (0.57.0)
packages/apple/Sources/Models/Types.swift

[Warning] 154-154: String enum values can be omitted when they are equal to the enumcase name

(redundant_string_enum_value)


[Warning] 155-155: String enum values can be omitted when they are equal to the enumcase name

(redundant_string_enum_value)


[Warning] 159-159: String enum values can be omitted when they are equal to the enumcase name

(redundant_string_enum_value)


[Warning] 160-160: String enum values can be omitted when they are equal to the enumcase name

(redundant_string_enum_value)


[Warning] 203-203: String enum values can be omitted when they are equal to the enumcase name

(redundant_string_enum_value)


[Warning] 974-974: Type name 'RequestVerifyPurchaseWithIapkitAppleProps' should be between 3 and 40 characters long

(type_name)


[Warning] 993-993: Type name 'RequestVerifyPurchaseWithIapkitGoogleProps' should be between 3 and 40 characters long

(type_name)


[Warning] 1527-1527: Type name 'MutationVerifyPurchaseWithProviderHandler' should be between 3 and 40 characters long

(type_name)

packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift

[Warning] 104-104: Returning Void in a function declaration is redundant

(redundant_void_return)


[Warning] 108-108: Returning Void in a function declaration is redundant

(redundant_void_return)


[Warning] 154-154: Returning Void in a function declaration is redundant

(redundant_void_return)

⏰ 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 (12)
packages/google/CONTRIBUTING.md (1)

106-106: Now I need to check how the public API exports these types with the OpenIap prefix:

Based on the verification, I found that the codebase contains VerifyPurchaseResultAndroid and VerifyPurchaseResultIOS, which are platform-specific type implementations of the sealed interface VerifyPurchaseResult. However, the CONTRIBUTING.md documentation shows OpenIapVerifyPurchaseResult without platform suffixes as the example type.

The discrepancy between the documented type name and the actual platform-specific implementations in the codebase cannot be fully resolved without inspecting the public API export layer (which may apply the OpenIap prefix via type aliases or public interfaces not visible in the current search).

Verify that the VerifyPurchase types in the public API match the documented examples.

Specifically, confirm whether:

  • The public API exposes OpenIapVerifyPurchaseResult as a generic/sealed interface with platform-specific variants (OpenIapVerifyPurchaseResultAndroid, OpenIapVerifyPurchaseResultIOS), or
  • If the documentation should be updated to reference the actual type names and platform-specific variants that users will import.
packages/gql/src/type-android.graphql (1)

156-182: VerifyPurchase Android GraphQL types look consistent and well‑shaped

VerifyPurchaseAndroidOptions and VerifyPurchaseResultAndroid line up with the Kotlin/TS generated models and the Google Play validation flow (packageName + productToken + sku + isSub), so the schema surface here looks correct and coherent with the rest of the PR.

packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt (1)

36-39: Provider-based verifyPurchase wiring is consistent and safe

verifyPurchaseWithProvider correctly enforces provider == PurchaseVerificationProvider.Iapkit, validates that iapkit options are present, delegates to verifyPurchaseWithIapkit, and exposes the result via mutationHandlers. This cleanly layers the new provider flow alongside the existing verifyPurchase without changing old behavior.

Also applies to: 66-66, 811-820, 849-851

packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (1)

211-244: Generated verify-purchase / provider types and handlers are internally consistent

The new IAPKit enums, verify‑purchase result models, props (including RequestVerifyPurchaseWithIapkit*, VerifyPurchaseProps, VerifyPurchaseWithProviderProps), the VerifyPurchaseResult sealed interface, and the matching mutation/query handler aliases all line up with the GraphQL/TS surfaces and with the module wiring in this PR. The fromJson/toJson implementations are mechanically correct and preserve nullability, so the generated layer looks solid.

Also applies to: 375-387, 1472-1491, 1629-1760, 2193-2359, 2429-2441, 2678-2681, 2703-2705, 2726-2727

packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt (1)

29-84: Google Play validation refactor to VerifyPurchaseProps looks correct

Switching validateReceiptWithGooglePlay to VerifyPurchaseProps and VerifyPurchaseResultAndroid preserves the previous behavior: required Android options are validated up front, the URL path matches the products/subscriptions split, and errors are cleanly mapped to OpenIapError.InvalidReceipt / OpenIapError.NetworkError. The Gson parse into VerifyPurchaseResultAndroid is straightforward and consistent with the new model.

Also applies to: 71-73

packages/docs/src/pages/docs/apis.tsx (2)

578-605: verifyPurchase docs correctly aligned with new types

The “Verify Purchase” section now matches the unified API (VerifyPurchaseProps / VerifyPurchaseResult) and clearly explains the deprecation path from validateReceipt / validateReceiptIOS. This looks consistent with the GraphQL schema changes and is safe to ship.


1109-1110: validateReceiptIOS snippet matches new VerifyPurchase surface

The updated iOS snippet (VerifyPurchaseProps in / VerifyPurchaseResultIOS out) lines up with the schema and Swift APIs while keeping the field deprecated in favor of verifyPurchase. No issues here.

packages/gql/src/type-ios.graphql (1)

226-243: VerifyPurchaseResultIOS rename looks consistent and non‑disruptive

Renaming the result type to VerifyPurchaseResultIOS while keeping the field shapes the same preserves behavior and matches the new VerifyPurchase naming across the schema. latestTransaction: Purchase also stays compatible with the existing Purchase union.

packages/gql/src/api-ios.graphql (1)

64-69: validateReceiptIOS GraphQL signature correctly migrated to VerifyPurchase types

The field now uses VerifyPurchaseProps and VerifyPurchaseResultIOS while remaining deprecated in favor of verifyPurchase, which aligns cleanly with the rest of the schema and docs. Looks good.

packages/apple/Sources/Models/OpenIapSerialization.swift (1)

116-118: verifyPurchaseProps helper is consistent with existing decoding utilities

This helper cleanly mirrors purchaseOptions(from:) and other decode wrappers, routing through the shared decode(object:as:) path. It keeps the serialization surface in sync with the new VerifyPurchaseProps type without introducing new failure modes beyond the existing JSONSerialization checks.

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

371-391: New verifyPurchase/validateReceipt bridging and provider API look consistent.

validateReceipt(sku:) cleanly forwards into verifyPurchase(sku:) while remaining deprecated, and verifyPurchase(sku:) correctly constrains the union to the iOS result, throwing .featureNotSupported otherwise. The new verifyPurchaseWithProvider(_:) thinly delegates to the module and exposes the IAPKit result list, which matches the module and model types.

packages/gql/src/type.graphql (1)

33-47: Schema for VerifyPurchase and provider‑based verification matches native models.

The new enums and input/output types for VerifyPurchaseProps, VerifyPurchaseResult, and VerifyPurchaseWithProvider* line up with the generated Swift/Kotlin models and the platform implementations (Apple’s and Horizon’s modules enforce the per‑store requirements). I don’t see schema‑level issues; this looks ready for codegen.

Also applies to: 206-287

Comment thread packages/apple/CONTRIBUTING.md Outdated
Comment thread packages/apple/Sources/OpenIapProtocol.swift
Comment thread packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt Outdated
@hyochan hyochan added the 🎯 feature New feature label Nov 22, 2025
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/apple/Sources/Models/ReceiptValidationCompat.swift (1)

1-6: LGTM! Consider adding deprecation warnings.

The compatibility layer is well-structured and provides a clean migration path. To guide developers toward the new API names, consider marking these aliases as deprecated:

 // Compatibility aliases for legacy receipt validation APIs
 // These map old ReceiptValidation* names to the new VerifyPurchase* types.

+@available(*, deprecated, renamed: "VerifyPurchaseProps")
 public typealias ReceiptValidationProps = VerifyPurchaseProps
+@available(*, deprecated, renamed: "VerifyPurchaseResult")
 public typealias ReceiptValidationResult = VerifyPurchaseResult
+@available(*, deprecated, renamed: "VerifyPurchaseResultIOS")
 public typealias ReceiptValidationResultIOS = VerifyPurchaseResultIOS

This will display compiler warnings at call sites, helping developers migrate to the new names while maintaining backward compatibility.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0f86d2b and ecc463b.

📒 Files selected for processing (3)
  • packages/apple/Sources/Models/ReceiptValidationCompat.swift (1 hunks)
  • packages/docs/src/pages/docs/types.tsx (5 hunks)
  • packages/google/openiap/src/main/java/dev/hyo/openiap/compat/ReceiptValidationCompat.kt (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 17
File: packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt:620-629
Timestamp: 2025-10-18T05:46:51.596Z
Learning: In packages/google/openiap/src/main/java/**/*.kt (Android-only package): DO NOT add Android suffix to function names, even for Android-specific APIs. Exception: Only use Android suffix for cross-platform API types (e.g., ProductAndroid, PurchaseAndroid) that contrast with iOS types. Examples of correct naming: isHorizonEnvironment(context: Context), buildModule(context: Context), acknowledgePurchase(), consumePurchase().
📚 Learning: 2025-10-18T05:46:51.596Z
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 17
File: packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt:620-629
Timestamp: 2025-10-18T05:46:51.596Z
Learning: In packages/google/openiap/src/main/java/**/*.kt (Android-only package): DO NOT add Android suffix to function names, even for Android-specific APIs. Exception: Only use Android suffix for cross-platform API types (e.g., ProductAndroid, PurchaseAndroid) that contrast with iOS types. Examples of correct naming: isHorizonEnvironment(context: Context), buildModule(context: Context), acknowledgePurchase(), consumePurchase().

Applied to files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/compat/ReceiptValidationCompat.kt
  • packages/docs/src/pages/docs/types.tsx
⏰ 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 (1)
packages/docs/src/pages/docs/types.tsx (1)

1733-1899: LGTM! Consistent rename across the documentation.

The renaming from ReceiptValidation* to VerifyPurchase* is applied consistently across section headers, interface names, and union types. This aligns with the PR objective to refactor receipt validation constructs.

Comment thread packages/docs/src/pages/docs/types.tsx Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
packages/docs/src/pages/docs/types.tsx (1)

2210-2238: Critical: The documented endpoint field is still missing from the interface.

This is a duplicate of a previous review comment. The RequestVerifyPurchaseWithIapkitProps interface requires an endpoint field to specify the absolute URL to the IAPKit /purchase/verify endpoint. Without this field, the HTTP client cannot make verification requests.

Apply this diff to add the missing endpoint field:

 interface RequestVerifyPurchaseWithIapkitProps {
+  endpoint: string; // Absolute URL to the IAPKit /purchase/verify endpoint
   apiKey?: string; // sent as Authorization: Bearer {apiKey}
   store?: IapkitStore; // defaults to 'apple' on Apple, 'google' on Android
   apple?: {

And update the field documentation:

 <ul>
   <li>
+    <code>endpoint</code> — Absolute URL to the IAPKit /purchase/verify endpoint
+  </li>
+  <li>
     <code>apiKey</code> — Optional bearer token for the Authorization
     header.
   </li>
🧹 Nitpick comments (1)
packages/docs/src/pages/docs/types.tsx (1)

2240-2255: Consider adding usage examples for the new provider-based verification.

The result types are well-defined, but the documentation would benefit from practical usage examples showing how to call verifyPurchaseWithProvider with IAPKit.

Consider adding a usage example like:

// Example: Verify purchase with IAPKit
const result = await verifyPurchaseWithProvider({
  provider: 'iapkit',
  props: {
    endpoint: 'https://api.iapkit.com/purchase/verify',
    apiKey: 'your-api-key',
    store: 'apple',
    apple: {
      receipt: purchaseToken,
      environment: 'production',
      appId: 'your.app.id'
    }
  }
});

if (result.iapkit[0].valid) {
  console.log('Purchase verified successfully');
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ecc463b and a9fbfa9.

📒 Files selected for processing (2)
  • packages/docs/src/pages/docs/types.tsx (5 hunks)
  • packages/google/openiap/src/main/java/dev/hyo/openiap/compat/ReceiptValidationCompat.kt (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 17
File: packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt:620-629
Timestamp: 2025-10-18T05:46:51.596Z
Learning: In packages/google/openiap/src/main/java/**/*.kt (Android-only package): DO NOT add Android suffix to function names, even for Android-specific APIs. Exception: Only use Android suffix for cross-platform API types (e.g., ProductAndroid, PurchaseAndroid) that contrast with iOS types. Examples of correct naming: isHorizonEnvironment(context: Context), buildModule(context: Context), acknowledgePurchase(), consumePurchase().
📚 Learning: 2025-10-18T05:46:51.596Z
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 17
File: packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt:620-629
Timestamp: 2025-10-18T05:46:51.596Z
Learning: In packages/google/openiap/src/main/java/**/*.kt (Android-only package): DO NOT add Android suffix to function names, even for Android-specific APIs. Exception: Only use Android suffix for cross-platform API types (e.g., ProductAndroid, PurchaseAndroid) that contrast with iOS types. Examples of correct naming: isHorizonEnvironment(context: Context), buildModule(context: Context), acknowledgePurchase(), consumePurchase().

Applied to files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/compat/ReceiptValidationCompat.kt
  • packages/docs/src/pages/docs/types.tsx
🧬 Code graph analysis (1)
packages/google/openiap/src/main/java/dev/hyo/openiap/compat/ReceiptValidationCompat.kt (1)
packages/google/openiap/src/play/java/dev/hyo/openiap/store/OpenIapStoreExtensions.kt (1)
  • ReplaceWith (33-39)
⏰ 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 (4)
packages/google/openiap/src/main/java/dev/hyo/openiap/compat/ReceiptValidationCompat.kt (1)

7-23: LGTM! Previous feedback addressed.

The deprecation annotations have been properly implemented for all three type aliases. Each annotation includes:

  • A clear message directing users to the new type name
  • A ReplaceWith expression with the correct import path for IDE-assisted migration

This provides excellent backward compatibility while encouraging migration to the new VerifyPurchase* naming.

packages/docs/src/pages/docs/types.tsx (3)

1733-1774: LGTM! Clean rename from ReceiptValidation to VerifyPurchase.

The section header and type naming are consistent with the PR objectives. The interface structure and field documentation are clear and complete.


1776-1900: LGTM! Comprehensive result type documentation.

The VerifyPurchaseResult types are well-documented for both platforms, with clear field references and proper union type definition.


2194-2209: LGTM! Clear introduction of provider-based verification.

The new Purchase Verification Providers section is well-structured with clear type definitions for IapkitStore and IapkitEnvironment.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a9fbfa9 and dc23cd2.

📒 Files selected for processing (3)
  • packages/apple/CONTRIBUTING.md (1 hunks)
  • packages/apple/Sources/Models/ReceiptValidationCompat.swift (1 hunks)
  • packages/apple/Sources/OpenIapProtocol.swift (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/apple/Sources/Models/ReceiptValidationCompat.swift
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 46
File: packages/docs/src/pages/docs/types.tsx:2210-2238
Timestamp: 2025-11-22T19:30:03.835Z
Learning: In the OpenIAP SDK, the IAPKit verification endpoint is handled internally using a default base URL. Users no longer need to provide an `endpoint` field in `RequestVerifyPurchaseWithIapkitProps` - the SDK automatically routes to the correct IAPKit `/purchase/verify` endpoint.
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 17
File: packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt:620-629
Timestamp: 2025-10-18T05:46:51.596Z
Learning: In packages/google/openiap/src/main/java/**/*.kt (Android-only package): DO NOT add Android suffix to function names, even for Android-specific APIs. Exception: Only use Android suffix for cross-platform API types (e.g., ProductAndroid, PurchaseAndroid) that contrast with iOS types. Examples of correct naming: isHorizonEnvironment(context: Context), buildModule(context: Context), acknowledgePurchase(), consumePurchase().
📚 Learning: 2025-10-18T05:46:51.596Z
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 17
File: packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt:620-629
Timestamp: 2025-10-18T05:46:51.596Z
Learning: In packages/google/openiap/src/main/java/**/*.kt (Android-only package): DO NOT add Android suffix to function names, even for Android-specific APIs. Exception: Only use Android suffix for cross-platform API types (e.g., ProductAndroid, PurchaseAndroid) that contrast with iOS types. Examples of correct naming: isHorizonEnvironment(context: Context), buildModule(context: Context), acknowledgePurchase(), consumePurchase().

Applied to files:

  • packages/apple/CONTRIBUTING.md
  • packages/apple/Sources/OpenIapProtocol.swift
📚 Learning: 2025-11-22T19:30:03.835Z
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 46
File: packages/docs/src/pages/docs/types.tsx:2210-2238
Timestamp: 2025-11-22T19:30:03.835Z
Learning: In the OpenIAP SDK, the IAPKit verification endpoint is handled internally using a default base URL. Users no longer need to provide an `endpoint` field in `RequestVerifyPurchaseWithIapkitProps` - the SDK automatically routes to the correct IAPKit `/purchase/verify` endpoint.

Applied to files:

  • packages/apple/Sources/OpenIapProtocol.swift
🧬 Code graph analysis (1)
packages/apple/Sources/OpenIapProtocol.swift (3)
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (7)
  • validateReceiptIOS (2629-2629)
  • message (1362-1381)
  • validateReceipt (2543-2543)
  • verifyPurchase (2547-2547)
  • verifyPurchaseWithProvider (2551-2551)
  • code (1200-1222)
  • androidOptions (2317-2340)
packages/apple/Sources/OpenIapStore.swift (3)
  • deprecated (371-374)
  • verifyPurchase (376-386)
  • verifyPurchaseWithProvider (388-391)
packages/apple/Sources/OpenIapModule.swift (4)
  • deprecated (553-556)
  • deprecated (584-587)
  • verifyPurchase (589-592)
  • verifyPurchaseWithProvider (594-606)
⏰ 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 (2)
packages/apple/CONTRIBUTING.md (1)

45-45: Changes successfully resolve previous critical issue.

The example type list on line 45 now correctly reflects the actual type names in the codebase, addressing all six inaccuracies flagged in the previous review. The correction includes:

  • Removed the six incorrect type names (e.g., ProductIOSRequest, PurchaseIOSOffer, ProductIOSTypeIOS)
  • Added the correct counterparts (e.g., ProductRequest, PurchaseOfferIOS, ProductTypeIOS)
  • Introduced the new VerifyPurchase* types (VerifyPurchaseProps, VerifyPurchaseResultIOS) reflecting the PR's refactoring from ReceiptValidation* to a unified verification API

The examples now align with both the actual codebase definitions and the naming conventions outlined earlier in the document.

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

87-99: No issues found—ReceiptValidationProps is properly defined and accessible.

Verification confirms that ReceiptValidationProps is a public typealias in packages/apple/Sources/Models/ReceiptValidationCompat.swift (line 5) mapping to VerifyPurchaseProps. Since both OpenIapProtocol.swift and ReceiptValidationCompat.swift are in the same SPM target ("OpenIAP"), the type is automatically accessible without an explicit import. The referenced fields (androidOptions and sku) exist in VerifyPurchaseProps, so the backward compatibility bridges are correctly implemented.

Comment thread packages/apple/Sources/OpenIapProtocol.swift
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt (1)

162-167: Consider using mapOf instead of mutableMapOf for immutable payload.

The payload map is never mutated after construction, so mapOf better expresses the intent and avoids unnecessary mutability.

-            mutableMapOf<String, Any?>(
+            mapOf<String, Any?>(
                 "store" to store.toJson(),
                 "packageName" to google.packageName,
                 "purchaseId" to google.purchaseId,
                 "purchaseToken" to google.purchaseToken
             )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc23cd2 and 0d01008.

📒 Files selected for processing (3)
  • packages/apple/Sources/OpenIapModule.swift (3 hunks)
  • packages/apple/Sources/OpenIapProtocol.swift (2 hunks)
  • packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt (4 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 46
File: packages/docs/src/pages/docs/types.tsx:2210-2238
Timestamp: 2025-11-22T19:30:03.835Z
Learning: In the OpenIAP SDK, the IAPKit verification endpoint is handled internally using a default base URL. Users no longer need to provide an `endpoint` field in `RequestVerifyPurchaseWithIapkitProps` - the SDK automatically routes to the correct IAPKit `/purchase/verify` endpoint.
📚 Learning: 2025-10-18T05:46:51.596Z
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 17
File: packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt:620-629
Timestamp: 2025-10-18T05:46:51.596Z
Learning: In packages/google/openiap/src/main/java/**/*.kt (Android-only package): DO NOT add Android suffix to function names, even for Android-specific APIs. Exception: Only use Android suffix for cross-platform API types (e.g., ProductAndroid, PurchaseAndroid) that contrast with iOS types. Examples of correct naming: isHorizonEnvironment(context: Context), buildModule(context: Context), acknowledgePurchase(), consumePurchase().

Applied to files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt
  • packages/apple/Sources/OpenIapModule.swift
📚 Learning: 2025-11-22T19:30:03.835Z
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 46
File: packages/docs/src/pages/docs/types.tsx:2210-2238
Timestamp: 2025-11-22T19:30:03.835Z
Learning: In the OpenIAP SDK, the IAPKit verification endpoint is handled internally using a default base URL. Users no longer need to provide an `endpoint` field in `RequestVerifyPurchaseWithIapkitProps` - the SDK automatically routes to the correct IAPKit `/purchase/verify` endpoint.

Applied to files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt
  • packages/apple/Sources/OpenIapProtocol.swift
  • packages/apple/Sources/OpenIapModule.swift
🧬 Code graph analysis (3)
packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt (1)
packages/google/openiap/src/test/java/dev/hyo/openiap/ReceiptValidatorTest.kt (1)
  • setRequestProperty (256-260)
packages/apple/Sources/OpenIapProtocol.swift (4)
packages/apple/Tests/OpenIapTests/VerifyPurchaseTests.swift (4)
  • validateReceiptIOS (102-107)
  • validateReceipt (109-111)
  • verifyPurchase (113-115)
  • verifyPurchaseWithProvider (117-119)
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (6)
  • validateReceiptIOS (2629-2629)
  • message (1362-1381)
  • validateReceipt (2543-2543)
  • verifyPurchase (2547-2547)
  • verifyPurchaseWithProvider (2551-2551)
  • code (1200-1222)
packages/apple/Sources/OpenIapModule.swift (4)
  • deprecated (553-556)
  • deprecated (584-587)
  • verifyPurchase (589-592)
  • verifyPurchaseWithProvider (594-606)
packages/apple/Sources/OpenIapStore.swift (3)
  • deprecated (371-374)
  • verifyPurchase (376-386)
  • verifyPurchaseWithProvider (388-391)
packages/apple/Sources/OpenIapModule.swift (3)
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (27)
  • verifyPurchase (2547-2547)
  • verifyPurchaseWithProvider (2551-2551)
  • iapkit (1737-1759)
  • iapkit (2342-2359)
  • code (1200-1222)
  • message (1362-1381)
  • rawValue (15-44)
  • rawValue (46-157)
  • rawValue (162-185)
  • rawValue (187-209)
  • rawValue (211-226)
  • rawValue (228-243)
  • rawValue (245-261)
  • rawValue (263-284)
  • rawValue (286-304)
  • rawValue (306-321)
  • rawValue (323-344)
  • rawValue (346-373)
  • rawValue (375-387)
  • rawValue (389-404)
  • rawValue (406-430)
  • store (1472-1491)
  • request (2007-2058)
  • apiKey (2255-2290)
  • appId (541-593)
  • appId (2193-2222)
  • packageName (2224-2253)
packages/gql/src/generated/Types.kt (13)
  • verifyPurchase (2614-2614)
  • verifyPurchaseWithProvider (2618-2618)
  • iapkit (1804-1826)
  • iapkit (2409-2426)
  • code (1267-1289)
  • message (1429-1448)
  • rawValue (14-43)
  • store (1539-1558)
  • request (2074-2125)
  • apiKey (2322-2357)
  • appId (608-660)
  • appId (2260-2289)
  • packageName (2291-2320)
packages/apple/Sources/OpenIapStore.swift (2)
  • verifyPurchase (376-386)
  • verifyPurchaseWithProvider (388-391)
⏰ 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). (2)
  • GitHub Check: Test iOS
  • GitHub Check: Test Android
🔇 Additional comments (6)
packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt (4)

5-13: LGTM! Imports and constants align with IAPKit verification feature.

The new imports support TypeToken-based JSON parsing and IAPKit types. The hardcoded DEFAULT_IAPKIT_ENDPOINT is correct per learnings—the SDK intentionally routes to IAPKit internally without requiring users to provide an endpoint field.

Based on learnings, the endpoint is handled internally by design.

Also applies to: 22-22


29-84: LGTM! Function signature correctly updated for unified VerifyPurchase API.

The migration from ReceiptValidationProps/ReceiptValidationResultAndroid to VerifyPurchaseProps/VerifyPurchaseResultAndroid maintains all validation logic and error handling while aligning with the new unified API surface.


86-143: LGTM! IAPKit verification implementation is sound and forward-compatible.

The function correctly:

  • Restricts Android to IapkitStore.Google with clear validation (lines 95-97)
  • Uses a list structure that supports future multi-store expansion while currently handling single-store Android requirements
  • Validates HTTP responses and maps failures to appropriate OpenIapError types
  • Employs TypeToken-based parsing for generic type safety (line 129)
  • Handles resources properly with try-finally cleanup

The hardcoded endpoint aligns with the learning that the SDK internally routes to IAPKit without requiring user-provided endpoints.

Based on learnings, endpoint is intentionally hardcoded.


151-161: LGTM! Comprehensive field validation with clear error messages.

The validation ensures all required Google payload fields are present and non-blank before constructing the request, preventing malformed API calls.

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

43-49: New VerifyPurchase-based protocol signatures look consistent with module/store usage

Switching the validation/verification methods to VerifyPurchaseProps/VerifyPurchaseResult* here matches the concrete implementations in OpenIapModule and the OpenIapStore façade, and cleanly aligns the protocol surface with the new VerifyPurchase API. I don’t see issues with these signatures from the protocol side.

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

553-592: Unified VerifyPurchase pipeline on iOS looks correct

The refactor to:

  • Have validateReceiptIOS(_:) delegate to performVerifyPurchaseIOS(_:),
  • Have validateReceipt(_:) delegate to verifyPurchase(_:), and
  • Wrap VerifyPurchaseResultIOS into the VerifyPurchaseResult enum in verifyPurchase(_:)

gives a single, consistent verification path on Apple while preserving the legacy entry points via deprecation. The internal performVerifyPurchaseIOS logic (latest verified transaction + getReceiptDataIOS()) is straightforward and matches expected StoreKit2 behavior.

Comment thread packages/apple/Sources/OpenIapModule.swift
Comment thread packages/apple/Sources/OpenIapProtocol.swift
@hyochan hyochan force-pushed the feat/verify-receipt-with-provider branch 2 times, most recently from 8aef7b2 to fbc9af4 Compare November 26, 2025 14:28
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/docs/src/components/SearchModal.tsx (1)

130-138: Fix incorrect type references in the verify-purchase entry—the referenced types do not exist.

The parameters and returns fields reference ReceiptValidationProps! and ReceiptValidationResult!, but these types do not exist in the codebase. The correct types are VerifyPurchaseProps and VerifyPurchaseResult, which are defined in packages/gql/src/generated/types.ts.

Update lines 134–135:

parameters: 'VerifyPurchaseProps!',
returns: 'VerifyPurchaseResult!',
packages/docs/src/pages/docs/features/external-purchase.tsx (1)

1276-1280: Update User Choice flow table to match the current requestPurchase API

In the “User Choice Billing” implementation‑flow table, Step 2 currently shows:

<code>requestPurchase(useAlternativeBilling: true)</code>

The surrounding TS/Kotlin examples now configure User Choice via alternativeBillingModeAndroid on initConnection, and call requestPurchase(...) without a useAlternativeBilling parameter. To avoid confusion, consider updating this row to reflect the current API, for example:

- <code>requestPurchase(useAlternativeBilling: true)</code>
+ <code>requestPurchase(...)</code>

and clarify in the description that the User Choice dialog is enabled by initializing with alternativeBillingModeAndroid: 'userChoice' (TS) or the corresponding Kotlin config.

🧹 Nitpick comments (8)
packages/docs/src/styles/components.css (1)

309-339: Consider adding word-break handling for alert card content.

Alert card content (paragraphs, lists) may contain long URLs or identifiers that overflow narrow viewports. Per coding guidelines, apply word-break: break-word or word-wrap: break-word to prevent overflow.

Apply this diff:

 .alert-card p {
   margin: 0;
+  word-break: break-word;
 }
 
 .alert-card ul,
 .alert-card ol {
   margin-top: 0.5rem;
   margin-bottom: 0;
+  word-break: break-word;
 }
packages/docs/src/components/AnchorLink.tsx (1)

14-31: Consider replacing non‑standard 'instant' with a standard behavior (and/or shared helper)

scrollIntoView({ behavior: 'instant' }) uses a non‑standard value; DOM types usually only allow 'auto' | 'smooth', so this can cause TS type errors and relies on browsers ignoring the unknown value. You might want to:

  • Switch to the standard 'auto' for “jump immediately” behavior, and
  • (Optionally) reuse the same “scroll with header offset” logic you already have in useScrollToHash so anchor clicks don’t get hidden under the fixed header.

For example:

- element.scrollIntoView({ behavior: 'instant', block: 'start' });
+ element.scrollIntoView({ behavior: 'auto', block: 'start' });

Apply similarly in the click handler.

packages/docs/src/pages/docs/lifecycle/index.tsx (1)

205-210: Align “validate purchase” wording with the new verifyPurchase naming

In the “Always Handle Pending Purchases” CodeBlock, the step currently says:

→ validate purchase

To stay consistent with the new verifyPurchase terminology you just introduced on this page, consider updating it to something like:

-   → validate purchase
+   → verify purchase (server-side)
.claude/guides/04-apple-package.md (1)

35-46: Consider simplifying "API interface" to "API".

The phrase "API interface" is redundant since "I" in API stands for "interface". This is a minor style suggestion.

-Defines the public API interface:
+Defines the public API:
packages/docs/src/pages/docs/lifecycle/subscription.tsx (1)

36-139: Inline styles could be extracted to CSS classes for maintainability.

The extensive inline styles throughout the table and other elements work correctly but could be extracted to reusable CSS classes in a stylesheet. This would improve maintainability and reduce component verbosity. However, this is consistent with the existing documentation page patterns.

packages/docs/src/pages/docs.tsx (1)

80-87: Life Cycle dropdown won’t auto-expand on /docs/lifecycle/subscription

MenuDropdown currently treats the title as active only when location.pathname === titleTo. With titleTo="/docs/lifecycle" and a child route at /docs/lifecycle/subscription, the Life Cycle title won’t be marked active or auto-expanded when viewing the subscription lifecycle page.

Consider updating MenuDropdown (or adding an option) to treat child paths as active (e.g., pathname.startsWith(titleTo)) so this new subpage keeps the Life Cycle section open.

packages/gql/src/type.graphql (1)

205-287: VerifyPurchase and IAPKit provider types look coherent; rely on resolver validation*

The new VerifyPurchaseProps/VerifyPurchaseResult and RequestVerifyPurchaseWithIapkit*/VerifyPurchaseWithProvider* types form a consistent surface for both direct verifyPurchase calls and provider-based (IAPKit) verification. Removing the endpoint from RequestVerifyPurchaseWithIapkitProps matches the SDK behavior where the IAPKit base URL is handled internally.

One thing to keep in mind: the schema allows VerifyPurchaseWithProviderProps.iapkit to be null even when provider is Iapkit. That’s fine if resolvers enforce the invariant and return a clear error, but if you want stronger guarantees you could tighten it later (e.g., additional docs or non-null when new providers are introduced).

packages/apple/Sources/Models/Types.swift (1)

153-161: Generated Swift types correctly mirror the new verification schema

The additions for provider-based verification and subscription expiry look consistent and well-integrated:

  • IapkitEnvironment, IapkitStore, and PurchaseVerificationProvider enums match the GraphQL enums and respect the Swift acronym/casing rules.
  • RequestVerifyPurchaseWithIapkit*, VerifyPurchaseAndroidOptions, VerifyPurchaseProps, VerifyPurchaseResult*, VerifyPurchaseWithProviderProps, and VerifyPurchaseWithProviderResult line up with the new GraphQL inputs/unions/results.
  • ActiveSubscription.willExpireSoon is added as an optional with clear deprecation-style comments, matching the GraphQL deprecation metadata.
  • MutationResolver/QueryResolver signatures and the corresponding handler typealiases/handler fields are updated to use the new VerifyPurchase shapes, so consumers can implement the new flows without breaking existing patterns.

Given this file is auto-generated, any SwiftLint warnings about redundant string raw values or long type names are better addressed by excluding generated sources or adjusting the generator/config, rather than editing this file directly.

Also applies to: 202-205, 254-279, 563-566, 604-640, 977-1083, 1382-1385, 1445-1490, 1528-1531, 1624-1684

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0d01008 and fbc9af4.

⛔ Files ignored due to path filters (5)
  • bun.lock is excluded by !**/*.lock
  • packages/gql/src/generated/Types.kt is excluded by !**/generated/**
  • packages/gql/src/generated/Types.swift is excluded by !**/generated/**
  • packages/gql/src/generated/types.dart is excluded by !**/generated/**
  • packages/gql/src/generated/types.ts is excluded by !**/generated/**
📒 Files selected for processing (55)
  • .claude/guides/01-overview.md (1 hunks)
  • .claude/guides/02-api-naming.md (1 hunks)
  • .claude/guides/03-deprecations.md (1 hunks)
  • .claude/guides/04-apple-package.md (1 hunks)
  • .claude/guides/05-google-package.md (1 hunks)
  • .claude/guides/06-gql-package.md (1 hunks)
  • .claude/guides/07-docs-package.md (1 hunks)
  • .claude/guides/08-deployment.md (1 hunks)
  • .gitignore (1 hunks)
  • CLAUDE.md (4 hunks)
  • openiap-versions.json (1 hunks)
  • packages/apple/Sources/Models/Types.swift (14 hunks)
  • packages/docs/openiap-versions.json (0 hunks)
  • packages/docs/openiap-versions.json (1 hunks)
  • packages/docs/package.json (1 hunks)
  • packages/docs/public/sitemap.xml (1 hunks)
  • packages/docs/src/components/AnchorLink.tsx (2 hunks)
  • packages/docs/src/components/CodeBlock.tsx (4 hunks)
  • packages/docs/src/components/HighlightText.tsx (1 hunks)
  • packages/docs/src/components/LanguageTabs.tsx (1 hunks)
  • packages/docs/src/components/PlatformTabs.tsx (2 hunks)
  • packages/docs/src/components/SEO.tsx (1 hunks)
  • packages/docs/src/components/SearchModal.tsx (3 hunks)
  • packages/docs/src/hooks/useScrollToHash.ts (2 hunks)
  • packages/docs/src/main.tsx (1 hunks)
  • packages/docs/src/pages/docs.tsx (8 hunks)
  • packages/docs/src/pages/docs/android-setup.tsx (1 hunks)
  • packages/docs/src/pages/docs/apis.tsx (47 hunks)
  • packages/docs/src/pages/docs/ecosystem.tsx (2 hunks)
  • packages/docs/src/pages/docs/errors.tsx (5 hunks)
  • packages/docs/src/pages/docs/events.tsx (7 hunks)
  • packages/docs/src/pages/docs/features.tsx (0 hunks)
  • packages/docs/src/pages/docs/features/external-purchase.tsx (15 hunks)
  • packages/docs/src/pages/docs/features/offer-code-redemption.tsx (1 hunks)
  • packages/docs/src/pages/docs/features/purchase.tsx (1 hunks)
  • packages/docs/src/pages/docs/features/subscription-upgrade-downgrade.tsx (22 hunks)
  • packages/docs/src/pages/docs/features/subscription.tsx (1 hunks)
  • packages/docs/src/pages/docs/horizon-setup.tsx (1 hunks)
  • packages/docs/src/pages/docs/ios-setup.tsx (3 hunks)
  • packages/docs/src/pages/docs/lifecycle/index.tsx (3 hunks)
  • packages/docs/src/pages/docs/lifecycle/subscription.tsx (1 hunks)
  • packages/docs/src/pages/docs/updates/announcements.tsx (1 hunks)
  • packages/docs/src/pages/docs/updates/notes.tsx (3 hunks)
  • packages/docs/src/pages/docs/updates/versions.tsx (2 hunks)
  • packages/docs/src/pages/home.tsx (2 hunks)
  • packages/docs/src/pages/introduction.tsx (1 hunks)
  • packages/docs/src/pages/languages.tsx (1 hunks)
  • packages/docs/src/pages/sponsors.tsx (1 hunks)
  • packages/docs/src/pages/tutorials.tsx (5 hunks)
  • packages/docs/src/styles/base.css (0 hunks)
  • packages/docs/src/styles/components.css (2 hunks)
  • packages/docs/src/styles/dark-mode.css (1 hunks)
  • packages/docs/src/styles/variables.css (1 hunks)
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (12 hunks)
  • packages/gql/src/type.graphql (3 hunks)
💤 Files with no reviewable changes (2)
  • packages/docs/src/styles/base.css
  • packages/docs/src/pages/docs/features.tsx
🚧 Files skipped from review as they are similar to previous changes (28)
  • .claude/guides/08-deployment.md
  • packages/docs/src/components/PlatformTabs.tsx
  • packages/docs/src/styles/dark-mode.css
  • packages/docs/src/hooks/useScrollToHash.ts
  • packages/docs/src/pages/docs/horizon-setup.tsx
  • packages/docs/src/styles/variables.css
  • packages/docs/src/pages/tutorials.tsx
  • packages/docs/openiap-versions.json
  • .claude/guides/06-gql-package.md
  • packages/docs/src/pages/docs/updates/versions.tsx
  • .claude/guides/02-api-naming.md
  • packages/docs/src/components/SEO.tsx
  • packages/docs/src/pages/introduction.tsx
  • packages/docs/src/pages/home.tsx
  • packages/docs/src/components/LanguageTabs.tsx
  • packages/docs/src/components/HighlightText.tsx
  • .claude/guides/01-overview.md
  • packages/docs/package.json
  • packages/docs/src/pages/languages.tsx
  • packages/docs/src/components/CodeBlock.tsx
  • packages/docs/src/pages/docs/updates/announcements.tsx
  • packages/docs/src/pages/sponsors.tsx
  • packages/docs/src/pages/docs/ecosystem.tsx
  • .gitignore
  • .claude/guides/07-docs-package.md
  • packages/docs/src/pages/docs/errors.tsx
  • packages/docs/src/main.tsx
  • packages/docs/src/pages/docs/android-setup.tsx
🧰 Additional context used
📓 Path-based instructions (12)
{**/*.{ts,tsx,js,jsx},packages/docs/**/*.{ts,tsx,js,jsx}}

📄 CodeRabbit inference engine (CLAUDE.md)

{**/*.{ts,tsx,js,jsx},packages/docs/**/*.{ts,tsx,js,jsx}}: iOS-only functions must end with 'IOS' suffix (e.g., clearTransactionIOS, getStorefrontIOS, presentCodeRedemptionSheetIOS)
Android-only functions must end with 'Android' suffix (e.g., acknowledgePurchaseAndroid, consumePurchaseAndroid, getPackageNameAndroid)
Cross-platform functions must have no platform suffix (e.g., fetchProducts, requestPurchase, getAvailablePurchases)

Files:

  • packages/docs/src/pages/docs/features/external-purchase.tsx
  • packages/docs/src/components/SearchModal.tsx
  • packages/docs/src/pages/docs/updates/notes.tsx
  • packages/docs/src/pages/docs/features/subscription-upgrade-downgrade.tsx
  • packages/docs/src/pages/docs/features/purchase.tsx
  • packages/docs/src/pages/docs.tsx
  • packages/docs/src/pages/docs/features/offer-code-redemption.tsx
  • packages/docs/src/pages/docs/features/subscription.tsx
  • packages/docs/src/pages/docs/lifecycle/subscription.tsx
  • packages/docs/src/pages/docs/lifecycle/index.tsx
  • packages/docs/src/pages/docs/events.tsx
  • packages/docs/src/pages/docs/apis.tsx
  • packages/docs/src/components/AnchorLink.tsx
  • packages/docs/src/pages/docs/ios-setup.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use 'get' prefix for data retrieval functions (e.g., getPromotedProductIOS)
Use 'request' prefix for async operations/requests (e.g., requestPurchase)
Use 'clear' prefix for remove/reset operations (e.g., clearTransactionIOS)
Use 'is/has' prefix for boolean checks (e.g., isEligibleForIntroOfferIOS, hasActiveSubscriptions)
Use 'show/present' prefix for UI display operations (e.g., showManageSubscriptionsIOS)
Use 'begin' prefix to start a process (e.g., beginRefundRequestIOS)
Use 'finish/end' prefix to complete a process (e.g., finishTransaction, endConnection)

Files:

  • packages/docs/src/pages/docs/features/external-purchase.tsx
  • packages/docs/src/components/SearchModal.tsx
  • packages/docs/src/pages/docs/updates/notes.tsx
  • packages/docs/src/pages/docs/features/subscription-upgrade-downgrade.tsx
  • packages/docs/src/pages/docs/features/purchase.tsx
  • packages/docs/src/pages/docs.tsx
  • packages/docs/src/pages/docs/features/offer-code-redemption.tsx
  • packages/docs/src/pages/docs/features/subscription.tsx
  • packages/docs/src/pages/docs/lifecycle/subscription.tsx
  • packages/docs/src/pages/docs/lifecycle/index.tsx
  • packages/docs/src/pages/docs/events.tsx
  • packages/docs/src/pages/docs/apis.tsx
  • packages/docs/src/components/AnchorLink.tsx
  • packages/docs/src/pages/docs/ios-setup.tsx
packages/docs/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

packages/docs/**/*.{ts,tsx}: Use kebab-case for search modal IDs (e.g., id: 'request-products')
Modal state signals should be defined in src/lib/signals.ts with helper functions like openAuthModal() and closeAuthModal()
Delete unused components, functions, and imports immediately; do not keep commented-out code or unused variables/parameters
Run bun run tsc or npm run typecheck to check for TypeScript errors before committing documentation

Files:

  • packages/docs/src/pages/docs/features/external-purchase.tsx
  • packages/docs/src/components/SearchModal.tsx
  • packages/docs/src/pages/docs/updates/notes.tsx
  • packages/docs/src/pages/docs/features/subscription-upgrade-downgrade.tsx
  • packages/docs/src/pages/docs/features/purchase.tsx
  • packages/docs/src/pages/docs.tsx
  • packages/docs/src/pages/docs/features/offer-code-redemption.tsx
  • packages/docs/src/pages/docs/features/subscription.tsx
  • packages/docs/src/pages/docs/lifecycle/subscription.tsx
  • packages/docs/src/pages/docs/lifecycle/index.tsx
  • packages/docs/src/pages/docs/events.tsx
  • packages/docs/src/pages/docs/apis.tsx
  • packages/docs/src/components/AnchorLink.tsx
  • packages/docs/src/pages/docs/ios-setup.tsx
packages/docs/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

packages/docs/**/*.{tsx,ts}: Modal instances should be defined once at app root level and managed via global state using Preact Signals
Any function that returns a Promise must be wrapped with void operator when used where a void return is expected (e.g., onClick handlers)

Files:

  • packages/docs/src/pages/docs/features/external-purchase.tsx
  • packages/docs/src/components/SearchModal.tsx
  • packages/docs/src/pages/docs/updates/notes.tsx
  • packages/docs/src/pages/docs/features/subscription-upgrade-downgrade.tsx
  • packages/docs/src/pages/docs/features/purchase.tsx
  • packages/docs/src/pages/docs.tsx
  • packages/docs/src/pages/docs/features/offer-code-redemption.tsx
  • packages/docs/src/pages/docs/features/subscription.tsx
  • packages/docs/src/pages/docs/lifecycle/subscription.tsx
  • packages/docs/src/pages/docs/lifecycle/index.tsx
  • packages/docs/src/pages/docs/events.tsx
  • packages/docs/src/pages/docs/apis.tsx
  • packages/docs/src/components/AnchorLink.tsx
  • packages/docs/src/pages/docs/ios-setup.tsx
packages/docs/{src,components}/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

packages/docs/{src,components}/**/*.{tsx,ts}: Reusable React components should be placed in src/components/ directory; single-use components should be co-located with their parent
When a component has sub-components used only within it, create a folder structure with index.tsx for main component and separate files for sub-components

Files:

  • packages/docs/src/pages/docs/features/external-purchase.tsx
  • packages/docs/src/components/SearchModal.tsx
  • packages/docs/src/pages/docs/updates/notes.tsx
  • packages/docs/src/pages/docs/features/subscription-upgrade-downgrade.tsx
  • packages/docs/src/pages/docs/features/purchase.tsx
  • packages/docs/src/pages/docs.tsx
  • packages/docs/src/pages/docs/features/offer-code-redemption.tsx
  • packages/docs/src/pages/docs/features/subscription.tsx
  • packages/docs/src/pages/docs/lifecycle/subscription.tsx
  • packages/docs/src/pages/docs/lifecycle/index.tsx
  • packages/docs/src/pages/docs/events.tsx
  • packages/docs/src/pages/docs/apis.tsx
  • packages/docs/src/components/AnchorLink.tsx
  • packages/docs/src/pages/docs/ios-setup.tsx
packages/docs/**/*.{tsx,ts,css}

📄 CodeRabbit inference engine (CLAUDE.md)

All React components must respect parent boundaries and children must never overflow outside parent containers

Files:

  • packages/docs/src/pages/docs/features/external-purchase.tsx
  • packages/docs/src/components/SearchModal.tsx
  • packages/docs/src/pages/docs/updates/notes.tsx
  • packages/docs/src/pages/docs/features/subscription-upgrade-downgrade.tsx
  • packages/docs/src/pages/docs/features/purchase.tsx
  • packages/docs/src/pages/docs.tsx
  • packages/docs/src/pages/docs/features/offer-code-redemption.tsx
  • packages/docs/src/pages/docs/features/subscription.tsx
  • packages/docs/src/pages/docs/lifecycle/subscription.tsx
  • packages/docs/src/pages/docs/lifecycle/index.tsx
  • packages/docs/src/pages/docs/events.tsx
  • packages/docs/src/pages/docs/apis.tsx
  • packages/docs/src/styles/components.css
  • packages/docs/src/components/AnchorLink.tsx
  • packages/docs/src/pages/docs/ios-setup.tsx
packages/docs/**/*.{css,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

packages/docs/**/*.{css,tsx}: Use overflow-hidden on parent containers when necessary to prevent children overflow
Apply break-words for text content that might be long to prevent overflow
Use whitespace-nowrap for navigation items to prevent wrapping

Files:

  • packages/docs/src/pages/docs/features/external-purchase.tsx
  • packages/docs/src/components/SearchModal.tsx
  • packages/docs/src/pages/docs/updates/notes.tsx
  • packages/docs/src/pages/docs/features/subscription-upgrade-downgrade.tsx
  • packages/docs/src/pages/docs/features/purchase.tsx
  • packages/docs/src/pages/docs.tsx
  • packages/docs/src/pages/docs/features/offer-code-redemption.tsx
  • packages/docs/src/pages/docs/features/subscription.tsx
  • packages/docs/src/pages/docs/lifecycle/subscription.tsx
  • packages/docs/src/pages/docs/lifecycle/index.tsx
  • packages/docs/src/pages/docs/events.tsx
  • packages/docs/src/pages/docs/apis.tsx
  • packages/docs/src/styles/components.css
  • packages/docs/src/components/AnchorLink.tsx
  • packages/docs/src/pages/docs/ios-setup.tsx
packages/docs/**/*.{ts,tsx,css,md,mdx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Run npx prettier --write to format all documentation files before committing

Files:

  • packages/docs/src/pages/docs/features/external-purchase.tsx
  • packages/docs/src/components/SearchModal.tsx
  • packages/docs/src/pages/docs/updates/notes.tsx
  • packages/docs/src/pages/docs/features/subscription-upgrade-downgrade.tsx
  • packages/docs/src/pages/docs/features/purchase.tsx
  • packages/docs/openiap-versions.json
  • packages/docs/src/pages/docs.tsx
  • packages/docs/src/pages/docs/features/offer-code-redemption.tsx
  • packages/docs/src/pages/docs/features/subscription.tsx
  • packages/docs/src/pages/docs/lifecycle/subscription.tsx
  • packages/docs/src/pages/docs/lifecycle/index.tsx
  • packages/docs/src/pages/docs/events.tsx
  • packages/docs/src/pages/docs/apis.tsx
  • packages/docs/src/styles/components.css
  • packages/docs/src/components/AnchorLink.tsx
  • packages/docs/src/pages/docs/ios-setup.tsx
packages/docs/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Run npm run lint to check for linting issues before committing documentation changes

Files:

  • packages/docs/src/pages/docs/features/external-purchase.tsx
  • packages/docs/src/components/SearchModal.tsx
  • packages/docs/src/pages/docs/updates/notes.tsx
  • packages/docs/src/pages/docs/features/subscription-upgrade-downgrade.tsx
  • packages/docs/src/pages/docs/features/purchase.tsx
  • packages/docs/src/pages/docs.tsx
  • packages/docs/src/pages/docs/features/offer-code-redemption.tsx
  • packages/docs/src/pages/docs/features/subscription.tsx
  • packages/docs/src/pages/docs/lifecycle/subscription.tsx
  • packages/docs/src/pages/docs/lifecycle/index.tsx
  • packages/docs/src/pages/docs/events.tsx
  • packages/docs/src/pages/docs/apis.tsx
  • packages/docs/src/components/AnchorLink.tsx
  • packages/docs/src/pages/docs/ios-setup.tsx
packages/google/**/*.kt

📄 CodeRabbit inference engine (CLAUDE.md)

packages/google/**/*.kt: In Android package (packages/google), do not add 'Android' suffix to function names even for Android-specific APIs
Cross-platform API functions in Android should have no suffix (e.g., initConnection, fetchProducts, requestPurchase)

Files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
packages/apple/**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

packages/apple/**/*.swift: In iOS package, iOS-specific functions MUST have 'IOS' suffix (e.g., presentCodeRedemptionSheetIOS, showManageSubscriptionsIOS)
In iOS package, cross-platform functions have NO suffix (e.g., initConnection, fetchProducts, requestPurchase, finishTransaction)
In Swift, acronyms should be ALL CAPS only when they appear as a suffix; at beginning or middle use Pascal case (e.g., OpenIAP, IapManager, IapPurchase, ProductIAP)

Files:

  • packages/apple/Sources/Models/Types.swift
packages/apple/Sources/Models/Types.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Do not edit auto-generated Types.swift file in packages/apple/Sources/Models/ directly; regenerate via ./scripts/generate-types.sh

Files:

  • packages/apple/Sources/Models/Types.swift
🧠 Learnings (30)
📓 Common learnings
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 46
File: packages/docs/src/pages/docs/types.tsx:2210-2238
Timestamp: 2025-11-22T19:30:03.876Z
Learning: In the OpenIAP SDK, the IAPKit verification endpoint is handled internally using a default base URL. Users no longer need to provide an `endpoint` field in `RequestVerifyPurchaseWithIapkitProps` - the SDK automatically routes to the correct IAPKit `/purchase/verify` endpoint.
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 17
File: packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt:620-629
Timestamp: 2025-10-18T05:46:51.596Z
Learning: In packages/google/openiap/src/main/java/**/*.kt (Android-only package): DO NOT add Android suffix to function names, even for Android-specific APIs. Exception: Only use Android suffix for cross-platform API types (e.g., ProductAndroid, PurchaseAndroid) that contrast with iOS types. Examples of correct naming: isHorizonEnvironment(context: Context), buildModule(context: Context), acknowledgePurchase(), consumePurchase().
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to packages/google/openiap/src/main/java/*/Types.kt : Do not edit generated Android file packages/google/openiap/src/main/Types.kt directly; regenerate via ./scripts/generate-types.sh

Applied to files:

  • .claude/guides/05-google-package.md
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to packages/google/**/*.kt : In Android package (packages/google), do not add 'Android' suffix to function names even for Android-specific APIs

Applied to files:

  • .claude/guides/05-google-package.md
📚 Learning: 2025-10-18T05:46:51.596Z
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 17
File: packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt:620-629
Timestamp: 2025-10-18T05:46:51.596Z
Learning: In packages/google/openiap/src/main/java/**/*.kt (Android-only package): DO NOT add Android suffix to function names, even for Android-specific APIs. Exception: Only use Android suffix for cross-platform API types (e.g., ProductAndroid, PurchaseAndroid) that contrast with iOS types. Examples of correct naming: isHorizonEnvironment(context: Context), buildModule(context: Context), acknowledgePurchase(), consumePurchase().

Applied to files:

  • .claude/guides/05-google-package.md
  • packages/docs/src/pages/docs/features/external-purchase.tsx
  • packages/docs/src/pages/docs/features/subscription-upgrade-downgrade.tsx
  • .claude/guides/03-deprecations.md
  • packages/docs/src/pages/docs/apis.tsx
  • CLAUDE.md
  • packages/gql/src/type.graphql
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
  • packages/apple/Sources/Models/Types.swift
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to packages/google/**/*.kt : Cross-platform API functions in Android should have no suffix (e.g., initConnection, fetchProducts, requestPurchase)

Applied to files:

  • .claude/guides/05-google-package.md
  • CLAUDE.md
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Load and review the relevant CONVENTION.md file before writing or editing in packages/gql, packages/google, or packages/apple

Applied to files:

  • .claude/guides/05-google-package.md
  • openiap-versions.json
  • .claude/guides/04-apple-package.md
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to packages/google/openiap/src/main/java/dev/hyo/openiap/utils/**/*.kt : Put reusable Kotlin helpers in packages/google/openiap/src/main/java/dev/hyo/openiap/utils/

Applied to files:

  • .claude/guides/05-google-package.md
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to {**/*.{ts,tsx,js,jsx},packages/docs/**/*.{ts,tsx,js,jsx}} : Android-only functions must end with 'Android' suffix (e.g., acknowledgePurchaseAndroid, consumePurchaseAndroid, getPackageNameAndroid)

Applied to files:

  • .claude/guides/05-google-package.md
  • packages/docs/src/pages/docs/features/external-purchase.tsx
  • packages/docs/src/pages/docs/features/purchase.tsx
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to {**/*.{ts,tsx,js,jsx},packages/docs/**/*.{ts,tsx,js,jsx}} : Cross-platform functions must have no platform suffix (e.g., fetchProducts, requestPurchase, getAvailablePurchases)

Applied to files:

  • packages/docs/src/pages/docs/features/external-purchase.tsx
  • packages/docs/src/pages/docs/features/purchase.tsx
  • packages/docs/src/pages/docs/lifecycle/index.tsx
  • packages/docs/src/pages/docs/events.tsx
  • packages/docs/src/pages/docs/apis.tsx
  • CLAUDE.md
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to {**/*.{ts,tsx,js,jsx},packages/docs/**/*.{ts,tsx,js,jsx}} : iOS-only functions must end with 'IOS' suffix (e.g., clearTransactionIOS, getStorefrontIOS, presentCodeRedemptionSheetIOS)

Applied to files:

  • packages/docs/src/pages/docs/features/external-purchase.tsx
  • packages/docs/src/pages/docs/features/offer-code-redemption.tsx
  • packages/docs/src/pages/docs/apis.tsx
  • CLAUDE.md
  • packages/docs/src/pages/docs/ios-setup.tsx
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use 'request' prefix for async operations/requests (e.g., requestPurchase)

Applied to files:

  • packages/docs/src/pages/docs/features/external-purchase.tsx
  • packages/docs/src/pages/docs/features/subscription-upgrade-downgrade.tsx
  • packages/docs/src/pages/docs/features/purchase.tsx
  • packages/docs/src/pages/docs/apis.tsx
  • CLAUDE.md
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to packages/docs/**/*.{md,mdx,html} : Use kebab-case for URL anchors (e.g., #fetch-products, #get-app-transaction-ios)

Applied to files:

  • packages/docs/src/pages/docs/features/external-purchase.tsx
  • packages/docs/src/pages/docs/updates/notes.tsx
  • packages/docs/src/pages/docs/features/subscription-upgrade-downgrade.tsx
  • packages/docs/src/pages/docs/lifecycle/index.tsx
  • packages/docs/src/pages/docs/apis.tsx
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to packages/docs/{src,components}/**/*.{tsx,ts} : Reusable React components should be placed in src/components/ directory; single-use components should be co-located with their parent

Applied to files:

  • packages/docs/src/pages/docs/features/external-purchase.tsx
  • packages/docs/src/pages/docs.tsx
  • packages/docs/src/pages/docs/features/offer-code-redemption.tsx
📚 Learning: 2025-11-22T19:30:03.876Z
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 46
File: packages/docs/src/pages/docs/types.tsx:2210-2238
Timestamp: 2025-11-22T19:30:03.876Z
Learning: In the OpenIAP SDK, the IAPKit verification endpoint is handled internally using a default base URL. Users no longer need to provide an `endpoint` field in `RequestVerifyPurchaseWithIapkitProps` - the SDK automatically routes to the correct IAPKit `/purchase/verify` endpoint.

Applied to files:

  • packages/docs/src/pages/docs/features/external-purchase.tsx
  • packages/docs/src/components/SearchModal.tsx
  • packages/docs/src/pages/docs/lifecycle/index.tsx
  • packages/docs/src/pages/docs/apis.tsx
  • CLAUDE.md
  • packages/gql/src/type.graphql
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
  • packages/docs/src/pages/docs/ios-setup.tsx
  • packages/apple/Sources/Models/Types.swift
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to packages/docs/**/*.{ts,tsx} : Use kebab-case for search modal IDs (e.g., id: 'request-products')

Applied to files:

  • packages/docs/src/components/SearchModal.tsx
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to packages/docs/**/*.{ts,tsx} : Modal state signals should be defined in src/lib/signals.ts with helper functions like openAuthModal() and closeAuthModal()

Applied to files:

  • packages/docs/src/components/SearchModal.tsx
  • CLAUDE.md
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to packages/docs/{src,components}/**/*.{tsx,ts} : When a component has sub-components used only within it, create a folder structure with index.tsx for main component and separate files for sub-components

Applied to files:

  • packages/docs/src/pages/docs/updates/notes.tsx
  • packages/docs/src/pages/docs/features/subscription-upgrade-downgrade.tsx
  • packages/docs/src/pages/docs.tsx
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use 'show/present' prefix for UI display operations (e.g., showManageSubscriptionsIOS)

Applied to files:

  • packages/docs/src/pages/docs/features/subscription-upgrade-downgrade.tsx
  • packages/docs/src/pages/docs/apis.tsx
  • CLAUDE.md
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use 'is/has' prefix for boolean checks (e.g., isEligibleForIntroOfferIOS, hasActiveSubscriptions)

Applied to files:

  • packages/docs/src/pages/docs/features/subscription-upgrade-downgrade.tsx
  • packages/docs/src/pages/docs/apis.tsx
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Update iOS GQL types by editing openiap-versions.json gql version and running ./scripts/generate-types.sh

Applied to files:

  • openiap-versions.json
  • packages/gql/src/type.graphql
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use 'clear' prefix for remove/reset operations (e.g., clearTransactionIOS)

Applied to files:

  • packages/docs/src/pages/docs/apis.tsx
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to packages/apple/**/*.swift : In iOS package, cross-platform functions have NO suffix (e.g., initConnection, fetchProducts, requestPurchase, finishTransaction)

Applied to files:

  • packages/docs/src/pages/docs/apis.tsx
  • CLAUDE.md
  • packages/apple/Sources/Models/Types.swift
  • .claude/guides/04-apple-package.md
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use 'begin' prefix to start a process (e.g., beginRefundRequestIOS)

Applied to files:

  • packages/docs/src/pages/docs/apis.tsx
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to packages/apple/**/*.swift : In iOS package, iOS-specific functions MUST have 'IOS' suffix (e.g., presentCodeRedemptionSheetIOS, showManageSubscriptionsIOS)

Applied to files:

  • packages/docs/src/pages/docs/apis.tsx
  • .claude/guides/04-apple-package.md
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to packages/docs/**/*.{tsx,ts} : Modal instances should be defined once at app root level and managed via global state using Preact Signals

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use 'get' prefix for data retrieval functions (e.g., getPromotedProductIOS)

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to packages/gql/src/generated/**/*.{ts,swift,kt,dart} : Do not edit auto-generated GraphQL type files; regenerate types from GraphQL schema via bun run generate

Applied to files:

  • packages/gql/src/type.graphql
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to packages/apple/Sources/Models/Types.swift : Do not edit auto-generated Types.swift file in packages/apple/Sources/Models/ directly; regenerate via ./scripts/generate-types.sh

Applied to files:

  • packages/apple/Sources/Models/Types.swift
  • .claude/guides/04-apple-package.md
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to packages/apple/**/*.swift : In Swift, acronyms should be ALL CAPS only when they appear as a suffix; at beginning or middle use Pascal case (e.g., OpenIAP, IapManager, IapPurchase, ProductIAP)

Applied to files:

  • packages/apple/Sources/Models/Types.swift
  • .claude/guides/04-apple-package.md
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Organize Apple package files: official types in Sources/Models/, helper classes in Sources/Helpers/, main modules in Sources/

Applied to files:

  • .claude/guides/04-apple-package.md
🧬 Code graph analysis (10)
packages/docs/src/pages/docs/features/external-purchase.tsx (1)
packages/docs/src/hooks/useScrollToHash.ts (1)
  • useScrollToHash (4-28)
packages/docs/src/pages/docs/updates/notes.tsx (1)
packages/docs/src/hooks/useScrollToHash.ts (1)
  • useScrollToHash (4-28)
packages/docs/src/pages/docs/features/subscription-upgrade-downgrade.tsx (1)
packages/docs/src/hooks/useScrollToHash.ts (1)
  • useScrollToHash (4-28)
packages/docs/src/pages/docs/features/purchase.tsx (4)
packages/gql/src/generated/types.ts (1)
  • Purchase (443-443)
packages/docs/src/hooks/useScrollToHash.ts (1)
  • useScrollToHash (4-28)
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (1)
  • code (1205-1227)
packages/gql/src/generated/Types.kt (1)
  • code (1272-1294)
packages/docs/src/pages/docs.tsx (1)
packages/docs/src/components/MenuDropdown.tsx (1)
  • MenuDropdown (16-128)
packages/docs/src/pages/docs/features/offer-code-redemption.tsx (1)
packages/docs/src/hooks/useScrollToHash.ts (1)
  • useScrollToHash (4-28)
packages/docs/src/pages/docs/lifecycle/index.tsx (1)
packages/docs/src/hooks/useScrollToHash.ts (1)
  • useScrollToHash (4-28)
packages/docs/src/pages/docs/events.tsx (1)
packages/docs/src/hooks/useScrollToHash.ts (1)
  • useScrollToHash (4-28)
packages/docs/src/pages/docs/apis.tsx (3)
packages/docs/src/hooks/useScrollToHash.ts (1)
  • useScrollToHash (4-28)
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (1)
  • code (1205-1227)
packages/gql/src/generated/Types.kt (1)
  • code (1272-1294)
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (1)
packages/gql/src/generated/types.ts (10)
  • RequestVerifyPurchaseWithIapkitResult (779-782)
  • VerifyPurchaseResultAndroid (857-876)
  • VerifyPurchaseResultIOS (878-887)
  • VerifyPurchaseWithProviderResult (894-898)
  • RequestVerifyPurchaseWithIapkitAppleProps (750-757)
  • RequestVerifyPurchaseWithIapkitGoogleProps (759-766)
  • RequestVerifyPurchaseWithIapkitProps (768-777)
  • VerifyPurchaseAndroidOptions (841-846)
  • VerifyPurchaseProps (848-853)
  • VerifyPurchaseWithProviderProps (889-892)
🪛 LanguageTool
.claude/guides/04-apple-package.md

[style] ~35-~35: This phrase is redundant (‘I’ stands for ‘interface’). Use simply “API”.
Context: ...enIapProtocol.swift Defines the public API interface: ```swift public protocol OpenIapModul...

(ACRONYM_TAUTOLOGY)

🪛 SwiftLint (0.57.0)
packages/apple/Sources/Models/Types.swift

[Warning] 154-154: String enum values can be omitted when they are equal to the enumcase name

(redundant_string_enum_value)


[Warning] 155-155: String enum values can be omitted when they are equal to the enumcase name

(redundant_string_enum_value)


[Warning] 159-159: String enum values can be omitted when they are equal to the enumcase name

(redundant_string_enum_value)


[Warning] 160-160: String enum values can be omitted when they are equal to the enumcase name

(redundant_string_enum_value)


[Warning] 203-203: String enum values can be omitted when they are equal to the enumcase name

(redundant_string_enum_value)


[Warning] 977-977: Type name 'RequestVerifyPurchaseWithIapkitAppleProps' should be between 3 and 40 characters long

(type_name)


[Warning] 996-996: Type name 'RequestVerifyPurchaseWithIapkitGoogleProps' should be between 3 and 40 characters long

(type_name)


[Warning] 1530-1530: Type name 'MutationVerifyPurchaseWithProviderHandler' should be between 3 and 40 characters long

(type_name)

⏰ 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 (41)
packages/docs/src/styles/components.css (5)

236-242: Language Tabs header layout looks good.

The overflow-x: auto on .language-tabs-header appropriately handles horizontal scrolling when tabs exceed viewport width, and gap: 0.25rem provides proper spacing.


244-256: White-space nowrap prevents tab wrapping.

Good use of white-space: nowrap to prevent language tab names from wrapping. The sizing and padding follow existing platform-tabs patterns.


278-291: Code block integration styling is solid.

The seamless integration of language tabs with code blocks (margin-top: 0 and border-radius adjustments) ensures clean visual connection between tab header and content.


294-302: Info Note border styling approved.

The border and border-radius additions to .info-note maintain consistency with other card components in the stylesheet.


1-339: Format CSS file with prettier before committing.

Per coding guidelines for packages/docs/**/*.css, run npx prettier --write on this file to ensure consistent formatting across documentation styles.

packages/docs/src/pages/docs/ios-setup.tsx (6)

7-12: Good SEO implementation with dual terminology.

The SEO component is properly positioned and includes both "purchase verification" and "receipt validation" in keywords, which helps users find this page using either the modern or legacy terminology.


402-406: Clear and accurate terminology update.

The content correctly explains purchase verification while maintaining technical accuracy about StoreKit 2's JWS implementation and legacy receipt formats.


1-580: I'll verify the coding guidelines compliance by checking the project structure and running the suggested verification commands on the file.
<function_calls>

#!/bin/bash

Check if packages/docs exists and its structure

ls -la packages/docs 2>&1 | head -20


</function_calls>
<function_calls>

#!/bin/bash

Check package.json scripts in packages/docs

cat packages/docs/package.json | grep -A 20 '"scripts"'


</function_calls>
<function_calls>

#!/bin/bash

Verify the file exists and check its structure

ls -la packages/docs/src/pages/docs/ios-setup.tsx
wc -l packages/docs/src/pages/docs/ios-setup.tsx


</function_calls>

Let me wait for the shell results to proceed with verification.


542-559: No anchor ID conflicts detected — the old anchor ID does not exist in the codebase.

The search confirmed that receipt-validation-failures was never used as an anchor ID in the documentation. The new anchor verification-failures is correctly implemented at line 542-544 with proper self-referencing links. No broken links will result from this change.


1-2: Imports are valid and all compliance checks pass.

Both HighlightText and SEO components exist at packages/docs/src/components/ and the relative import paths in lines 1-2 are correct. TypeScript compilation, prettier formatting, and linting all pass without errors.


389-401: No broken links from anchor ID change — verified safe.

The search revealed zero references to the old receipt-validation anchor in the codebase. Additionally, purchase-verification is already the established standard across multiple documentation files (android-setup.tsx, lifecycle files, types.tsx). The new anchor ID follows the kebab-case convention consistently used throughout the documentation. This is a safe change with no risk of internal broken links.

packages/docs/src/components/SearchModal.tsx (2)

313-320: Terminology updated appropriately for deprecated API.

The description text has been updated to reference "purchase verification" instead of "receipt validation", which aligns with the broader terminology migration in this PR. Since this API is deprecated in favor of verifyPurchase, the reference consistency is appropriate.

Note: The same type name inconsistency flagged in lines 130-138 applies here, but since this is a deprecated API, it's less critical.


373-380: LGTM: Documentation path reorganization.

The path update from /docs/external-purchase to /docs/features/external-purchase appropriately reflects the documentation structure reorganization mentioned in the PR.

openiap-versions.json (1)

2-3: Confirm types/codegen are regenerated for gql/docs 1.2.6

The version bump looks good; just make sure any GraphQL/types artifacts that depend on the gql/docs version were regenerated (e.g., via ./scripts/generate-types.sh) so the shipped schema matches 1.2.6.

.claude/guides/05-google-package.md (1)

1-79: Google package guide aligns well with existing conventions

The guide clearly captures directory layout, Types.kt generation, build/test commands, and the “no Android suffix in Kotlin APIs” rule for the Android‑only package. This matches the existing conventions and should be very helpful for contributors.

.claude/guides/03-deprecations.md (1)

1-71: Deprecation guidance is clear and matches the new VerifyPurchase surface

The mapping from validateReceipt* to verifyPurchase, plus the wrapper/deprecation examples for Swift/Kotlin, is clear and consistent with the new API naming (fetchProducts/getStorefront/etc.). This should make migrations straightforward.

packages/docs/src/pages/docs/lifecycle/index.tsx (1)

10-18: Lifecycle purchase‑verification section and SEO wiring look solid

The new SEO block, the rename to “Purchase Verification” (with HighlightText calling out the legacy “Receipt Validation” term), and the “When to Verify” bullets + verifyPurchase link are all consistent with the new API surface and help clarify best practices.

Also applies to: 73-112, 254-257

packages/docs/src/pages/docs/updates/notes.tsx (1)

1-16: Notes page cleanly documents API/terminology changes

The new Notes page (SEO, useScrollToHash, and the “API & Terminology Changes” section) clearly explains the validateReceipt → verifyPurchase migration and ties in nicely with the lifecycle and external‑purchase docs. The component rename/export wiring also looks correct.

Also applies to: 19-115, 160-161, 497-497

packages/docs/public/sitemap.xml (1)

5-148: Verify sitemap URLs and lastmod dates against deployed routes

The expanded sitemap structure (languages/tutorials/sponsors, new docs/features & updates pages) looks good. Please just double‑check that:

  • Every listed <loc> corresponds to a real route in the deployed docs, and
  • The lastmod date of 2025-11-26 matches the actual deployment date of these changes.

This keeps search engines from indexing stale or non‑existent pages.

packages/docs/src/pages/docs/features/external-purchase.tsx (1)

1-66: External Purchase guide is comprehensive and follows naming conventions

The page is very thorough: SEO + useScrollToHash integration, consistent use of LanguageTabs/PlatformTabs, and API naming that matches the documented conventions (*IOS / *Android / cross‑platform). The multi‑language code samples for StoreKit External Purchase and Google Alternative Billing are especially helpful for real‑world adoption.

Also applies to: 86-423, 441-517, 565-771, 773-807, 808-1053, 1096-1322, 1330-1434

packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (1)

211-2710: New VerifyPurchase / IAPKit types in generated Kotlin look consistent with the GraphQL schema

The additions for IapkitEnvironment, IapkitStore, PurchaseVerificationProvider, the various RequestVerifyPurchaseWithIapkit* input types, VerifyPurchaseAndroidOptions / VerifyPurchaseProps / VerifyPurchaseWithProviderProps, and the VerifyPurchaseResult* hierarchy (including the sealed VerifyPurchaseResult and updated mutation/query signatures and handler aliases) all line up with the corresponding generated TypeScript GraphQL types you included.

The fromJson/toJson implementations follow the existing patterns and appear symmetrical, and the removal of any endpoint field from RequestVerifyPurchaseWithIapkitProps matches the documented SDK behavior of handling the IAPKit base URL internally. Given the file’s AUTO-GENERATED header and the repo guideline to regenerate via scripts rather than editing, this looks like a correct regeneration from the updated schema.

packages/docs/src/pages/docs/lifecycle/subscription.tsx (2)

1-19: Well-structured documentation page with proper SEO integration.

Good use of useScrollToHash hook and SEO component. The imports are properly organized and the component structure follows the established pattern in the docs package.


1374-1461: Summary section provides good cross-platform comparison.

The summary grid with iOS vs Android cards and the related documentation accordion provide a clear overview for users. The responsive grid layout with minmax(280px, 1fr) handles different screen sizes appropriately.

CLAUDE.md (4)

78-78: Correctly adds verifyPurchase to cross-platform functions list.

This aligns with the PR objective of replacing validateReceipt with verifyPurchase as a cross-platform function without platform suffix.


115-116: Clear deprecation notices for legacy function names.

Both validateReceipt and validateReceiptIOS are properly marked as deprecated with clear migration guidance to verifyPurchase.


317-318: Kotlin example correctly shows verifyPurchase() without Android suffix.

This follows the documented convention: cross-platform API functions in Android-only package should have no suffix. Based on learnings, this is the correct approach.


357-357: Swift example correctly includes verifyPurchase() as cross-platform function.

Consistent with the iOS package convention where cross-platform functions have no suffix.

packages/docs/src/pages/docs/features/subscription-upgrade-downgrade.tsx (4)

1-7: Clean import updates with new LanguageTabs and SEO components.

Imports are properly organized and the additions support multi-language code samples and SEO integration.


14-19: Good SEO metadata for the page.

The title, description, path, and keywords are appropriate and follow the pattern established in other docs pages.


270-340: Comprehensive multi-language examples for detecting upgrades.

The LanguageTabs component provides TypeScript, Swift, Kotlin, and Dart examples. All code samples are idiomatic for their respective languages and correctly demonstrate the pendingUpgradeProductId usage.


1055-1125: Android upgrade example missing Swift tab.

The Android-specific examples under PlatformTabs only show TypeScript, Kotlin, and Dart. This is intentional since Swift is iOS-only, so omitting it here is correct. The examples appropriately focus on platforms relevant to Android.

packages/docs/src/pages/docs/features/purchase.tsx (6)

1-6: Well-organized imports for the new purchase documentation page.

Imports follow the established pattern with proper relative paths. The combination of AnchorLink, CodeBlock, LanguageTabs, and SEO components enables comprehensive documentation with multi-language support.


12-17: Good SEO configuration for the purchase page.

The title, description, path, and keywords are appropriate and consistent with other feature documentation pages.


363-395: Correct usage of verifyPurchase API name.

The code example correctly uses verifyPurchase instead of the deprecated validateReceipt, consistent with the PR objectives.


700-803: Comprehensive complete example demonstrates best practices.

The TypeScript example shows the full purchase flow including verification, product granting, and transaction finishing. The use of useCallback with proper dependencies and cleanup in the useEffect follows React best practices.


1063-1068: Dart dispose method calls super.dispose() correctly.

The Dart example properly calls super.dispose() to ensure parent class cleanup. The stream subscription cancellation and connection ending are handled before the super call.


477-484: The tutorial link anchor verify-purchase exists and is correctly configured.

The anchor is defined at line 149 of packages/docs/src/pages/tutorials.tsx using the AnchorLink component with id="verify-purchase", which creates a valid scroll anchor. The page has the necessary useScrollToHash hook to handle anchor navigation.

packages/docs/src/pages/docs/features/subscription.tsx (1)

9-2124: Subscription docs page looks consistent and accurate

The new Subscription feature page is well-structured, uses the existing SEO/Anchor/Language/Platform tabs correctly, and its API examples (fetchProducts, subscriptionStatusIOS, verifyPurchase, deepLinkToSubscriptions, etc.) align with the current naming and platform conventions. I don’t see any issues that would block merging.

packages/docs/src/pages/docs.tsx (1)

6-7: Docs navigation and routes align with new features/updates pages

The new imports, sidebar links, and <Route> entries for lifecycle/subscription, the /features pages (purchase, subscription, offer-code-redemption, external-purchase, subscription-upgrade-downgrade), and the /updates pages (announcements, notes, versions) are all internally consistent and match their paths. No functional issues spotted.

Also applies to: 11-21, 145-190, 193-220, 224-255

packages/gql/src/type.graphql (2)

33-47: Provider and IAPKit enums are well-scoped

PurchaseVerificationProvider, IapkitStore, and IapkitEnvironment cleanly model the provider/store/environment axes and line up with the generated Swift enums. The naming is consistent with existing schema style and Swift casing rules (Iapkit* types).


288-322: ActiveSubscription deprecation metadata for willExpireSoon is clear

Adding willExpireSoon with a deprecation directive and explicit guidance to prefer daysUntilExpirationIOS provides a good migration path while keeping existing clients working. This also matches the updated Swift ActiveSubscription model.

Comment thread .claude/guides/04-apple-package.md Outdated
Comment thread packages/docs/src/pages/docs/apis.tsx
Comment thread packages/docs/src/pages/docs/apis.tsx
Comment thread packages/docs/src/pages/docs/apis.tsx
Comment thread packages/docs/src/pages/docs/events.tsx
@hyochan hyochan force-pushed the feat/verify-receipt-with-provider branch from fbc9af4 to 7aef997 Compare November 26, 2025 14:55
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (4)
packages/apple/Sources/OpenIapModule.swift (3)

609-611: Unreachable guard: hardcoded URL cannot fail.

URL(string: "https://api.iapkit.com/v1/purchase/verify") cannot return nil at runtime. Consider force-unwrapping with a static constant to eliminate this dead code path:

+private static let iapkitVerifyURL = URL(string: "https://api.iapkit.com/v1/purchase/verify")!
+
 private func verifyPurchaseWithIapkit(props: RequestVerifyPurchaseWithIapkitProps) async throws -> [RequestVerifyPurchaseWithIapkitResult] {
-    guard let url = URL(string: "https://api.iapkit.com/v1/purchase/verify") else {
-        throw makePurchaseError(code: .developerError, message: "IAPKit endpoint is required")
-    }
+    let url = Self.iapkitVerifyURL

Based on learnings, the IAPKit endpoint is handled internally using a default base URL.


623-625: Guard is unreachable; consider removing or updating error message.

This guard cannot trigger because line 617–619 already throws if props.apple is nil. If kept for defensive coding, update the message to "IAPKit verification on Apple requires an apple payload" for clarity, as this module only supports Apple verification.


708-724: Add clarifying comment for the unused .google case.

The .google branch in buildIapkitPayload exists for structural parity with the Android module but is never invoked in this Apple implementation (line 614 rejects non-Apple stores). Consider adding a brief comment to prevent confusion:

case .google:
    // Note: This Apple module only processes Apple payloads.
    // The Google case exists for structural parity with the Android SDK.
    guard let google = props.google else { ...
packages/apple/Sources/OpenIapProtocol.swift (1)

81-84: Self-referential deprecation message is confusing.

The deprecation message "Use verifyPurchaseWithProvider instead" on the default verifyPurchaseWithProvider implementation is self-referential. This will warn callers to use the exact method they're already calling.

Consider either removing the deprecation attribute or clarifying the intent:

-    @available(*, deprecated, message: "Use verifyPurchaseWithProvider instead")
+    // Default implementation for backward compatibility - override in your module
     func verifyPurchaseWithProvider(_ props: VerifyPurchaseWithProviderProps) async throws -> VerifyPurchaseWithProviderResult {
🧹 Nitpick comments (1)
packages/apple/Sources/Models/Types.swift (1)

1390-1450: New VerifyPurchase methods and handlers are wired through correctly

The MutationResolver/QueryResolver protocols, their handler typealiases, and the MutationHandlers/QueryHandlers structs consistently use VerifyPurchaseProps, VerifyPurchaseResult, VerifyPurchaseWithProviderProps, VerifyPurchaseWithProviderResult, and VerifyPurchaseResultIOS. This keeps the Swift surface in sync with Kotlin and the GraphQL schema. As per coding guidelines, just ensure these changes came from running the generation script (./scripts/generate-types.sh) rather than manual edits.

Also applies to: 1509-1531, 1532-1603, 1489-1490, 1624-1684

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fbc9af4 and 7aef997.

⛔ Files ignored due to path filters (4)
  • packages/gql/src/generated/Types.kt is excluded by !**/generated/**
  • packages/gql/src/generated/Types.swift is excluded by !**/generated/**
  • packages/gql/src/generated/types.dart is excluded by !**/generated/**
  • packages/gql/src/generated/types.ts is excluded by !**/generated/**
📒 Files selected for processing (25)
  • .vscode/settings.json (1 hunks)
  • packages/apple/CONTRIBUTING.md (1 hunks)
  • packages/apple/Sources/Models/OpenIapSerialization.swift (1 hunks)
  • packages/apple/Sources/Models/ReceiptValidationCompat.swift (1 hunks)
  • packages/apple/Sources/Models/Types.swift (13 hunks)
  • packages/apple/Sources/OpenIapModule.swift (3 hunks)
  • packages/apple/Sources/OpenIapProtocol.swift (2 hunks)
  • packages/apple/Sources/OpenIapStore.swift (2 hunks)
  • packages/apple/Tests/OpenIapTests/VerifyPurchaseTests.swift (5 hunks)
  • packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift (1 hunks)
  • packages/google/CONTRIBUTING.md (1 hunks)
  • packages/google/Example/src/main/java/dev/hyo/martie/screens/SubscriptionFlowScreen.kt (1 hunks)
  • packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt (3 hunks)
  • packages/google/openiap/src/main/java/dev/hyo/openiap/OpenIapProtocol.kt (1 hunks)
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (11 hunks)
  • packages/google/openiap/src/main/java/dev/hyo/openiap/compat/ReceiptValidationCompat.kt (1 hunks)
  • packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt (4 hunks)
  • packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt (5 hunks)
  • packages/google/openiap/src/test/java/dev/hyo/openiap/ReceiptValidatorTest.kt (4 hunks)
  • packages/gql/CONVENTION.md (1 hunks)
  • packages/gql/src/api-ios.graphql (1 hunks)
  • packages/gql/src/api.graphql (1 hunks)
  • packages/gql/src/type-android.graphql (1 hunks)
  • packages/gql/src/type-ios.graphql (1 hunks)
  • packages/gql/src/type.graphql (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • packages/apple/Sources/Models/ReceiptValidationCompat.swift
  • packages/google/Example/src/main/java/dev/hyo/martie/screens/SubscriptionFlowScreen.kt
  • packages/gql/CONVENTION.md
  • packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift
  • packages/google/openiap/src/main/java/dev/hyo/openiap/compat/ReceiptValidationCompat.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/OpenIapProtocol.kt
  • packages/apple/CONTRIBUTING.md
  • packages/apple/Tests/OpenIapTests/VerifyPurchaseTests.swift
🧰 Additional context used
📓 Path-based instructions (4)
packages/apple/**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

packages/apple/**/*.swift: In iOS package, iOS-specific functions MUST have 'IOS' suffix (e.g., presentCodeRedemptionSheetIOS, showManageSubscriptionsIOS)
In iOS package, cross-platform functions have NO suffix (e.g., initConnection, fetchProducts, requestPurchase, finishTransaction)
In Swift, acronyms should be ALL CAPS only when they appear as a suffix; at beginning or middle use Pascal case (e.g., OpenIAP, IapManager, IapPurchase, ProductIAP)

Files:

  • packages/apple/Sources/OpenIapStore.swift
  • packages/apple/Sources/OpenIapProtocol.swift
  • packages/apple/Sources/Models/OpenIapSerialization.swift
  • packages/apple/Sources/OpenIapModule.swift
  • packages/apple/Sources/Models/Types.swift
packages/google/**/*.kt

📄 CodeRabbit inference engine (CLAUDE.md)

packages/google/**/*.kt: In Android package (packages/google), do not add 'Android' suffix to function names even for Android-specific APIs
Cross-platform API functions in Android should have no suffix (e.g., initConnection, fetchProducts, requestPurchase)

Files:

  • packages/google/openiap/src/test/java/dev/hyo/openiap/ReceiptValidatorTest.kt
  • packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt
  • packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
packages/google/openiap/src/main/java/dev/hyo/openiap/utils/**/*.kt

📄 CodeRabbit inference engine (CLAUDE.md)

Put reusable Kotlin helpers in packages/google/openiap/src/main/java/dev/hyo/openiap/utils/

Files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt
packages/apple/Sources/Models/Types.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Do not edit auto-generated Types.swift file in packages/apple/Sources/Models/ directly; regenerate via ./scripts/generate-types.sh

Files:

  • packages/apple/Sources/Models/Types.swift
🧠 Learnings (19)
📓 Common learnings
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 46
File: packages/docs/src/pages/docs/types.tsx:2210-2238
Timestamp: 2025-11-22T19:30:03.876Z
Learning: In the OpenIAP SDK, the IAPKit verification endpoint is handled internally using a default base URL. Users no longer need to provide an `endpoint` field in `RequestVerifyPurchaseWithIapkitProps` - the SDK automatically routes to the correct IAPKit `/purchase/verify` endpoint.
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 17
File: packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt:620-629
Timestamp: 2025-10-18T05:46:51.596Z
Learning: In packages/google/openiap/src/main/java/**/*.kt (Android-only package): DO NOT add Android suffix to function names, even for Android-specific APIs. Exception: Only use Android suffix for cross-platform API types (e.g., ProductAndroid, PurchaseAndroid) that contrast with iOS types. Examples of correct naming: isHorizonEnvironment(context: Context), buildModule(context: Context), acknowledgePurchase(), consumePurchase().
📚 Learning: 2025-10-18T05:46:51.596Z
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 17
File: packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt:620-629
Timestamp: 2025-10-18T05:46:51.596Z
Learning: In packages/google/openiap/src/main/java/**/*.kt (Android-only package): DO NOT add Android suffix to function names, even for Android-specific APIs. Exception: Only use Android suffix for cross-platform API types (e.g., ProductAndroid, PurchaseAndroid) that contrast with iOS types. Examples of correct naming: isHorizonEnvironment(context: Context), buildModule(context: Context), acknowledgePurchase(), consumePurchase().

Applied to files:

  • packages/google/CONTRIBUTING.md
  • packages/google/openiap/src/test/java/dev/hyo/openiap/ReceiptValidatorTest.kt
  • packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt
  • packages/gql/src/type-android.graphql
  • packages/gql/src/type.graphql
  • packages/apple/Sources/OpenIapModule.swift
  • packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt
  • packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
  • packages/apple/Sources/Models/Types.swift
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to {**/*.{ts,tsx,js,jsx},packages/docs/**/*.{ts,tsx,js,jsx}} : Android-only functions must end with 'Android' suffix (e.g., acknowledgePurchaseAndroid, consumePurchaseAndroid, getPackageNameAndroid)

Applied to files:

  • packages/google/CONTRIBUTING.md
  • packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to packages/google/openiap/src/main/java/*/Types.kt : Do not edit generated Android file packages/google/openiap/src/main/Types.kt directly; regenerate via ./scripts/generate-types.sh

Applied to files:

  • packages/google/CONTRIBUTING.md
  • packages/google/openiap/src/test/java/dev/hyo/openiap/ReceiptValidatorTest.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt
  • packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to packages/google/**/*.kt : Cross-platform API functions in Android should have no suffix (e.g., initConnection, fetchProducts, requestPurchase)

Applied to files:

  • packages/google/CONTRIBUTING.md
  • packages/google/openiap/src/test/java/dev/hyo/openiap/ReceiptValidatorTest.kt
  • packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt
  • packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to packages/apple/**/*.swift : In Swift, acronyms should be ALL CAPS only when they appear as a suffix; at beginning or middle use Pascal case (e.g., OpenIAP, IapManager, IapPurchase, ProductIAP)

Applied to files:

  • packages/google/CONTRIBUTING.md
  • packages/apple/Sources/OpenIapModule.swift
  • packages/apple/Sources/Models/Types.swift
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to packages/apple/**/*.swift : In iOS package, cross-platform functions have NO suffix (e.g., initConnection, fetchProducts, requestPurchase, finishTransaction)

Applied to files:

  • packages/google/CONTRIBUTING.md
  • packages/apple/Sources/OpenIapStore.swift
  • packages/apple/Sources/Models/Types.swift
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to packages/apple/**/*.swift : In iOS package, iOS-specific functions MUST have 'IOS' suffix (e.g., presentCodeRedemptionSheetIOS, showManageSubscriptionsIOS)

Applied to files:

  • packages/google/CONTRIBUTING.md
  • packages/apple/Sources/OpenIapModule.swift
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use 'request' prefix for async operations/requests (e.g., requestPurchase)

Applied to files:

  • packages/google/CONTRIBUTING.md
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to {**/*.{ts,tsx,js,jsx},packages/docs/**/*.{ts,tsx,js,jsx}} : iOS-only functions must end with 'IOS' suffix (e.g., clearTransactionIOS, getStorefrontIOS, presentCodeRedemptionSheetIOS)

Applied to files:

  • packages/google/CONTRIBUTING.md
📚 Learning: 2025-11-22T19:30:03.876Z
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 46
File: packages/docs/src/pages/docs/types.tsx:2210-2238
Timestamp: 2025-11-22T19:30:03.876Z
Learning: In the OpenIAP SDK, the IAPKit verification endpoint is handled internally using a default base URL. Users no longer need to provide an `endpoint` field in `RequestVerifyPurchaseWithIapkitProps` - the SDK automatically routes to the correct IAPKit `/purchase/verify` endpoint.

Applied to files:

  • packages/google/CONTRIBUTING.md
  • packages/apple/Sources/OpenIapStore.swift
  • packages/google/openiap/src/test/java/dev/hyo/openiap/ReceiptValidatorTest.kt
  • packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt
  • packages/apple/Sources/OpenIapProtocol.swift
  • packages/gql/src/type.graphql
  • packages/apple/Sources/Models/OpenIapSerialization.swift
  • packages/apple/Sources/OpenIapModule.swift
  • packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt
  • packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
  • packages/apple/Sources/Models/Types.swift
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use 'get' prefix for data retrieval functions (e.g., getPromotedProductIOS)

Applied to files:

  • packages/google/CONTRIBUTING.md
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Update iOS GQL types by editing openiap-versions.json gql version and running ./scripts/generate-types.sh

Applied to files:

  • packages/gql/src/type-ios.graphql
  • packages/gql/src/api-ios.graphql
  • packages/gql/src/type.graphql
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to packages/gql/src/generated/**/*.{ts,swift,kt,dart} : Do not edit auto-generated GraphQL type files; regenerate types from GraphQL schema via bun run generate

Applied to files:

  • packages/gql/src/type-ios.graphql
  • packages/gql/src/type.graphql
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to packages/google/openiap/src/main/java/dev/hyo/openiap/utils/**/*.kt : Put reusable Kotlin helpers in packages/google/openiap/src/main/java/dev/hyo/openiap/utils/

Applied to files:

  • packages/google/openiap/src/test/java/dev/hyo/openiap/ReceiptValidatorTest.kt
  • packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt
  • packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to {**/*.{ts,tsx,js,jsx},packages/docs/**/*.{ts,tsx,js,jsx}} : Cross-platform functions must have no platform suffix (e.g., fetchProducts, requestPurchase, getAvailablePurchases)

Applied to files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Compile Kotlin code with ./gradlew :openiap:compileDebugKotlin to verify changes

Applied to files:

  • packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to packages/google/**/*.kt : In Android package (packages/google), do not add 'Android' suffix to function names even for Android-specific APIs

Applied to files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
📚 Learning: 2025-11-26T05:50:26.217Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to packages/apple/Sources/Models/Types.swift : Do not edit auto-generated Types.swift file in packages/apple/Sources/Models/ directly; regenerate via ./scripts/generate-types.sh

Applied to files:

  • packages/apple/Sources/Models/Types.swift
🧬 Code graph analysis (9)
packages/apple/Sources/OpenIapStore.swift (2)
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (4)
  • verifyPurchase (2552-2552)
  • verifyPurchaseWithProvider (2556-2556)
  • iapkit (1742-1764)
  • iapkit (2347-2364)
packages/apple/Sources/OpenIapModule.swift (2)
  • verifyPurchase (589-592)
  • verifyPurchaseWithProvider (594-606)
packages/google/openiap/src/test/java/dev/hyo/openiap/ReceiptValidatorTest.kt (3)
packages/gql/src/generated/types.ts (4)
  • VerifyPurchaseProps (848-853)
  • VerifyPurchaseAndroidOptions (841-846)
  • RequestVerifyPurchaseWithIapkitProps (768-777)
  • RequestVerifyPurchaseWithIapkitGoogleProps (759-766)
packages/apple/Sources/OpenIapModule.swift (1)
  • verifyPurchaseWithIapkit (608-670)
packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt (1)
  • verifyPurchaseWithIapkit (86-143)
packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt (3)
packages/gql/src/generated/types.ts (1)
  • VerifyPurchaseWithProviderResult (894-898)
packages/apple/Sources/OpenIapModule.swift (1)
  • verifyPurchaseWithIapkit (608-670)
packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt (1)
  • verifyPurchaseWithIapkit (86-143)
packages/apple/Sources/OpenIapProtocol.swift (2)
packages/gql/src/generated/Types.kt (1)
  • androidOptions (2389-2412)
packages/gql/src/generated/types.ts (1)
  • VerifyPurchaseProps (848-853)
packages/apple/Sources/Models/OpenIapSerialization.swift (1)
packages/gql/src/generated/types.ts (1)
  • VerifyPurchaseProps (848-853)
packages/apple/Sources/OpenIapModule.swift (2)
packages/gql/src/generated/Types.kt (11)
  • verifyPurchase (2619-2619)
  • verifyPurchaseWithProvider (2623-2623)
  • iapkit (1809-1831)
  • iapkit (2414-2431)
  • code (1272-1294)
  • store (1544-1563)
  • request (2079-2130)
  • apiKey (2327-2362)
  • appId (613-665)
  • appId (2265-2294)
  • packageName (2296-2325)
packages/gql/src/generated/types.ts (3)
  • VerifyPurchaseResultIOS (878-887)
  • VerifyPurchaseWithProviderResult (894-898)
  • RequestVerifyPurchaseWithIapkitResult (779-782)
packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt (1)
packages/google/openiap/src/test/java/dev/hyo/openiap/ReceiptValidatorTest.kt (1)
  • setRequestProperty (256-260)
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (1)
packages/gql/src/generated/types.ts (10)
  • RequestVerifyPurchaseWithIapkitResult (779-782)
  • VerifyPurchaseResultAndroid (857-876)
  • VerifyPurchaseResultIOS (878-887)
  • VerifyPurchaseWithProviderResult (894-898)
  • RequestVerifyPurchaseWithIapkitAppleProps (750-757)
  • RequestVerifyPurchaseWithIapkitGoogleProps (759-766)
  • RequestVerifyPurchaseWithIapkitProps (768-777)
  • VerifyPurchaseAndroidOptions (841-846)
  • VerifyPurchaseProps (848-853)
  • VerifyPurchaseWithProviderProps (889-892)
packages/apple/Sources/Models/Types.swift (2)
packages/apple/Sources/OpenIapModule.swift (2)
  • verifyPurchase (589-592)
  • verifyPurchaseWithProvider (594-606)
packages/apple/Sources/OpenIapStore.swift (2)
  • verifyPurchase (376-386)
  • verifyPurchaseWithProvider (388-391)
⏰ 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 (32)
packages/google/CONTRIBUTING.md (1)

106-106: Documentation update accurately reflects new type names.

The examples have been correctly updated to include OpenIapVerifyPurchaseProps and OpenIapVerifyPurchaseResult in place of the deprecated receipt validation types, aligning with the provider-based verification feature introduced in this PR. All examples consistently follow the OpenIap prefix convention.

.vscode/settings.json (1)

4-4: Correct the spelling entry to match actual casing used in codebase.

The cSpell entry should be "IAPKit" (with capital I, A, P, K) to match the actual casing used throughout the codebase, as indicated in the learnings and documentation (e.g., RequestVerifyPurchaseWithIapkitProps). The current entry "Iapkit" may cause the spell checker to not recognize correctly-capitalized instances.

Apply this diff to correct the capitalization:

-    "Iapkit",
+    "IAPKit",
⛔ Skipped due to learnings
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to packages/apple/**/*.swift : In Swift, acronyms should be ALL CAPS only when they appear as a suffix; at beginning or middle use Pascal case (e.g., OpenIAP, IapManager, IapPurchase, ProductIAP)
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T05:50:26.217Z
Learning: Applies to {**/*.{ts,tsx,js,jsx},packages/docs/**/*.{ts,tsx,js,jsx}} : iOS-only functions must end with 'IOS' suffix (e.g., clearTransactionIOS, getStorefrontIOS, presentCodeRedemptionSheetIOS)
packages/gql/src/type-ios.graphql (1)

226-243: VerifyPurchaseResultIOS rename is consistent and non-breaking

The type keeps the previous shape and updates naming to the new VerifyPurchase* convention; looks good and should be backward-compatible at the schema level.

packages/gql/src/type-android.graphql (1)

156-182: Android VerifyPurchase schema migration looks correct*

VerifyPurchaseAndroidOptions and VerifyPurchaseResultAndroid mirror the prior receipt validation structures with updated names, keeping field-level compatibility and matching the generated client types.

packages/gql/src/api-ios.graphql (1)

64-69: iOS validateReceiptIOS correctly migrated to VerifyPurchase types

The query now accepts VerifyPurchaseProps and returns VerifyPurchaseResultIOS! while remaining deprecated in favor of verifyPurchase, which is a clean, minimal breaking-change path.

packages/apple/Sources/Models/OpenIapSerialization.swift (1)

116-118: VerifyPurchaseProps decoding helper is consistent with existing serializers

Decoding VerifyPurchaseProps via the shared decode(object:as:) path matches the other builder helpers and keeps error handling centralized; no issues spotted.

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

371-391: Store-level verifyPurchase APIs are wired correctly to the module

  • validateReceipt(sku:) now thinly delegates to verifyPurchase(sku:) with a proper deprecation message.
  • verifyPurchase(sku:) constructs VerifyPurchaseProps and safely unwraps the iOS variant of VerifyPurchaseResult, falling back to a clear error if a non‑iOS result ever appears.
  • verifyPurchaseWithProvider(_:) exposes the new provider-based flow while returning the IAPKit result list, which matches the underlying module contracts and docs about internal endpoint handling.
packages/google/openiap/src/test/java/dev/hyo/openiap/ReceiptValidatorTest.kt (2)

3-22: VerifyPurchase / IAPKit test coverage is thorough and matches the validator semantics

  • Google Play validation tests correctly migrated to VerifyPurchaseProps/VerifyPurchaseAndroidOptions and still assert success parsing and non‑2xx → InvalidReceipt.
  • New verifyPurchaseWithIapkit tests cover argument validation (missing/invalid payloads), success paths (including Authorization header and payload shape via Gson), and HTTP error wrapping, which closely mirror the implementation in ReceiptValidator.verifyPurchaseWithIapkit.
  • The use of the injected connectionFactory plus assertions that it should not be invoked in invalid‑input cases is a solid guard against accidental network calls in those paths.

Also applies to: 28-103, 105-240


243-270: FakeHttpURLConnection correctly captures headers and JSON body for assertions

The overridden setRequestProperty and getOutputStream implementations provide just enough behavior for the tests (status code, body, headers, captured request payload) without leaking real network usage, and integrate cleanly with the validator’s outputStream.use { ... } pattern.

packages/gql/src/api.graphql (1)

71-83: Core GraphQL mutations cleanly migrated to VerifyPurchase and provider-based flow

The mutations now:

  • Deprecate validateReceipt in favor of verifyPurchase while sharing VerifyPurchaseProps/VerifyPurchaseResult.
  • Introduce verifyPurchaseWithProvider with dedicated props/result and documentation.
    This keeps the public schema coherent with the new provider model and platform-specific APIs.
packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt (1)

39-42: Horizon module’s verifyPurchaseWithProvider wiring is correct and provider-scoped

  • verifyPurchaseWithProvider explicitly supports only PurchaseVerificationProvider.Iapkit, returning FeatureNotSupported for others, which is appropriate for this Android/Horizon flavor.
  • It requires non‑null props.iapkit and delegates to verifyPurchaseWithIapkit(options, TAG), then wraps the results in VerifyPurchaseWithProviderResult with the original provider, matching the shared type definitions.
  • The new handler is also correctly registered in mutationHandlers, so the GraphQL/SDK surface will see the provider-based flow.

Also applies to: 660-700

packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt (2)

811-820: LGTM! Provider-based verification implementation is correct.

The handler properly validates that only the Iapkit provider is supported, throws appropriate errors when options are missing, and delegates to the utility function. The function naming follows the coding guidelines (no Android suffix for cross-platform API).


849-850: Correctly wired into mutation handlers.

The new verifyPurchaseWithProvider handler is properly added to MutationHandlers alongside the existing verification handlers.

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

594-606: Provider verification entry point is well-implemented.

The method correctly validates the provider type, ensures required IAPKit parameters are present, and delegates to the internal verification function. Error handling is appropriate with clear messages.

packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt (3)

86-99: IAPKit verification function correctly restricts to Google store.

The implementation properly validates that only Google payloads are supported on Android, aligning with the symmetric Apple-only restriction in the Swift module. Per learnings, the endpoint is handled internally using the default base URL.


111-141: Comprehensive error handling with proper resource cleanup.

The function handles HTTP errors, JSON parsing failures, and IO exceptions appropriately, with consistent logging that includes the store identifier. The finally block ensures the connection is disconnected.


145-171: Payload builder correctly validates required Google fields.

The function ensures packageName, purchaseId, and purchaseToken are all non-blank before building the payload, with clear error messages for missing requirements.

packages/apple/Sources/OpenIapProtocol.swift (2)

86-99: Backward compatibility wrappers are correctly implemented.

The deprecated wrappers properly map legacy ReceiptValidationProps to the new VerifyPurchaseProps and delegate to the updated protocol methods, preserving source compatibility for existing callers.


45-49: Protocol changes are breaking but mitigated by default implementation.

Adding verifyPurchaseWithProvider to the protocol is a breaking change for external conformers. The default implementation (lines 81-84) that throws .featureNotSupported allows existing conformers to compile, though they'll get runtime errors if the new method is called.

Ensure the CHANGELOG documents this as a potentially breaking change for custom protocol conformers.

packages/gql/src/type.graphql (3)

33-46: New enums for provider-based verification are well-defined.

The PurchaseVerificationProvider, IapkitStore, and IapkitEnvironment enums provide clear discriminators for the multi-provider verification flow.


221-273: IAPKit input and result types are well-structured.

The schema correctly separates Apple and Google verification parameters, with appropriate required/optional field designations. The environment defaulting to Sandbox is sensible for development safety.


275-286: Provider-scoped types are extensible for future providers.

The design with PurchaseVerificationProvider enum and optional provider-specific payloads allows for adding new verification providers in the future while maintaining backward compatibility.

packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (6)

211-226: IAPKit enums and provider look consistent and robust

IapkitEnvironment, IapkitStore, and PurchaseVerificationProvider follow the existing enum pattern (case-insensitive fromJson, rawValue-based toJson) and match the TS GraphQL definitions. No issues here.

Also applies to: 228-243, 375-387


1477-1496: Provider result aggregation matches GraphQL contract

RequestVerifyPurchaseWithIapkitResult and VerifyPurchaseWithProviderResult mirror the TS interfaces (store, valid, iapkit, provider) and use the same JSON shapes, so Kotlin ↔︎ GraphQL ↔︎ Swift stay aligned.

Also applies to: 1742-1764


2198-2227: IAPKit Apple/Google props and root props align with docs and TS

The three RequestVerifyPurchaseWithIapkit* data classes match the TS types and omit any endpoint field, which is correct now that the SDK routes to the IAPKit /purchase/verify endpoint internally. Based on learnings, this is the desired shape.

Also applies to: 2229-2258, 2260-2295


2297-2320: VerifyPurchase Android options and props are well-shaped

VerifyPurchaseAndroidOptions and VerifyPurchaseProps match the TS schema (optional androidOptions, required sku) and keep Android-specific details encapsulated. JSON mapping is straightforward and consistent with the rest of the file.

Also applies to: 2322-2345


2347-2364: VerifyPurchase union wiring looks correct

VerifyPurchaseWithProviderProps and the sealed VerifyPurchaseResult union use the same union-dispatch pattern as Product/Purchase, with fromJson switching on __typename and delegating to platform-specific result types. This matches the generated TS unions.

Also applies to: 2434-2446


2453-2557: Verification confirmed: All new APIs coherently use VerifyPurchaseProps; legacy ReceiptValidation references are intentional backward-compatibility measures

All remaining ReceiptValidation* references are properly accounted for:

  • Apple protocol functions (validateReceiptIOS, validateReceipt, verifyPurchase) are marked @deprecated and serve as wrapper functions that convert ReceiptValidationProps to VerifyPurchaseProps before calling the new implementations
  • Type aliases in ReceiptValidationCompat.kt and ReceiptValidationCompat.swift provide intentional backward compatibility
  • Handler typealias definitions (lines 2683–2686, 2731–2752) and handler containers (lines 2688–2710) correctly wire the new VerifyPurchase* handlers with VerifyPurchaseProps
  • No problematic legacy code remains in active use
packages/apple/Sources/Models/Types.swift (4)

153-156: IAPKit enums/providers match naming and style guidelines

IapkitEnvironment, IapkitStore, and PurchaseVerificationProvider follow the existing enum conventions (String, Codable, CaseIterable) and use Pascal‑case identifiers with lowercase raw values, which fits the Swift naming guidance for acronyms in this project.

Also applies to: 158-161, 202-204


563-566: IAPKit and provider result models are consistent with Kotlin & TS

RequestVerifyPurchaseWithIapkitResult, VerifyPurchaseResultAndroid, VerifyPurchaseResultIOS, and VerifyPurchaseWithProviderResult expose the same fields as the Kotlin data classes and TS interfaces, giving a clean cross‑platform contract for verification results.

Also applies to: 604-640


977-995: VerifyPurchase IAPKit props mirror schema and remove endpoint as intended

The three RequestVerifyPurchaseWithIapkit* structs, VerifyPurchaseAndroidOptions, VerifyPurchaseProps, and VerifyPurchaseWithProviderProps line up with the GraphQL/TS definitions and intentionally do not expose an endpoint field, relying on the internal default base URL instead. Based on learnings, this matches the updated IAPKit flow.

Also applies to: 996-1013, 1015-1036, 1038-1055, 1057-1070, 1072-1083


1382-1385: VerifyPurchaseResult enum mirrors previous receipt result union

The VerifyPurchaseResult enum adopts the same pattern as prior union enums (case verifyPurchaseResultAndroid, case verifyPurchaseResultIos) and aligns with the platform‑specific result structs. Given this file is generated, the Codable strategy should stay consistent with the existing unions.

…types

- Remove verificationMethod from LaunchedEffect dependencies (Android)
- Add processedPurchaseKey to prevent duplicate purchase processing (iOS/Android)
- Update IapkitPurchaseState: PURCHASED → ENTITLED, remove FAILED, add INAUTHENTIC
- Fix nullable purchaseToken handling in Android Example apps
- Simplify verifyWithIapkit helper functions
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

♻️ Duplicate comments (1)
packages/apple/Sources/OpenIapModule.swift (1)

609-623: Past review issues not addressed: unreachable URL guard and misleading error message.

As flagged in prior review:

  1. Line 609-611: The URL guard is dead code since URL(string: "https://api.iapkit.com/v1/purchase/verify") cannot fail.
  2. Line 622: The error message "IAPKit verification requires apple and/or google payloads" is misleading since this Apple module only supports Apple payloads.
-    private func verifyPurchaseWithIapkit(props: RequestVerifyPurchaseWithIapkitProps) async throws -> [RequestVerifyPurchaseWithIapkitResult] {
-        guard let url = URL(string: "https://api.iapkit.com/v1/purchase/verify") else {
-            throw makePurchaseError(code: .developerError, message: "IAPKit endpoint is required")
-        }
+    private static let iapkitVerifyURL = URL(string: "https://api.iapkit.com/v1/purchase/verify")!
+
+    private func verifyPurchaseWithIapkit(props: RequestVerifyPurchaseWithIapkitProps) async throws -> [RequestVerifyPurchaseWithIapkitResult] {
+        let url = Self.iapkitVerifyURL

         let targets: [(store: IapkitStore, body: Data)] = try {
-            // On Apple, only Apple verification is supported
+            // This Apple module only supports Apple verification payloads
             guard props.apple != nil else {
                 throw makePurchaseError(code: .developerError, message: "Apple verification parameters are required")
             }
             return [(.apple, try buildIapkitPayload(props: props, store: .apple))]
         }()

-        guard targets.isEmpty == false else {
-            throw makePurchaseError(code: .developerError, message: "IAPKit verification requires apple and/or google payloads")
-        }
+        // Note: targets.isEmpty check removed as the guard above guarantees at least one Apple target
🧹 Nitpick comments (15)
packages/apple/Example/OpenIapExample/Info.plist.example (1)

1-8: Example plist is fine; consider reinforcing “don’t use in production”

The plist structure and placeholder key are fine. To avoid accidental use of a real production secret here, consider:

  • Renaming the placeholder to something like DEVELOPMENT_IAPKIT_API_KEY_HERE, and/or
  • Mentioning in docs that this file is for local/example use only, not for shipping builds.
packages/google/Example/build.gradle.kts (1)

39-43: IAPKit API key wiring looks correct for the example

The fallback chain from local.properties → project property → empty string is solid, and exposing it via BuildConfig.IAPKIT_API_KEY matches how the example screens consume it. Just keep in mind this embeds the key into the APK, so this pattern should remain strictly for sample/debug apps rather than production.

packages/docs/src/pages/docs/example.tsx (1)

613-628: Align Gradle snippet with actual Example/build.gradle.kts

The Android “How API Key is Loaded” snippet is conceptually correct but the buildConfigField line:

buildConfigField(
    "String",
    "IAPKIT_API_KEY",
    ""${'$'}{localProperties.getProperty("iapkit.api.key", "")}""
)

is not something you’d want to copy verbatim into build.gradle.kts (quoting is awkward and doesn’t match the real Example module, which first assigns val iapkitApiKey = ... and then wraps it in quotes).

Consider updating the snippet to mirror the actual Example/build.gradle.kts structure (compute iapkitApiKey first, then buildConfigField("String", "IAPKIT_API_KEY", "\"${'$'}iapkitApiKey\"")) so readers can safely copy it into their own projects.

packages/google/Example/src/main/java/dev/hyo/martie/screens/PurchaseFlowScreen.kt (1)

682-689: Use StateFlow value instead of repeatedly calling first() in the retry loop

The connection retry loop currently does:

while (!iapStore.isConnected.first() && System.currentTimeMillis() - started < 1500) {
    delay(100)
}

If isConnected is a StateFlow<Boolean> (which it looks like, given how it’s used elsewhere), repeatedly calling first() creates a new collector on every iteration. You can avoid unnecessary Flow collection and mirror the subscription screen by reading the current value instead:

-while (!iapStore.isConnected.first() && System.currentTimeMillis() - started < 1500) {
+while (!iapStore.isConnected.value && System.currentTimeMillis() - started < 1500) {
     delay(100)
}

This keeps the loop cheap and avoids any subtle Flow lifecycle issues.

packages/docs/src/pages/docs.tsx (1)

19-19: LGTM! Example page integration is straightforward.

The new Example page is properly wired into navigation and routing. The placement under "Setup Guide" is functional, though you might consider a dedicated "Examples" or "Getting Started" section for better organization.

Also applies to: 143-151, 263-263

packages/google/openiap/src/test/java/dev/hyo/openiap/PurchaseVerificationValidatorTest.kt (1)

85-103: Consider using PurchaseVerificationFailed instead of InvalidReceipt.

These tests catch OpenIapError.InvalidReceipt, but the PR introduces a new error taxonomy with PurchaseVerificationFailed. Based on the error code updates in error.graphql and OpenIapError.swift, the new verification flows should use the updated error type.

Verify the correct error type and update if needed:

         try {
             validateReceiptWithGooglePlay(
                 props,
                 "TEST_TAG"
             ) { _ -> FakeHttpURLConnection(401, """{"error":"unauthorized"}""") }
             throw AssertionError("Expected InvalidReceipt for non-2xx response")
-        } catch (error: OpenIapError.InvalidReceipt) {
-            assertEquals("Invalid receipt", error.message)
+        } catch (error: OpenIapError.PurchaseVerificationFailed) {
+            assertEquals("Purchase verification failed", error.message)
         }

Also applies to: 213-233

packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift (2)

45-78: Misleading test name: tests empty array, not nil.

The test name testStoreReturnsNilWhenProviderResultIsNil suggests testing a nil scenario, but the test actually verifies that an empty iapkit array results in result.count == 0. Consider renaming to better reflect the actual behavior being tested.

     @MainActor
-    func testStoreReturnsNilWhenProviderResultIsNil() async throws {
+    func testStoreReturnsEmptyWhenProviderResultIsEmpty() async throws {

102-106: Remove redundant Void return types.

As flagged by SwiftLint, returning Void in function declarations is redundant. This applies to lines 102, 106, and 152.

-    func restorePurchases() async throws -> Void { () }
+    func restorePurchases() async throws { }
 
     // ...
 
-    func finishTransaction(purchase: PurchaseInput, isConsumable: Bool?) async throws -> Void { () }
+    func finishTransaction(purchase: PurchaseInput, isConsumable: Bool?) async throws { }
 
     // ... line 152
 
-    func deepLinkToSubscriptions(_ options: DeepLinkOptions?) async throws -> Void { () }
+    func deepLinkToSubscriptions(_ options: DeepLinkOptions?) async throws { }
packages/apple/Example/OpenIapExample/Screens/PurchaseFlowScreen.swift (1)

203-277: Consider extracting shared VerificationMethodCard component.

This VerificationMethodCard implementation is nearly identical to the one in SubscriptionFlowScreen.swift (see relevant code snippet at lines 479-562). Consider extracting this into a shared UI component to avoid duplication.

packages/google/openiap/src/main/java/dev/hyo/openiap/utils/PurchaseVerificationValidator.kt (3)

136-143: Log the swallowed exception for debuggability.

As flagged by detekt, the exception in the catch block is swallowed without logging. While the fallback behavior is appropriate, logging the exception at debug level would aid troubleshooting.

                 val errorMessage = try {
                     val mapType = object : TypeToken<Map<String, Any?>>() {}.type
                     val errorJson = gson.fromJson<Map<String, Any?>>(responseBody, mapType)
                     extractIapkitErrorMessage(errorJson) ?: "HTTP $statusCode"
                 } catch (e: Exception) {
+                    OpenIapLog.debug("Failed to extract error message: ${e.message}", tag)
                     "HTTP $statusCode"
                 }

163-166: Log the swallowed exception for debuggability.

Similar to above, logging the JSON parsing exception would help diagnose issues.

             } catch (jsonError: Exception) {
-                OpenIapLog.warn("Failed to parse IAPKit verification response: ${jsonError.message}", tag)
+                OpenIapLog.warn("Failed to parse IAPKit verification response: ${jsonError.message}", tag, jsonError)
                 throw OpenIapError.PurchaseVerificationFailed("Failed to parse response")
             }

200-252: LGTM with minor suggestion.

The extractIapkitErrorMessage function properly handles nested IAPKit error structures, mirroring the Swift implementation. The recursive extraction logic is sound.

Consider logging the swallowed exception at line 237 at debug level:

             return try {
                 val nested = gson.fromJson(originalError, Map::class.java) as? Map<String, Any?>
                 if (nested != null) {
                     extractIapkitErrorMessage(nested) ?: originalError
                 } else {
                     originalError
                 }
             } catch (e: Exception) {
-                // Not JSON, use as-is
+                // Not JSON, use as-is (debug: ${e.message})
                 originalError
             }
packages/gql/src/type.graphql (1)

230-243: Consider documenting the optional apiKey field behavior.

The apiKey field is optional, but it's not clear from the schema what happens when it's omitted. Is verification allowed without an API key, or will it fail?

 input RequestVerifyPurchaseWithIapkitProps {
   """
-  API key used for the Authorization header (Bearer {apiKey}).
+  API key used for the Authorization header (Bearer {apiKey}).
+  When omitted, the request is sent without authentication.
   """
   apiKey: String
packages/apple/Example/OpenIapExample/Screens/SubscriptionFlowScreen.swift (1)

20-24: API key from Info.plist may be visible in IPA.

Reading IAPKIT_API_KEY from Info.plist means it could be extracted from the compiled app bundle. Environment variables are preferable for secrets. Consider documenting this security tradeoff for production apps.

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

646-660: Consider adding a timeout to the URLSession request.

The HTTP request to IAPKit has no explicit timeout, which could leave the app waiting indefinitely if the service is unresponsive.

+                    request.timeoutInterval = 30 // 30 second timeout
                     request.httpBody = target.body

                     // Log request details for debugging
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7aef997 and 2894018.

⛔ Files ignored due to path filters (16)
  • packages/docs/public/examples/1. [Android] Example.png is excluded by !**/*.png
  • packages/docs/public/examples/1. [IOS] Example.png is excluded by !**/*.png
  • packages/docs/public/examples/2. [Android] Purchase Flow.png is excluded by !**/*.png
  • packages/docs/public/examples/2. [IOS] Purchase Flow.png is excluded by !**/*.png
  • packages/docs/public/examples/3. [Android] Subscription Flow.png is excluded by !**/*.png
  • packages/docs/public/examples/3. [IOS] Subscription Flow Upgrade.png is excluded by !**/*.png
  • packages/docs/public/examples/4. [Android] Subscription Flow Upgrade.png is excluded by !**/*.png
  • packages/docs/public/examples/4. [IOS] Available Purchases.png is excluded by !**/*.png
  • packages/docs/public/examples/5. [Android] Available Purchases.png is excluded by !**/*.png
  • packages/docs/public/examples/5. [IOS] Offer Code.png is excluded by !**/*.png
  • packages/docs/public/examples/6. [Android] Redeem Offer Code.png is excluded by !**/*.png
  • packages/docs/public/examples/6. [IOS] Alternative Billing.png is excluded by !**/*.png
  • packages/gql/src/generated/Types.kt is excluded by !**/generated/**
  • packages/gql/src/generated/Types.swift is excluded by !**/generated/**
  • packages/gql/src/generated/types.dart is excluded by !**/generated/**
  • packages/gql/src/generated/types.ts is excluded by !**/generated/**
📒 Files selected for processing (32)
  • packages/apple/Example/.gitignore (1 hunks)
  • packages/apple/Example/Martie.xcodeproj/project.pbxproj (2 hunks)
  • packages/apple/Example/Martie.xcodeproj/xcshareddata/xcschemes/OpenIapExample.xcscheme (1 hunks)
  • packages/apple/Example/OpenIapExample/Info.plist.example (1 hunks)
  • packages/apple/Example/OpenIapExample/Screens/PurchaseFlowScreen.swift (8 hunks)
  • packages/apple/Example/OpenIapExample/Screens/SubscriptionFlowScreen.swift (5 hunks)
  • packages/apple/README.md (3 hunks)
  • packages/apple/Sources/Models/OpenIapError.swift (1 hunks)
  • packages/apple/Sources/Models/Types.swift (15 hunks)
  • packages/apple/Sources/OpenIapModule.swift (4 hunks)
  • packages/apple/Tests/OpenIapTests.swift (1 hunks)
  • packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift (1 hunks)
  • packages/docs/package.json (1 hunks)
  • packages/docs/src/pages/docs.tsx (3 hunks)
  • packages/docs/src/pages/docs/errors.tsx (7 hunks)
  • packages/docs/src/pages/docs/example.tsx (1 hunks)
  • packages/docs/src/styles/pages.css (1 hunks)
  • packages/google/Example/build.gradle.kts (1 hunks)
  • packages/google/Example/src/main/java/dev/hyo/martie/screens/PurchaseFlowScreen.kt (6 hunks)
  • packages/google/Example/src/main/java/dev/hyo/martie/screens/SubscriptionFlowScreen.kt (10 hunks)
  • packages/google/README.md (1 hunks)
  • packages/google/local.properties.example (1 hunks)
  • packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt (3 hunks)
  • packages/google/openiap/src/main/java/dev/hyo/openiap/OpenIapError.kt (2 hunks)
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (14 hunks)
  • packages/google/openiap/src/main/java/dev/hyo/openiap/utils/PurchaseVerificationValidator.kt (1 hunks)
  • packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt (0 hunks)
  • packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt (5 hunks)
  • packages/google/openiap/src/test/java/dev/hyo/openiap/PurchaseVerificationValidatorTest.kt (1 hunks)
  • packages/gql/package.json (1 hunks)
  • packages/gql/src/error.graphql (1 hunks)
  • packages/gql/src/type.graphql (2 hunks)
💤 Files with no reviewable changes (1)
  • packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ReceiptValidator.kt
✅ Files skipped from review due to trivial changes (2)
  • packages/google/openiap/src/main/java/dev/hyo/openiap/OpenIapError.kt
  • packages/gql/package.json
🧰 Additional context used
🧬 Code graph analysis (12)
packages/apple/Example/OpenIapExample/Screens/PurchaseFlowScreen.swift (1)
packages/apple/Example/OpenIapExample/Screens/SubscriptionFlowScreen.swift (1)
  • VerificationMethodCard (480-563)
packages/google/Example/src/main/java/dev/hyo/martie/screens/PurchaseFlowScreen.kt (3)
packages/gql/src/generated/types.ts (2)
  • RequestVerifyPurchaseWithIapkitProps (764-771)
  • RequestVerifyPurchaseWithIapkitGoogleProps (759-762)
packages/apple/Sources/OpenIapModule.swift (1)
  • verifyPurchaseWithIapkit (608-700)
packages/google/Example/src/main/java/dev/hyo/martie/screens/SubscriptionFlowScreen.kt (2)
  • verifyLocally (1288-1290)
  • verifyWithIapkit (1269-1285)
packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt (3)
packages/google/openiap/src/main/java/dev/hyo/openiap/utils/PurchaseVerificationValidator.kt (2)
  • verifyPurchaseWithGooglePlay (26-81)
  • verifyPurchaseWithIapkit (83-174)
packages/gql/src/generated/types.ts (1)
  • VerifyPurchaseWithProviderResult (891-895)
packages/apple/Sources/OpenIapModule.swift (1)
  • verifyPurchaseWithIapkit (608-700)
packages/docs/src/pages/docs/errors.tsx (3)
packages/google/openiap/src/main/java/dev/hyo/openiap/OpenIapError.kt (1)
  • code (6-319)
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (1)
  • code (1268-1290)
packages/gql/src/generated/Types.kt (1)
  • code (1333-1355)
packages/google/Example/src/main/java/dev/hyo/martie/screens/SubscriptionFlowScreen.kt (1)
packages/google/Example/src/main/java/dev/hyo/martie/screens/PurchaseFlowScreen.kt (2)
  • verifyLocally (581-583)
  • verifyWithIapkit (561-577)
packages/google/openiap/src/main/java/dev/hyo/openiap/utils/PurchaseVerificationValidator.kt (2)
packages/google/openiap/src/test/java/dev/hyo/openiap/PurchaseVerificationValidatorTest.kt (1)
  • setRequestProperty (249-253)
packages/apple/Sources/OpenIapModule.swift (1)
  • extractIapkitErrorMessage (745-769)
packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt (3)
packages/google/openiap/src/main/java/dev/hyo/openiap/utils/PurchaseVerificationValidator.kt (2)
  • verifyPurchaseWithGooglePlay (26-81)
  • verifyPurchaseWithIapkit (83-174)
packages/gql/src/generated/types.ts (1)
  • VerifyPurchaseWithProviderResult (891-895)
packages/apple/Sources/OpenIapModule.swift (1)
  • verifyPurchaseWithIapkit (608-700)
packages/docs/src/pages/docs/example.tsx (2)
packages/google/openiap/src/main/java/dev/hyo/openiap/OpenIapError.kt (1)
  • code (6-319)
packages/gql/src/generated/Types.kt (1)
  • code (1333-1355)
packages/apple/Example/OpenIapExample/Screens/SubscriptionFlowScreen.swift (3)
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (20)
  • id (1406-1428)
  • iapkit (1814-1836)
  • iapkit (2389-2406)
  • verifyPurchase (2594-2594)
  • verifyPurchaseWithProvider (2598-2598)
  • rawValue (15-44)
  • rawValue (46-166)
  • rawValue (171-194)
  • rawValue (196-221)
  • rawValue (226-289)
  • rawValue (291-306)
  • rawValue (308-324)
  • rawValue (326-347)
  • rawValue (349-367)
  • rawValue (369-384)
  • rawValue (386-407)
  • rawValue (409-436)
  • rawValue (438-450)
  • rawValue (452-467)
  • rawValue (469-493)
packages/gql/src/generated/Types.kt (6)
  • id (1471-1493)
  • iapkit (1879-1901)
  • iapkit (2454-2471)
  • verifyPurchase (2659-2659)
  • verifyPurchaseWithProvider (2663-2663)
  • rawValue (14-43)
packages/apple/Sources/OpenIapStore.swift (2)
  • verifyPurchase (376-386)
  • verifyPurchaseWithProvider (388-391)
packages/google/openiap/src/test/java/dev/hyo/openiap/PurchaseVerificationValidatorTest.kt (3)
packages/gql/src/generated/types.ts (4)
  • VerifyPurchaseProps (845-850)
  • VerifyPurchaseAndroidOptions (838-843)
  • RequestVerifyPurchaseWithIapkitProps (764-771)
  • RequestVerifyPurchaseWithIapkitGoogleProps (759-762)
packages/apple/Sources/OpenIapModule.swift (1)
  • verifyPurchaseWithIapkit (608-700)
packages/google/openiap/src/main/java/dev/hyo/openiap/utils/PurchaseVerificationValidator.kt (1)
  • verifyPurchaseWithIapkit (83-174)
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (1)
packages/gql/src/generated/types.ts (10)
  • RequestVerifyPurchaseWithIapkitResult (773-779)
  • VerifyPurchaseResultAndroid (854-873)
  • VerifyPurchaseResultIOS (875-884)
  • VerifyPurchaseWithProviderResult (891-895)
  • RequestVerifyPurchaseWithIapkitAppleProps (754-757)
  • RequestVerifyPurchaseWithIapkitGoogleProps (759-762)
  • RequestVerifyPurchaseWithIapkitProps (764-771)
  • VerifyPurchaseAndroidOptions (838-843)
  • VerifyPurchaseProps (845-850)
  • VerifyPurchaseWithProviderProps (886-889)
packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift (4)
packages/apple/Sources/OpenIapModule.swift (2)
  • verifyPurchaseWithProvider (594-606)
  • verifyPurchase (589-592)
packages/gql/src/generated/types.ts (9)
  • RequestVerifyPurchaseWithIapkitResult (773-779)
  • VerifyPurchaseResult (852-852)
  • VerifyPurchaseResultIOS (875-884)
  • VerifyPurchaseWithProviderResult (891-895)
  • VerifyPurchaseWithProviderProps (886-889)
  • RequestVerifyPurchaseWithIapkitProps (764-771)
  • RequestVerifyPurchaseWithIapkitAppleProps (754-757)
  • IapkitStore (187-187)
  • PurchaseError (491-495)
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (9)
  • isValid (1540-1568)
  • isValid (1775-1812)
  • iapkit (1814-1836)
  • iapkit (2389-2406)
  • apiKey (2308-2337)
  • verifyPurchaseWithProvider (2598-2598)
  • code (1268-1290)
  • message (1430-1449)
  • verifyPurchase (2594-2594)
packages/gql/scripts/fix-generated-types.mjs (1)
  • result (428-428)
🪛 detekt (1.23.8)
packages/google/openiap/src/main/java/dev/hyo/openiap/utils/PurchaseVerificationValidator.kt

[warning] 140-140: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)


[warning] 167-167: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)


[warning] 237-237: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

🪛 SwiftLint (0.57.0)
packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift

[Warning] 102-102: Returning Void in a function declaration is redundant

(redundant_void_return)


[Warning] 106-106: Returning Void in a function declaration is redundant

(redundant_void_return)


[Warning] 152-152: Returning Void in a function declaration is redundant

(redundant_void_return)

⏰ 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 (44)
packages/google/local.properties.example (1)

1-11: Template looks good; confirm local.properties is ignored.

The placeholders and comments are clear and match the new IAPKit integration. Since this file is an .example, it’s fine to commit, but please double‑check that any real local.properties (at the repo root and under packages/google/ if applicable) is covered by .gitignore so SDK paths and iapkit.api.key never get committed by mistake.

packages/docs/src/styles/pages.css (1)

374-451: Well-structured screenshot card styling that aligns with design system.

The new .screenshot-card component follows established patterns in the file, using CSS custom properties and flexbox effectively. Dark mode and responsive behavior are properly implemented.

packages/apple/Example/.gitignore (1)

9-11: Ignoring the real Info.plist for local secrets looks good

The ignore entry matches the target’s INFOPLIST_FILE path and pairs well with the checked‑in Info.plist.example, keeping API keys out of git while still documenting the required shape.

packages/apple/Example/Martie.xcodeproj/project.pbxproj (1)

421-422: Confirm the GENERATE_INFOPLIST_FILE + INFOPLIST_FILE combo behaves as you expect

Pointing INFOPLIST_FILE at OpenIapExample/Info.plist (while it’s git‑ignored) fits the “local secrets from plist” pattern and lines up with Info.plist.example. From a clean clone, Xcode should still generate or allow creating that Info.plist at build/config time.

Please double‑check in Xcode that:

  • The target builds cleanly from a fresh checkout before manually copying the example file, and
  • Editing Info.plist (to add IAPKIT_API_KEY) is stable and not overwritten unexpectedly by Xcode’s generated‑plist behavior.

Also applies to: 452-453

packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt (1)

36-42: Provider-based verification wiring looks consistent

The Horizon module’s verifyPurchase and verifyPurchaseWithProvider handlers are correctly wired:

  • verifyPurchase reuses verifyPurchaseWithGooglePlay(props, TAG).
  • verifyPurchaseWithProvider explicitly supports only PurchaseVerificationProvider.Iapkit, requires props.iapkit, and returns a VerifyPurchaseWithProviderResult wrapping the verifyPurchaseWithIapkit list.
  • mutationHandlers now exposes both verifyPurchase and verifyPurchaseWithProvider, keeping the protocol surface consistent.

No functional issues spotted here.

Also applies to: 656-669, 688-699

packages/docs/package.json (1)

4-4: No action needed. The @hyodotdev/openiap-docs package is marked as "private": true and is not published or distributed. The version 1.0.0 is the standard starting point for packages in the monorepo structure following the migration commit. Semver concerns do not apply to private packages.

Likely an incorrect or invalid review comment.

packages/apple/README.md (1)

68-68: LGTM! Clear version variable approach.

The use of $version variable placeholder and the note pointing to openiap-versions.json provides a maintainable approach for version references in documentation.

Also applies to: 83-83, 92-93

packages/google/README.md (1)

51-51: LGTM! Consistent with Apple package documentation.

The version variable approach is consistent across packages and provides clear guidance for users.

Also applies to: 59-59, 63-64

packages/apple/Sources/Models/OpenIapError.swift (1)

16-22: LGTM! Well-structured deprecation path.

The error code migration maintains backward compatibility by preserving the old receipt* cases while introducing new purchaseVerification* variants. The deprecation comments clearly indicate the replacement path.

packages/gql/src/error.graphql (1)

12-20: LGTM! GraphQL schema aligned with code changes.

The deprecation comments and new error enum members align with the corresponding Swift error code updates, maintaining consistency across the API surface.

packages/apple/Tests/OpenIapTests.swift (1)

270-271: LGTM! Test validates backward-compatible error mapping.

The test properly verifies that both the new purchase-verification-failed code and the legacy receipt-failed alias map to .purchaseVerificationFailed, ensuring backward compatibility.

packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift (1)

8-43: LGTM!

The test properly verifies that verifyPurchaseWithProvider returns the expected IAPKit result with correct store and validity values. The fake module setup is well-structured.

packages/apple/Example/OpenIapExample/Screens/PurchaseFlowScreen.swift (5)

4-16: LGTM!

The VerificationMethod enum is well-designed with clear raw values and descriptive display names with appropriate emoji indicators.


30-38: LGTM!

Good addition of state management for verification flow. The iapkitApiKey computed property correctly checks both environment variables and Info.plist as fallback.


401-411: Good defensive coding for duplicate purchase prevention.

The processedPurchaseKey logic using transaction ID and date effectively prevents duplicate processing of the same purchase callback.


422-537: LGTM!

The verifyAndFinishPurchase flow is well-structured with clear branching for each verification method. Error handling is comprehensive, and the IAPKit branch correctly only finishes the transaction if verification passes.


495-504: Review comment is incorrect.

The RequestVerifyPurchaseWithIapkitAppleProps type definition does not include an environment parameter—it only accepts jws. The code at lines 495-504 is correct as written. The struct is defined with a single parameter:

public struct RequestVerifyPurchaseWithIapkitAppleProps: Codable {
    public var jws: String
    
    public init(jws: String) {
        self.jws = jws
    }
}

Both example files (PurchaseFlowScreen.swift and SubscriptionFlowScreen.swift) follow this pattern correctly.

Likely an incorrect or invalid review comment.

packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt (3)

36-38: LGTM!

Clean import additions for the new provider-based verification support.

Also applies to: 48-48, 65-66


807-820: LGTM!

The verifyPurchaseWithProvider implementation correctly:

  1. Validates the provider is IAPKit (throws FeatureNotSupported otherwise)
  2. Checks for required iapkit options (throws DeveloperError if missing)
  3. Delegates to verifyPurchaseWithIapkit utility and wraps result appropriately

849-850: LGTM!

The new handlers are correctly wired into the mutationHandlers alongside the existing deprecated validateReceipt for backward compatibility.

packages/docs/src/pages/docs/errors.tsx (3)

254-290: LGTM!

The validation errors table is correctly updated with the new naming convention:

  • PurchaseVerificationFailed (clearer than ReceiptFailed)
  • TransactionFinished and TransactionFinishFailed (more precise terminology)

508-663: LGTM!

All language-specific error code enums (TypeScript, Swift, Kotlin, Dart) are consistently updated with the new naming convention, maintaining alignment across platforms.


679-692: LGTM!

The iOS sandbox testing table is correctly updated to reference PurchaseVerificationFailed instead of the old ReceiptFailed.

packages/google/openiap/src/main/java/dev/hyo/openiap/utils/PurchaseVerificationValidator.kt (2)

26-81: LGTM!

The verifyPurchaseWithGooglePlay function correctly:

  1. Validates required Android options
  2. Constructs appropriate URL for subscriptions vs products
  3. Handles HTTP errors and JSON parsing with proper exception mapping
  4. Ensures connection cleanup in finally block

176-196: LGTM!

The buildPayload function correctly constructs the IAPKit request payload for Google verification with appropriate validation.

packages/gql/src/type.graphql (4)

33-41: LGTM! Well-defined provider enums.

The PurchaseVerificationProvider and IapkitStore enums are cleanly defined and properly scoped for future extensibility.


216-228: LGTM! Clean separation of platform-specific verification parameters.

The Apple and Google verification input types properly encapsulate their respective required fields (jws for Apple, purchaseToken for Google).


245-285: LGTM! Comprehensive purchase state enum.

The IapkitPurchaseState enum covers a thorough range of purchase lifecycle states with clear documentation for each case.


299-310: LGTM! Provider-based verification result structure is well-designed.

The VerifyPurchaseWithProviderResult properly aggregates results by provider and supports multiple verification results in the iapkit array.

packages/apple/Example/OpenIapExample/Screens/SubscriptionFlowScreen.swift (3)

567-656: LGTM! Verification flow is well-structured.

The verifyAndFinishPurchase method cleanly handles all three verification modes (none, local, iapkit) with proper async/await patterns and error handling. The JWS token validation at line 612 is a good safeguard.


695-708: LGTM! Purchase deduplication prevents duplicate processing.

Using purchase.id + transactionDate as a composite key is a solid approach to prevent processing the same purchase event multiple times.


480-563: LGTM! Clean verification method UI component.

The VerificationMethodCard provides good UX with clear API key status feedback and configuration guidance.

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

625-699: LGTM! Robust HTTP request handling with proper error parsing.

The task group approach enables future multi-target support. Response parsing handles both success and error formats, with good state normalization from UPPER_SNAKE_CASE to lower-kebab-case.


702-741: LGTM! Clean payload construction with proper validation.

The buildIapkitPayload method validates required fields before encoding and uses appropriate JSON encoder settings.


743-769: LGTM! Thoughtful error message extraction.

The extractIapkitErrorMessage helper recursively extracts the most relevant error message from IAPKit's nested error structures.


1346-1352: LGTM! Error messages properly updated for verification codes.

The new purchaseVerification* error codes have appropriate default messages, and the deprecated receipt* codes are retained for backward compatibility.

packages/apple/Sources/Models/Types.swift (7)

1-4: LGTM! Auto-generated types header.

The header clearly indicates this file should not be edited directly and provides regeneration instructions.


162-187: LGTM! IAPKit enums properly defined with raw values.

The IapkitPurchaseState and IapkitStore enums use kebab-case raw values consistent with the GraphQL schema normalization.


589-595: LGTM! IAPKit result struct is complete.

The RequestVerifyPurchaseWithIapkitResult struct properly captures all verification response fields.


654-663: LGTM! VerifyPurchaseResultIOS properly structured.

The iOS result type captures validity, JWS representation, optional latest transaction, and receipt data.


1006-1045: LGTM! IAPKit input types with proper initializers.

All input structs have complete initializers with appropriate default values for optional fields.


1391-1394: LGTM! VerifyPurchaseResult union enum.

The enum properly wraps platform-specific result types, enabling polymorphic handling.


1454-1458: LGTM! MutationResolver updated with new verification methods.

The protocol properly declares the new verifyPurchaseWithProvider method alongside the existing verifyPurchase and deprecated validateReceipt.

packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (1)

1-2808: LGTM! Well-structured auto-generated types.

The auto-generated types file successfully adds provider-based purchase verification support with IAPKit. All new enums, data classes, and interfaces follow consistent patterns and match the TypeScript definitions. Key additions include:

  • New enums (IapkitPurchaseState, IapkitStore, PurchaseVerificationProvider) with proper documentation
  • Verification result types (VerifyPurchaseResultAndroid, VerifyPurchaseResultIOS, VerifyPurchaseWithProviderResult)
  • Input/props classes for Apple and Google verification flows
  • Updated resolver interfaces with the new verifyPurchaseWithProvider method

The type mappings between TypeScript and Kotlin are correct, nullable types are properly handled, and all fromJson/toJson methods follow the established conventions.

Comment thread packages/docs/src/styles/pages.css
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift (2)

14-27: Consider simplifying the test setup.

The validateResult parameter configured here is never used by verifyPurchaseWithProvider. The test only exercises the providerResult path, so the elaborate validateResult setup adds unnecessary complexity.

Consider using a simpler default or nil-pattern for unused parameters in the fake module initialization to make the test's intent clearer:

 let module = FakeVerifyPurchaseModule(
-    validateResult: VerifyPurchaseResult.verifyPurchaseResultIos(
-        VerifyPurchaseResultIOS(
-            isValid: true,
-            jwsRepresentation: "",
-            latestTransaction: nil,
-            receiptData: ""
-        )
-    ),
+    validateResult: .verifyPurchaseResultIos(VerifyPurchaseResultIOS(isValid: true, jwsRepresentation: "", latestTransaction: nil, receiptData: "")),
     providerResult: VerifyPurchaseWithProviderResult(
         iapkit: [iapkitResult],
         provider: .iapkit
     )
 )

103-103: Remove redundant Void return type annotations.

Swift functions that don't return a value implicitly return Void, so explicitly declaring -> Void is redundant.

Apply this diff:

-func restorePurchases() async throws -> Void { () }
+func restorePurchases() async throws { () }

-func finishTransaction(purchase: PurchaseInput, isConsumable: Bool?) async throws -> Void { () }
+func finishTransaction(purchase: PurchaseInput, isConsumable: Bool?) async throws { () }

-func deepLinkToSubscriptions(_ options: DeepLinkOptions?) async throws -> Void { () }
+func deepLinkToSubscriptions(_ options: DeepLinkOptions?) async throws { () }

Also applies to: 107-107, 153-153

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2894018 and d686c31.

📒 Files selected for processing (4)
  • packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift (1 hunks)
  • packages/google/openiap/src/main/java/dev/hyo/openiap/utils/PurchaseVerificationValidator.kt (1 hunks)
  • packages/google/openiap/src/test/java/dev/hyo/openiap/OpenIapErrorTest.kt (2 hunks)
  • packages/google/openiap/src/test/java/dev/hyo/openiap/PurchaseVerificationValidatorTest.kt (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/google/openiap/src/main/java/dev/hyo/openiap/utils/PurchaseVerificationValidator.kt (2)
packages/google/openiap/src/test/java/dev/hyo/openiap/PurchaseVerificationValidatorTest.kt (1)
  • setRequestProperty (239-243)
packages/apple/Sources/OpenIapModule.swift (1)
  • extractIapkitErrorMessage (745-769)
🪛 detekt (1.23.8)
packages/google/openiap/src/main/java/dev/hyo/openiap/utils/PurchaseVerificationValidator.kt

[warning] 136-136: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)


[warning] 163-163: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)


[warning] 233-233: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

🪛 SwiftLint (0.57.0)
packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift

[Warning] 103-103: Returning Void in a function declaration is redundant

(redundant_void_return)


[Warning] 107-107: Returning Void in a function declaration is redundant

(redundant_void_return)


[Warning] 153-153: Returning Void in a function declaration is redundant

(redundant_void_return)

⏰ 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 (5)
packages/google/openiap/src/test/java/dev/hyo/openiap/OpenIapErrorTest.kt (1)

54-57: LGTM! Deprecation handling is correct.

The test correctly suppresses deprecation warnings for InvalidReceipt and validates that it now maps to PurchaseVerificationFailed. This ensures backward compatibility while supporting the new error terminology.

Also applies to: 323-323

packages/google/openiap/src/test/java/dev/hyo/openiap/PurchaseVerificationValidatorTest.kt (1)

23-223: LGTM! Comprehensive test coverage.

This test file provides thorough coverage of both Google Play and IAPKit verification flows, including:

  • Missing options validation
  • Successful response parsing with field assertions
  • Non-2xx error handling and proper exception mapping
  • API key authorization header composition
  • Request body content validation

The FakeHttpURLConnection test double is well-implemented and captures the necessary request details for assertions. All past review comments have been addressed.

packages/google/openiap/src/main/java/dev/hyo/openiap/utils/PurchaseVerificationValidator.kt (3)

26-81: LGTM! Google Play verification is well-implemented.

The function properly:

  • Validates required androidOptions fields
  • Constructs the correct Google Play API URL based on product type (subscription vs product)
  • Handles HTTP errors by throwing InvalidPurchaseVerification
  • Catches JSON parsing errors with appropriate fallbacks
  • Maps network errors to NetworkError

83-170: LGTM! IAPKit verification flow is solid.

The function correctly:

  • Validates Google payload presence (appropriate for Android)
  • Constructs and sends POST requests with optional Bearer token
  • Logs request/response details for debugging
  • Extracts concise error messages from nested IAPKit responses
  • Normalizes response state from UPPER_SNAKE_CASE to lower-kebab-case
  • Handles network and parsing errors with descriptive messages

The past review comment about duplicate logging has been addressed—only OpenIapLog calls are present.


206-248: LGTM! Error extraction logic is well-structured.

The extractIapkitErrorMessage function handles multiple IAPKit error formats with appropriate fallbacks:

  1. Errors array format
  2. Nested details.originalError (with recursive JSON parsing)
  3. Message field (avoiding verbose nested JSON)
  4. Error code fallback

Static analysis flags swallowed exceptions at lines 233, but this is intentional—exceptions during error message extraction fall back to simpler formats. The function is defensive and guarantees a meaningful error message.

Based on the relevant code snippet from OpenIapModule.swift (lines 744-768), the Android implementation correctly mirrors the Swift error extraction logic.

@hyodotdev hyodotdev deleted a comment from coderabbitai Bot Dec 6, 2025
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/gql/scripts/generate-swift-types.mjs (1)

312-341: Kotlin implementation missing legacy error code alias support found in Swift.

The Swift implementation intentionally aliases "receipt-failed" to .purchaseVerificationFailed (documented as legacy alias in tests), but the Kotlin implementation in packages/google/openiap/src/main/kotlin/dev/hyo/openiap/Types.kt maps these as distinct enum values:

  • "receipt-failed"ErrorCode.ReceiptFailed
  • "purchase-verification-failed"ErrorCode.PurchaseVerificationFailed

If the backend or other platforms send "receipt-failed" error codes, Kotlin clients will receive a different enum value than Swift/iOS clients, breaking cross-platform consistency. Add equivalent legacy alias handling in Kotlin to match the Swift behavior, or document why this intentional divergence is acceptable.

♻️ Duplicate comments (3)
packages/google/Example/src/main/java/dev/hyo/martie/screens/PurchaseFlowScreen.kt (1)

585-676: IAPKit exception path still finishes the transaction, undermining verification

The main flow now correctly short-circuits when isValid == false so failed verifications do not reach finishTransaction—nice improvement.

However, in the IAPKit branch’s catch you still force isValid to true for “test mode”:

iapStore.postStatusMessage(
    message = "Verification error: ${e.message}. Finishing transaction anyway for testing.",
    …
)
// For testing: return true to continue with finishTransaction
println("PurchaseFlow: [TEST MODE] Continuing with finishTransaction despite verification error")
true

This means that on network / provider errors you still acknowledge/consume the purchase, even though verification never actually succeeded. For anyone copying this example, it effectively turns verification failures into “best-effort logging only”.

To keep the behavior aligned with the rest of the flow (only finish when verification succeeds), I’d strongly recommend treating exceptions as verification failure and not finishing the transaction:

-                        iapStore.postStatusMessage(
-                            message = "Verification error: ${e.message}. Finishing transaction anyway for testing.",
-                            status = PurchaseResultStatus.Error,
-                            productId = purchase.productId
-                        )
-                        // For testing: return true to continue with finishTransaction
-                        println("PurchaseFlow: [TEST MODE] Continuing with finishTransaction despite verification error")
-                        true
+                        iapStore.postStatusMessage(
+                            message = "Verification error: ${e.message}. Purchase not acknowledged.",
+                            status = PurchaseResultStatus.Error,
+                            productId = purchase.productId
+                        )
+                        false

With that change, the existing if (!isValid) { … return@LaunchedEffect } will skip finishTransaction in both the “explicitly invalid” and “verification errored” cases, which matches the explanatory messaging and models the recommended pattern more accurately.

Also applies to: 693-719

packages/google/Example/src/main/java/dev/hyo/martie/screens/SubscriptionFlowScreen.kt (2)

135-147: Verification flow mostly solid; consider tightening “test-mode” IAPKit error handling

The new verification state, UI, and LaunchedEffect flow correctly:

  • Gate finishTransaction on isValid, including for local and IAPKit paths.
  • Prevent re-processing via processedPurchaseKey.
  • Clear premature status messages and surface verification results in verificationResultMessage.

However, the IAPKit error path still forces isValid = true and proceeds to finishTransaction with a “Finishing transaction anyway for testing” message (Lines 1361–1372). This pattern can easily be copied into production and effectively bypasses verification whenever the IAPKit call errors (network/server issues).

Consider gating this branch behind an explicit debug/test flag (e.g., BuildConfig.DEBUG) or flipping the default to “fail closed” (do not finish on verification error) so the example teaches a safer pattern by default, with comments showing how to override in test-only builds.

Also applies to: 413-558, 1269-1426


579-583: Active subscriptions now correctly limited to purchased state

Filtering activeSubscriptions by both productId and purchaseState == PurchaseState.Purchased aligns this section with the rest of the screen and avoids showing pending/failed/other transient states as “active” subscriptions.

🧹 Nitpick comments (3)
packages/docs/src/pages/docs/apis.tsx (1)

1175-1343: verifyPurchaseWithProvider docs are solid; tighten props requirements & key guidance

The multi-language signatures and examples for verifyPurchaseWithProvider look consistent and easy to follow. A couple of small refinements would make this even clearer:

  • In the TypeScript interface, iapkit is optional, but every example using the IAPKit provider supplies it. Consider documenting (or typing) that iapkit is required whenever provider === 'iapkit' so readers don’t think provider alone is sufficient.
  • Since apiKey is shown as a literal string, a brief note that production apps should source this from secure configuration or a backend (rather than hard-coding it in the client) would help nudge users toward safer integration patterns.

These are docs-only tweaks; the core design and examples otherwise look good.

packages/google/Example/src/main/java/dev/hyo/martie/screens/PurchaseFlowScreen.kt (1)

56-60: Verification enum, state, and UI wiring look good; only minor polish possible

The new VerificationMethod enum, state variables, and the “Purchase Verification” card are wired cleanly and make the flows (None / Local / IAPKit) very explicit. Surfacing API key configuration inline is also helpful.

Two optional nits you might consider, not blockers:

  • isVerifying is maintained but not currently reflected in the UI (e.g., disabling purchase buttons or showing a small progress indicator during verification). If you want to prevent double-actions while verification runs, you could plumb this into the button enabled states.
  • OpenIapLog.isEnabled = true inside the init LaunchedEffect is perfect for the sample, but if you ever ship this example as part of a release build, gating it on BuildConfig.DEBUG would avoid always-on verbose logging in production-like builds.

Also applies to: 95-107, 119-121, 251-396

packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift (1)

101-105: Clean up redundant Void returns in fake module (SwiftLint warnings)

SwiftLint flags redundant_void_return for a few stubs that declare -> Void and return (). You can simplify these to bare async/throws functions with empty bodies, e.g.:

-    func restorePurchases() async throws -> Void { () }
+    func restorePurchases() async throws { }

-    func finishTransaction(purchase: PurchaseInput, isConsumable: Bool?) async throws -> Void { () }
+    func finishTransaction(purchase: PurchaseInput, isConsumable: Bool?) async throws { }

-    func deepLinkToSubscriptions(_ options: DeepLinkOptions?) async throws -> Void { () }
+    func deepLinkToSubscriptions(_ options: DeepLinkOptions?) async throws { }

This keeps the fake concise and satisfies SwiftLint.

Also applies to: 107-107, 149-153

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d686c31 and 58170c6.

⛔ Files ignored due to path filters (1)
  • packages/gql/src/generated/Types.swift is excluded by !**/generated/**
📒 Files selected for processing (10)
  • .vscode/settings.json (1 hunks)
  • packages/apple/Sources/Models/Types.swift (15 hunks)
  • packages/apple/Sources/OpenIapModule.swift (4 hunks)
  • packages/apple/Sources/OpenIapProtocol.swift (2 hunks)
  • packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift (1 hunks)
  • packages/docs/src/pages/docs/apis.tsx (2 hunks)
  • packages/docs/src/styles/pages.css (1 hunks)
  • packages/google/Example/src/main/java/dev/hyo/martie/screens/PurchaseFlowScreen.kt (6 hunks)
  • packages/google/Example/src/main/java/dev/hyo/martie/screens/SubscriptionFlowScreen.kt (10 hunks)
  • packages/gql/scripts/generate-swift-types.mjs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/docs/src/styles/pages.css
🧰 Additional context used
🧬 Code graph analysis (5)
packages/google/Example/src/main/java/dev/hyo/martie/screens/SubscriptionFlowScreen.kt (2)
packages/gql/src/generated/types.ts (2)
  • RequestVerifyPurchaseWithIapkitProps (764-771)
  • RequestVerifyPurchaseWithIapkitGoogleProps (759-762)
packages/google/Example/src/main/java/dev/hyo/martie/screens/PurchaseFlowScreen.kt (2)
  • verifyLocally (581-583)
  • verifyWithIapkit (561-577)
packages/apple/Sources/Models/Types.swift (5)
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (4)
  • iapkit (1814-1836)
  • iapkit (2389-2406)
  • verifyPurchase (2594-2594)
  • verifyPurchaseWithProvider (2598-2598)
packages/gql/src/generated/Types.kt (4)
  • iapkit (1879-1901)
  • iapkit (2454-2471)
  • verifyPurchase (2659-2659)
  • verifyPurchaseWithProvider (2663-2663)
packages/apple/Sources/OpenIapModule.swift (2)
  • verifyPurchase (589-592)
  • verifyPurchaseWithProvider (594-606)
packages/apple/Sources/OpenIapStore.swift (2)
  • verifyPurchase (376-386)
  • verifyPurchaseWithProvider (388-391)
packages/apple/Sources/OpenIapProtocol.swift (1)
  • verifyPurchaseWithProvider (82-84)
packages/apple/Sources/OpenIapModule.swift (2)
packages/apple/Sources/OpenIapStore.swift (2)
  • verifyPurchase (376-386)
  • verifyPurchaseWithProvider (388-391)
packages/apple/Sources/OpenIapProtocol.swift (1)
  • verifyPurchaseWithProvider (82-84)
packages/apple/Sources/OpenIapProtocol.swift (2)
packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift (4)
  • validateReceiptIOS (117-122)
  • validateReceipt (124-126)
  • verifyPurchase (128-130)
  • verifyPurchaseWithProvider (132-134)
packages/apple/Tests/OpenIapTests/VerifyPurchaseTests.swift (4)
  • validateReceiptIOS (102-107)
  • validateReceipt (109-111)
  • verifyPurchase (113-115)
  • verifyPurchaseWithProvider (117-119)
packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift (2)
packages/apple/Sources/OpenIapStore.swift (2)
  • verifyPurchaseWithProvider (388-391)
  • verifyPurchase (376-386)
packages/apple/Sources/OpenIapProtocol.swift (1)
  • verifyPurchaseWithProvider (82-84)
🪛 SwiftLint (0.57.0)
packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift

[Warning] 103-103: Returning Void in a function declaration is redundant

(redundant_void_return)


[Warning] 107-107: Returning Void in a function declaration is redundant

(redundant_void_return)


[Warning] 153-153: Returning Void in a function declaration is redundant

(redundant_void_return)

🔇 Additional comments (6)
.vscode/settings.json (1)

2-7: LGTM!

The spell-check dictionary has been appropriately updated with the new terms introduced in this PR. The entries are alphabetically ordered and align with the IAPKit-based verification features and SKU handling being added.

packages/docs/src/pages/docs/apis.tsx (1)

169-184: Dart initConnection examples now match the documented config-based signature

Wrapping alternativeBillingModeAndroid inside InitConnectionConfig via the config named parameter aligns correctly with the Dart function signature and keeps the examples consistent with the other platform snippets.

packages/gql/scripts/generate-swift-types.mjs (1)

312-316: LGTM - Legacy alias mapping structure.

The legacyAliases map correctly includes both kebab-case and PascalCase variants for backward compatibility.

packages/google/Example/src/main/java/dev/hyo/martie/screens/SubscriptionFlowScreen.kt (1)

143-147: IAPKit API key handling and local verification helper look good

Using a nullable iapkitApiKey with takeIf { it.isNotBlank() } and a simple verifyLocally that checks purchaseState == PurchaseState.Purchased is straightforward and consistent with the rest of the codebase (e.g., PurchaseFlowScreen). No issues here.

Also applies to: 1288-1291

packages/apple/Sources/Models/Types.swift (1)

34-37: VerifyPurchase / IAPKit type additions and ErrorCode mappings look consistent

The new verification-related enums/structs (IapkitPurchaseState, IapkitStore, PurchaseVerificationProvider, RequestVerifyPurchaseWithIapkit*, VerifyPurchase* types) and their wiring into ErrorCode, result unions, and handler typealiases are internally consistent and match the usage patterns in OpenIapModule/OpenIapStore. The legacy "receipt-failed".purchaseVerificationFailed mapping plus updated default messages provide a reasonable compat story without breaking newer purchaseVerification* codes.

Also applies to: 82-93, 162-187, 228-230, 589-605, 633-669, 654-669, 1006-1092, 1391-1394, 1537-1540, 1561-1564, 1633-1633

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

553-592: IAPKit-backed verification flow and VerifyPurchase wiring look correct

The iOS-side VerifyPurchase implementations are coherent:

  • validateReceiptIOSperformVerifyPurchaseIOS builds a VerifyPurchaseResultIOS from latest StoreKit transaction and current receipt data.
  • verifyPurchase(_:) wraps that in the new VerifyPurchaseResult union, and validateReceipt(_:) simply delegates to verifyPurchase(_:) for backward compatibility.
  • verifyPurchaseWithProvider(_:) correctly:
    • Enforces .iapkit as the only supported provider here.
    • Requires props.iapkit and surfaces missing params as .developerError.
    • Delegates to verifyPurchaseWithIapkit, which:
      • Uses a fixed verify URL constant.
      • Restricts this module to Apple payloads (documented in comments).
      • Validates payloads, sends JSON POST with optional Bearer API key, and robustly parses/normalizes responses into RequestVerifyPurchaseWithIapkitResult.

The auxiliary helpers (buildIapkitPayload, extractIapkitErrorMessage, and the new purchaseVerification* branches in defaultMessage(for:)) are well-structured and match the data shapes defined in Types.swift.

Also applies to: 594-698, 700-739, 741-767, 1345-1350

Comment thread packages/apple/Sources/OpenIapProtocol.swift
Comment thread packages/gql/scripts/generate-swift-types.mjs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🎯 feature New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant