|
|
@@ -0,0 +1,220 @@
|
|
|
+<template>
|
|
|
+ <q-dialog ref="dialogRef" @hide="onDialogHide">
|
|
|
+ <q-card class="q-dialog-plugin" style="width: 700px; max-width: 90vw">
|
|
|
+ <DefaultDialogHeader :title="title" @close="onDialogCancel" />
|
|
|
+ <q-form ref="formRef" @submit="onOKClick">
|
|
|
+ <q-card-section class="row q-col-gutter-sm">
|
|
|
+ <q-input
|
|
|
+ v-model="form.card_number"
|
|
|
+ :label="$t('client_payment_methods.card_number')"
|
|
|
+ :rules="[inputRules.required, validateCardNumber]"
|
|
|
+ :error="!!serverErrors?.card_number"
|
|
|
+ :error-message="serverErrors?.card_number"
|
|
|
+ mask="**** **** **** ####"
|
|
|
+ unmasked-value
|
|
|
+ class="col-12"
|
|
|
+ @update:model-value="onCardNumberChange"
|
|
|
+ >
|
|
|
+ <template #append>
|
|
|
+ <q-icon name="mdi-credit-card-outline" />
|
|
|
+ </template>
|
|
|
+ </q-input>
|
|
|
+
|
|
|
+ <q-input
|
|
|
+ v-model="form.holder_name"
|
|
|
+ :label="$t('client_payment_methods.holder_name')"
|
|
|
+ :rules="[inputRules.required]"
|
|
|
+ :error="!!serverErrors?.holder_name"
|
|
|
+ :error-message="serverErrors?.holder_name"
|
|
|
+ class="col-12"
|
|
|
+ @update:model-value="serverErrors.holder_name = null"
|
|
|
+ />
|
|
|
+
|
|
|
+ <q-input
|
|
|
+ v-model="form.expiration"
|
|
|
+ :label="$t('client_payment_methods.expiration')"
|
|
|
+ :rules="[inputRules.required, validateExpiration]"
|
|
|
+ :error="!!serverErrors?.expiration"
|
|
|
+ :error-message="serverErrors?.expiration"
|
|
|
+ mask="##/####"
|
|
|
+ placeholder="MM/YYYY"
|
|
|
+ class="col-md-6 col-12"
|
|
|
+ @update:model-value="serverErrors.expiration = null"
|
|
|
+ >
|
|
|
+ <template #append>
|
|
|
+ <q-icon name="mdi-calendar-outline" />
|
|
|
+ </template>
|
|
|
+ </q-input>
|
|
|
+
|
|
|
+ <q-input
|
|
|
+ v-model="form.cvv"
|
|
|
+ :label="$t('client_payment_methods.cvv')"
|
|
|
+ :rules="[inputRules.required]"
|
|
|
+ :error="!!serverErrors?.cvv"
|
|
|
+ :error-message="serverErrors?.cvv"
|
|
|
+ mask="####"
|
|
|
+ unmasked-value
|
|
|
+ type="password"
|
|
|
+ class="col-md-6 col-12"
|
|
|
+ @update:model-value="serverErrors.cvv = null"
|
|
|
+ >
|
|
|
+ <template #append>
|
|
|
+ <q-icon name="mdi-lock-outline" />
|
|
|
+ </template>
|
|
|
+ </q-input>
|
|
|
+
|
|
|
+ <q-input
|
|
|
+ v-model="form.card_name"
|
|
|
+ :label="$t('client_payment_methods.card_name')"
|
|
|
+ :error="!!serverErrors?.card_name"
|
|
|
+ :error-message="serverErrors?.card_name"
|
|
|
+ class="col-md-6 col-12"
|
|
|
+ @update:model-value="serverErrors.card_name = null"
|
|
|
+ />
|
|
|
+
|
|
|
+ <q-input
|
|
|
+ v-model="form.brand"
|
|
|
+ :label="$t('client_payment_methods.brand')"
|
|
|
+ :error="!!serverErrors?.brand"
|
|
|
+ :error-message="serverErrors?.brand"
|
|
|
+ readonly
|
|
|
+ class="col-md-6 col-12"
|
|
|
+ >
|
|
|
+ <template #append>
|
|
|
+ <q-icon name="mdi-credit-card-check-outline" />
|
|
|
+ </template>
|
|
|
+ </q-input>
|
|
|
+
|
|
|
+ <q-checkbox
|
|
|
+ v-model="form.is_active"
|
|
|
+ :label="$t('client_payment_methods.is_active')"
|
|
|
+ class="col-12"
|
|
|
+ />
|
|
|
+ </q-card-section>
|
|
|
+
|
|
|
+ <q-card-actions align="right">
|
|
|
+ <q-btn
|
|
|
+ flat
|
|
|
+ :label="$t('common.actions.cancel')"
|
|
|
+ color="negative"
|
|
|
+ @click="onDialogCancel"
|
|
|
+ />
|
|
|
+ <q-btn
|
|
|
+ type="submit"
|
|
|
+ :label="$t('common.actions.save')"
|
|
|
+ :loading="loading"
|
|
|
+ :disable="!hasUpdatedFields"
|
|
|
+ color="primary"
|
|
|
+ />
|
|
|
+ </q-card-actions>
|
|
|
+ </q-form>
|
|
|
+ </q-card>
|
|
|
+ </q-dialog>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, onMounted } from 'vue'
|
|
|
+import { useDialogPluginComponent } from 'quasar'
|
|
|
+import { useI18n } from 'vue-i18n'
|
|
|
+import { useFormUpdateTracker } from 'src/composables/useFormUpdateTracker'
|
|
|
+import { useSubmitHandler } from 'src/composables/useSubmitHandler'
|
|
|
+import {
|
|
|
+ createClientPaymentMethod,
|
|
|
+ updateClientPaymentMethod
|
|
|
+} from 'src/api/clientPaymentMethod'
|
|
|
+import DefaultDialogHeader from 'src/components/defaults/DefaultDialogHeader.vue'
|
|
|
+import { useInputRules } from 'src/composables/useInputRules'
|
|
|
+import {
|
|
|
+ validateCardNumberLuhn,
|
|
|
+ detectCardBrand,
|
|
|
+ validateCardExpiration
|
|
|
+} from 'src/helpers/utils'
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ paymentMethod: {
|
|
|
+ type: Object,
|
|
|
+ default: null
|
|
|
+ },
|
|
|
+ clientId: {
|
|
|
+ type: Number,
|
|
|
+ required: true
|
|
|
+ },
|
|
|
+ title: {
|
|
|
+ type: Function,
|
|
|
+ default: () => ''
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+defineEmits([...useDialogPluginComponent.emits])
|
|
|
+const { t } = useI18n()
|
|
|
+const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
|
|
|
+const { inputRules } = useInputRules()
|
|
|
+const formRef = ref(null)
|
|
|
+
|
|
|
+const { form, getUpdatedFields, hasUpdatedFields } = useFormUpdateTracker({
|
|
|
+ client_id: props.paymentMethod ? props.paymentMethod.client_id : props.clientId,
|
|
|
+ card_number: props.paymentMethod ? props.paymentMethod.card_number : null,
|
|
|
+ holder_name: props.paymentMethod ? props.paymentMethod.holder_name : null,
|
|
|
+ expiration: props.paymentMethod ? props.paymentMethod.expiration : null,
|
|
|
+ cvv: props.paymentMethod ? props.paymentMethod.cvv : null,
|
|
|
+ card_name: props.paymentMethod ? props.paymentMethod.card_name : null,
|
|
|
+ brand: props.paymentMethod ? props.paymentMethod.brand : null,
|
|
|
+ last_four_digits: props.paymentMethod ? props.paymentMethod.last_four_digits : null,
|
|
|
+ is_active: props.paymentMethod ? props.paymentMethod.is_active : true
|
|
|
+})
|
|
|
+
|
|
|
+const {
|
|
|
+ loading,
|
|
|
+ serverErrors,
|
|
|
+ execute: submitForm,
|
|
|
+} = useSubmitHandler({
|
|
|
+ onSuccess: () => onDialogOK(true),
|
|
|
+ formRef: formRef,
|
|
|
+})
|
|
|
+
|
|
|
+const validateCardNumber = (val) => {
|
|
|
+ if (!val) return true
|
|
|
+ if (!validateCardNumberLuhn(val)) {
|
|
|
+ return t('client_payment_methods.invalid_card_number')
|
|
|
+ }
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
+const validateExpiration = (val) => {
|
|
|
+ if (!val) return true
|
|
|
+ if (!validateCardExpiration(val)) {
|
|
|
+ return t('client_payment_methods.expired_card')
|
|
|
+ }
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
+const onCardNumberChange = () => {
|
|
|
+ serverErrors.card_number = null
|
|
|
+
|
|
|
+ if (form.card_number && form.card_number.length >= 6 && !form.card_number.includes('*')) {
|
|
|
+ const brand = detectCardBrand(form.card_number)
|
|
|
+ if (brand) {
|
|
|
+ form.brand = brand
|
|
|
+ }
|
|
|
+
|
|
|
+ const digits = form.card_number.replace(/\D/g, '')
|
|
|
+ if (digits.length >= 4) {
|
|
|
+ form.last_four_digits = digits.slice(-4)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const onOKClick = async () => {
|
|
|
+ if (props.paymentMethod) {
|
|
|
+ await submitForm(() => updateClientPaymentMethod(props.paymentMethod.id, getUpdatedFields.value))
|
|
|
+ } else {
|
|
|
+ await submitForm(() => createClientPaymentMethod({ ...form }))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ if (props.clientId) {
|
|
|
+ form.client_id = props.clientId
|
|
|
+ }
|
|
|
+})
|
|
|
+</script>
|