Merged
Conversation
Introduces a new `.custom` product type that allows developers using an external PurchaseController to purchase products through their own payment system. Product data is fetched from the Superwall API and templated into paywalls. A custom transaction ID is generated before purchase and used as the original transaction identifier in transaction_complete. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixes a bug where cancelling and retrying a purchase would reuse the same transaction ID, causing potential duplicate-ID collisions in analytics. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Avoids shifting .other's implicit Int raw value from 5 to 6, which could break external consumers persisting rawValue or using ObjC bridged constants. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ing PurchaseController Route APIStoreProduct price formatting through the shared PriceFormatterProvider so custom products render currencies consistently with real App Store products on the same paywall. Also surface an explicit error when a custom product is purchased without an external PurchaseController, since the default SK1/SK2 purchasers bail silently and leave the paywall stuck. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Collaborator
Author
…urchase error Build the APIStoreProduct price formatter locale from the SuperwallProduct storefront so currency rendering matches what Apple returns for App Store products — USD on any device now renders as `$3.99`, not `US$3.99`. Introduce `PurchaseError.customProductWithoutPurchaseController` so the failure is distinguishable from generic productUnavailable, while keeping the user-facing message short. The developer-facing hint lives in the log. Also fixes a stale docstring on APIStoreProduct, removes an unused `subUnit` binding flagged in review, and silences function_body_length warnings introduced by the diagnostic debugPrints. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Strip the ~12 debugPrint statements that were left in AddPaywallProducts after local debugging, and drop the two swiftlint:disable annotations that were only needed to keep those functions under the body-length limit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
.customproduct type enabling developers with an externalPurchaseControllerto purchase products through their own payment systemgetSuperwallProductsendpoint) and cached for paywall templating (price, period, trial info)originalTransactionIdentifierintransaction_completeAPIStoreProductprice formatting through the sharedPriceFormatterProviderso custom products render currencies consistently with real App Store products on the same paywallPurchaseController— logs an explicit error and returns.failed(.productUnavailable)instead of silently bailing inside the default SK1/SK2 purchasersNew files
CustomStoreProduct.swift— config model for custom products (decoded fromstore: "CUSTOM")CustomStoreTransaction.swift—StoreTransactionTypeimplementation with pre-generated transaction IDCustomProductTests.swift— unit tests covering decoding, attribute computation, trial eligibility, and product variablesModified files
Product.swift— added.custom(CustomStoreProduct)case toStoreProductTypeProductStore.swift— added.customcaseStoreProductAdapterObjc.swift— addedcustomProductfieldStoreProduct.swift— addedisCustomProductflag andcustomTransactionIdproperty, plusinit(customProduct:)StorePayment.swift— added init for custom productsPaywall.swift— addedcustomProductscomputed propertyPaywallLogic.swift— addedgetCustomProducts(from:)filterAddPaywallProducts.swift— fetches and caches custom products from API, merges intoproductsById, adds custom trial eligibility checkAPIStoreProduct.swift— price formatting now goes throughPriceFormatterProvider.priceFormatterForSK2for SK2-parity currency renderingTransactionManager.swift— generates custom transaction ID before purchase, createsCustomStoreTransactionon purchase completion, and fails fast with a clear error when a custom product is purchased without an externalPurchaseControllerFactoryProtocols.swift/DependencyContainer.swift— addedmakeStoreTransaction(from: CustomStoreTransaction)V2ProductsResponse.swift— added.customtoSuperwallProductPlatformTest plan
CustomStoreProductdecoding and round-trip encodingProductwith.customtype decodingProductStore.customdecodingPaywallLogic.getCustomProductsfilteringStoreProduct(customProduct:)settingisCustomProductflagAPIStoreProductattribute computation (price, period, trial)CustomStoreTransactionproperty valuesgetProductVariableswith custom productsCHANGELOG.mdfor any breaking changes, enhancements, or bug fixes.swiftlintin the main directory and fixed any issues.🤖 Generated with Claude Code
Greptile Summary
This PR adds a new
.customproduct type for developers using an externalPurchaseControllerto handle purchases through non-App-Store payment systems (e.g. Stripe, web). Product data is fetched from the Superwall API and cached for paywall templating; a pre-generated UUID serves as the transaction identifier; and trial eligibility follows the same entitlement-history approach used for Stripe products.The implementation is well-structured and comes with thorough unit test coverage. Prior review concerns (duplicate-ID crash via
Dictionary(uniqueKeysWithValues:), transaction-ID reuse across retries) have been addressed in this revision.Confidence Score: 5/5
Safe to merge; the only remaining finding is a P2 Swift compiler warning about an unused variable binding.
All previously flagged P1 concerns (duplicate-key crash, transaction-ID reuse) are resolved. The single remaining finding — subUnit bound but unused in trialPeriodPricePerUnit — will produce a Swift compiler warning but does not affect runtime behavior. Tests are comprehensive and cover the critical paths.
APIStoreProduct.swift — unused subUnit binding in trialPeriodPricePerUnit warrants a one-line fix.
Important Files Changed
Sequence Diagram
sequenceDiagram participant App as App / Dev participant SDK as SuperwallKit participant API as Superwall API participant SKM as StoreKitManager participant PC as PurchaseController App->>SDK: Paywall request SDK->>API: fetchAndCacheCustomProducts() API-->>SDK: SuperwallProduct data (price, period, trial) SDK->>SKM: cache StoreProduct(customProduct:) SDK->>SDK: checkCustomTrialEligibility() note over SDK: Uses entitlement history,<br/>same as Stripe path SDK->>App: Show paywall (custom product vars populated) App->>SDK: User taps purchase SDK->>SDK: prepareToPurchase() - generate customTransactionId (UUID) SDK->>SDK: isCustomProductFreeTrialAvailable() - checks customerInfo entitlements SDK->>PC: purchase(product) PC-->>SDK: PurchaseResult.purchased SDK->>SDK: didPurchase() - build CustomStoreTransaction(customTransactionId) SDK->>SDK: trackTransactionDidSucceed(transaction) note over SDK: Receipt loading skipped<br/>for custom products SDK->>App: Dismiss paywall / purchased(product)Prompt To Fix All With AI
Reviews (6): Last reviewed commit: "Update CHANGELOG.md" | Re-trigger Greptile