feat(dashboard,core-flows): ability to refund payment post RMA flow (#8685)

This commit is contained in:
Riqwan Thamir
2024-08-21 09:58:09 +02:00
committed by GitHub
parent cc3e84f081
commit a7d03ec562
7 changed files with 107 additions and 27 deletions

View File

@@ -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": {

View File

@@ -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"),
},

View File

@@ -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<zod.infer<typeof CreateRefundSchema>>({
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 = ({
<form onSubmit={handleSubmit} className="flex flex-1 flex-col">
<RouteDrawer.Body>
<div className="flex flex-col gap-y-4">
<Select
value={payment?.id}
onValueChange={(value) => {
navigate(`/orders/${order.id}/refund?paymentId=${value}`, {
replace: true,
})
}}
>
<Label className="font-sans txt-compact-small font-medium mb-[-6px]">
{t("orders.payment.selectPaymentToRefund")}
</Label>
<Select.Trigger>
<Select.Value
placeholder={t("orders.payment.selectPaymentToRefund")}
/>
</Select.Trigger>
<Select.Content>
{payments.map((payment) => (
<Select.Item value={payment!.id} key={payment.id}>
<span>
{getLocaleAmount(
payment.amount as number,
payment.currency_code
)}
{" - "}
</span>
<span>{payment.provider_id}</span>
<span> - ({payment.id.replace("pay_", "")})</span>
</Select.Item>
))}
</Select.Content>
</Select>
<Form.Field
control={form.control}
name="amount"
@@ -109,8 +172,8 @@ export const CreateRefundForm = ({
}
}
}}
code={payment.currency_code}
symbol={getCurrencySymbol(payment.currency_code)}
code={order.currency_code}
symbol={getCurrencySymbol(order.currency_code)}
value={field.value}
/>
</Form.Control>

View File

@@ -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 = () => {
<Heading>{t("orders.payment.createRefund")}</Heading>
</RouteDrawer.Header>
{!isLoading && !isRefundReasonsLoading && payment && refundReasons && (
<CreateRefundForm payment={payment} refundReasons={refundReasons} />
{order && !isRefundReasonsLoading && refundReasons && (
<CreateRefundForm order={order} refundReasons={refundReasons} />
)}
</RouteDrawer>
)

View File

@@ -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: <XCircle />,
to: `/orders/${order.id}/payments/${payment.id}/refund`,
to: `/orders/${order.id}/refund?paymentId=${payment.id}`,
disabled: !payment.captured_at,
},
],

View File

@@ -212,6 +212,21 @@ export const OrderSummarySection = ({ order }: OrderSummarySectionProps) => {
{t("orders.payment.markAsPaid")}
</Button>
)}
{showRefund && (
<Button
size="small"
variant="secondary"
onClick={() => navigate(`/orders/${order.id}/refund`)}
>
{t("orders.payment.refundAmount", {
amount: getStylizedAmount(
(order?.summary?.pending_difference || 0) * -1,
order?.currency_code
),
})}
</Button>
)}
</div>
)}
</Container>

View File

@@ -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(