Skip to content

Support custom products in the purchase controller - #23

Merged
ianrumac merged 3 commits into
mainfrom
ir/custom-product-purchases
Nov 6, 2026
Merged

ianrumac merged 3 commits into
mainfrom
ir/custom-product-purchases

Conversation

@ianrumac

@ianrumac ianrumac commented Nov 6, 2026

Copy link
Copy Markdown
Collaborator

Custom products (store set to Custom on the Superwall dashboard) are sold through the game's own payment system: Stripe, Paddle, a web checkout or another store. With a purchase controller set, both native SDKs hand them to the controller, but the Unity bridges didn't support them:

  • Android failed them in the bridge ("The Unity purchase controller does not handle custom products"), so C# never saw the request.
  • iOS sent them to PurchaseFromAppStore with only the ID, so a controller tried to buy them from StoreKit with an ID Apple doesn't have.

Changes

  • C#: new IPurchaseController.PurchaseCustomProduct(CustomProductPurchase purchase, Action<PurchaseResult> completion). CustomProductPurchase carries ProductId, CustomTransactionId (Superwall's pre-generated transaction ID, to send back from the server as custom_transaction_id for revenue attribution) and, on Android, Replacement. The default implementation fails the purchase, so existing controllers compile and behave as before.
  • Android bridge: isCustomProduct products go to purchaseCustomProduct with fullIdentifier, customTransactionId and any replacement. Play products are unchanged. The request JSON is built by googlePlayPurchaseRequest / customProductPurchaseRequest so it can be unit-tested.
  • iOS bridge: StoreProduct.isCustomProduct is internal in SuperwallKit, so the bridge uses customTransactionId. It's public, and TransactionManager.prepareToPurchase sets it only for custom products, before calling the purchase controller. The routing is in purchaseRequest(productIdentifier:customTransactionId:callbackId:).
  • README section and an [Unreleased] changelog entry.

Tests

All run locally:

  • Android (PurchaseRequestTest, 4 new): JSON for custom products with and without a transaction ID and replacement, plus the unchanged Play shape. 21/21 pass on AGP 8.5.2 and 9.0.1, assembleRelease too.
  • iOS (PurchaseRequestTests, 2 new): custom products go to purchaseCustomProduct, App Store products to purchaseFromAppStore. 12/12 pass on the simulator with SuperwallKit 4.19.1.
  • C# (BridgeContractTests, 4 new): a request reaches PurchaseCustomProduct with its fields and the reply reaches native; replacement parsing; a controller without the method replies failed and never reaches the store methods; a throwing controller replies failed. 54/54 pass in ci~/csharp-harness, and the UNITY_IOS / UNITY_ANDROID builds compile.

Not tested end to end with a real custom product on a device; the test project has none set up.

🤖 Generated with Claude Code

Custom products (store "Custom" on the dashboard) are bought through the
game's own payment system. Android failed them in the bridge, and iOS sent
them to PurchaseFromAppStore as if they were App Store products. Both now
call IPurchaseController.PurchaseCustomProduct with the product ID, the
customTransactionId Superwall generated and, on Android, any subscription it
replaces. The method's default implementation fails the purchase, so
existing controllers compile unchanged.
@maple-review-bot

maple-review-bot Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Maple review

🟢 Confidence 10/10 · safe to merge. Request-shape and callback tests cover the new contract, replacement parsing, legacy-controller fallback, and thrown exceptions; real-device checkout remains unverified.
no findings · tests covered · risk high

Routes custom-product purchases to a dedicated C# controller callback on Android and iOS, preserving transaction IDs and Android replacement details. No merge-blocking defects found.

  • PurchaseCustomProduct defaults to a failed result for existing controllers.
  • HandlePurchaseCustomProduct forwards purchase details and converts controller exceptions into failures.
  • Native request builders distinguish custom purchases from store purchases.

703ba2c · What was checked, coverage and telemetry details are on the Maple / review check. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

@maple-review-bot

maple-review-bot Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Warning

The review of 909dda6 could not finish. The model provider returned an error. Comment @maple-review-bot review to try again.

@ianrumac
ianrumac merged commit 4468549 into main Nov 6, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant