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:
@@ -621,6 +621,38 @@ export async function getPaymentMethods(): Promise<PaymentMethod[]> {
|
||||
}));
|
||||
}
|
||||
|
||||
export type CheckoutPaymentOption = PaymentMethod & { hint?: string };
|
||||
|
||||
// Kreditkarte and PayPal both resolve to `provider: 'stripe'` today, and
|
||||
// both end up on the exact same Stripe PaymentIntent
|
||||
// (`automatic_payment_methods: { enabled: true }` — Stripe's own
|
||||
// recommended Payment Element pattern lets Stripe itself decide which
|
||||
// eligible method to show, rather than the older per-method
|
||||
// Checkout-Session split). Pre-selecting one of two identical-behind-the-
|
||||
// scenes rows before the payment step is therefore no longer a real
|
||||
// choice, just redundant friction — so this collapses every active
|
||||
// `stripe` row into one "Online-Zahlung" option (representative id =
|
||||
// the first such row's, since app/api/checkout/route.ts only branches on
|
||||
// `provider`, never on which specific stripe row was picked) with a hint
|
||||
// explaining that the actual instrument is chosen on the next screen.
|
||||
// `manual` rows (Überweisung) pass through unchanged — one real gateway
|
||||
// there, one option, nothing to collapse.
|
||||
export function groupPaymentMethodsForCheckout(methods: PaymentMethod[]): CheckoutPaymentOption[] {
|
||||
const manual = methods.filter((m) => m.provider !== "stripe");
|
||||
const stripeMethods = methods.filter((m) => m.provider === "stripe");
|
||||
if (stripeMethods.length === 0) return manual;
|
||||
|
||||
const combinedIcons = Array.from(new Set(stripeMethods.flatMap((m) => m.icons)));
|
||||
const online: CheckoutPaymentOption = {
|
||||
id: stripeMethods[0].id,
|
||||
title: "Online-Zahlung",
|
||||
icons: combinedIcons,
|
||||
provider: "stripe",
|
||||
hint: "Kreditkarte, PayPal & weitere Methoden — die genaue Zahlungsart wählst du im nächsten Schritt.",
|
||||
};
|
||||
return [...manual, online];
|
||||
}
|
||||
|
||||
export type WerkzeugeCard = {
|
||||
id: number;
|
||||
title: string;
|
||||
|
||||
@@ -49,6 +49,29 @@ async function attachOrderMetadata(providerReference: string, metadata: { orderI
|
||||
|
||||
export const stripeProvider: PaymentProvider = { createPaymentIntent, attachOrderMetadata };
|
||||
|
||||
const PAYMENT_METHOD_LABELS: Record<string, string> = { card: "Kreditkarte", paypal: "PayPal" };
|
||||
|
||||
// Called only by the real webhook route on `payment_intent.succeeded` —
|
||||
// the checkout route snapshots a neutral "Online-Zahlung" title at order
|
||||
// creation (see its own comment: the customer hasn't chosen an instrument
|
||||
// yet at that point, Stripe's Payment Element does that next), this
|
||||
// resolves the actual one once Stripe reports it so the order/invoice/
|
||||
// confirmation email reflect what was really used, not a placeholder.
|
||||
// Best-effort: an unresolvable label just leaves the neutral title in
|
||||
// place (confirmPayment.ts only overwrites paymentMethodTitle when this
|
||||
// returns something), it doesn't fail the payment confirmation itself.
|
||||
export async function resolveStripePaymentMethodLabel(intent: Stripe.PaymentIntent): Promise<string | undefined> {
|
||||
const pm = intent.payment_method;
|
||||
const pmId = typeof pm === "string" ? pm : pm?.id;
|
||||
if (!pmId) return undefined;
|
||||
try {
|
||||
const resolved = pm && typeof pm === "object" ? pm : await getClient().paymentMethods.retrieve(pmId);
|
||||
return PAYMENT_METHOD_LABELS[resolved.type] ?? resolved.type;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Only used by the real webhook route (never through the PaymentProvider
|
||||
// interface — signature verification is inherently Stripe-shaped, no
|
||||
// other provider exists to share this contract with yet).
|
||||
|
||||
Reference in New Issue
Block a user