import { autoUpdate, useFloating } from "@floating-ui/vue"; import { toTypedSchema } from "@vee-validate/zod"; import { storeToRefs } from "pinia"; import { useForm } from "vee-validate"; import { computed, reactive, ref, watch } from "vue"; import { apiBack } from "src/boot/axios"; import { quasarNotify } from "src/functions/quasarNotify"; import { useFormStore } from "src/stores/forms"; import { checkoutSchema } from "src/utils/zod/schemas"; import { useLocalStorage } from "./useLocalStorage"; export function useCheckoutForm() { const { addItem, getItem, removeItem } = useLocalStorage(); //! Elements ref const postalCodeRef = ref(null); const postalCodeTooltip = ref(null); const phoneInputRef = ref(null); const phoneSenderInputRef = ref(null); const redsysFormRef = ref(null); //! Form const formStore = useFormStore(); const { availability: availabilityForm } = storeToRefs(formStore); const { handleCheckoutData } = formStore; const availability = availabilityForm.value.dateExpired || getItem("availability"); const phoneData = ref({ country: { name: "", iso2: "", dialCode: "", priority: 0, areaCodes: null, }, countryCallingCode: "", nationalNumber: "", number: "", countryCode: "", valid: false, formatted: "", }); const phoneSenderData = ref({ country: { name: "", iso2: "", dialCode: "", priority: 0, areaCodes: null, }, countryCallingCode: "", nationalNumber: "", number: "", countryCode: "", valid: false, formatted: "", }); const provinceOptions = ref([ { code: "es", name: "España" }, // { code: "fr", name: "Francia" }, // { code: "pt", name: "Portugal" }, ]); const { meta, errors, handleSubmit, defineField, resetForm } = useForm({ validationSchema: toTypedSchema(checkoutSchema), initialValues: { paymentMethod: "", terms: false, postalCode: availabilityForm.value.postalCode || availability.postalCode, phone: "", senderPhone: "", }, }); const [name, nameAttrs] = defineField("name"); const [surname, surnameAttrs] = defineField("surname"); const [address, addressAttrs] = defineField("address"); const [postalCode, postalCodeAttrs] = defineField("postalCode"); const [phone, phoneAttrs] = defineField("phone", { validateOnModelUpdate: false, }); const [city, cityAttrs] = defineField("city"); const [province, provinceAttrs] = defineField("province"); const [senderName, senderNameAttrs] = defineField("senderName"); const [senderCifNif, senderCifNifAttrs] = defineField("senderCifNif"); const [senderEmail, senderEmailAttrs] = defineField("senderEmail"); const [senderPhone, senderPhoneAttrs] = defineField("senderPhone", { validateOnModelUpdate: false, }); const [senderNotes, senderNotesAttrs] = defineField("senderNotes"); const [paymentMethod, paymentMethodAttrs] = defineField("paymentMethod"); const [terms, termsAttrs] = defineField("terms"); //! Tooltip hook const { floatingStyles } = useFloating(postalCodeRef, postalCodeTooltip, { placement: "top-start", whileElementsMounted: autoUpdate, }); const isHidden = ref(true); const hideTooltip = () => { isHidden.value = true; }; const showTooltip = () => { isHidden.value = false; }; // TODO hacer el await de las provincias /** * const provinceOptions = getProvinces(); * onBeforeMount(async () => {}); * */ watch( [ () => phoneInputRef.value?.modelValue, () => phoneSenderInputRef.value?.modelValue, ], ([a, b]) => { phoneData.value = phoneInputRef.value.phoneObject; phoneSenderData.value = phoneSenderInputRef.value.phoneObject; } ); const stepActive = reactive({ data: 1 }); const stepList = reactive({ data: [ { value: 1, name: "Paso 1", description: "Datos de facturación", active: true, }, { value: 2, name: "Paso 2", description: "Confirmación", active: false, }, { value: 3, name: "Paso 3", description: "Pago", active: false, }, ], }); const stepsFormated = computed(() => { return stepList["data"].map((step) => { if (step.value === stepActive["data"]) { return { ...step, active: true }; } return step; }); }); const isError = ref(false); const onError = () => { isError.value = true; }; const handleClickStep = (value) => { stepActive["data"] = value; }; const checkoutBlock = ref(true); const cart = getItem("cart"); const totalPrice = computed(() => { return cart?.reduce((acc, { price }) => { if (price) { //const priceWithoutLetter = price?.replace("€", ""); return +price + acc; } }, 0); }); const redsysData = ref({ Ds_MerchantParameters: "", Ds_Signature: "", Ds_SignatureVersion: "", orderId: null, }); const isLoadingSubmit = ref(false); const isErrorSubmit = ref(false); /** * Handles the fetching of the payment method. * * @param {string} type - The type of payment method paypal or redsys are valid!. * @param {Object} values - The values needed for the payment method. * @returns {Promise} - A promise that resolves when the payment method is fetched. */ const handleFetchPaymentMethod = async ({ type, values }) => { try { const productsId = cart.map((item) => item.id); const cartItensData = cart.map(({ id, message, ...rest }) => ({ id, message: message || "", })); const deliveryData = { customerData: { custumerName: `${values.name} ${values.surname}`, email: values.senderEmail, custumerPhone: phoneData.value.number, }, itemData: cartItensData, deliveryData: { dated: availability.dateExpired, deliveryName: values.senderName, address: values.address, postalCode: availability.postalCode, deliveryPhone: phoneSenderData.value.number, deliveryMessage: values.senderNotes, }, }; const customerName = `${values.name} ${values.surname}`; addItem("costumer", deliveryData); const productData = { products: productsId, dateExpired: availability.dateExpired, postalCode: postalCode.value, customer: { customerData: { customerName, email: values.senderEmail, customerPhone: phoneData.value.number, message: values.senderNotes, deliveryName: values.senderName || customerName, address: values.address, deliveryPhone: phoneSenderData.value.number || phoneData.value.number, }, }, type: values.paymentMethod, }; addItem("payment", values.paymentMethod); const typeObj = { paypal: async () => { const { data: { data }, } = await apiBack.post("payment", productData); location.href = data.link; }, redsys: async () => { const { data: { data }, } = await apiBack.post("payment", productData); redsysData.value = await data; document.querySelector("input[name='Ds_SignatureVersion']").value = redsysData.value.Ds_SignatureVersion; document.querySelector("input[name='Ds_MerchantParameters']").value = redsysData.value.Ds_MerchantParameters; document.querySelector("input[name='Ds_Signature']").value = redsysData.value.Ds_Signature; redsysFormRef.value.click(); }, default: () => { console.error( `FATAL ERROR ::: Payment method not found, TYPE: ${type}` ); }, }; const paymentMethod = typeObj[type] || typeObj["default"]; await paymentMethod(); // removeItem("cart"); // removeItem("availability"); } catch (error) { console.error(`FATAL ERROR ::: ${error}`); quasarNotify({ type: "erro", message: "Se produjo un error al procesar tu compra, inténtalo de nuevo.", }); isErrorSubmit.value = true; } finally { isLoadingSubmit.value = false; handleCheckoutData(values); resetForm(); } }; const onSuccess = async (values, actions) => { const INVALID_NUMBER = "Número no válido introducido, por favor, compruébelo e inténtelo de nuevo"; if (!phoneData.value.valid) { actions.setFieldError("phone", INVALID_NUMBER); return; } if (values.senderPhone.length > 0 && !phoneSenderData.value.valid) { actions.setFieldError("senderPhone", INVALID_NUMBER); return; } isLoadingSubmit.value = true; stepsFormated.value[1].active = true; await handleFetchPaymentMethod({ type: values.paymentMethod, values, }); }; const onSubmit = handleSubmit(onSuccess); return { handleClickStep, provinceOptions, stepsFormated, stepList, checkoutBlock, cart, totalPrice, isError, redsysData, phoneInputRef, phoneSenderInputRef, redsysFormRef, phone: { phoneData, phoneSenderData }, onError, tooltip: { postalCode: { postalCodeRef, postalCodeTooltip, floatingStyles, isHidden, hideTooltip, showTooltip, }, }, formState: { meta, errors, onSubmit, isLoadingSubmit, }, fields: { name, nameAttrs, surname, surnameAttrs, address, addressAttrs, postalCode, postalCodeAttrs, phone, phoneAttrs, city, cityAttrs, province, provinceAttrs, senderName, senderNameAttrs, senderCifNif, senderCifNifAttrs, senderEmail, senderEmailAttrs, senderPhone, senderPhoneAttrs, senderNotes, senderNotesAttrs, paymentMethod, paymentMethodAttrs, terms, termsAttrs, }, }; }