Files
negareh-console/src/pages/payment/PayInvoice.tsx
T
hamid zarghami 7e17191c1b ccredit user
2026-02-24 12:14:02 +03:30

269 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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