Allow switching an unpaid Überweisung order to Stripe payment
New "Zahlungsart ändern" button on the account order detail page, shown for a still-'received', still-manual (Überweisung) order when an active Stripe payment method exists. Reuses PaymentStep (the same Stripe Payment Element checkout uses) and /checkout/verarbeitung's polling logic (both now take a returnContext prop/param to land back on the order page instead of clearing the cart and redirecting to /bestellbestaetigung). Also: - Payment status badge (Offen/Bezahlt/...) next to the existing Zahlungsart display on the order detail page. - A one-line mention of the switch option in the Vorkasse unpaid notice in the order-confirmation email, shown only when a Stripe option is actually active (hasOnlinePaymentOption). - CustomerOrderDetail gained paymentProvider (was missing from the type entirely, even though the field already existed on the order). Backend counterpart: docker/payload's switchPaymentToStripeEndpoint.
This commit is contained in:
@@ -24,25 +24,43 @@ type Props = {
|
||||
testMode: boolean;
|
||||
/** Only present in test mode — see api/checkout/route.ts's own comment. */
|
||||
providerReference?: string;
|
||||
/** Where /checkout/verarbeitung sends the customer once payment is
|
||||
* confirmed — "checkout" (default) clears the cart/draft and lands on
|
||||
* /bestellbestaetigung, exactly like today. "account" is used by the
|
||||
* account order detail page's "Zahlungsart ändern" flow (an existing,
|
||||
* already-confirmed order — nothing to clear, and /bestellbestaetigung
|
||||
* would be the wrong destination): lands back on that same order's
|
||||
* page instead. See VerarbeitungContent.tsx's own branching on this. */
|
||||
returnContext?: "checkout" | "account";
|
||||
};
|
||||
|
||||
// Rendered by CheckoutContent once /api/checkout returns
|
||||
// `requiresPayment: true` (Kreditkarte/PayPal) — see
|
||||
// spicy-leaping-pizza.md §3/§7. The order already exists in Payload at
|
||||
// this point (status 'pending_payment'); this step only collects/confirms
|
||||
// the actual payment, it doesn't create anything.
|
||||
export function PaymentStep({ clientSecret, orderNumber, orderId, testMode, providerReference }: Props) {
|
||||
// the actual payment, it doesn't create anything. Also reused as-is by
|
||||
// the account order detail page's payment-method-switch flow (see
|
||||
// returnContext above) — the Stripe collection UI itself is identical
|
||||
// either way, only the post-payment destination differs.
|
||||
export function PaymentStep({ clientSecret, orderNumber, orderId, testMode, providerReference, returnContext = "checkout" }: Props) {
|
||||
if (testMode) {
|
||||
return <TestPaymentButtons orderNumber={orderNumber} orderId={orderId} providerReference={providerReference ?? ""} />;
|
||||
return (
|
||||
<TestPaymentButtons
|
||||
orderNumber={orderNumber}
|
||||
orderId={orderId}
|
||||
providerReference={providerReference ?? ""}
|
||||
returnContext={returnContext}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Elements stripe={getStripe()} options={{ clientSecret }}>
|
||||
<StripePaymentForm orderNumber={orderNumber} />
|
||||
<StripePaymentForm orderNumber={orderNumber} returnContext={returnContext} />
|
||||
</Elements>
|
||||
);
|
||||
}
|
||||
|
||||
function StripePaymentForm({ orderNumber }: { orderNumber: string }) {
|
||||
function StripePaymentForm({ orderNumber, returnContext }: { orderNumber: string; returnContext: "checkout" | "account" }) {
|
||||
const stripe = useStripe();
|
||||
const elements = useElements();
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
@@ -62,7 +80,7 @@ function StripePaymentForm({ orderNumber }: { orderNumber: string }) {
|
||||
const { error: confirmError } = await stripe.confirmPayment({
|
||||
elements,
|
||||
confirmParams: {
|
||||
return_url: `${window.location.origin}/checkout/verarbeitung?orderNumber=${encodeURIComponent(orderNumber)}`,
|
||||
return_url: `${window.location.origin}/checkout/verarbeitung?orderNumber=${encodeURIComponent(orderNumber)}&context=${returnContext}`,
|
||||
},
|
||||
});
|
||||
// Only reached for immediate client-side failures (e.g. invalid card
|
||||
@@ -88,7 +106,17 @@ function StripePaymentForm({ orderNumber }: { orderNumber: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
function TestPaymentButtons({ orderNumber, orderId, providerReference }: { orderNumber: string; orderId: number; providerReference: string }) {
|
||||
function TestPaymentButtons({
|
||||
orderNumber,
|
||||
orderId,
|
||||
providerReference,
|
||||
returnContext,
|
||||
}: {
|
||||
orderNumber: string;
|
||||
orderId: number;
|
||||
providerReference: string;
|
||||
returnContext: "checkout" | "account";
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const [submitting, setSubmitting] = useState<"paid" | "failed" | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -108,7 +136,7 @@ function TestPaymentButtons({ orderNumber, orderId, providerReference }: { order
|
||||
setSubmitting(null);
|
||||
return;
|
||||
}
|
||||
router.push(`/checkout/verarbeitung?orderNumber=${encodeURIComponent(orderNumber)}`);
|
||||
router.push(`/checkout/verarbeitung?orderNumber=${encodeURIComponent(orderNumber)}&context=${returnContext}`);
|
||||
} catch {
|
||||
setError("Testzahlung konnte nicht ausgeführt werden.");
|
||||
setSubmitting(null);
|
||||
|
||||
Reference in New Issue
Block a user