Gate-focused checkpoint() API - #4151
Conversation
Generated by 🚫 Danger |
f047b1c to
18d7136
Compare
18d7136 to
6e4ca05
Compare
6e4ca05 to
0167501
Compare
0167501 to
b9cfeff
Compare
b9cfeff to
ad2c2c4
Compare
8c6fd87 to
877c1af
Compare
877c1af to
e6196e0
Compare
39b6772 to
22459e6
Compare
0975842 to
e20c109
Compare
e3b15d7 to
c73c72e
Compare
c73c72e to
b400df0
Compare
694af08 to
4df5d65
Compare
4df5d65 to
cdccbe5
Compare
| import kotlin.coroutines.resume | ||
|
|
||
| /** | ||
| * The entitlements active before a checkpoint runs, so grants can be told apart from what the user already |
There was a problem hiding this comment.
I actually had it fetch the customer info beforehand 🤔 but now that I think about it this also makes sense and is a bit less complex.
There was a problem hiding this comment.
Right, I try to minimize the requests when hitting a checkpoint, so only getting it from cache.
5d455c6 to
fcf039a
Compare
fcf039a to
e32107a
Compare
e32107a to
28d837c
Compare
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A checkpoint flow used to be taken down before the app was told the user went through, so the host screen showed its pre-callback state for a frame before the app reacted. The presenter now reports first and takes the window down once checkpoint() has invoked the callback (or skipped it for a backed-out flow), so whatever the callback puts on screen is already there when the flow goes away. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…sented A second checkpoint() call while a flow was on screen came back as nothing presented, so its callback fired with null and the gate let the user through behind the paywall that was still showing. The blocked call now skips its callback entirely; the call that presented the flow is the one that reports. A checkpoint that resolves to no flow still passes null. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit f8c0a57. Configure here.
| _state.update { it.copy(running = true, message = null) } | ||
| viewModelScope.launch { | ||
| val message = try { | ||
| val result = Purchases.sharedInstance.awaitCheckpoint( |
There was a problem hiding this comment.
Back-out leaves screens stuck
Medium Severity
The new checkpoint() callback is intentionally skipped when the user backs out, but these screens set running/waitingFor before the call and only clear it in that callback. System back (or navigate_back on the first step) therefore leaves the UI disabled with no way to retry, restart, or continue. Onboarding still claims a flow outcome must not strand the user mid-flow; hard paywall still claims dismissal leaves the content locked and lets the user try again. The same latch exists on the entitlement gate, custom, soft paywall, and use-case screens.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit f8c0a57. Configure here.
There was a problem hiding this comment.
This is actually fixed in the follow-up PR: #4155
rickvdl
left a comment
There was a problem hiding this comment.
I think this looks great! We're fully aligned now as far as I could see 💪 Just one small difference
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>


Description
New Checkpoints API based on gating behind a closure when the user goes through the checkpoint flow, if any:
When the callback fires
CheckpointPassedCallback.onCheckpointPassedis invoked at most once, on the main thread, when the user passes the checkpoint:nullwhen nothing was presented, whatever the reason.FlowResultwhen the presented flow ended with a purchase or restore, or through a close action.It is not invoked when the user backs out of the presented flow (system back, or a
navigate_backaction on a workflow's first step).Only one checkpoint flow is presented at a time. A checkpoint that resolves to a flow while another flow is already on screen is ignored and its callback is never invoked: the call that presented the flow is the one that reports. A checkpoint that resolves to nothing still passes
nullmeanwhile.Note
High Risk
Breaking internal checkpoint API and paywall presentation lifecycle changes affect monetization gating, concurrent checkpoint behavior, and entitlement reporting for integrators on the new surface.
Overview
Replaces the suspend
awaitCheckpoint/CheckpointResultsurface with a callback-basedPurchases.checkpoint()API aimed at gating:CheckpointPassedCallbackreceivesFlowResult?(nullwhen nothing was presented; otherwiseobtainedEntitlementsfor what the user gained). The call does not throw—failures and no-match cases are logged and surface asnull. The callback is skipped when the user backs out of the flow or when a second checkpoint would present while another flow is already on screen.Public
CheckpointResultandCheckpointPaywallOutcomeare removed; presentation outcomes move to internalCheckpointFlowOutcome. Offering resolutions now present a fallback offering paywall instead of returning data for app-owned UI.CheckpointWorkflowPresenterdefers tearing down the window until after the app is notified viafinishPresentation.Core seam renames:
resolveCheckpoint→internalResolveCp,checkpointManagerSlot→internalCpManagerSlot.LocalRulesEvaluatorlogging is tightened. API testers, checkpointtester / paywall-tester samples, and unit tests are updated for the new contract.Reviewed by Cursor Bugbot for commit caba0f3. Bugbot is set up for automated code reviews on this repo. Configure here.