269 lines
11 KiB
TypeScript
269 lines
11 KiB
TypeScript
import { type FC, useState, useMemo, useEffect } from 'react'
|
||
import { useFormik } from 'formik'
|
||
import * as Yup from 'yup'
|
||
import { useParams } from 'react-router-dom'
|
||
import Input from '@/components/Input'
|
||
import Select from '@/components/Select'
|
||
import type { ItemsSelectType } from '@/components/Select'
|
||
import Textarea from '@/components/Textarea'
|
||
import UploadBox from '@/components/UploadBox'
|
||
import Button from '@/components/Button'
|
||
import { TickCircle } from 'iconsax-react'
|
||
import { useGetInvoiceDetail } from '@/pages/invoice/hooks/useInvoiceData'
|
||
import { usePayInvoice } from './hooks/usePaymentData'
|
||
import { useMultiUpload } from '@/pages/uploader/hooks/useUploader'
|
||
import { NumberFormat } from '@/config/func'
|
||
import { toast } from '@/shared/toast'
|
||
import type { ErrorType } from '@/helpers/types'
|
||
import type { PayInvoiceParamsType } from './types/Types'
|
||
import { PaymentGatewayEnum, PaymentMethodEnum } from './enum/Enum'
|
||
import { useGetMe } from '../user/hooks/useUserData'
|
||
|
||
const getMethodItems = (userCredit: number): ItemsSelectType[] => [
|
||
{ value: PaymentMethodEnum.Online, label: 'آنلاین' },
|
||
{ value: PaymentMethodEnum.Cash, label: 'نقد' },
|
||
{
|
||
value: PaymentMethodEnum.Credit,
|
||
label: 'اعتباری',
|
||
disabled: userCredit <= 0,
|
||
},
|
||
]
|
||
|
||
const gatewayItems: ItemsSelectType[] = [
|
||
{ value: PaymentGatewayEnum.ZarinPal, label: 'زرینپال' },
|
||
]
|
||
|
||
const formatPrice = (num: number) => `${NumberFormat(num)} تومان`
|
||
|
||
const PayInvoice: FC = () => {
|
||
|
||
const { data: user } = useGetMe()
|
||
const { id } = useParams()
|
||
const { data, isPending } = useGetInvoiceDetail(id ?? '')
|
||
const { mutate: payInvoice, isPending: isPaying } = usePayInvoice()
|
||
const multiUpload = useMultiUpload()
|
||
const [attachmentFiles, setAttachmentFiles] = useState<File[]>([])
|
||
|
||
const userCredit = user?.maxCredit ?? 0
|
||
const methodItems = useMemo(() => getMethodItems(userCredit), [userCredit])
|
||
|
||
const invoice = data?.data
|
||
|
||
useEffect(() => {
|
||
if (userCredit <= 0 && formik.values.method === PaymentMethodEnum.Credit) {
|
||
formik.setFieldValue('method', PaymentMethodEnum.Online)
|
||
}
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps -- only run when userCredit is known
|
||
}, [userCredit])
|
||
|
||
const formik = useFormik<PayInvoiceParamsType>({
|
||
initialValues: {
|
||
amount: invoice?.balance ?? 0,
|
||
method: PaymentMethodEnum.Online,
|
||
gateway: PaymentGatewayEnum.ZarinPal,
|
||
description: '',
|
||
attachments: [],
|
||
},
|
||
validationSchema: Yup.object({
|
||
amount: Yup.number()
|
||
.required('مبلغ اجباری است')
|
||
.min(1, 'مبلغ باید بیشتر از صفر باشد'),
|
||
method: Yup.string().required('روش پرداخت اجباری است'),
|
||
gateway: Yup.string().when('method', {
|
||
is: PaymentMethodEnum.Online,
|
||
then: (schema) => schema.required('درگاه پرداخت اجباری است'),
|
||
}),
|
||
description: Yup.string(),
|
||
attachments: Yup.array().of(Yup.string()),
|
||
}),
|
||
enableReinitialize: true,
|
||
onSubmit: async (values) => {
|
||
if (!id) return
|
||
|
||
let attachments: string[] = values.attachments ?? []
|
||
|
||
if (attachmentFiles.length > 0) {
|
||
try {
|
||
const uploadResult = await multiUpload.mutateAsync(attachmentFiles)
|
||
attachments = (uploadResult?.data ?? []).map(
|
||
(item: { url?: string }) => item?.url ?? ''
|
||
).filter(Boolean)
|
||
} catch (error) {
|
||
toast((error as ErrorType)?.response?.data?.error?.message?.[0] ?? 'خطا در آپلود فایلها', 'error')
|
||
return
|
||
}
|
||
}
|
||
|
||
payInvoice(
|
||
{
|
||
invoiceId: id,
|
||
params: {
|
||
...values,
|
||
attachments,
|
||
},
|
||
},
|
||
{
|
||
onSuccess: (res) => {
|
||
const paymentUrl = res?.data?.paymentUrl
|
||
if (paymentUrl) {
|
||
toast('در حال انتقال به درگاه پرداخت', 'success')
|
||
window.location.href = paymentUrl
|
||
return
|
||
}
|
||
toast('پرداخت با موفقیت ثبت شد', 'success')
|
||
},
|
||
onError: (error: ErrorType) => {
|
||
toast(
|
||
error?.response?.data?.error?.message?.[0] ?? 'خطا در ثبت پرداخت',
|
||
'error'
|
||
)
|
||
},
|
||
}
|
||
)
|
||
},
|
||
})
|
||
|
||
if (isPending && !invoice) {
|
||
return (
|
||
<div className="mt-4 text-sm flex items-center justify-center min-h-[200px]">
|
||
<span className="text-[#8C90A3]">در حال بارگذاری...</span>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
if (!id || !invoice) {
|
||
return (
|
||
<div className="mt-4 text-sm flex items-center justify-center min-h-[200px]">
|
||
<span className="text-[#8C90A3]">صورت حساب یافت نشد</span>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<div className="mt-4 text-sm">
|
||
<div className="text-sm mb-6">پرداخت صورت حساب</div>
|
||
|
||
{/* خلاصه صورت حساب */}
|
||
<div className="bg-white rounded-2xl p-8 mb-6">
|
||
<div className="text-sm mb-4">اطلاعات صورت حساب</div>
|
||
<div className="flex flex-wrap gap-x-12 gap-y-3 text-xs">
|
||
<div className="flex gap-1.5">
|
||
<span className="text-[#8C90A3]">شماره صورت حساب:</span>
|
||
<span className="text-black">{invoice.invoiceNumber}</span>
|
||
</div>
|
||
<div className="flex gap-1.5">
|
||
<span className="text-[#8C90A3]">مبلغ کل:</span>
|
||
<span className="text-black">{formatPrice(invoice.total)}</span>
|
||
</div>
|
||
<div className="flex gap-1.5">
|
||
<span className="text-[#8C90A3]">پرداخت شده:</span>
|
||
<span className="text-black">{formatPrice(invoice.paidAmount)}</span>
|
||
</div>
|
||
<div className="flex gap-1.5">
|
||
<span className="text-[#8C90A3]">مانده:</span>
|
||
<span className="text-black font-medium">{formatPrice(invoice.balance)}</span>
|
||
</div>
|
||
<div className="flex gap-1.5">
|
||
<span className="text-[#8C90A3]">اعتبار:</span>
|
||
<span className="text-black">{formatPrice(userCredit)}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* فرم پرداخت */}
|
||
<div className="bg-white rounded-2xl p-8">
|
||
<div className="text-sm mb-6">ثبت پرداخت</div>
|
||
|
||
<form onSubmit={formik.handleSubmit} className="flex flex-col gap-6">
|
||
<Input
|
||
label="مبلغ (تومان)"
|
||
placeholder="مبلغ را وارد کنید"
|
||
name="amount"
|
||
value={formik.values.amount ?? ''}
|
||
onChange={(e) =>
|
||
formik.setFieldValue(
|
||
'amount',
|
||
Number((e.target.value ?? '').replace(/,/g, '')) || 0
|
||
)
|
||
}
|
||
onBlur={formik.handleBlur}
|
||
seprator
|
||
error_text={
|
||
formik.touched.amount && formik.errors.amount
|
||
? formik.errors.amount
|
||
: ''
|
||
}
|
||
/>
|
||
|
||
<Select
|
||
label="روش پرداخت"
|
||
placeholder="انتخاب کنید"
|
||
items={methodItems}
|
||
name="method"
|
||
value={formik.values.method}
|
||
onChange={formik.handleChange}
|
||
onBlur={formik.handleBlur}
|
||
error_text={
|
||
formik.touched.method && formik.errors.method
|
||
? formik.errors.method
|
||
: ''
|
||
}
|
||
/>
|
||
|
||
{formik.values.method === PaymentMethodEnum.Online && (
|
||
<Select
|
||
label="درگاه پرداخت"
|
||
placeholder="انتخاب کنید"
|
||
items={gatewayItems}
|
||
name="gateway"
|
||
value={formik.values.gateway}
|
||
onChange={formik.handleChange}
|
||
onBlur={formik.handleBlur}
|
||
error_text={
|
||
formik.touched.gateway && formik.errors.gateway
|
||
? formik.errors.gateway
|
||
: ''
|
||
}
|
||
/>
|
||
)}
|
||
|
||
{formik.values.method !== PaymentMethodEnum.Online && (
|
||
<>
|
||
<Textarea
|
||
label="توضیحات (اختیاری)"
|
||
placeholder="توضیحات پرداخت"
|
||
{...formik.getFieldProps('description')}
|
||
error_text={
|
||
formik.touched.description && formik.errors.description
|
||
? formik.errors.description
|
||
: ''
|
||
}
|
||
/>
|
||
|
||
<UploadBox
|
||
label="پیوستها (اختیاری)"
|
||
isMultiple
|
||
onChange={setAttachmentFiles}
|
||
/>
|
||
</>
|
||
)}
|
||
|
||
<div className="flex justify-end pt-2">
|
||
<Button
|
||
type="submit"
|
||
isLoading={isPaying || multiUpload.isPending}
|
||
>
|
||
<div className="flex items-center gap-2">
|
||
<TickCircle size={20} color="black" />
|
||
<span>ثبت پرداخت</span>
|
||
</div>
|
||
</Button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default PayInvoice
|