|
|
@@ -1,14 +1,73 @@
|
|
|
<template>
|
|
|
- <q-dialog ref="dialogRef" persistent maximized transition-show="slide-up" transition-hide="slide-down">
|
|
|
+ <q-dialog
|
|
|
+ ref="dialogRef"
|
|
|
+ maximized
|
|
|
+ persistent
|
|
|
+ transition-hide="slide-down"
|
|
|
+ transition-show="slide-up"
|
|
|
+ >
|
|
|
<div class="bg-page full-height column no-shadow">
|
|
|
+ <form
|
|
|
+ ref="pagarmeFormRef"
|
|
|
+ action="#"
|
|
|
+ class="pagarme-token-form"
|
|
|
+ data-pagarmecheckout-form
|
|
|
+ method="POST"
|
|
|
+ >
|
|
|
+ <input
|
|
|
+ v-model="form.holder_name"
|
|
|
+ data-pagarmecheckout-element="holder_name"
|
|
|
+ name="holder-name"
|
|
|
+ type="text"
|
|
|
+ >
|
|
|
+
|
|
|
+ <input
|
|
|
+ v-model="pagarmeCardNumber"
|
|
|
+ data-pagarmecheckout-element="number"
|
|
|
+ name="card-number"
|
|
|
+ type="text"
|
|
|
+ >
|
|
|
+
|
|
|
+ <input
|
|
|
+ v-model="pagarmeExpirationMonth"
|
|
|
+ data-pagarmecheckout-element="exp_month"
|
|
|
+ name="card-exp-month"
|
|
|
+ type="text"
|
|
|
+ >
|
|
|
+
|
|
|
+ <input
|
|
|
+ v-model="pagarmeExpirationYear"
|
|
|
+ data-pagarmecheckout-element="exp_year"
|
|
|
+ name="card-exp-year"
|
|
|
+ type="text"
|
|
|
+ >
|
|
|
+
|
|
|
+ <input
|
|
|
+ v-model="pagarmeCvv"
|
|
|
+ data-pagarmecheckout-element="cvv"
|
|
|
+ name="cvv"
|
|
|
+ type="text"
|
|
|
+ >
|
|
|
+ </form>
|
|
|
|
|
|
<div class="row items-center q-px-md q-pt-md q-pb-sm bg-white shadow-profile bg-surface">
|
|
|
- <q-btn icon="mdi-chevron-left" flat round dense color="primary" @click="onDialogCancel" />
|
|
|
+ <q-btn
|
|
|
+ color="primary"
|
|
|
+ dense
|
|
|
+ flat
|
|
|
+ icon="mdi-chevron-left"
|
|
|
+ round
|
|
|
+ @click="onDialogCancel"
|
|
|
+ />
|
|
|
+
|
|
|
<q-space />
|
|
|
+
|
|
|
<span class="text-subtitle1 text-weight-bold text-primary">
|
|
|
{{ $t('profile.payments.title') }}
|
|
|
</span>
|
|
|
+
|
|
|
<q-space />
|
|
|
+
|
|
|
<div style="width: 32px"></div>
|
|
|
</div>
|
|
|
|
|
|
@@ -19,38 +78,59 @@
|
|
|
|
|
|
<div class="virtual-card q-mb-xl">
|
|
|
<div class="card-top-row row items-start justify-between">
|
|
|
- <span class="card-brand-label">{{ brandDisplayName }}</span>
|
|
|
- <q-icon name="mdi-wifi-strength-4" size="24px" color="white" class="nfc-icon" style="transform: rotate(90deg);" />
|
|
|
+ <span class="card-brand-label">
|
|
|
+ {{ brandDisplayName }}
|
|
|
+ </span>
|
|
|
+
|
|
|
+ <q-icon
|
|
|
+ class="nfc-icon"
|
|
|
+ color="white"
|
|
|
+ name="mdi-wifi-strength-4"
|
|
|
+ size="24px"
|
|
|
+ style="transform: rotate(90deg);"
|
|
|
+ />
|
|
|
</div>
|
|
|
+
|
|
|
<div class="card-number-preview">
|
|
|
{{ maskedCardNumberPreview }}
|
|
|
</div>
|
|
|
+
|
|
|
<div class="card-bottom-row row items-end justify-between">
|
|
|
<div class="column">
|
|
|
- <span class="card-holder-name">{{ form.holder_name || 'Nome do Titular' }}</span>
|
|
|
+ <span class="card-holder-name">
|
|
|
+ {{ form.holder_name || 'Nome do Titular' }}
|
|
|
+ </span>
|
|
|
</div>
|
|
|
+
|
|
|
<div class="column items-end">
|
|
|
- <span class="card-expiry">{{ form.expiration || '**/****' }}</span>
|
|
|
+ <span class="card-expiry">
|
|
|
+ {{ form.expiration || '**/****' }}
|
|
|
+ </span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <q-form ref="formRef" class="column q-gutter-y-sm text-text" @submit="onOKClick">
|
|
|
+ <q-form
|
|
|
+ ref="formRef"
|
|
|
+ class="column q-gutter-y-sm text-text"
|
|
|
+ @submit="onOKClick"
|
|
|
+ >
|
|
|
<div>
|
|
|
<div class="input-label">
|
|
|
{{ $t('profile.payments.card_number') }}
|
|
|
</div>
|
|
|
+
|
|
|
<q-input
|
|
|
v-model="form.card_number"
|
|
|
- :rules="[inputRules.requiredHideMessage]"
|
|
|
- :error="!!serverErrors.card_number"
|
|
|
- :error-message="serverErrors.card_number"
|
|
|
+ class="input-field bg-surface input-border-dark"
|
|
|
+ hide-bottom-space
|
|
|
+ input-class="text-text"
|
|
|
mask="#### #### #### ####"
|
|
|
- unmasked-value
|
|
|
outlined
|
|
|
- input-class="text-text"
|
|
|
- hide-bottom-space
|
|
|
- class="input-field bg-surface input-border-dark"
|
|
|
+ unmasked-value
|
|
|
+ :error="!!serverErrors.card_number"
|
|
|
+ :error-message="serverErrors.card_number"
|
|
|
+ :rules="cardNumberRules"
|
|
|
@update:model-value="onCardNumberChange"
|
|
|
/>
|
|
|
</div>
|
|
|
@@ -59,16 +139,17 @@
|
|
|
<div class="input-label">
|
|
|
{{ $t('profile.payments.holder_name') }}
|
|
|
</div>
|
|
|
+
|
|
|
<q-input
|
|
|
v-model="form.holder_name"
|
|
|
+ class="input-field bg-surface input-border-dark"
|
|
|
+ hide-bottom-space
|
|
|
+ input-class="text-text"
|
|
|
+ outlined
|
|
|
placeholder="Nome como está no cartão"
|
|
|
- :rules="[inputRules.requiredHideMessage]"
|
|
|
:error="!!serverErrors.holder_name"
|
|
|
:error-message="serverErrors.holder_name"
|
|
|
- outlined
|
|
|
- input-class="text-text"
|
|
|
- hide-bottom-space
|
|
|
- class="input-field bg-surface input-border-dark"
|
|
|
+ :rules="[inputRules.requiredHideMessage]"
|
|
|
@update:model-value="serverErrors.holder_name = null"
|
|
|
/>
|
|
|
</div>
|
|
|
@@ -78,161 +159,439 @@
|
|
|
<div class="input-label">
|
|
|
{{ $t('profile.payments.expiration') }}
|
|
|
</div>
|
|
|
+
|
|
|
<q-input
|
|
|
v-model="form.expiration"
|
|
|
- :placeholder="$t('profile.payments.mmyyyy')"
|
|
|
- :rules="[inputRules.requiredHideMessage, validateExpiration]"
|
|
|
- :error="!!serverErrors.expiration"
|
|
|
- :error-message="serverErrors.expiration"
|
|
|
- mask="##/####"
|
|
|
- outlined
|
|
|
class="col input-field bg-surface input-border-dark"
|
|
|
- input-class="text-text"
|
|
|
hide-bottom-space
|
|
|
+ input-class="text-text"
|
|
|
+ mask="##/####"
|
|
|
+ outlined
|
|
|
+ :error="!!serverErrors.expiration"
|
|
|
+ :error-message="serverErrors.expiration"
|
|
|
+ :placeholder="$t('profile.payments.mmyyyy')"
|
|
|
+ :rules="[inputRules.requiredHideMessage, validateExpiration]"
|
|
|
@update:model-value="serverErrors.expiration = null"
|
|
|
/>
|
|
|
</div>
|
|
|
+
|
|
|
<div class="col-5">
|
|
|
<div class="input-label row items-center q-gutter-x-xs">
|
|
|
- <span>{{ $t('profile.payments.cvv') }}</span>
|
|
|
- <q-icon name="mdi-help-circle-outline" color="grey-8" size="16px" class="cursor-pointer">
|
|
|
- <q-tooltip>{{ $t('profile.payments.cvv_help') }}</q-tooltip>
|
|
|
+ <span>
|
|
|
+ {{ $t('profile.payments.cvv') }}
|
|
|
+ </span>
|
|
|
+
|
|
|
+ <q-icon
|
|
|
+ class="cursor-pointer"
|
|
|
+ color="grey-8"
|
|
|
+ name="mdi-help-circle-outline"
|
|
|
+ size="16px"
|
|
|
+ >
|
|
|
+ <q-tooltip>
|
|
|
+ {{ $t('profile.payments.cvv_help') }}
|
|
|
+ </q-tooltip>
|
|
|
</q-icon>
|
|
|
</div>
|
|
|
+
|
|
|
<q-input
|
|
|
v-model="form.cvv"
|
|
|
+ class="col input-field bg-surface input-border-dark"
|
|
|
+ hide-bottom-space
|
|
|
+ input-class="text-text"
|
|
|
+ mask="####"
|
|
|
+ outlined
|
|
|
placeholder="***"
|
|
|
- :rules="[inputRules.requiredHideMessage]"
|
|
|
+ type="password"
|
|
|
+ unmasked-value
|
|
|
:error="!!serverErrors.cvv"
|
|
|
:error-message="serverErrors.cvv"
|
|
|
- mask="####"
|
|
|
- unmasked-value
|
|
|
- type="password"
|
|
|
- outlined
|
|
|
- input-class="text-text"
|
|
|
- class="col input-field bg-surface input-border-dark"
|
|
|
- hide-bottom-space
|
|
|
+ :rules="cvvRules"
|
|
|
@update:model-value="serverErrors.cvv = null"
|
|
|
/>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="row items-center justify-center q-gutter-x-xs q-mt-sm q-mb-xs">
|
|
|
- <q-icon name="mdi-shield-check" color="positive" size="18px" />
|
|
|
- <span class="security-text-new">{{ $t('profile.payments.security_badge') }}</span>
|
|
|
+ <q-icon
|
|
|
+ color="positive"
|
|
|
+ name="mdi-shield-check"
|
|
|
+ size="18px"
|
|
|
+ />
|
|
|
+
|
|
|
+ <span class="security-text-new">
|
|
|
+ {{ $t('profile.payments.security_badge') }}
|
|
|
+ </span>
|
|
|
</div>
|
|
|
|
|
|
<q-btn
|
|
|
- type="submit"
|
|
|
- unelevated
|
|
|
- rounded
|
|
|
- no-caps
|
|
|
- color="primary"
|
|
|
class="full-width q-mt-md save-btn q-mb-md"
|
|
|
+ color="primary"
|
|
|
+ no-caps
|
|
|
padding="14px 16px"
|
|
|
- :label="paymentMethod ? $t('profile.payments.save_btn') : $t('profile.payments.add_card')"
|
|
|
- :loading="loading"
|
|
|
+ rounded
|
|
|
+ type="submit"
|
|
|
+ unelevated
|
|
|
:disable="!hasUpdatedFields"
|
|
|
+ :label="paymentMethod ? $t('profile.payments.save_btn') : $t('profile.payments.add_card')"
|
|
|
+ :loading="loading || tokenizing"
|
|
|
/>
|
|
|
</q-form>
|
|
|
</div>
|
|
|
-
|
|
|
</div>
|
|
|
</q-dialog>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { ref, computed } from 'vue';
|
|
|
-import { useDialogPluginComponent } from 'quasar';
|
|
|
+import { computed, ref } from 'vue';
|
|
|
+
|
|
|
+import { useDialogPluginComponent, useQuasar } from 'quasar';
|
|
|
import { useI18n } from 'vue-i18n';
|
|
|
+
|
|
|
+import { createClientPaymentMethod, updateClientPaymentMethod } from 'src/api/clientPaymentMethod';
|
|
|
+
|
|
|
import { useFormUpdateTracker } from 'src/composables/useFormUpdateTracker';
|
|
|
-import { useSubmitHandler } from 'src/composables/useSubmitHandler';
|
|
|
import { useInputRules } from 'src/composables/useInputRules';
|
|
|
-import { /*validateCardNumberLuhn,*/ detectCardBrand, validateCardExpiration } from 'src/helpers/utils';
|
|
|
-import { createClientPaymentMethod, updateClientPaymentMethod } from 'src/api/clientPaymentMethod';
|
|
|
+import { useSubmitHandler } from 'src/composables/useSubmitHandler';
|
|
|
+
|
|
|
+import { detectCardBrand, validateCardExpiration } from 'src/helpers/utils';
|
|
|
|
|
|
const props = defineProps({
|
|
|
- paymentMethod: {
|
|
|
- type: Object,
|
|
|
- default: null,
|
|
|
- },
|
|
|
clientId: {
|
|
|
- type: Number,
|
|
|
required: true,
|
|
|
+ type: Number,
|
|
|
+ },
|
|
|
+
|
|
|
+ paymentMethod: {
|
|
|
+ default: null,
|
|
|
+ type: Object,
|
|
|
},
|
|
|
});
|
|
|
|
|
|
defineEmits([...useDialogPluginComponent.emits]);
|
|
|
|
|
|
-const { dialogRef, onDialogOK, onDialogCancel } = useDialogPluginComponent();
|
|
|
-const { t } = useI18n();
|
|
|
+const $q = useQuasar();
|
|
|
+
|
|
|
+const { dialogRef, onDialogCancel, onDialogOK } = useDialogPluginComponent();
|
|
|
+
|
|
|
const { inputRules } = useInputRules();
|
|
|
-const formRef = ref(null);
|
|
|
+
|
|
|
+const { t } = useI18n();
|
|
|
|
|
|
const { form, hasUpdatedFields } = useFormUpdateTracker({
|
|
|
+ brand: props.paymentMethod ? props.paymentMethod.brand : null,
|
|
|
+ card_name: props.paymentMethod ? props.paymentMethod.card_name : null,
|
|
|
+ card_number: null,
|
|
|
client_id: props.paymentMethod ? props.paymentMethod.client_id : props.clientId,
|
|
|
- card_number: props.paymentMethod ? props.paymentMethod.card_number : null,
|
|
|
+ cvv: null,
|
|
|
+
|
|
|
+ expiration: props.paymentMethod
|
|
|
+ ? (
|
|
|
+ props.paymentMethod.expiration?.includes('/')
|
|
|
+ && props.paymentMethod.expiration.split('/')[1].length === 2
|
|
|
+ ? `${props.paymentMethod.expiration.split('/')[0]}/20${props.paymentMethod.expiration.split('/')[1]}`
|
|
|
+ : props.paymentMethod.expiration
|
|
|
+ )
|
|
|
+ : null,
|
|
|
+
|
|
|
holder_name: props.paymentMethod ? props.paymentMethod.holder_name : null,
|
|
|
- expiration: props.paymentMethod ? (props.paymentMethod.expiration?.includes('/') && props.paymentMethod.expiration.split('/')[1].length === 2 ? `${props.paymentMethod.expiration.split('/')[0]}/20${props.paymentMethod.expiration.split('/')[1]}` : 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,
|
|
|
+ last_four_digits: props.paymentMethod ? props.paymentMethod.last_four_digits : null,
|
|
|
});
|
|
|
|
|
|
-const { loading, serverErrors, execute: submitForm } = useSubmitHandler(() => onDialogOK(true));
|
|
|
+const {
|
|
|
+ execute: submitForm,
|
|
|
+ loading,
|
|
|
+ serverErrors,
|
|
|
+} = useSubmitHandler(() => onDialogOK(true));
|
|
|
+
|
|
|
+const formRef = ref(null);
|
|
|
+
|
|
|
+const pagarmeFormRef = ref(null);
|
|
|
+
|
|
|
+const tokenizing = ref(false);
|
|
|
|
|
|
const brandDisplayName = computed(() => {
|
|
|
- if (!form.brand) return '';
|
|
|
+ if (!form.brand) {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+
|
|
|
const names = {
|
|
|
- visa: 'VISA',
|
|
|
- mastercard: 'Mastercard',
|
|
|
- elo: 'Elo',
|
|
|
- hipercard: 'Hipercard',
|
|
|
diners: 'Diners',
|
|
|
discover: 'Discover',
|
|
|
+ elo: 'Elo',
|
|
|
+ hipercard: 'Hipercard',
|
|
|
+ mastercard: 'Mastercard',
|
|
|
+ visa: 'VISA',
|
|
|
};
|
|
|
+
|
|
|
return names[form.brand] ?? form.brand;
|
|
|
});
|
|
|
|
|
|
+const cardNumberRules = computed(() => (
|
|
|
+ props.paymentMethod
|
|
|
+ ? []
|
|
|
+ : [inputRules.requiredHideMessage]
|
|
|
+));
|
|
|
+
|
|
|
+const cvvRules = computed(() => (
|
|
|
+ props.paymentMethod
|
|
|
+ ? []
|
|
|
+ : [inputRules.requiredHideMessage]
|
|
|
+));
|
|
|
+
|
|
|
+const expirationMonth = computed(() => (
|
|
|
+ form.expiration
|
|
|
+ ? form.expiration.split('/')[0]
|
|
|
+ : ''
|
|
|
+));
|
|
|
+
|
|
|
+const expirationYear = computed(() => (
|
|
|
+ form.expiration
|
|
|
+ ? form.expiration.split('/')[1]
|
|
|
+ : ''
|
|
|
+));
|
|
|
+
|
|
|
const maskedCardNumberPreview = computed(() => {
|
|
|
- const raw = form.card_number ? String(form.card_number).replace(/\D/g, '') : '';
|
|
|
- if (!raw || raw.includes('*')) return '**** **** **** ****';
|
|
|
- const lastFour = raw.slice(-4).padStart(4, '*');
|
|
|
- return `**** **** **** ${lastFour}`;
|
|
|
+ const raw = form.card_number
|
|
|
+ ? String(form.card_number).replace(/\D/g, '')
|
|
|
+ : '';
|
|
|
+
|
|
|
+ if (raw && !raw.includes('*')) {
|
|
|
+ const lastFour = raw.slice(-4).padStart(4, '*');
|
|
|
+
|
|
|
+ return `**** **** **** ${lastFour}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (form.last_four_digits) {
|
|
|
+ return `**** **** **** ${String(form.last_four_digits).padStart(4, '*')}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ return '**** **** **** ****';
|
|
|
});
|
|
|
|
|
|
-// const validateCardNumber = (val) => {
|
|
|
-// if (!val) return true;
|
|
|
-// if (val.includes('*')) return true;
|
|
|
-// if (!validateCardNumberLuhn(val)) return t('profile.payments.invalid_card_number');
|
|
|
-// return true;
|
|
|
-// };
|
|
|
+const pagarmeCardNumber = computed({
|
|
|
+ get: () => (
|
|
|
+ form.card_number
|
|
|
+ ? String(form.card_number).replace(/\D/g, '')
|
|
|
+ : ''
|
|
|
+ ),
|
|
|
+ set: () => {},
|
|
|
+});
|
|
|
|
|
|
-const validateExpiration = (val) => {
|
|
|
- if (!val) return true;
|
|
|
- if (!validateCardExpiration(val)) return t('profile.payments.expired_card');
|
|
|
- return true;
|
|
|
+const pagarmeCvv = computed({
|
|
|
+ get: () => (
|
|
|
+ form.cvv
|
|
|
+ ? String(form.cvv).replace(/\D/g, '')
|
|
|
+ : ''
|
|
|
+ ),
|
|
|
+ set: () => {},
|
|
|
+});
|
|
|
+
|
|
|
+const pagarmeExpirationMonth = computed({
|
|
|
+ get: () => expirationMonth.value,
|
|
|
+ set: () => {},
|
|
|
+});
|
|
|
+
|
|
|
+const pagarmeExpirationYear = computed({
|
|
|
+ get: () => expirationYear.value,
|
|
|
+ set: () => {},
|
|
|
+});
|
|
|
+
|
|
|
+const PAGARME_SCRIPT_URL = 'https://checkout.pagar.me/v1/tokenizecard.js';
|
|
|
+
|
|
|
+let pagarmeScriptPromise = null;
|
|
|
+
|
|
|
+const getPagarmePublicKey = () => (
|
|
|
+ process.env.PAGARME_PUBLIC_KEY
|
|
|
+);
|
|
|
+
|
|
|
+const getPagarmeTokenFromResponse = (data) => {
|
|
|
+ const tokenKey = Object.keys(data || {}).find((key) => (
|
|
|
+ key.startsWith('pagarmetoken')
|
|
|
+ ));
|
|
|
+
|
|
|
+ return tokenKey ? data[tokenKey] : null;
|
|
|
+};
|
|
|
+
|
|
|
+const loadPagarmeScript = () => {
|
|
|
+ if (window.PagarmeCheckout) {
|
|
|
+ return Promise.resolve(window.PagarmeCheckout);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!pagarmeScriptPromise) {
|
|
|
+ pagarmeScriptPromise = new Promise((resolve, reject) => {
|
|
|
+ const publicKey = getPagarmePublicKey();
|
|
|
+
|
|
|
+ if (!publicKey) {
|
|
|
+ reject(new Error('PAGARME_PUBLIC_KEY não configurada'));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const existingScript = document.querySelector('script[data-pagarmecheckout-script="true"]');
|
|
|
+
|
|
|
+ if (existingScript) {
|
|
|
+ existingScript.addEventListener(
|
|
|
+ 'error',
|
|
|
+ () => reject(new Error('Falha ao carregar o tokenizecard.js')),
|
|
|
+ { once: true },
|
|
|
+ );
|
|
|
+
|
|
|
+ existingScript.addEventListener(
|
|
|
+ 'load',
|
|
|
+ () => resolve(window.PagarmeCheckout),
|
|
|
+ { once: true },
|
|
|
+ );
|
|
|
+
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const script = document.createElement('script');
|
|
|
+
|
|
|
+ script.async = true;
|
|
|
+ script.onerror = () => reject(new Error('Falha ao carregar o tokenizecard.js'));
|
|
|
+ script.onload = () => resolve(window.PagarmeCheckout);
|
|
|
+ script.src = PAGARME_SCRIPT_URL;
|
|
|
+
|
|
|
+ script.setAttribute('data-pagarmecheckout-app-id', publicKey);
|
|
|
+ script.setAttribute('data-pagarmecheckout-script', 'true');
|
|
|
+
|
|
|
+ document.body.appendChild(script);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ return pagarmeScriptPromise;
|
|
|
};
|
|
|
|
|
|
const onCardNumberChange = () => {
|
|
|
serverErrors.value.card_number = null;
|
|
|
- const raw = form.card_number ? String(form.card_number).replace(/\D/g, '') : '';
|
|
|
+
|
|
|
+ const raw = form.card_number
|
|
|
+ ? String(form.card_number).replace(/\D/g, '')
|
|
|
+ : '';
|
|
|
+
|
|
|
+ if (!raw) {
|
|
|
+ form.brand = null;
|
|
|
+ form.last_four_digits = null;
|
|
|
+
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
if (raw.length >= 6 && !String(form.card_number).includes('*')) {
|
|
|
const brand = detectCardBrand(raw);
|
|
|
- if (brand) form.brand = brand;
|
|
|
- if (raw.length >= 4) form.last_four_digits = raw.slice(-4);
|
|
|
+
|
|
|
+ if (brand) {
|
|
|
+ form.brand = brand;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (raw.length >= 4) {
|
|
|
+ form.last_four_digits = raw.slice(-4);
|
|
|
+ }
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+const tokenizeCard = async () => {
|
|
|
+ const publicKey = getPagarmePublicKey();
|
|
|
+
|
|
|
+ if (!publicKey) {
|
|
|
+ throw new Error('PAGARME_PUBLIC_KEY não configurada');
|
|
|
+ }
|
|
|
+
|
|
|
+ await loadPagarmeScript();
|
|
|
+
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ const success = (data) => {
|
|
|
+ const token = getPagarmeTokenFromResponse(data);
|
|
|
+
|
|
|
+ if (!token) {
|
|
|
+ reject(new Error('Token do cartão não retornado pela Pagar.me.'));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ resolve(token);
|
|
|
+ return false;
|
|
|
+ };
|
|
|
+
|
|
|
+ const fail = (error) => {
|
|
|
+ reject(error);
|
|
|
+ return false;
|
|
|
+ };
|
|
|
+
|
|
|
+ window.PagarmeCheckout.init(success, fail);
|
|
|
+
|
|
|
+ if (!pagarmeFormRef.value) {
|
|
|
+ reject(new Error('Formulário de tokenização não encontrado.'));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ pagarmeFormRef.value.dispatchEvent(new Event('submit', {
|
|
|
+ bubbles: true,
|
|
|
+ cancelable: true,
|
|
|
+ }));
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const validateExpiration = (val) => {
|
|
|
+ if (!val) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!validateCardExpiration(val)) {
|
|
|
+ return t('profile.payments.expired_card');
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+};
|
|
|
+
|
|
|
+//
|
|
|
+
|
|
|
const onOKClick = async () => {
|
|
|
const valid = await formRef.value?.validate();
|
|
|
- if (!valid) return;
|
|
|
+
|
|
|
+ if (!valid) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
if (props.paymentMethod) {
|
|
|
- await submitForm(() => updateClientPaymentMethod(props.paymentMethod.id, { ...form }));
|
|
|
- } else {
|
|
|
- await submitForm(() => createClientPaymentMethod({ ...form }));
|
|
|
+ const payload = {
|
|
|
+ brand: form.brand,
|
|
|
+ card_name: form.card_name,
|
|
|
+ client_id: props.paymentMethod ? props.paymentMethod.client_id : props.clientId,
|
|
|
+ expiration: form.expiration,
|
|
|
+ holder_name: form.holder_name,
|
|
|
+ is_active: form.is_active,
|
|
|
+ last_four_digits: form.last_four_digits,
|
|
|
+ }
|
|
|
+
|
|
|
+ await submitForm(() => updateClientPaymentMethod(props.paymentMethod.id, payload));
|
|
|
+
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ tokenizing.value = true;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const token = await tokenizeCard();
|
|
|
+
|
|
|
+ const payload = {
|
|
|
+ client_id: props.paymentMethod ? props.paymentMethod.client_id : props.clientId,
|
|
|
+ expiration: form.expiration,
|
|
|
+ holder_name: form.holder_name,
|
|
|
+ last_four_digits: form.last_four_digits,
|
|
|
+ token,
|
|
|
+ };
|
|
|
+
|
|
|
+ await submitForm(() => createClientPaymentMethod(payload));
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Erro ao tokenizar o cartão:', error);
|
|
|
+
|
|
|
+ $q.notify({
|
|
|
+ message: error instanceof Error
|
|
|
+ ? error.message
|
|
|
+ : 'Não foi possível tokenizar o cartão.',
|
|
|
+ type: 'negative',
|
|
|
+ });
|
|
|
+ } finally {
|
|
|
+ tokenizing.value = false;
|
|
|
}
|
|
|
};
|
|
|
</script>
|
|
|
@@ -254,6 +613,15 @@ const onOKClick = async () => {
|
|
|
color: white;
|
|
|
}
|
|
|
|
|
|
+.pagarme-token-form {
|
|
|
+ height: 0;
|
|
|
+ opacity: 0;
|
|
|
+ overflow: hidden;
|
|
|
+ pointer-events: none;
|
|
|
+ position: absolute;
|
|
|
+ width: 0;
|
|
|
+}
|
|
|
+
|
|
|
.nfc-icon {
|
|
|
opacity: 0.85;
|
|
|
}
|
|
|
@@ -337,4 +705,4 @@ const onOKClick = async () => {
|
|
|
font-size: 16px;
|
|
|
font-weight: 700;
|
|
|
}
|
|
|
-</style>
|
|
|
+</style>
|