Skip to content

Prototype: in-sheet checkout session update for billing address tax#13233

Draft
cttsai-stripe wants to merge 18 commits into
masterfrom
cttsai/checkout-session-in-sheet-tax-update
Draft

Prototype: in-sheet checkout session update for billing address tax#13233
cttsai-stripe wants to merge 18 commits into
masterfrom
cttsai/checkout-session-in-sheet-tax-update

Conversation

@cttsai-stripe

@cttsai-stripe cttsai-stripe commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • When a checkout session has automatic_tax enabled with billing address as the source, the SDK now updates the session with billing details before dismissing the FC/embedded sheet
  • This ensures the amount includes tax before confirmation, preventing the checkout_amount_mismatch error
  • Adds InSheetCheckoutSessionUpdater as shared logic for both FlowController and Embedded paths
  • Updates CheckoutSessionResponseJsonParser to read tax status from tax_meta/tax_context fields
  • Uses withInternalStateForInSheetUpdate to bypass the integrationLaunched guard (analogous to iOS's requireOpenSessionForInSheetUpdate)

Test plan

  • Playground: CheckoutSession + Automatic Tax on + US_tax merchant + FlowController
  • Fill billing address with US zip, click Continue, verify spinner shows briefly
  • Verify confirm succeeds with tax-inclusive amount
  • Verify checkout.checkoutSession StateFlow emits updated totals after dismiss

🤖 Generated with Claude Code

Screen_recording_20260611_160322.mp4

cttsai-stripe and others added 11 commits June 10, 2026 16:38
When a checkout session has automatic tax enabled with billing address
as the source, update the session with billing details before dismissing
the FC/embedded sheet. This ensures the amount includes tax before
confirmation, preventing the checkout_amount_mismatch error.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Committed-By-Agent: claude
The server returns tax_meta with requires_location_inputs even without
billing_address_collection set. The only fix needed was the parser.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Committed-By-Agent: claude
- Make withInternalStateForInSheetUpdate private
- Add SERVER_UPDATE_TIMEOUT_MS to withInternalStateLocked
- Fix parseTaxStatusFromMeta default to UNKNOWN (not REQUIRES_BILLING_ADDRESS)
- Fail early when billing address has no country

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Committed-By-Agent: claude
Network-layer timeouts in StripeNetworkClient already handle server
hangs. The SERVER_UPDATE_TIMEOUT_MS is only for merchant server calls
in runServerUpdate where we don't control the network layer.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Committed-By-Agent: claude
Instead of a separate withInternalStateForInSheetUpdate method, add a
flag to the existing withInternalState. Future in-sheet updates just
pass allowWhileSheetPresented = true (analogous to iOS's
requireOpenSessionForInSheetUpdate vs requireOpenSession).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Committed-By-Agent: claude
No need for a separate method. The existing updateBillingAddress now
accepts allowWhileSheetPresented to bypass the integration guard.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Committed-By-Agent: claude
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Committed-By-Agent: claude
The public API delegates to an internal function that accepts the
allowWhileSheetPresented flag. Avoids breaking the API contract.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Committed-By-Agent: claude
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Committed-By-Agent: claude
Clarifies that this flag means the mutation originates from the
currently presented sheet, not that arbitrary mutations are allowed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Committed-By-Agent: claude
Resolve Checkout from CheckoutInstances at construction time instead
of passing PaymentMethodMetadata and doing the lookup internally.
Makes the dependency explicit and removes coupling to the singleton.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Committed-By-Agent: claude
import com.stripe.android.paymentsheet.repositories.CheckoutSessionResponse

@OptIn(CheckoutSessionPreview::class)
internal class InSheetCheckoutSessionUpdater(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Add tests and make it injected when ready for prod.

The caller doesn't need to know what specific update is needed. The
class now handles the decision internally and can be extended for
shipping address tax or other pre-dismiss updates without changing
call sites.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Committed-By-Agent: claude
Comment on lines +115 to +120
private fun currentExpectedAmount(): Long? {
val checkout = CheckoutInstances[integrationMetadata.instancesKey].firstOrNull()
?: return null
return checkout.internalState.checkoutSessionResponse.amount
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We should find a better way to pass along the updated expected amount.

cttsai-stripe and others added 6 commits June 11, 2026 15:37
- Generic naming unrelated to tax (supports future update types)
- FC path: show PrimaryButton.State.StartProcessing spinner during update
- Embedded path: uses setProcessing which disables button during update

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Committed-By-Agent: claude
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Committed-By-Agent: claude
The "tax" field format was speculatively added but never existed in
real server responses. iOS and web both read tax_meta + tax_context.
Align Android with the actual server format.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Committed-By-Agent: claude
…ion-open check

1. Align with iOS/web: default to REQUIRES_BILLING_ADDRESS for
   unrecognized address source (only shipping if explicitly stated)
2. Suppress _isLoading during in-sheet updates to avoid triggering
   merchant's CurrencySelectorContent loading state
3. Extract resolveCheckout() into InSheetCheckoutSessionUpdater.create()
   factory to remove duplication from both callers
4. Add session-open status check in withInternalState (aligned with
   iOS's requireOpenSessionForInSheetUpdate validation)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Committed-By-Agent: claude
The merchant's Activity is paused while the sheet is presented, so
lifecycle-aware collectors won't observe the emission. Even if they
did, the sheet occludes their UI. Let Checkout.isLoading behave
uniformly for all mutations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Committed-By-Agent: claude
MPE doesn't have a shipping address component. Shipping goes through
the Address Element (a separate sheet). Only billing address tax
applies to the L4 FC/Embedded form sheet.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Committed-By-Agent: claude
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