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)}
)}
)
}