import { OrderCreditLineDTO } from "@medusajs/types" import { ArrowDownRightMini, DocumentText, XCircle } from "@medusajs/icons" import { AdminOrder, AdminPayment, HttpTypes } from "@medusajs/types" import { Badge, Button, Container, Heading, StatusBadge, Text, toast, Tooltip, usePrompt, } from "@medusajs/ui" import { format } from "date-fns" import { Trans, useTranslation } from "react-i18next" import { ActionMenu } from "../../../../../components/common/action-menu" import DisplayId from "../../../../../components/common/display-id/display-id" import { useCapturePayment, useAuthorizePaymentSession, } from "../../../../../hooks/api" import { formatCurrency } from "../../../../../lib/format-currency" import { getLocaleAmount, getStylizedAmount, } from "../../../../../lib/money-amount-helpers" import { getOrderPaymentStatus } from "../../../../../lib/order-helpers" import { getPaymentsFromOrder } from "../../../../../lib/orders" import { getTotalCaptured, getTotalPending } from "../../../../../lib/payment" import { getLoyaltyPlugin } from "../../../../../lib/plugins" import { ExtendedOrder, ExtendedRefund } from "../../constants" type OrderPaymentSectionProps = { order: ExtendedOrder plugins: HttpTypes.AdminPlugin[] } export const OrderPaymentSection = ({ order, plugins, }: OrderPaymentSectionProps) => { const payments = getPaymentsFromOrder(order) const refunds = payments .map((payment) => payment?.refunds) .flat(1) .filter(Boolean) as ExtendedRefund[] return (
) } const Header = ({ order }: { order: ExtendedOrder }) => { const { t } = useTranslation() const { label, color } = getOrderPaymentStatus(t, order.payment_status) return (
{t("orders.payment.title")} {label}
) } const Refund = ({ refund, currencyCode, }: { refund: HttpTypes.AdminRefund currencyCode: string }) => { const { t } = useTranslation() const RefundReasonBadge = refund?.refund_reason && ( {refund.refund_reason.label} ) const RefundNoteIndicator = refund.note && ( ) return (
{t("orders.payment.refund")} {RefundNoteIndicator} {format(new Date(refund.created_at), "dd MMM, yyyy, HH:mm:ss")}
{RefundReasonBadge}
- {getLocaleAmount(refund.amount as number, currencyCode)}
) } const Payment = ({ order, payment, refunds, currencyCode, }: { order: ExtendedOrder payment: HttpTypes.AdminPayment refunds: HttpTypes.AdminRefund[] currencyCode: string }) => { const { t } = useTranslation() const prompt = usePrompt() const { mutateAsync } = useCapturePayment(order.id, payment.id) const handleCapture = async () => { const res = await prompt({ title: t("orders.payment.capture"), description: t("orders.payment.capturePayment", { amount: formatCurrency(payment.amount as number, currencyCode), }), confirmText: t("actions.confirm"), cancelText: t("actions.cancel"), variant: "confirmation", }) if (!res) { return } await mutateAsync( { amount: payment.amount as number }, { onSuccess: () => { toast.success( t("orders.payment.capturePaymentSuccess", { amount: formatCurrency(payment.amount as number, currencyCode), }) ) }, onError: (error) => { toast.error(error.message) }, } ) } const getPaymentStatusAttributes = (payment: AdminPayment) => { if (payment.canceled_at) { return ["Canceled", "red"] } else if (payment.captured_at) { return ["Captured", "green"] } else { return ["Pending", "orange"] } } const [status, color] = getPaymentStatusAttributes(payment) as [ string, "green" | "orange" | "red" ] const showCapture = payment.captured_at === null && payment.canceled_at === null const totalRefunded = (payment.refunds ?? []).reduce( (acc, next) => next.amount + acc, 0 ) return (
{format( new Date(payment.created_at as string), "dd MMM, yyyy, HH:mm:ss" )}
{payment.provider_id}
{status}
{getLocaleAmount(payment.amount as number, payment.currency_code)}
, to: `/orders/${order.id}/refund?paymentId=${payment.id}`, disabled: !payment.captured_at || !!payment.canceled_at || totalRefunded >= payment.amount, }, ], }, ]} />
{showCapture && (
]} />
)} {refunds.map((refund) => ( ))}
) } const CreditLine = ({ creditLine, currencyCode, plugins, }: { creditLine: OrderCreditLineDTO currencyCode: string plugins: HttpTypes.AdminPlugin[] }) => { const loyaltyPlugin = getLoyaltyPlugin(plugins) if (!loyaltyPlugin) { return null } const prettyReference = creditLine.reference ?.split("_") .join(" ") .split("-") .join(" ") const prettyReferenceId = creditLine.reference_id ? ( ) : null return (
{loyaltyPlugin ? ( Store credit refund ) : ( )} {format( new Date(creditLine.created_at as unknown as string), "dd MMM, yyyy, HH:mm:ss" )}
{prettyReference} ({prettyReferenceId})
{getLocaleAmount(creditLine.amount as number, currencyCode)}
) } const PendingAuthorizationBanner = ({ order, sessionId, }: { order: HttpTypes.AdminOrder sessionId: string }) => { const { t } = useTranslation() const { mutateAsync, isPending } = useAuthorizePaymentSession( order.id, sessionId ) const handleCheckStatus = async () => { await mutateAsync(undefined, { onSuccess: ({ is_authorized }) => { if (is_authorized) { toast.success(t("orders.payment.checkStatusSuccess")) } else { toast.info(t("orders.payment.stillPending")) } }, onError: (error) => { toast.error(error.message) }, }) } return (
{t("orders.payment.pendingAuthorization")}
) } const PaymentBreakdown = ({ order, payments, refunds, currencyCode, plugins, }: { order: ExtendedOrder payments: HttpTypes.AdminPayment[] refunds: ExtendedRefund[] currencyCode: string plugins: HttpTypes.AdminPlugin[] }) => { const pendingAuthSessions = (order.payment_collections ?? []).flatMap((pc) => ((pc as any).payment_sessions ?? []) .filter((s: any) => s.status === "pending_authorization") .map((s: any) => ({ session_id: s.id })) ) /** * Refunds that are not associated with a payment. */ const orderRefunds = refunds.filter((refund) => refund.payment_id === null) const creditLines = order.credit_lines ?? [] const creditLineRefunds = creditLines.filter( (creditLine) => (creditLine.amount as number) < 0 ) const entries = [...orderRefunds, ...payments, ...creditLineRefunds] .sort((a, b) => { return ( new Date(a.created_at as string).getTime() - new Date(b.created_at as string).getTime() ) }) .map((entry) => { let type = entry.id.startsWith("pay_") ? "payment" : "refund" if (entry.id.startsWith("ordcl_")) { type = "credit_line_refund" } return { event: entry, type } }) as ( | { type: "payment"; event: HttpTypes.AdminPayment } | { type: "refund"; event: HttpTypes.AdminRefund } | { type: "credit_line_refund" event: OrderCreditLineDTO } )[] return (
{pendingAuthSessions.map(({ session_id }) => ( ))} {entries.map(({ type, event }) => { switch (type) { case "payment": return ( refund.payment_id === event.id )} currencyCode={currencyCode} /> ) case "refund": return ( ) case "credit_line_refund": return ( ) } })}
) } const Total = ({ order }: { order: AdminOrder }) => { const { t } = useTranslation() const totalPending = getTotalPending(order.payment_collections) return (
{t("orders.payment.totalPaidByCustomer")} {getStylizedAmount( getTotalCaptured(order.payment_collections), order.currency_code )}
{order.status !== "canceled" && totalPending > 0 && (
Total pending {getStylizedAmount(totalPending, order.currency_code)}
)}
) }