Consolidate Kreditkarte/PayPal into one "Online-Zahlung" checkout option
Both already route through the same Stripe PaymentIntent (automatic_payment_methods: enabled — Stripe's own recommended Payment Element pattern, letting Stripe itself decide which eligible method to show). Pre-selecting one of two identical-behind-the-scenes rows before the payment step was redundant friction, not a real choice. Collapses them into one option with a hint text explaining the actual instrument is picked on the next screen; Überweisung is unaffected. Also refines paymentMethodTitle from a neutral "Online-Zahlung" placeholder (snapshotted at order-creation time, before the customer has picked an instrument) to the real one Stripe reports, once payment confirms — carried through to both the stored order and the sessionStorage snapshot shown on /bestellbestaetigung. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -308,6 +308,30 @@ exactly as before: no gateway involved, order goes straight to `received`.
|
||||
branch — `'manual'` (Überweisung) or `'stripe'` (Kreditkarte/PayPal).
|
||||
`app/lib/payload.ts`'s `getPaymentMethods()` exposes it; the checkout
|
||||
route re-resolves it server-side, never trusts a client-submitted value.
|
||||
- **Checkout UI collapses Kreditkarte + PayPal into one "Online-Zahlung"
|
||||
option** (`groupPaymentMethodsForCheckout()` in `app/lib/payload.ts`,
|
||||
used by `CheckoutContent.tsx`). Both admin rows still exist and both
|
||||
still need `provider: 'stripe'` — this is a display-layer grouping, not
|
||||
a data change. Reasoning: the PaymentIntent is created with
|
||||
`automatic_payment_methods: { enabled: true }` (Stripe's own recommended
|
||||
Payment Element pattern — Stripe itself decides which eligible method to
|
||||
show), so pre-selecting "Kreditkarte" vs. "PayPal" before that never
|
||||
actually restricted anything; it was redundant friction, not a real
|
||||
choice. The combined option shows a hint text ("die genaue Zahlungsart
|
||||
wählst du im nächsten Schritt") so the consolidation reads as intentional,
|
||||
not a missing option. Überweisung stays a separate, real option.
|
||||
- **`paymentMethodTitle` is snapshotted as a neutral `"Online-Zahlung"`**
|
||||
at order-creation time for the `stripe` branch (the customer hasn't
|
||||
picked an instrument yet at that point) and **refined to the real one**
|
||||
(`"Kreditkarte"`/`"PayPal"`) once Stripe reports it —
|
||||
`resolveStripePaymentMethodLabel()` in `stripeProvider.ts` reads the
|
||||
confirmed PaymentIntent's `payment_method.type` in the webhook route and
|
||||
passes it to `confirm-payment` as an optional field. Best-effort: an
|
||||
unresolved label just leaves the neutral title in place. The
|
||||
`/checkout/verarbeitung` polling page also patches this into the
|
||||
provisional `sessionStorage` snapshot before promoting it, so
|
||||
`/bestellbestaetigung` shows the real instrument too, not the neutral
|
||||
placeholder.
|
||||
- **`app/api/checkout/route.ts`, `provider === 'stripe'` branch**: creates
|
||||
a Stripe PaymentIntent *before* the order (`app/lib/payments/
|
||||
stripeProvider.ts`) — its id is known immediately and gets persisted as
|
||||
@@ -337,10 +361,16 @@ exactly as before: no gateway involved, order goes straight to `received`.
|
||||
`{paymentStatus, providerReference, paidAt}`. Returns a non-2xx status on
|
||||
any internal failure so Stripe's own retry schedule (~3 days) provides
|
||||
resilience for free, rather than this app building its own retry queue.
|
||||
That backend endpoint is what actually flips the order to `received`,
|
||||
assigns the (until-then-deferred) invoice number, and sends the
|
||||
confirmation email/invoice + the internal admin new-order notification —
|
||||
see the backend repo's own README for that half.
|
||||
That backend endpoint flips the order to `received`, assigns the
|
||||
(until-then-deferred) invoice number, and queues the internal admin
|
||||
new-order notification — see the backend repo's own README for that
|
||||
half. It has no SMTP sender of its own, though: it returns a full order
|
||||
snapshot in its response instead, and **this webhook route is what
|
||||
actually sends the confirmation email + invoice PDF**
|
||||
(`app/lib/payments/confirmPaymentEmail.ts`, only when the response isn't
|
||||
`alreadyProcessed: true` — a repeat webhook delivery must never resend
|
||||
it), mirroring exactly what the checkout route already does inline for
|
||||
a manual/Überweisung order.
|
||||
- **`/checkout/verarbeitung`** (`VerarbeitungContent.tsx`) is the
|
||||
`return_url` target. Neither a client-side `confirmPayment()` success nor
|
||||
landing back from a PayPal redirect is trusted as proof of payment on its
|
||||
|
||||
Reference in New Issue
Block a user