multi card number in payment method

This commit is contained in:
hamid zarghami
2026-06-29 11:41:03 +03:30
parent 6324cabb32
commit 72ca39c284
6 changed files with 153 additions and 74 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
VITE_TOKEN_NAME = 'dmnu_a_t'
VITE_REFRESH_TOKEN_NAME = 'dmnu-a-rt'
VITE_BASE_URL = 'https://dmenu-api.danakcorp.com'
# VITE_BASE_URL = 'http://192.168.99.131:2000'
# VITE_BASE_URL = 'https://dmenu-api.danakcorp.com'
VITE_BASE_URL = 'http://192.168.99.131:2000'
VITE_SOCKET_URL = 'https://dmenu-api.danakcorp.com'
+14 -31
View File
@@ -11,6 +11,7 @@ import Select from '@/components/Select'
import Switch from '@/components/Switch'
import Textarea from '@/components/Textarea'
import type { CreateRestaurantPaymentMethodType } from './types/Types'
import PaymentMethodCardsSection from './components/PaymentMethodCardsSection'
import { useCreateRestaurantPaymentMethod } from './hooks/usePaymentMethodData'
import { PaymentMethodEnum, PaymentGatewayEnum } from './enum/Enum'
import { Pages } from '@/config/Pages'
@@ -41,8 +42,7 @@ const CreatePaymentMethod: FC = () => {
enabled: true,
order: 0,
merchantId: '',
cardNumber: '',
cardOwner: '',
cards: [{ cardNumber: '', owner: '' }],
},
validationSchema: Yup.object().shape({
method: Yup.string().required('روش پرداخت الزامی است'),
@@ -59,14 +59,17 @@ const CreatePaymentMethod: FC = () => {
then: (schema) => schema.required('شناسه مرچنت الزامی است'),
otherwise: (schema) => schema.notRequired(),
}),
cardNumber: Yup.string().when('method', {
cards: Yup.array().when('method', {
is: PaymentMethodEnum.CreditCard,
then: (schema) => schema.required('شماره کارت الزامی است'),
otherwise: (schema) => schema.notRequired(),
}),
cardOwner: Yup.string().when('method', {
is: PaymentMethodEnum.CreditCard,
then: (schema) => schema.required('نام صاحب کارت الزامی است'),
then: (schema) =>
schema
.min(1, 'حداقل یک کارت الزامی است')
.of(
Yup.object().shape({
cardNumber: Yup.string().required('شماره کارت الزامی است'),
owner: Yup.string().required('نام صاحب کارت الزامی است'),
})
),
otherwise: (schema) => schema.notRequired(),
}),
}),
@@ -84,8 +87,7 @@ const CreatePaymentMethod: FC = () => {
: {}),
...(values.method === PaymentMethodEnum.CreditCard
? {
cardNumber: values.cardNumber,
cardOwner: values.cardOwner,
cards: values.cards,
}
: {}),
}
@@ -160,26 +162,7 @@ const CreatePaymentMethod: FC = () => {
</>
)}
{formik.values.method === PaymentMethodEnum.CreditCard && (
<>
<div className='mt-6'>
<Input
label='شماره کارت'
name='cardNumber'
value={formik.values.cardNumber}
onChange={formik.handleChange}
error_text={formik.touched.cardNumber && formik.errors.cardNumber ? formik.errors.cardNumber : ''}
/>
</div>
<div className='mt-6'>
<Input
label='نام صاحب کارت'
name='cardOwner'
value={formik.values.cardOwner}
onChange={formik.handleChange}
error_text={formik.touched.cardOwner && formik.errors.cardOwner ? formik.errors.cardOwner : ''}
/>
</div>
</>
<PaymentMethodCardsSection formik={formik} />
)}
<div className='mt-6'>
<Textarea
+15 -33
View File
@@ -11,6 +11,7 @@ import Select from '@/components/Select'
import Switch from '@/components/Switch'
import Textarea from '@/components/Textarea'
import type { CreateRestaurantPaymentMethodType } from './types/Types'
import PaymentMethodCardsSection from './components/PaymentMethodCardsSection'
import { useGetRestaurantPaymentMethod, useUpdateRestaurantPaymentMethod } from './hooks/usePaymentMethodData'
import { PaymentMethodEnum, PaymentGatewayEnum } from './enum/Enum'
import { Pages } from '@/config/Pages'
@@ -45,8 +46,7 @@ const UpdatePaymentMethod: FC = () => {
enabled: data.enabled ?? true,
order: data.order || 0,
merchantId: data.merchantId || '',
cardNumber: data.cardNumber || '',
cardOwner: data.cardOwner || '',
cards: data.cards?.length ? data.cards : [{ cardNumber: '', owner: '' }],
}
}
return {
@@ -56,8 +56,7 @@ const UpdatePaymentMethod: FC = () => {
enabled: true,
order: 0,
merchantId: '',
cardNumber: '',
cardOwner: '',
cards: [{ cardNumber: '', owner: '' }],
}
}, [paymentMethodData])
@@ -79,14 +78,17 @@ const UpdatePaymentMethod: FC = () => {
then: (schema) => schema.required('شناسه مرچنت الزامی است'),
otherwise: (schema) => schema.notRequired(),
}),
cardNumber: Yup.string().when('method', {
cards: Yup.array().when('method', {
is: PaymentMethodEnum.CreditCard,
then: (schema) => schema.required('شماره کارت الزامی است'),
otherwise: (schema) => schema.notRequired(),
}),
cardOwner: Yup.string().when('method', {
is: PaymentMethodEnum.CreditCard,
then: (schema) => schema.required('نام صاحب کارت الزامی است'),
then: (schema) =>
schema
.min(1, 'حداقل یک کارت الزامی است')
.of(
Yup.object().shape({
cardNumber: Yup.string().required('شماره کارت الزامی است'),
owner: Yup.string().required('نام صاحب کارت الزامی است'),
})
),
otherwise: (schema) => schema.notRequired(),
}),
}),
@@ -106,8 +108,7 @@ const UpdatePaymentMethod: FC = () => {
: {}),
...(values.method === PaymentMethodEnum.CreditCard
? {
cardNumber: values.cardNumber,
cardOwner: values.cardOwner,
cards: values.cards,
}
: {}),
}
@@ -201,26 +202,7 @@ const UpdatePaymentMethod: FC = () => {
</>
)}
{formik.values.method === PaymentMethodEnum.CreditCard && (
<>
<div className='mt-6'>
<Input
label='شماره کارت'
name='cardNumber'
value={formik.values.cardNumber}
onChange={formik.handleChange}
error_text={formik.touched.cardNumber && formik.errors.cardNumber ? formik.errors.cardNumber : ''}
/>
</div>
<div className='mt-6'>
<Input
label='نام صاحب کارت'
name='cardOwner'
value={formik.values.cardOwner}
onChange={formik.handleChange}
error_text={formik.touched.cardOwner && formik.errors.cardOwner ? formik.errors.cardOwner : ''}
/>
</div>
</>
<PaymentMethodCardsSection formik={formik} />
)}
<div className='mt-6'>
<Textarea
@@ -0,0 +1,109 @@
import { type FC } from 'react'
import type { FormikProps } from 'formik'
import { Add, Card, Trash } from 'iconsax-react'
import Input from '@/components/Input'
import type { CreateRestaurantPaymentMethodType } from '../types/Types'
type PaymentMethodCardsSectionProps = {
formik: FormikProps<CreateRestaurantPaymentMethodType>
}
const PaymentMethodCardsSection: FC<PaymentMethodCardsSectionProps> = ({ formik }) => {
const cards = formik.values.cards ?? []
const cardsError = formik.touched.cards && typeof formik.errors.cards === 'string' ? formik.errors.cards : ''
const cardErrors = Array.isArray(formik.errors.cards) ? formik.errors.cards : []
const getFieldError = (index: number, field: 'cardNumber' | 'owner') => {
const fieldErrors = cardErrors[index]
if (
!formik.touched.cards?.[index]?.[field] ||
!fieldErrors ||
typeof fieldErrors !== 'object' ||
!(field in fieldErrors)
) {
return ''
}
return String(fieldErrors[field])
}
return (
<div className='mt-6'>
<div className='flex items-center justify-between mb-3'>
<div className='text-sm'>کارتهای بانکی</div>
{cards.length > 0 && (
<span className='text-xs text-description'>{cards.length} کارت</span>
)}
</div>
<div className='space-y-3'>
{cards.map((card, index) => (
<div
key={index}
className='bg-gray-50 rounded-2xl p-4 border border-border'
>
<div className='flex items-center justify-between mb-3'>
<div className='flex items-center gap-2.5'>
<div className='w-9 h-9 rounded-xl bg-white border border-border flex items-center justify-center shrink-0'>
<Card size={18} color='#8C90A3' variant='Bold' />
</div>
<span className='text-sm text-gray-700'>کارت {index + 1}</span>
</div>
{cards.length > 1 && (
<button
type='button'
className='p-2 rounded-xl hover:bg-red-50 transition-colors'
onClick={() => {
formik.setFieldValue(
'cards',
cards.filter((_, i) => i !== index)
)
}}
aria-label={`حذف کارت ${index + 1}`}
>
<Trash size={18} color='#EF4444' />
</button>
)}
</div>
<div className='grid grid-cols-1 md:grid-cols-2 gap-3'>
<Input
label='شماره کارت'
name={`cards.${index}.cardNumber`}
value={card.cardNumber}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
inputMode='numeric'
placeholder='مثال: ۶۲۱۹۸۶۱۹۲۸۹۴۸۵۹۵'
error_text={getFieldError(index, 'cardNumber')}
/>
<Input
label='نام صاحب کارت'
name={`cards.${index}.owner`}
value={card.owner}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
placeholder='نام صاحب حساب'
error_text={getFieldError(index, 'owner')}
/>
</div>
</div>
))}
<button
type='button'
className='w-full flex items-center justify-center gap-2 h-11 rounded-2xl border border-dashed border-border text-sm text-description hover:border-primary hover:text-primary hover:bg-primary/5 transition-colors'
onClick={() => {
formik.setFieldValue('cards', [...cards, { cardNumber: '', owner: '' }])
}}
>
<Add size={20} />
افزودن کارت جدید
</button>
{cardsError && <div className='text-xs text-red-500'>{cardsError}</div>}
</div>
</div>
)
}
export default PaymentMethodCardsSection
@@ -61,17 +61,19 @@ export const getPaymentMethodTableColumns = ({ onDelete, isDeleting }: GetPaymen
}
},
{
key: 'cardNumber',
key: 'cards',
title: 'شماره کارت',
render: (item: RestaurantPaymentMethod) => {
return item.cardNumber || '-'
if (!item.cards?.length) return '-'
return item.cards.map((card) => card.cardNumber).join('، ')
}
},
{
key: 'cardOwner',
key: 'cardOwners',
title: 'نام صاحب کارت',
render: (item: RestaurantPaymentMethod) => {
return item.cardOwner || '-'
if (!item.cards?.length) return '-'
return item.cards.map((card) => card.owner).join('، ')
}
},
{
+7 -4
View File
@@ -55,6 +55,11 @@ export type RestaurantPaymentMethodsResponse = IResponse<
RestaurantPaymentMethod[]
>;
export type PaymentMethodCard = {
cardNumber: string;
owner: string;
};
export type RestaurantPaymentMethod = {
id: string;
createdAt: string;
@@ -67,8 +72,7 @@ export type RestaurantPaymentMethod = {
enabled: boolean;
order: number;
merchantId: string;
cardNumber?: string;
cardOwner?: string;
cards?: PaymentMethodCard[];
};
export type RestaurantPaymentMethodResponse = IResponse<
@@ -82,8 +86,7 @@ export type CreateRestaurantPaymentMethodType = {
enabled: boolean;
order: number;
merchantId?: string;
cardNumber?: string;
cardOwner?: string;
cards?: PaymentMethodCard[];
};
export type GetRestaurantPaymentMethodsParams = {