diff --git a/packages/admin-next/dashboard/src/i18n/translations/en.json b/packages/admin-next/dashboard/src/i18n/translations/en.json index d3b6133df3..b28791dc73 100644 --- a/packages/admin-next/dashboard/src/i18n/translations/en.json +++ b/packages/admin-next/dashboard/src/i18n/translations/en.json @@ -837,7 +837,8 @@ "refundPaymentSuccess": "Refund of amount {{amount}} successful", "createRefundWrongQuantity": "Quantity should be a number between 1 and {{number}}", "refundAmount": "Refund {{ amount }}", - "paymentLink": "Copy payment link for {{ amount }}" + "paymentLink": "Copy payment link for {{ amount }}", + "selectPaymentToRefund": "Select payment to refund" }, "edits": { diff --git a/packages/admin-next/dashboard/src/providers/router-provider/route-map.tsx b/packages/admin-next/dashboard/src/providers/router-provider/route-map.tsx index 19c65ad4ff..12ea6d84f4 100644 --- a/packages/admin-next/dashboard/src/providers/router-provider/route-map.tsx +++ b/packages/admin-next/dashboard/src/providers/router-provider/route-map.tsx @@ -258,7 +258,7 @@ export const RouteMap: RouteObject[] = [ import("../../routes/orders/order-create-exchange"), }, { - path: "payments/:paymentId/refund", + path: "refund", lazy: () => import("../../routes/orders/order-create-refund"), }, diff --git a/packages/admin-next/dashboard/src/routes/orders/order-create-refund/components/create-refund-form/create-refund-form.tsx b/packages/admin-next/dashboard/src/routes/orders/order-create-refund/components/create-refund-form/create-refund-form.tsx index e35165a173..4e9a1b788a 100644 --- a/packages/admin-next/dashboard/src/routes/orders/order-create-refund/components/create-refund-form/create-refund-form.tsx +++ b/packages/admin-next/dashboard/src/routes/orders/order-create-refund/components/create-refund-form/create-refund-form.tsx @@ -1,19 +1,28 @@ import { zodResolver } from "@hookform/resolvers/zod" import { HttpTypes } from "@medusajs/types" -import { Button, CurrencyInput, Textarea, toast } from "@medusajs/ui" +import { + Button, + CurrencyInput, + Label, + Select, + Textarea, + toast, +} from "@medusajs/ui" +import { useEffect } from "react" import { useForm } from "react-hook-form" import { useTranslation } from "react-i18next" +import { useNavigate, useSearchParams } from "react-router-dom" import * as zod from "zod" -import { upperCaseFirst } from "../../../../../../../../core/utils/src/common/upper-case-first" import { Form } from "../../../../../components/common/form" -import { Combobox } from "../../../../../components/inputs/combobox" import { RouteDrawer, useRouteModal } from "../../../../../components/modals" import { useRefundPayment } from "../../../../../hooks/api" import { getCurrencySymbol } from "../../../../../lib/data/currencies" import { formatCurrency } from "../../../../../lib/format-currency" +import { getLocaleAmount } from "../../../../../lib/money-amount-helpers" +import { getPaymentsFromOrder } from "../../../order-detail/components/order-payment-section" type CreateRefundFormProps = { - payment: HttpTypes.AdminPayment + order: HttpTypes.AdminOrder refundReasons: HttpTypes.AdminRefundReason[] } @@ -24,12 +33,17 @@ const CreateRefundSchema = zod.object({ }) export const CreateRefundForm = ({ - payment, + order, refundReasons, }: CreateRefundFormProps) => { const { t } = useTranslation() const { handleSuccess } = useRouteModal() - const paymentAmount = payment.amount as unknown as number + const navigate = useNavigate() + const [searchParams] = useSearchParams() + const paymentId = searchParams.get("paymentId") + const payments = getPaymentsFromOrder(order) + const payment = payments.find((p) => p.id === paymentId)! + const paymentAmount = (payment?.amount || 0) as number const form = useForm>({ defaultValues: { @@ -39,7 +53,21 @@ export const CreateRefundForm = ({ resolver: zodResolver(CreateRefundSchema), }) - const { mutateAsync, isPending } = useRefundPayment(payment.id) + useEffect(() => { + const pendingDifference = order.summary.pending_difference as number + const paymentAmount = (payment?.amount || 0) as number + const pendingAmount = + pendingDifference < 0 + ? Math.min(pendingDifference, paymentAmount) + : paymentAmount + + const normalizedAmount = + pendingAmount < 0 ? pendingAmount * -1 : pendingAmount + + form.setValue("amount", normalizedAmount as number) + }, [payment]) + + const { mutateAsync, isPending } = useRefundPayment(payment?.id!) const handleSubmit = form.handleSubmit(async (data) => { await mutateAsync( @@ -52,7 +80,7 @@ export const CreateRefundForm = ({ onSuccess: () => { toast.success( t("orders.payment.refundPaymentSuccess", { - amount: formatCurrency(data.amount, payment.currency_code), + amount: formatCurrency(data.amount, payment?.currency_code!), }) ) @@ -70,6 +98,41 @@ export const CreateRefundForm = ({
+ + diff --git a/packages/admin-next/dashboard/src/routes/orders/order-create-refund/order-create-refund.tsx b/packages/admin-next/dashboard/src/routes/orders/order-create-refund/order-create-refund.tsx index 326cc7bb14..4c3b378906 100644 --- a/packages/admin-next/dashboard/src/routes/orders/order-create-refund/order-create-refund.tsx +++ b/packages/admin-next/dashboard/src/routes/orders/order-create-refund/order-create-refund.tsx @@ -2,13 +2,17 @@ import { Heading } from "@medusajs/ui" import { useTranslation } from "react-i18next" import { useParams } from "react-router-dom" import { RouteDrawer } from "../../../components/modals" -import { usePayment, useRefundReasons } from "../../../hooks/api" +import { useOrder, useRefundReasons } from "../../../hooks/api" +import { DEFAULT_FIELDS } from "../order-detail/constants" import { CreateRefundForm } from "./components/create-refund-form" export const OrderCreateRefund = () => { const { t } = useTranslation() const params = useParams() - const { payment, isLoading, isError, error } = usePayment(params.paymentId!) + const { order } = useOrder(params.id!, { + fields: DEFAULT_FIELDS, + }) + const { refund_reasons: refundReasons, isLoading: isRefundReasonsLoading, @@ -16,10 +20,6 @@ export const OrderCreateRefund = () => { error: refundReasonsError, } = useRefundReasons() - if (isError) { - throw error - } - if (isRefundReasonsError) { throw refundReasonsError } @@ -30,8 +30,8 @@ export const OrderCreateRefund = () => { {t("orders.payment.createRefund")} - {!isLoading && !isRefundReasonsLoading && payment && refundReasons && ( - + {order && !isRefundReasonsLoading && refundReasons && ( + )} ) diff --git a/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-payment-section/order-payment-section.tsx b/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-payment-section/order-payment-section.tsx index e2fe50c898..97478f3663 100644 --- a/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-payment-section/order-payment-section.tsx +++ b/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-payment-section/order-payment-section.tsx @@ -31,11 +31,11 @@ type OrderPaymentSectionProps = { order: HttpTypes.AdminOrder } -const getPaymentsFromOrder = (order: HttpTypes.AdminOrder) => { +export const getPaymentsFromOrder = (order: HttpTypes.AdminOrder) => { return order.payment_collections .map((collection: HttpTypes.AdminPaymentCollection) => collection.payments) .flat(1) - .filter(Boolean) + .filter(Boolean) as HttpTypes.AdminPayment[] } export const OrderPaymentSection = ({ order }: OrderPaymentSectionProps) => { @@ -222,7 +222,7 @@ const Payment = ({ { label: t("orders.payment.refund"), icon: , - to: `/orders/${order.id}/payments/${payment.id}/refund`, + to: `/orders/${order.id}/refund?paymentId=${payment.id}`, disabled: !payment.captured_at, }, ], diff --git a/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-summary-section/order-summary-section.tsx b/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-summary-section/order-summary-section.tsx index c932fc11e9..5d6e5a8e48 100644 --- a/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-summary-section/order-summary-section.tsx +++ b/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-summary-section/order-summary-section.tsx @@ -212,6 +212,21 @@ export const OrderSummarySection = ({ order }: OrderSummarySectionProps) => { {t("orders.payment.markAsPaid")} )} + + {showRefund && ( + + )}
)} diff --git a/packages/core/core-flows/src/order/workflows/create-or-update-order-payment-collection.ts b/packages/core/core-flows/src/order/workflows/create-or-update-order-payment-collection.ts index c12a3b1dfc..401046651c 100644 --- a/packages/core/core-flows/src/order/workflows/create-or-update-order-payment-collection.ts +++ b/packages/core/core-flows/src/order/workflows/create-or-update-order-payment-collection.ts @@ -57,17 +57,18 @@ export const createOrUpdateOrderPaymentCollectionWorkflow = createWorkflow( }).config({ name: "payment-collection-query" }) const amountPending = transform({ order, input }, ({ order, input }) => { - const pendingPayment = + const amountToCharge = input.amount ?? 0 + const amountPending = order.summary.raw_pending_difference ?? order.summary.pending_difference - if (MathBN.gt(input.amount ?? 0, pendingPayment)) { + if (amountToCharge > 0 && MathBN.gt(amountToCharge, amountPending)) { throw new MedusaError( MedusaError.Types.NOT_ALLOWED, - `Amount cannot be greater than ${pendingPayment}` + `Amount cannot be greater than ${amountPending}` ) } - return pendingPayment + return amountPending }) const updatedPaymentCollections = when(