1405/02/07-add authentication card for garoosi
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import useAxios from '@core/utils/useAxios'
|
||||
import type { AddId, Customer } from '@/model/customer'
|
||||
import type { TableInput } from '@core/type/baseTable'
|
||||
import type {CheckCardRequest, CheckCardResponse} from "@/model/user";
|
||||
|
||||
export const createCustomer = async (obj: Customer) =>
|
||||
await useAxios.post<AddId>(`/admin/customer`, obj)
|
||||
@@ -34,3 +35,6 @@ export const getSingleTable = async (params: TableInput) =>
|
||||
|
||||
export const getSingleArchiveTable = async (params: TableInput) =>
|
||||
await useAxios.post<Array<Customer>>('/admin/customer/single/archive/table', params)
|
||||
export const checkCard = async (data: CheckCardRequest) => {
|
||||
return await useAxios.post<CheckCardResponse>('/admin/check_card', data)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<DatePicker :locale="locale" v-model:value="_date" :size="props.size" v-bind="_attrs" inputReadOnly/>
|
||||
<DatePicker :locale="locale" v-model:value="_date" :size="props.size" v-bind="_attrs" :inputReadOnly="inputReadOnly" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -14,6 +14,7 @@ interface Props {
|
||||
value?: string | Dayjs
|
||||
size?: string
|
||||
format?: string
|
||||
inputReadOnly?:boolean
|
||||
}
|
||||
|
||||
const attrs = useAttrs()
|
||||
@@ -25,7 +26,8 @@ const _attrs = computed(() => {
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
value: '',
|
||||
format: 'YYYY/MM/DD'
|
||||
format: 'YYYY/MM/DD',
|
||||
inputReadOnly:true
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -69,6 +69,7 @@ export function setupRouter(app: App) {
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: routerStore.routes,
|
||||
|
||||
/*scrollBehavior(to, from, savedPosition) {
|
||||
/!*if (savedPosition) {
|
||||
return savedPosition
|
||||
|
||||
@@ -21,3 +21,14 @@ export interface User {
|
||||
export interface AddId {
|
||||
id: number
|
||||
}
|
||||
export interface CheckCardRequest {
|
||||
birth_date: string
|
||||
national_code: string
|
||||
cart_number: string
|
||||
}
|
||||
|
||||
export interface CheckCardResponse {
|
||||
success: boolean
|
||||
message?: string
|
||||
data?: any
|
||||
}
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
<template>
|
||||
<div class="flex items-center justify-center p-4">
|
||||
<div class="w-full max-w-md">
|
||||
<a-card class="shadow-2xl rounded-2xl border-0" :bordered="false">
|
||||
<!-- Header -->
|
||||
<div class="text-center mb-8">
|
||||
<div class="w-20 h-20 mx-auto mb-4 rounded-full bg-gradient-to-r from-blue-500 to-indigo-600 flex items-center justify-center">
|
||||
<svg class="w-10 h-10 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="text-3xl font-bold text-gray-800 mb-2">احراز هویت</h2>
|
||||
<p class="text-gray-500 text-sm">لطفاً اطلاعات کارت بانکی فرد مورد نظر را وارد کنید</p>
|
||||
</div>
|
||||
|
||||
<!-- Form -->
|
||||
<a-form
|
||||
:model="formState"
|
||||
:rules="rules"
|
||||
:label-col="{ span: 24 }"
|
||||
:wrapper-col="{ span: 24 }"
|
||||
@finish="handleSubmit"
|
||||
class="rtl-form"
|
||||
>
|
||||
<a-form-item label="تاریخ تولد" name="birth_date" required>
|
||||
<BaseDatePicker
|
||||
v-model:value="formState.birth_date"
|
||||
placeholder="انتخاب تاریخ تولد"
|
||||
format="YYYY-MM-DD"
|
||||
class="w-full"
|
||||
size="large"
|
||||
:disabled-date="disabledDate"
|
||||
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="کد ملی" name="national_code" required>
|
||||
<a-input
|
||||
v-model:value="formState.national_code"
|
||||
placeholder="کد ملی خود را وارد کنید"
|
||||
size="large"
|
||||
maxlength="10"
|
||||
class=" text-lg"
|
||||
>
|
||||
<template #prefix>
|
||||
<span class="text-gray-400 ml-2">🇮🇷</span>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="شماره کارت" name="cart_number" required>
|
||||
<a-input
|
||||
v-model:value="formState.cart_number"
|
||||
placeholder="شماره ۱۶ رقمی کارت را وارد کنید"
|
||||
size="large"
|
||||
maxlength="16"
|
||||
class=" text-lg tracking-wider"
|
||||
>
|
||||
<template #prefix>
|
||||
<svg class="w-5 h-5 text-gray-400 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" />
|
||||
</svg>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item class="mt-3">
|
||||
<a-button
|
||||
type="primary"
|
||||
html-type="submit"
|
||||
size="large"
|
||||
:loading="loading"
|
||||
block
|
||||
class="h-12 text-base font-semibold border-0"
|
||||
>
|
||||
<template #icon>
|
||||
<svg class="w-5 h-5 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
||||
</svg>
|
||||
</template>
|
||||
تأیید و ادامه
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, h } from 'vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import type { FormRules } from 'ant-design-vue/es/form'
|
||||
import type { CheckCardRequest } from "@/model/user"
|
||||
import { checkCard } from "@/api/customer"
|
||||
import DatePicker from "@core/components/_sample/components/datePicker.vue";
|
||||
|
||||
const formState = reactive<CheckCardRequest>({
|
||||
birth_date: '',
|
||||
national_code: '',
|
||||
cart_number: ''
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
// Validation rules
|
||||
const rules: FormRules = {
|
||||
birth_date: [
|
||||
{ required: true, message: 'لطفاً تاریخ تولد را انتخاب کنید', trigger: 'change' }
|
||||
],
|
||||
national_code: [
|
||||
{ required: true, message: 'لطفاً کد ملی را وارد کنید', trigger: 'blur' },
|
||||
{ len: 10, message: 'کد ملی باید ۱۰ رقم باشد', trigger: 'blur' },
|
||||
{ pattern: /^[0-9]+$/, message: 'کد ملی فقط میتواند شامل اعداد باشد', trigger: 'blur' },
|
||||
{ validator: validateNationalCode, trigger: 'blur' }
|
||||
],
|
||||
cart_number: [
|
||||
{ required: true, message: 'لطفاً شماره کارت را وارد کنید', trigger: 'blur' },
|
||||
{ len: 16, message: 'شماره کارت باید ۱۶ رقم باشد', trigger: 'blur' },
|
||||
{ pattern: /^[0-9]+$/, message: 'شماره کارت فقط میتواند شامل اعداد باشد', trigger: 'blur' },
|
||||
{ validator: validateLuhn, trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// Validate Iranian National Code
|
||||
function validateNationalCode(_rule: any, value: string) {
|
||||
if (!value) return Promise.resolve()
|
||||
|
||||
const code = value.toString()
|
||||
if (code.length !== 10 || !/^\d{10}$/.test(code)) {
|
||||
return Promise.reject('کد ملی نامعتبر است')
|
||||
}
|
||||
|
||||
const check = parseInt(code[9])
|
||||
let sum = 0
|
||||
for (let i = 0; i < 9; i++) {
|
||||
sum += parseInt(code[i]) * (10 - i)
|
||||
}
|
||||
const remainder = sum % 11
|
||||
const isValid = (remainder < 2 && check === remainder) || (remainder >= 2 && check === 11 - remainder)
|
||||
|
||||
return isValid ? Promise.resolve() : Promise.reject('کد ملی نامعتبر است')
|
||||
}
|
||||
|
||||
// Validate Luhn algorithm for card number
|
||||
function validateLuhn(_rule: any, value: string) {
|
||||
if (!value) return Promise.resolve()
|
||||
|
||||
if (!/^\d{16}$/.test(value)) {
|
||||
return Promise.reject('شماره کارت نامعتبر است')
|
||||
}
|
||||
|
||||
let sum = 0
|
||||
let isEven = false
|
||||
|
||||
for (let i = value.length - 1; i >= 0; i--) {
|
||||
let digit = parseInt(value[i])
|
||||
|
||||
if (isEven) {
|
||||
digit *= 2
|
||||
if (digit > 9) digit -= 9
|
||||
}
|
||||
|
||||
sum += digit
|
||||
isEven = !isEven
|
||||
}
|
||||
|
||||
return sum % 10 === 0 ? Promise.resolve() : Promise.reject('شماره کارت نامعتبر است')
|
||||
}
|
||||
|
||||
// Disable future dates
|
||||
function disabledDate(current: any) {
|
||||
return current && current > new Date()
|
||||
}
|
||||
|
||||
// Format card number with spaces (XXXX XXXX XXXX XXXX)
|
||||
function formatCardNumber(cardNumber: string): string {
|
||||
return cardNumber.replace(/(\d{4})/g, '$1 ').trim()
|
||||
}
|
||||
|
||||
// Get Persian bank name
|
||||
function getBankPersianName(bankCode: string): string {
|
||||
const banks: Record<string, string> = {
|
||||
'SAMAN': 'بانک سامان',
|
||||
'MELLI': 'بانک ملی ایران',
|
||||
'MELAT': 'بانک ملت',
|
||||
'SADERAT': 'بانک صادرات ایران',
|
||||
'TEJARAT': 'بانک تجارت',
|
||||
'PASARGAD': 'بانک پاسارگاد',
|
||||
'KARAFARIN': 'بانک کارآفرین',
|
||||
'EGHTESAD_NOVIN': 'بانک اقتصاد نوین',
|
||||
'PARSIAN': 'بانک پارسیان',
|
||||
'SINAA': 'بانک سینا',
|
||||
'MELLAT': 'بانک ملت'
|
||||
}
|
||||
return banks[bankCode] || bankCode
|
||||
}
|
||||
|
||||
// Display success modal with user information
|
||||
function showSuccessModal(owners: any[], cartNumber: string, bankName: string) {
|
||||
if (!owners || owners.length === 0) {
|
||||
message.success({
|
||||
content: 'احراز هویت با موفقیت انجام شد',
|
||||
duration: 3
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const owner = owners[0]
|
||||
const fullName = `${owner.firstName} ${owner.lastName}`
|
||||
const formattedCardNumber = formatCardNumber(cartNumber)
|
||||
const persianBankName = getBankPersianName(bankName)
|
||||
|
||||
Modal.success({
|
||||
title: 'تأیید هویت',
|
||||
content: h('div', { style: 'direction: rtl; text-align: right;' }, [
|
||||
h('p', {
|
||||
style: 'margin-bottom: 16px; font-size: 14px; line-height: 1.6; color: #1f2937; font-weight: 500;'
|
||||
},
|
||||
`احراز هویت آقای / سرکار خانم ${fullName} با موفقیت انجام گردید.`
|
||||
),
|
||||
h('div', {
|
||||
style: 'margin-top: 20px; padding: 12px; background-color: #f3f4f6; border-radius: 8px;'
|
||||
}, [
|
||||
h('p', { style: 'margin-bottom: 8px; font-size: 13px; color: #4B5563;' }, [
|
||||
h('span', { style: 'font-weight: 600;' }, 'شماره کارت: '),
|
||||
h('span', { style: 'font-family: monospace; direction: ltr; display: inline-block;' }, formattedCardNumber)
|
||||
]),
|
||||
h('p', { style: 'font-size: 13px; color: #4B5563; margin: 0;' }, [
|
||||
h('span', { style: 'font-weight: 600;' }, 'بانک صادرکننده: '),
|
||||
h('span', {}, persianBankName)
|
||||
])
|
||||
])
|
||||
]),
|
||||
okText: 'تأیید',
|
||||
width: 420,
|
||||
maskClosable: true
|
||||
})
|
||||
}
|
||||
|
||||
// Submit form
|
||||
async function handleSubmit() {
|
||||
if (!formState.birth_date) {
|
||||
message.warning('لطفاً تاریخ تولد را انتخاب کنید')
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
const response = await checkCard(formState)
|
||||
|
||||
// Check if the response is successful based on the provided structure
|
||||
if (response?.cartMatching?.matched === true && response?.cartData) {
|
||||
const { cartData } = response
|
||||
const owners = cartData.ibanInfo?.owners || []
|
||||
const bankName = cartData.ibanInfo?.bank || ''
|
||||
|
||||
// Show success message with user information
|
||||
showSuccessModal(owners, cartData.number, bankName)
|
||||
|
||||
// Optional: Redirect to another page after modal is closed
|
||||
// You can add a callback here for redirection
|
||||
} else {
|
||||
message.error({
|
||||
content: 'اطلاعات وارد شده با دادههای بانکی مطابقت ندارد. لطفاً مجدداً تلاش نمایید.',
|
||||
duration: 4
|
||||
})
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Error:', error)
|
||||
|
||||
// Handle different error scenarios
|
||||
if (error.response?.status === 404) {
|
||||
message.error('کاربری با این اطلاعات یافت نشد. لطفاً اطلاعات را بررسی نمایید.')
|
||||
} else if (error.response?.status === 400) {
|
||||
message.error('درخواست نامعتبر. لطفاً اطلاعات را به درستی وارد نمایید.')
|
||||
} else if (error.response?.status === 500) {
|
||||
message.error('خطای داخلی سرور. لطفاً چند دقیقه دیگر مجدداً تلاش نمایید.')
|
||||
} else {
|
||||
message.error(error.response?.data?.message || 'خطا در ارتباط با سرور. لطفاً مجدداً تلاش نمایید.')
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
@@ -47,8 +47,11 @@ export function setupRoutes(permissionsKeys: string[], router?: Router) {
|
||||
function filterRoutesByApp(routes: RouteRecordRaw) {
|
||||
if (!routes.children) return
|
||||
routes.children = routes.children.filter((item) => {
|
||||
|
||||
if (item.name === 'discounts' && appName !== 'goldsun') return false
|
||||
if (item.name === 'banners' && appName !== 'garoosi') return false
|
||||
if (item.name === 'authentication' && appName !== 'garoosi') return false
|
||||
|
||||
return true
|
||||
})
|
||||
routes.children.forEach((child) => filterRoutesByApp(child))
|
||||
|
||||
@@ -47,6 +47,17 @@ const Customers: RouteRecordRaw = {
|
||||
icon: 'vuesax-linear:save-2',
|
||||
title: 'بایگانی'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'authentication',
|
||||
path: 'authentication',
|
||||
component: () => import('@/pages/menuPages/customers/authentication/index.vue'),
|
||||
meta: {
|
||||
// permissionsKey: 'user',
|
||||
icon: 'vuesax-linear:card-tick',
|
||||
title: 'احراز هویت',
|
||||
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user