diff --git a/.env b/.env deleted file mode 100755 index e69de29..0000000 diff --git a/.env.prod b/.env.prod deleted file mode 100644 index e69de29..0000000 diff --git a/Dockerfile.dev b/Dockerfile.dev index a7a3010..d4536dc 100755 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -5,6 +5,8 @@ ENV VITE_AUTO_URL="http://localhost:9514" ENV VITE_MONEY_URL="http://localhost:9513" ENV VITE_AUTHORIZE_URL="http://localhost:9516" ENV VITE_VOIPMS_URL="http://localhost:9517" + + ENV VITE_VOIPMS_TOKEN="my_secret_token" ENV VITE_COMPANY_ID='1' diff --git a/Dockerfile.prod b/Dockerfile.prod index c908471..bf6bf82 100644 --- a/Dockerfile.prod +++ b/Dockerfile.prod @@ -1,12 +1,18 @@ # --- STAGE 1: Build the Vue application --- FROM node:20.11.1 AS builder + + # Set build-time environment variables for your API URLs ENV VITE_BASE_URL="https://apioil.edwineames.com" ENV VITE_AUTO_URL="https://apiauto.edwineames.com" ENV VITE_MONEY_URL="https://apimoney.edwineames.com" ENV VITE_AUTHORIZE_URL="https://apicard.edwineames.com" -ENV VITE_VOIPMS_URL="http://apiphone.edwineames.com:9516" +ENV VITE_VOIPMS_URL="https://apiphone.edwineames.com" + +ENV VITE_VOIPMS_TOKEN="my_secret_token" + + WORKDIR /app COPY package.json ./ diff --git a/src/layouts/headers/headerauth.vue b/src/layouts/headers/headerauth.vue index f2b8849..b7f6fa2 100755 --- a/src/layouts/headers/headerauth.vue +++ b/src/layouts/headers/headerauth.vue @@ -316,6 +316,7 @@ export default defineComponent({ method: 'get', url: path, headers: authHeader(), + withCredentials: true, }) .then((response: any) => { if (response.data.current_phone) { @@ -331,9 +332,7 @@ export default defineComponent({ return axios({ method: 'post', url: path, - headers: { - 'Authorization': `Bearer ${import.meta.env.VITE_VOIPMS_TOKEN}`, - }, + withCredentials: true, headers: authHeader() }) .then((response: any) => { this.routeResponse = response.data; @@ -380,9 +379,7 @@ export default defineComponent({ axios({ method: 'get', url: path, - headers: { - 'Authorization': `Bearer ${import.meta.env.VITE_VOIPMS_TOKEN}`, - }, + withCredentials: true, headers: authHeader() }) .then((response: any) => { this.testResponse = response.data; diff --git a/src/pages/automatic/view.vue b/src/pages/automatic/view.vue index 3b7a04f..0a14857 100644 --- a/src/pages/automatic/view.vue +++ b/src/pages/automatic/view.vue @@ -388,7 +388,7 @@ export default defineComponent({ this.userCardfound = false; } }) - .catch((error: any) => { + .catch((_error: any) => { this.userCard = {} as UserCard; this.userCardfound = false; }); @@ -409,7 +409,7 @@ export default defineComponent({ } this.getAutoDelivery(autoTicketId); }) - .catch((error: any) => { + .catch((_error: any) => { notify({ title: "Error", text: "Could not get automatic ticket", diff --git a/src/pages/card/editcard.vue b/src/pages/card/editcard.vue index 6edc4cc..022d0e4 100755 --- a/src/pages/card/editcard.vue +++ b/src/pages/card/editcard.vue @@ -124,7 +124,10 @@
- +
@@ -140,6 +143,7 @@ import authHeader from '../../services/auth.header' import Footer from '../../layouts/footers/footer.vue' import useValidate from "@vuelidate/core"; import { minLength, required } from "@vuelidate/validators"; +import { notify } from "@kyvg/vue3-notification"; export default defineComponent({ name: 'EditCard', @@ -152,6 +156,9 @@ export default defineComponent({ user: null as any, customer: {} as any, card: {} as any, // To store original card details for display + isLoading: false, + isLoadingAuthorize: true, + authorizeCheck: { profile_exists: false, has_payment_methods: false, missing_components: [] as string[], valid_for_charging: false }, // --- REFACTORED: Simplified, flat form object --- CardForm: { name_on_card: '', @@ -194,7 +201,33 @@ export default defineComponent({ getCustomer(userid: any) { const path = `${import.meta.env.VITE_BASE_URL}/customer/${userid}`; axios.get(path, { headers: authHeader() }) - .then((response: any) => { this.customer = response.data; }); + .then((response: any) => { + this.customer = response.data; + this.checkAuthorizeAccount(); + }); + }, + async checkAuthorizeAccount() { + if (!this.customer.id) return; + + this.isLoadingAuthorize = true; + + try { + const path = `${import.meta.env.VITE_AUTHORIZE_URL}/user/check-authorize-account/${this.customer.id}`; + const response = await axios.get(path, { headers: authHeader() }); + this.authorizeCheck = response.data; + } catch (error) { + console.error("Failed to check authorize account:", error); + notify({ title: "Error", text: "Could not check payment account status.", type: "error" }); + // Set default error state + this.authorizeCheck = { + profile_exists: false, + has_payment_methods: false, + missing_components: ['api_error'], + valid_for_charging: false + }; + } finally { + this.isLoadingAuthorize = false; + } }, getCard(card_id: any) { const path = `${import.meta.env.VITE_BASE_URL}/payment/card/${card_id}`; @@ -241,14 +274,82 @@ editCard(payload: any) { }) .catch(console.log("error")); }, -onSubmit() { - this.v$.$validate(); - if (!this.v$.$error) { - this.editCard(this.CardForm); // This is correct, it sends the form object. - } else { - console.log("Form validation failed."); - } -}, +async onSubmit() { + this.v$.$validate(); + if (this.v$.$error) { + notify({ title: "Validation Error", text: "Please fill out all required fields.", type: "error" }); + return; + } + + this.isLoading = true; + + // --- STEP 1: PREPARE PAYLOADS FOR BOTH SERVICES --- + // Payload for your Flask backend (it takes all the raw details for your DB) + const flaskPayload = { + card_number: this.CardForm.card_number, + expiration_month: this.CardForm.expiration_month, + expiration_year: this.CardForm.expiration_year, + type_of_card: this.CardForm.type_of_card, + security_number: this.CardForm.security_number, + main_card: this.CardForm.main_card, + zip_code: this.CardForm.zip_code, + name_on_card: this.CardForm.name_on_card, + }; + + // Payload for your FastAPI backend (it only needs the essentials for Authorize.Net) + const fastapiPayload = { + card_number: this.CardForm.card_number.replace(/\s/g, ''), + expiration_date: `${this.CardForm.expiration_year}-${this.CardForm.expiration_month}`, + cvv: this.CardForm.security_number, + main_card: this.CardForm.main_card, + }; + + // --- STEP 2: CRITICAL CALL - UPDATE CARD TO LOCAL DATABASE VIA FLASK --- + try { + const flaskPath = `${import.meta.env.VITE_BASE_URL}/payment/card/edit/${this.$route.params.id}`; + console.log("Attempting to update card to local DB via Flask:", flaskPath); + const flaskResponse = await axios.put(flaskPath, flaskPayload, { withCredentials: true, headers: authHeader() }); + + if (!flaskResponse.data.ok) { + throw new Error(flaskResponse.data.error || "Failed to update card."); + } + console.log("Card successfully updated to local database via Flask with ID:", this.$route.params.id); + + } catch (error: any) { + const errorMessage = error.response?.data?.error || "A critical error occurred while updating the card."; + notify({ title: "Error", text: errorMessage, type: "error" }); + this.isLoading = false; + return; + } + + // --- CHECK IF AUTHORIZE.NET PROFILE EXISTS --- + if (!this.authorizeCheck.profile_exists) { + console.log("Skipping Authorize.Net tokenization as no profile exists for customer."); + // Show success and redirect (card updated locally without tokenization) + notify({ title: "Success", text: "Credit card has been updated.", type: "success" }); + this.isLoading = false; + this.$router.push({ name: "customerProfile", params: { id: this.customer.id } }); + return; + } + + // --- STEP 3: BEST-EFFORT CALL - TOKENIZE/UPDATE CARD VIA AUTHORIZE + try { + const fastapiPath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/payments/customers/${this.customer.id}/cards/${this.$route.params.id}`; + console.log("Attempting to update card tokenization with Authorize.Net via FastAPI:", fastapiPath); + await axios.put(fastapiPath, fastapiPayload, { withCredentials: true, headers: authHeader() }); + console.log("Card successfully updated with Authorize.Net via FastAPI."); + + } catch (error: any) { + // If this fails, we just log it for the developers. We DON'T show an error to the user. + console.warn("NON-CRITICAL-ERROR: Authorize.Net update failed, but the card was updated locally.", error.response?.data || error.message); + // Card is updated but Authorize.Net profile may not be current, which is ok. + } + + // --- STEP 4: ALWAYS SHOW SUCCESS AND REDIRECT --- + notify({ title: "Success", text: "Credit card has been updated.", type: "success" }); + this.isLoading = false; + this.$router.push({ name: "customerProfile", params: { id: this.customer.id } }); + }, }, }); - \ No newline at end of file + diff --git a/src/pages/customer/profile/profile/HistoryTabs.vue b/src/pages/customer/profile/profile/HistoryTabs.vue index 5aeae20..c9902d7 100644 --- a/src/pages/customer/profile/profile/HistoryTabs.vue +++ b/src/pages/customer/profile/profile/HistoryTabs.vue @@ -58,12 +58,12 @@ interface Transaction { charge_amount: number | null; transaction_type: number; status: number; - customer_name: string; created_at: string; auth_net_transaction_id: string | null; rejection_reason: string | null; delivery_id: number | null; service_id: number | null; + auto_id: number | null; } // 2. Define the Props interface diff --git a/src/pages/delivery/edit.vue b/src/pages/delivery/edit.vue index ebf7632..4d9756f 100755 --- a/src/pages/delivery/edit.vue +++ b/src/pages/delivery/edit.vue @@ -248,7 +248,7 @@ import Header from '../../layouts/headers/headerauth.vue' import SideBar from '../../layouts/sidebar/sidebar.vue' import Footer from '../../layouts/footers/footer.vue' import useValidate from "@vuelidate/core"; -import { required, minLength, requiredIf } from "@vuelidate/validators"; +import { required, requiredIf } from "@vuelidate/validators"; import { notify } from "@kyvg/vue3-notification"; // Interfaces to describe the shape of your data diff --git a/src/pages/delivery/update_tickets/finalize_ticket.vue b/src/pages/delivery/update_tickets/finalize_ticket.vue index ca7944f..c0be817 100755 --- a/src/pages/delivery/update_tickets/finalize_ticket.vue +++ b/src/pages/delivery/update_tickets/finalize_ticket.vue @@ -270,13 +270,7 @@ interface UserCard { card_number: string; security_number: string; } -interface PreAuthTransaction { - id: number; - transaction_type: number; - status: number; - auth_net_transaction_id: string; - preauthorize_amount: number; -} + export default defineComponent({ name: 'finalizeTicket', diff --git a/src/pages/delivery/view.vue b/src/pages/delivery/view.vue index bfe8624..585ac46 100755 --- a/src/pages/delivery/view.vue +++ b/src/pages/delivery/view.vue @@ -552,7 +552,7 @@ export default defineComponent({ .then((response: any) => { this.pricing = response.data; }) - .catch((error: any) => { + .catch((_error: any) => { notify({ title: "Error", text: "Could not get oil pricing", @@ -570,7 +570,7 @@ export default defineComponent({ .then((response: any) => { this.customer = response.data; }) - .catch((error: any) => { + .catch((_error: any) => { notify({ title: "Error", text: "Could not find customer", diff --git a/src/pages/pay/oil/authorize_preauthcharge.vue b/src/pages/pay/oil/authorize_preauthcharge.vue index 2fcd52f..94833ef 100644 --- a/src/pages/pay/oil/authorize_preauthcharge.vue +++ b/src/pages/pay/oil/authorize_preauthcharge.vue @@ -69,10 +69,20 @@
{{ selectedCard.type_of_card }}
Primary
-
-
{{ selectedCard.card_number }}
-
{{ selectedCard.name_on_card }}
-
Expires: {{ selectedCard.expiration_month }}/{{ selectedCard.expiration_year }}
+
+

{{ selectedCard.card_number }}

+

{{ selectedCard.name_on_card }}

+

+ Exp: + 0{{ selectedCard.expiration_month }} / {{ selectedCard.expiration_year }} +

+

CVV: {{ selectedCard.security_number }}

+
+ +
+ +
+ Edit
diff --git a/src/pages/pay/oil/pay_oil.vue b/src/pages/pay/oil/pay_oil.vue index 82e7e0a..551cacc 100755 --- a/src/pages/pay/oil/pay_oil.vue +++ b/src/pages/pay/oil/pay_oil.vue @@ -155,10 +155,26 @@
-
-
{{ card.type_of_card }} ending in {{ card.last_four_digits }}
-
{{ card.name_on_card }}
-
Expires: {{ card.expiration_month }}/{{ card.expiration_year }}
+
+
+
{{ card.type_of_card }}
+
Primary
+
+
+

{{ card.card_number }}

+

{{ card.name_on_card }}

+

+ Exp: + 0{{ card.expiration_month }} / {{ card.expiration_year }} +

+

CVV: {{ card.security_number }}

+
+ +
+ +
+ Edit +
diff --git a/src/pages/pay/service/capture_authorize.vue b/src/pages/pay/service/capture_authorize.vue index 0e04266..93cdcc9 100644 --- a/src/pages/pay/service/capture_authorize.vue +++ b/src/pages/pay/service/capture_authorize.vue @@ -333,18 +333,7 @@ const getTransactionType = (type: number): string => { // --- Methods --- -/** - * Toggles the selection of a credit card. - * If the clicked card is already selected, it deselects it. - * Otherwise, it selects the new card. - */ -const selectCard = (card: UserCard) => { - if (selectedCard.value?.id === card.id) { - selectedCard.value = null; // Deselect if clicked again - } else { - selectedCard.value = card; // Select the new card - } -}; + /** * Dedicated function to update the service cost using the new dedicated API @@ -408,7 +397,7 @@ const chargeService = async () => { if (response.data?.status === 0) { // Update service cost to the charged amount using the new dedicated function - const costUpdateSuccess = await updateServiceCost(service.value!.id, chargeAmount.value); + await updateServiceCost(service.value!.id, chargeAmount.value); // Update payment status to 3 for success await axios.put( @@ -446,7 +435,7 @@ const chargeService = async () => { if (captureResponse.data?.status === 0) { // Update service cost to the captured amount using the new dedicated function - const costUpdateSuccess = await updateServiceCost(service.value!.id, chargeAmount.value); + await updateServiceCost(service.value!.id, chargeAmount.value); // Update service payment status await axios.put(