"use client";
import { useCheckout } from "@/features/checkout/hooks/useCheckout";
import { PageLayout } from "@/components/templates/PageLayout";
import { SubCard } from "@/components/molecules/SubCard/SubCard";
import { Button } from "@/components/atoms/button";
import { AlertBanner } from "@/components/molecules/AlertBanner/AlertBanner";
import { PageAsync } from "@/components/molecules/AsyncBlock/AsyncBlock";
import { InlineToast } from "@/components/atoms/inline-toast";
import { StatusPill } from "@/components/atoms/status-pill";
import { AddressConfirmation } from "@/features/catalog/components/base/AddressConfirmation";
import { isLoading, isError, isSuccess } from "@customer-portal/domain/toolkit";
import { ShieldCheckIcon, CreditCardIcon } from "@heroicons/react/24/outline";
import type { PaymentMethod } from "@customer-portal/domain/payments";
export function CheckoutContainer() {
const {
checkoutState,
submitting,
orderType,
addressConfirmed,
paymentMethods,
paymentMethodsLoading,
paymentMethodsError,
paymentRefresh,
confirmAddress,
markAddressIncomplete,
handleSubmitOrder,
navigateBackToConfigure,
activeInternetWarning,
} = useCheckout();
if (isLoading(checkoutState)) {
return (
}
>
<>>
);
}
if (isError(checkoutState)) {
return (
}
>
{checkoutState.error.message}
);
}
if (!isSuccess(checkoutState)) {
return (
}
>
Checkout data is not available
);
}
const { items, totals } = checkoutState.data;
const paymentMethodList = paymentMethods?.paymentMethods ?? [];
const defaultPaymentMethod =
paymentMethodList.find(method => method.isDefault) ?? paymentMethodList[0] ?? null;
const paymentMethodDisplay = defaultPaymentMethod
? buildPaymentMethodDisplay(defaultPaymentMethod)
: null;
return (
}
>
{activeInternetWarning && (
{activeInternetWarning}
)}
Confirm Details
}
right={
paymentMethods && paymentMethods.paymentMethods.length > 0 ? (
) : undefined
}
>
{paymentMethodsLoading ? (
Checking payment methods...
) : paymentMethodsError ? (
) : paymentMethodList.length > 0 ? (
{paymentMethodDisplay ? (
Default payment method
{paymentMethodDisplay.title}
{paymentMethodDisplay.subtitle ? (
{paymentMethodDisplay.subtitle}
) : null}
) : null}
We securely charge your saved payment method after the order is approved. Need
to make changes? Visit Billing & Payments.
) : (
)}
Review & Submit
You’re almost done. Confirm your details above, then submit your order. We’ll review and
notify you when everything is ready.
What to expect
• Our team reviews your order and schedules setup if needed
• We may contact you to confirm details or availability
• We only charge your card after the order is approved
• You’ll receive confirmation and next steps by email
Estimated Total
¥{totals.monthlyTotal.toLocaleString()}/mo
{totals.oneTimeTotal > 0 && (
+ ¥{totals.oneTimeTotal.toLocaleString()} one-time
)}
);
}
function buildPaymentMethodDisplay(method: PaymentMethod): { title: string; subtitle?: string } {
const descriptor =
method.cardType?.trim() ||
method.bankName?.trim() ||
method.description?.trim() ||
method.gatewayName?.trim() ||
"Saved payment method";
const trimmedLastFour =
typeof method.cardLastFour === "string" && method.cardLastFour.trim().length > 0
? method.cardLastFour.trim().slice(-4)
: null;
const headline =
trimmedLastFour && method.type?.toLowerCase().includes("card")
? `${descriptor} · •••• ${trimmedLastFour}`
: descriptor;
const details = new Set();
if (method.bankName && !headline.toLowerCase().includes(method.bankName.trim().toLowerCase())) {
details.add(method.bankName.trim());
}
const expiry = normalizeExpiryLabel(method.expiryDate);
if (expiry) {
details.add(`Exp ${expiry}`);
}
if (!trimmedLastFour && method.cardLastFour && method.cardLastFour.trim().length > 0) {
details.add(`Ends ${method.cardLastFour.trim().slice(-4)}`);
}
if (method.type?.toLowerCase().includes("bank") && method.description?.trim()) {
details.add(method.description.trim());
}
const subtitle = details.size > 0 ? Array.from(details).join(" · ") : undefined;
return { title: headline, subtitle };
}
function normalizeExpiryLabel(expiry?: string | null): string | null {
if (!expiry) return null;
const value = expiry.trim();
if (!value) return null;
if (/^\d{4}-\d{2}$/.test(value)) {
const [year, month] = value.split("-");
return `${month}/${year.slice(-2)}`;
}
if (/^\d{2}\/\d{4}$/.test(value)) {
const [month, year] = value.split("/");
return `${month}/${year.slice(-2)}`;
}
if (/^\d{2}\/\d{2}$/.test(value)) {
return value;
}
const digits = value.replace(/\D/g, "");
if (digits.length === 6) {
const year = digits.slice(2, 4);
const month = digits.slice(4, 6);
return `${month}/${year}`;
}
if (digits.length === 4) {
const month = digits.slice(0, 2);
const year = digits.slice(2, 4);
return `${month}/${year}`;
}
return value;
}
export default CheckoutContainer;