Repository navigation
ORB-202: request 3D Secure for subscription checkout - #780
Merged
Merged
Conversation
There was a problem hiding this comment.
Important
Checkout retries spanning deployment can fail because the new request parameters reuse the previous policy's Stripe idempotency keys.
Reviewed changes Reviewed the subscription Checkout 3DS policy and its regression coverage.
- Checkout authentication: Sets
PaymentMethodOptions.Card.RequestThreeDSecuretoany, requesting supported-card authentication with a frictionless preference. - Regression coverage: Verifies the complete outgoing Checkout options for absent, empty, and referral coupons; all 41
StripeBillingServiceTestspassed locally.
gpt-6.1-sol | 𝕏
A checkout request with the new card authentication option must not reuse an idempotency key that a request without it created, because Stripe rejects changed parameters for a retained key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes Reviewed 7622b588 since the prior Pullfrog review and confirmed the resolved finding is addressed.
- Separated checkout policies: Included the 3DS policy in checkout idempotency key derivation, preventing reuse of pre-policy keys while preserving deterministic retries and leaving portal keys unchanged.
- Added regression coverage: Verified separation from pre-policy keys with and without referral coupons. All 43
StripeBillingServiceTestspassed locally.
gpt-6.1-sol | 𝕏
|
thomasluizon
added a commit
that referenced
this pull request
Oct 19, 2026
…nd accountability removal (#746) (#785) * ORB-202: request 3D Secure for subscription checkout (#780) * fix(billing): request 3D Secure for subscription checkout * fix(billing): key checkout retries by the 3D Secure policy A checkout request with the new card authentication option must not reuse an idempotency key that a request without it created, because Stripe rejects changed parameters for a retained key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 46e3aac) * Remove challenge and accountability endpoints (ORB-212) (#779) * feat: remove challenge and accountability endpoints (ORB-212) * fix: exclude removed achievements from earned totals (ORB-212) (cherry picked from commit c75ab1b) --------- Co-authored-by: Claude Opus 5.5 <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
Refs thomasluizon/orbit-tickets#208 (ORB-202).
StripeBillingService.CreateCheckoutSessionAsyncnow setsPaymentMethodOptions.Card.RequestThreeDSecuretoanyusing a named constant. This requests 3DS for every supported card in a new subscription Checkout Session rather than only elevated-risk cards. The policy follows the work order's stronger default, with a preference for frictionless authentication. The issuer controls the final flow; this does not guarantee a visible challenge or authentication for unsupported cards.The Stripe Checkout Session creation reference states: "3DS will always be attempted if it is supported for the card". It documents
anyas preferring a frictionless flow,challengeas preferring active authentication, andautomaticas the risk-based default. Rechecked at execution time. Stripe also recommends its SCA Engine default; the blanket request is the explicit policy chosen in the work order.The orchestrator's supplied acceptance probe created a test-mode Checkout Session with
mode=subscription, an existing recurring test price, andpayment_method_options[card][request_three_d_secure]=any. Stripe accepted it and returned this reported fragment:{"livemode":false,"payment_method_options":{"card":{"request_three_d_secure":"any"}}}That session was then expired. This probe was provided by the orchestrator, not rerun by this worker, and proves parameter acceptance rather than completed payment or Orbit activation.
The orchestrator selected this session-option path because there is no authenticated Stripe Dashboard session. Dashboard Radar configuration was not observed or changed. The session policy is versioned in code and reviewable. Only
src/Orbit.Infrastructure/Services/StripeBillingService.csand its existing unit test file change. The test captures the actual options passed to the SDK and checks subscription mode, customer, price, quantity, metadata, return URLs, discounts and promotion-code handling for null, empty and referral coupons. OpenAPI regeneration produced no changes. No DTO or client changes.Recurring renewals do not enter this Checkout Session creation path. Google Play Billing uses a separate rail. No renewal or Play code changes are included; observing the existing subscriber's next renewal remains a rollout check.
The extra bank step can cost card conversions when customers fail or abandon authentication. This covers the Stripe web flow only; Android purchases through Google Play Billing remain outside this policy. This is not complete payment-fraud protection and does not prevent legitimate cardholders from disputing purchases.
External interface evidence
The installed Stripe.net 52.4.2 package XML confirms
SessionCreateOptions.PaymentMethodOptions,SessionPaymentMethodOptionsOptions.CardandSessionPaymentMethodOptionsCardOptions.RequestThreeDSecure, includinganyas an allowed value. The installed NuGet manifest pins source commitf4f1c8ce704825d7d7f00b4c9d848a2d028757eb. Its corresponding source confirms the exact property types and serialized field names:Re-derive the evidence by reading the installed package's
stripe.net.nuspecandlib/net9.0/Stripe.net.xml, then fetching those three source files at its repository commit. The new assertion concerns an SDK request option, not an invented response fixture.Test evidence
dotnet test tests/Orbit.Infrastructure.Tests --filter FullyQualifiedName~StripeBillingServiceTestsexited 0, with all 38 existing tests passing while the 3DS option was absent.Checkout_RequestsThreeDSecureWithoutChangingSubscriptionOptionswith the implementation still unchanged:dotnet test tests/Orbit.Infrastructure.Tests --filter FullyQualifiedName~Checkout_RequestsThreeDSecureWithoutChangingSubscriptionOptionsexited 1. All three cases failed becausePaymentMethodOptionswas null instead of containing the card'sanyrequest.dotnet test tests/Orbit.Infrastructure.Tests --filter FullyQualifiedName~StripeBillingServiceTestsexited 0, all 41 tests passed, including the three new cases.dotnet build Orbit.slnxexited 0 with zero errors. Existing warnings remain. Both OpenAPI documents regenerated without a diff.dotnet test tests/Orbit.Domain.Testsexited 0, all 750 tests passed.dotnet test tests/Orbit.Application.Testsexited 0, all 5,573 tests passed.dotnet test tests/Orbit.Infrastructure.Testsexited 0, all 3,815 tests passed.Manual steps
.github/workflows/release.yml), dispatched frommain, withenvironment=stagingandbranch=thomasluizon/ticket-208-stripe-3ds. Use the staging web checkout with Stripe test credentials and the published 3DS-required card4000000000003220. Observe the hosted challenge, complete it, inspect the resulting Stripe test subscription for active status, and readGET /api/subscriptions/statusfor the same Orbit user to confirm the paid plan and interval. Repeat with a fresh checkout and fail or abandon authentication: verify no paid Orbit subscription and no successful charge, and a usable return path. These browser checks were not run by this worker, as prohibited by the work order.4000000000003055to confirm the explicit request applies beyond cards already requiring 3DS. Do not expect4242424242424242to display a challenge: Stripe documents it as unenrolled. Withany, frictionless authentication is allowed.environment=productionandbranch=main. Verify the release commit, observe the next real checkouts, and read the existing subscriber's next recurring invoice/subscription state to confirm renewal succeeds. No live release, renewal or payment was performed here. A report that a customer cannot pay is a rollback trigger.PaymentMethodOptions.Card.RequestThreeDSecureor set it toautomaticinStripeBillingService.CreateCheckoutSessionAsync, then release the API through the same workflow. Confirm newly created sessions use the default orautomaticpolicy. No Dashboard rule needs changing.