1405/02/07-add authentication card for garoosi
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import useAxios from '@core/utils/useAxios'
|
import useAxios from '@core/utils/useAxios'
|
||||||
import type { AddId, Customer } from '@/model/customer'
|
import type { AddId, Customer } from '@/model/customer'
|
||||||
import type { TableInput } from '@core/type/baseTable'
|
import type { TableInput } from '@core/type/baseTable'
|
||||||
|
import type {CheckCardRequest, CheckCardResponse} from "@/model/user";
|
||||||
|
|
||||||
export const createCustomer = async (obj: Customer) =>
|
export const createCustomer = async (obj: Customer) =>
|
||||||
await useAxios.post<AddId>(`/admin/customer`, obj)
|
await useAxios.post<AddId>(`/admin/customer`, obj)
|
||||||
@@ -34,3 +35,6 @@ export const getSingleTable = async (params: TableInput) =>
|
|||||||
|
|
||||||
export const getSingleArchiveTable = async (params: TableInput) =>
|
export const getSingleArchiveTable = async (params: TableInput) =>
|
||||||
await useAxios.post<Array<Customer>>('/admin/customer/single/archive/table', params)
|
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>
|
<template>
|
||||||
<div>
|
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@ interface Props {
|
|||||||
value?: string | Dayjs
|
value?: string | Dayjs
|
||||||
size?: string
|
size?: string
|
||||||
format?: string
|
format?: string
|
||||||
|
inputReadOnly?:boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const attrs = useAttrs()
|
const attrs = useAttrs()
|
||||||
@@ -25,7 +26,8 @@ const _attrs = computed(() => {
|
|||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
value: '',
|
value: '',
|
||||||
format: 'YYYY/MM/DD'
|
format: 'YYYY/MM/DD',
|
||||||
|
inputReadOnly:true
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ export function setupRouter(app: App) {
|
|||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
routes: routerStore.routes,
|
routes: routerStore.routes,
|
||||||
|
|
||||||
/*scrollBehavior(to, from, savedPosition) {
|
/*scrollBehavior(to, from, savedPosition) {
|
||||||
/!*if (savedPosition) {
|
/!*if (savedPosition) {
|
||||||
return savedPosition
|
return savedPosition
|
||||||
|
|||||||
@@ -21,3 +21,14 @@ export interface User {
|
|||||||
export interface AddId {
|
export interface AddId {
|
||||||
id: number
|
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) {
|
function filterRoutesByApp(routes: RouteRecordRaw) {
|
||||||
if (!routes.children) return
|
if (!routes.children) return
|
||||||
routes.children = routes.children.filter((item) => {
|
routes.children = routes.children.filter((item) => {
|
||||||
|
|
||||||
if (item.name === 'discounts' && appName !== 'goldsun') return false
|
if (item.name === 'discounts' && appName !== 'goldsun') return false
|
||||||
if (item.name === 'banners' && appName !== 'garoosi') return false
|
if (item.name === 'banners' && appName !== 'garoosi') return false
|
||||||
|
if (item.name === 'authentication' && appName !== 'garoosi') return false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
routes.children.forEach((child) => filterRoutesByApp(child))
|
routes.children.forEach((child) => filterRoutesByApp(child))
|
||||||
|
|||||||
@@ -47,6 +47,17 @@ const Customers: RouteRecordRaw = {
|
|||||||
icon: 'vuesax-linear:save-2',
|
icon: 'vuesax-linear:save-2',
|
||||||
title: 'بایگانی'
|
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