feat(checkout): plumb off-session card saving for yearly renewals (draft — needs consent UI)#265
Draft
kilbot wants to merge 2 commits into
Draft
feat(checkout): plumb off-session card saving for yearly renewals (draft — needs consent UI)#265kilbot wants to merge 2 commits into
kilbot wants to merge 2 commits into
Conversation
Forward the signed-in customer's Medusa JWT on the payment-collection and
payment-session calls so Medusa resolves `auth_context.actor_id` to the
customer and creates/links a Stripe account holder (cus_...) on the
PaymentIntent — instead of the throwaway "Guest" that Stripe invents for
publishable-key-only requests.
The store payment-sessions workflow only creates an account holder
`when("customer-id-exists")`, and `customer_id` is derived solely from the
request's auth context; today the storefront's medusaFetch sends only the
publishable key, so actor_id is empty and every sale shows as Guest.
getCustomer() in the route already validates the token against
/store/customers/me (which rejects an empty actor_id), so the forwarded
token is guaranteed to resolve to a real customer. A missing token degrades
to the previous guest behaviour rather than breaking checkout.
Scope: attach-customer only (bookkeeping/reconciliation). Saving the card
for off-session renewals is a separate follow-up.
Add the engineering seam that saves a buyer's card as a reusable, off-session Stripe payment method — the prerequisite for a future "click to renew" charge on the yearly plan. createPaymentSession now accepts an optional setup_future_usage, forwarded as the session `data`; Medusa's store payment-sessions route passes `data` into the Stripe provider's initiatePayment, where it lands on the PaymentIntent (verified against @medusajs/payment-stripe normalizePaymentIntentParameters). The route sets 'off_session' only when the buyer consented (saveCard), the plan is yearly (lifetime never renews), and it's the Stripe card provider (PayPal/BTCPay have no off-session-card concept). A saved card is only chargeable because the customer is attached (previous commit). DORMANT until a consent UI is added: no client sets saveCard yet, so today this changes nothing on the wire. The consent checkbox + mandate copy is a deliberate owner/legal decision and is intentionally NOT included here.
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
🚀 Preview: https://wcpos-m8t8i1ffr-wcpos.vercel.app |
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.
What this is
The engineering seam for Level 2 (save the card), building on #264 (attach a Stripe Customer). It lets a yearly buyer's card be saved as a reusable, off-session Stripe payment method — the prerequisite for a future "click to renew" charge.
How it works
createPaymentSessiongains an optionalsetupFutureUsage, forwarded as the sessiondata.dataverbatim into the Stripe provider'sinitiatePayment, wherenormalizePaymentIntentParameterscopiessetup_future_usageonto the PaymentIntent (verified in@medusajs/payment-stripesource).'off_session'only when all three hold: the buyer consented (saveCard), the plan is yearly (lifetime never renews), and it's the Stripe card provider (PayPal/BTCPay have no off-session-card concept).No client sets
saveCardyet, so on the wire this changes nothing today. I deliberately stopped at the engineering boundary and did not build the consent UI, because it's a legal/UX decision that's yours:To activate: add the consent checkbox, thread its value as
saveCardthrough thecomplete-cartclient wrapper → this route. The seam is ready and tested.Validation
vitest run— 1161/1161 pass (added:setup_future_usagethreading; yearly-only / Stripe-only / consent-absent / lifetime gating)eslint,tsc --noEmit,next build— all cleanNot in scope
Level 3 renewal-charge flow; the consent UI (see above); the Q2 order↔customer-linkage question flagged in #264.
🤖 Generated with Claude Code