AddEditClientPaymentMethodDialog.vue 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. <template>
  2. <q-dialog ref="dialogRef" @hide="onDialogHide">
  3. <q-card class="q-dialog-plugin" style="width: 700px; max-width: 90vw">
  4. <DefaultDialogHeader :title="title" @close="onDialogCancel" />
  5. <q-form ref="formRef" @submit="onOKClick">
  6. <q-card-section class="row q-col-gutter-sm">
  7. <q-input
  8. v-model="form.card_number"
  9. :label="$t('client_payment_methods.card_number')"
  10. :rules="[inputRules.required, validateCardNumber]"
  11. :error="!!serverErrors?.card_number"
  12. :error-message="serverErrors?.card_number"
  13. mask="**** **** **** ####"
  14. unmasked-value
  15. class="col-12"
  16. @update:model-value="onCardNumberChange"
  17. >
  18. <template #append>
  19. <q-icon name="mdi-credit-card-outline" />
  20. </template>
  21. </q-input>
  22. <q-input
  23. v-model="form.holder_name"
  24. :label="$t('client_payment_methods.holder_name')"
  25. :rules="[inputRules.required]"
  26. :error="!!serverErrors?.holder_name"
  27. :error-message="serverErrors?.holder_name"
  28. class="col-12"
  29. @update:model-value="serverErrors.holder_name = null"
  30. />
  31. <q-input
  32. v-model="form.expiration"
  33. :label="$t('client_payment_methods.expiration')"
  34. :rules="[inputRules.required, validateExpiration]"
  35. :error="!!serverErrors?.expiration"
  36. :error-message="serverErrors?.expiration"
  37. mask="##/####"
  38. placeholder="MM/YYYY"
  39. class="col-md-6 col-12"
  40. @update:model-value="serverErrors.expiration = null"
  41. >
  42. <template #append>
  43. <q-icon name="mdi-calendar-outline" />
  44. </template>
  45. </q-input>
  46. <q-input
  47. v-model="form.cvv"
  48. :label="$t('client_payment_methods.cvv')"
  49. :rules="[inputRules.required]"
  50. :error="!!serverErrors?.cvv"
  51. :error-message="serverErrors?.cvv"
  52. mask="####"
  53. unmasked-value
  54. type="password"
  55. class="col-md-6 col-12"
  56. @update:model-value="serverErrors.cvv = null"
  57. >
  58. <template #append>
  59. <q-icon name="mdi-lock-outline" />
  60. </template>
  61. </q-input>
  62. <q-input
  63. v-model="form.card_name"
  64. :label="$t('client_payment_methods.card_name')"
  65. :error="!!serverErrors?.card_name"
  66. :error-message="serverErrors?.card_name"
  67. class="col-md-6 col-12"
  68. @update:model-value="serverErrors.card_name = null"
  69. />
  70. <q-input
  71. v-model="form.brand"
  72. :label="$t('client_payment_methods.brand')"
  73. :error="!!serverErrors?.brand"
  74. :error-message="serverErrors?.brand"
  75. readonly
  76. class="col-md-6 col-12"
  77. >
  78. <template #append>
  79. <q-icon name="mdi-credit-card-check-outline" />
  80. </template>
  81. </q-input>
  82. <q-checkbox
  83. v-model="form.is_active"
  84. :label="$t('client_payment_methods.is_active')"
  85. class="col-12"
  86. />
  87. </q-card-section>
  88. <q-card-actions align="right">
  89. <q-btn
  90. flat
  91. :label="$t('common.actions.cancel')"
  92. color="negative"
  93. @click="onDialogCancel"
  94. />
  95. <q-btn
  96. type="submit"
  97. :label="$t('common.actions.save')"
  98. :loading="loading"
  99. :disable="!hasUpdatedFields"
  100. color="primary"
  101. />
  102. </q-card-actions>
  103. </q-form>
  104. </q-card>
  105. </q-dialog>
  106. </template>
  107. <script setup>
  108. import { ref, onMounted } from 'vue'
  109. import { useDialogPluginComponent } from 'quasar'
  110. import { useI18n } from 'vue-i18n'
  111. import { useFormUpdateTracker } from 'src/composables/useFormUpdateTracker'
  112. import { useSubmitHandler } from 'src/composables/useSubmitHandler'
  113. import {
  114. createClientPaymentMethod,
  115. updateClientPaymentMethod
  116. } from 'src/api/clientPaymentMethod'
  117. import DefaultDialogHeader from 'src/components/defaults/DefaultDialogHeader.vue'
  118. import { useInputRules } from 'src/composables/useInputRules'
  119. import {
  120. validateCardNumberLuhn,
  121. detectCardBrand,
  122. validateCardExpiration
  123. } from 'src/helpers/utils'
  124. const props = defineProps({
  125. paymentMethod: {
  126. type: Object,
  127. default: null
  128. },
  129. clientId: {
  130. type: Number,
  131. required: true
  132. },
  133. title: {
  134. type: Function,
  135. default: () => ''
  136. }
  137. })
  138. defineEmits([...useDialogPluginComponent.emits])
  139. const { t } = useI18n()
  140. const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
  141. const { inputRules } = useInputRules()
  142. const formRef = ref(null)
  143. const { form, getUpdatedFields, hasUpdatedFields } = useFormUpdateTracker({
  144. client_id: props.paymentMethod ? props.paymentMethod.client_id : props.clientId,
  145. card_number: props.paymentMethod ? props.paymentMethod.card_number : null,
  146. holder_name: props.paymentMethod ? props.paymentMethod.holder_name : null,
  147. expiration: props.paymentMethod ? props.paymentMethod.expiration : null,
  148. cvv: props.paymentMethod ? props.paymentMethod.cvv : null,
  149. card_name: props.paymentMethod ? props.paymentMethod.card_name : null,
  150. brand: props.paymentMethod ? props.paymentMethod.brand : null,
  151. last_four_digits: props.paymentMethod ? props.paymentMethod.last_four_digits : null,
  152. is_active: props.paymentMethod ? props.paymentMethod.is_active : true
  153. })
  154. const {
  155. loading,
  156. serverErrors,
  157. execute: submitForm,
  158. } = useSubmitHandler({
  159. onSuccess: () => onDialogOK(true),
  160. formRef: formRef,
  161. })
  162. const validateCardNumber = (val) => {
  163. if (!val) return true
  164. if (!validateCardNumberLuhn(val)) {
  165. return t('client_payment_methods.invalid_card_number')
  166. }
  167. return true
  168. }
  169. const validateExpiration = (val) => {
  170. if (!val) return true
  171. if (!validateCardExpiration(val)) {
  172. return t('client_payment_methods.expired_card')
  173. }
  174. return true
  175. }
  176. const onCardNumberChange = () => {
  177. serverErrors.card_number = null
  178. if (form.card_number && form.card_number.length >= 6 && !form.card_number.includes('*')) {
  179. const brand = detectCardBrand(form.card_number)
  180. if (brand) {
  181. form.brand = brand
  182. }
  183. const digits = form.card_number.replace(/\D/g, '')
  184. if (digits.length >= 4) {
  185. form.last_four_digits = digits.slice(-4)
  186. }
  187. }
  188. }
  189. const onOKClick = async () => {
  190. if (props.paymentMethod) {
  191. await submitForm(() => updateClientPaymentMethod(props.paymentMethod.id, getUpdatedFields.value))
  192. } else {
  193. await submitForm(() => createClientPaymentMethod({ ...form }))
  194. }
  195. }
  196. onMounted(() => {
  197. if (props.clientId) {
  198. form.client_id = props.clientId
  199. }
  200. })
  201. </script>