Conversation
This branch has not been deployed
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.
Consume the unified webapp/web2app post-purchase behaviors
Pairs with paywall-next
ir/feat/web2app-delivery-runtime(#3913), which now sendsredemption_codes, a populatedredirect_url, andpost_purchase_behavioronpost_checkout_complete. Before this, the SDK completed the purchase and silentlydropped all three — the paywall closed and nothing else happened.
Wire alignment (the reason nothing worked)
PostCheckoutCompleteMessagedeclaredtransactionData/redirectUrlin camelCasewhile the wire (and our own tests) used snake_case, so those lookups never matched
anything. Renamed to
transaction_data/redirect_urlwith snake_case inner keys,and
readTransactionFieldnow reads the right path. Also deleted the "these areALWAYS undefined" contract comment on the handler — no longer true.
Behavior handling
entitlements_tokenredirect_urlpaywallWillOpenURL, then navigateredemption_codesredeem()takes them as-isredemption_codesREDIRECT navigates the current tab rather than opening a new one. The
post_checkout_completehandler runs without transient user activation, sowindow.openis routinely popup-blocked and the redirect vanishes silently.Navigation is deferred one tick so the resolved purchase (
onDismiss,paywall_close, event dispatches) flushes before unload.paywallWillOpenURLstill fires first so consumers can route it themselves, and
options.paywalls.postPurchaseRedirect: "newTab"opts back into the old behaviorfor hosts that can't lose page state.
New public surface
PaywallResult(purchased) carriesredemptionCodesandpostPurchaseBehavior,so they arrive via
handler.onDismissandregister()'s return value.redemptionCodesReceivedevent (codes,productId,checkoutContextId,paywallInfo,behavior), bridged toSuperwallDelegate.onRedemptionCodesReceived. Local-only because the backendalready records the redemption server-side.
PostPurchaseBehaviortype ("GRANT_ACCESS" | "REDIRECT" | "REDEEM" | "CUSTOM");unknown future values are dropped rather than passed through.
sw.redeem(code)— redeems aredemption_…code for the current user. Theredeem logic previously lived inline in the automatic controller's deps, so it was
unreachable for anyone supplying a custom
PurchaseController; it's now a sharedredeemCode()used by both that path and the public method. SeedscustomerInfo,flips
subscriptionStatuson success, firesonWillRedeemLink/onDidRedeemLink,and never throws — failures resolve as
error/expired/invalid. Drive-byfix: an INVALID code now resolves as a clean
{ type: "invalid", code }instead ofstuffing an
errorfield into the invalid variant.paywallIdparity with the native SDKsThe init payload sent the identifier slug as both
paywallIdandpaywallIdentifier. Now parsespaywall_responses[].id(exposed asPaywallInfo.databaseId) and sends it aspaywallId, keeping the slug aspaywallIdentifier— matching iOS/Android. Configs predating the field fall back tothe slug so it's never empty. Applies to both
collector.paywallSliceandcheckoutContext.paywall.Compatibility
Every new wire field is optional, so an older paywall sends none and behaves exactly
as today. The exposure runs the other way: an older SDK against a paywall newly
authored REDIRECT/REDEEM/CUSTOM completes the purchase and drops the URL or code.
Gate those three behaviors in the dashboard picker on an SDK version floor, or don't
offer them for embedded until this ships.
Testing
New/extended coverage for the populated
post_checkout_completepath (codes on theresult, the event with purchase context, behavior passthrough on all three surfaces),
the navigate-by-default redirect and the
newTabopt-in,sw.redeem()success andINVALID paths, the
LOCAL_ONLYset, and thepaywallId/paywallIdentifiersplitincluding slug fallback.
tsc --noEmitclean inpaywalls-jsandpaywalls-react;package suite 278 pass with 86 pre-existing sandbox-network failures that fail
identically on a clean checkout.