diff --git a/src/pages/card/addcard.vue b/src/pages/card/addcard.vue index 39eeb06..8c34552 100755 --- a/src/pages/card/addcard.vue +++ b/src/pages/card/addcard.vue @@ -201,16 +201,18 @@ export default defineComponent({ }; // --- STEP 2: CRITICAL CALL - SAVE CARD TO LOCAL DATABASE VIA FLASK --- + let card_id: number; try { const flaskPath = `${import.meta.env.VITE_BASE_URL}/payment/card/create/${this.customer.id}`; console.log("Attempting to save card to local DB via Flask:", flaskPath); const flaskResponse = await axios.post(flaskPath, flaskPayload, { withCredentials: true, headers: authHeader() }); - + if (!flaskResponse.data.ok) { // If the primary save fails, stop everything and show an error. throw new Error(flaskResponse.data.error || "Failed to save card."); } - console.log("Card successfully saved to local database via Flask."); + card_id = flaskResponse.data.card_id; + console.log("Card successfully saved to local database via Flask with ID:", card_id); } catch (error: any) { const errorMessage = error.response?.data?.error || "A critical error occurred while saving the card."; @@ -219,15 +221,24 @@ export default defineComponent({ return; // End the function here } - // --- STEP 3: BEST-EFFORT CALL - TOKENIZE CARD VIA FASTAPI --- + // --- STEP 3: BEST-EFFORT CALL - TOKENIZE CARD VIA FASTAPI AND UPDATE LOCAL CARD --- try { const fastapiPath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/payments/customers/${this.customer.id}/cards`; console.log("Attempting to tokenize card with Authorize.Net via FastAPI:", fastapiPath); - await axios.post(fastapiPath, fastapiPayload, { withCredentials: true, headers: authHeader() }); + const fastapiResponse = await axios.post(fastapiPath, fastapiPayload, { withCredentials: true, headers: authHeader() }); console.log("Card successfully tokenized with Authorize.Net via FastAPI."); + + // --- STEP 4: UPDATE LOCAL CARD WITH PAYMENT_PROFILE_ID --- + const payment_profile_id = fastapiResponse.data.payment_profile_id; + console.log("Updating local card with payment_profile_id:", payment_profile_id); + const updatePath = `${import.meta.env.VITE_BASE_URL}/payment/card/update_payment_profile/${card_id}`; + await axios.put(updatePath, { auth_net_payment_profile_id: payment_profile_id }, { withCredentials: true, headers: authHeader() }); + console.log("Card successfully updated with payment_profile_id."); + } 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: Tokenization with Authorize.Net failed, but the card was saved locally.", error.response?.data || error.message); + // Card is saved but without payment_profile_id, which is ok as nullable. } // --- STEP 4: ALWAYS SHOW SUCCESS AND REDIRECT --- @@ -238,4 +249,4 @@ export default defineComponent({ }, }, }); - \ No newline at end of file + diff --git a/src/pages/customer/profile/profile.vue b/src/pages/customer/profile/profile.vue index ce20823..0ea7a76 100755 --- a/src/pages/customer/profile/profile.vue +++ b/src/pages/customer/profile/profile.vue @@ -59,9 +59,10 @@ - @@ -78,12 +79,13 @@ - @@ -248,10 +250,11 @@ export default defineComponent({ deliveries: [] as Delivery[], autodeliveries: [] as AutomaticDelivery[], serviceCalls: [] as ServiceCall[], + transactions: [] as any[], // --- END OF UPDATES --- automatic_response: 0, credit_cards_count: 0, - customer: { id: 0, user_id: 0, customer_first_name: '', customer_last_name: '', customer_town: '', customer_address: '', customer_state: 0, customer_zip: '', customer_apt: '', customer_home_type: 0, customer_phone_number: '', customer_latitude: 0, customer_longitude: 0, correct_address: true, account_number: '' }, + customer: { id: 0, user_id: 0, customer_first_name: '', customer_last_name: '', customer_town: '', customer_address: '', customer_state: 0, customer_zip: '', customer_apt: '', customer_home_type: 0, customer_phone_number: '', customer_latitude: 0, customer_longitude: 0, correct_address: true, account_number: '', auth_net_profile_id: null }, customer_description: { id: 0, customer_id: 0, account_number: '', company_id: '', fill_location: 0, description: '' }, customer_tank: { id: 0, last_tank_inspection: null, tank_status: false, outside_or_inside: false, tank_size: 0 }, customer_stats: { id: 0, customer_id: 0, total_calls: 0, service_calls_total: 0, service_calls_total_spent: 0, service_calls_total_profit: 0, oil_deliveries: 0, oil_total_gallons: 0, oil_total_spent: 0, oil_total_profit: 0 }, @@ -323,6 +326,7 @@ export default defineComponent({ this.getServiceCalls(this.customer.id); this.fetchCustomerParts(this.customer.id); this.loadServicePlan(this.customer.id); + this.getCustomerTransactions(this.customer.id); }).catch((error: any) => { console.error("CRITICAL: Failed to fetch main customer data. Aborting other calls.", error); @@ -576,6 +580,19 @@ onSubmitSocial(commentText: string) { console.error("Failed to get customer service calls:", error); }); }, + getCustomerTransactions(customerId: number) { + let path = `${import.meta.env.VITE_BASE_URL}/payment/transactions/customer/${customerId}/1`; + axios({ + method: 'get', + url: path, + headers: authHeader(), + }).then((response: any) => { + this.transactions = response.data; + }).catch((error: any) => { + console.error("Failed to get customer transactions:", error); + this.transactions = []; + }); + }, openEditModal(service: ServiceCall) { this.selectedServiceForEdit = service; }, diff --git a/src/pages/customer/profile/profile/CreditCards.vue b/src/pages/customer/profile/profile/CreditCards.vue index afe6e23..406bdbf 100644 --- a/src/pages/customer/profile/profile/CreditCards.vue +++ b/src/pages/customer/profile/profile/CreditCards.vue @@ -16,6 +16,9 @@
{{ count }} card(s) on file.
+
+ Authorize.net Profile ID: {{ auth_net_profile_id || 'No Authorize.net account' }} +
@@ -71,6 +74,7 @@ interface Props { cards: CreditCard[]; count: number; user_id: number; + auth_net_profile_id: string | null; } // 3. Use the generic defineProps to apply the types diff --git a/src/pages/customer/profile/profile/HistoryTabs.vue b/src/pages/customer/profile/profile/HistoryTabs.vue index 4eed15c..5aeae20 100644 --- a/src/pages/customer/profile/profile/HistoryTabs.vue +++ b/src/pages/customer/profile/profile/HistoryTabs.vue @@ -12,7 +12,14 @@ Service History
- + +
+ + + Transactions + +
+
@@ -21,6 +28,7 @@ import { ref } from 'vue'; import DeliveriesTable from './DeliveriesTable.vue'; import ServiceCallsTable from './ServiceCallsTable.vue'; +import TransactionsTable from './TransactionsTable.vue'; // 1. Define the interfaces for the data this component receives and passes down. // These should match the interfaces in the child components. @@ -37,19 +45,39 @@ interface Delivery { interface ServiceCall { id: number; scheduled_date: string; + customer_name: string; + customer_address: string; + customer_town: string; type_service_call: number; description: string; } +interface Transaction { + id: number; + preauthorize_amount: number | null; + 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; +} + // 2. Define the Props interface interface Props { deliveries: Delivery[]; serviceCalls: ServiceCall[]; + transactions: Transaction[]; } // 3. Use the typed defineProps defineProps(); -defineEmits(['openServiceModal']); +defineEmits<{ + openServiceModal: [service: ServiceCall]; +}>(); const activeTab = ref('deliveries'); - \ No newline at end of file + diff --git a/src/pages/customer/profile/profile/TransactionsTable.vue b/src/pages/customer/profile/profile/TransactionsTable.vue new file mode 100644 index 0000000..71509d2 --- /dev/null +++ b/src/pages/customer/profile/profile/TransactionsTable.vue @@ -0,0 +1,146 @@ + + + + diff --git a/src/pages/delivery/update_tickets/finalize_ticket.vue b/src/pages/delivery/update_tickets/finalize_ticket.vue index 8aea0d5..764cc1f 100755 --- a/src/pages/delivery/update_tickets/finalize_ticket.vue +++ b/src/pages/delivery/update_tickets/finalize_ticket.vue @@ -90,14 +90,45 @@
When Ordered
{{ deliveryOrder.when_ordered }}
-
-
When Delivered
-
{{ deliveryOrder.when_delivered }}
-
+ +
+

Transaction Summary

+
+
+
+ Transaction ID: + {{ transaction.auth_net_transaction_id }} +
+
+ Pre-Auth Amount: + ${{ transaction.preauthorize_amount || '0.00' }} +
+
+ Charge Amount: + ${{ transaction.charge_amount || '0.00' }} +
+
+ Type: + {{ transaction.transaction_type === 0 ? 'Charge' : transaction.transaction_type === 1 ? 'Auth' : 'Capture' }} +
+
+ Date: + {{ transaction.created_at }} +
+
+ Status: + + {{ transaction.status === 0 ? 'Approved' : 'Declined' }} + +
+
+
+
+

Financial Summary

@@ -134,10 +165,12 @@ No Payment Type Added
- +
{{ userCard.type_of_card }}
-
-

**** **** **** {{ userCard.last_four }}

+
+

Name: {{ userCard.name_on_card }}

+

Card: {{ userCard.card_number }}

+

CVV: {{ userCard.security_number }}

Exp: {{ userCard.expiration_month }} / {{ userCard.expiration_year }}

@@ -217,6 +250,9 @@ interface UserCard { type_of_card: string; expiration_month: number; expiration_year: number; + name_on_card: string; + card_number: string; + security_number: string; } interface PreAuthTransaction { id: number; @@ -276,6 +312,7 @@ export default defineComponent({ price_prime: 0, price_same_day: 0, }, + transaction: null as any, } }, computed: { @@ -297,6 +334,7 @@ export default defineComponent({ console.log(`[DEBUG] Component Mounted. Fetching data for delivery ID: ${deliveryId}`); this.getOilOrder(deliveryId); this.getOilPricing(); + this.getTransaction(deliveryId); }, methods: { async getOilOrder(delivery_id: any) { @@ -371,6 +409,31 @@ export default defineComponent({ .then((response: any) => { this.pricing = response.data; }) .catch((error: any) => { console.error("[DEBUG] Error fetching oil pricing:", error); }); }, + getTransaction(delivery_id: any) { + const path = `${import.meta.env.VITE_AUTHORIZE_URL}/api/transaction/delivery/${delivery_id}`; + axios.get(path, { withCredentials: true, headers: authHeader() }) + .then((response: any) => { + this.transaction = response.data; + }) + .catch((error: any) => { + // Handle 404 gracefully - delivery doesn't have transaction data in authorize system + if (error.response && error.response.status === 404) { + console.log(`No transaction found for delivery ${delivery_id} in authorize system`); + this.transaction = null; + } else { + console.error("Error fetching transaction:", error); + this.transaction = null; + } + }); + }, + getTypeColor(transactionType: number) { + switch (transactionType) { + case 1: return 'text-blue-600'; + case 0: return 'text-orange-600'; + case 2: return 'text-green-600'; + default: return 'text-gray-600'; + } + }, CreateTransaction() { const path = `${import.meta.env.VITE_MONEY_URL}/delivery/add/${this.deliveryOrder.id}`; axios.post(path, {}, { withCredentials: true, headers: authHeader() }) @@ -397,18 +460,29 @@ export default defineComponent({ } if ([1, 2].includes(this.deliveryOrder.payment_type)) { const transactionUrl = `${import.meta.env.VITE_AUTHORIZE_URL}/api/transaction/delivery/${this.deliveryOrder.id}`; - const transactionResponse = await axios.get(transactionUrl, { withCredentials: true, headers: authHeader() }); - const preAuthTx = transactionResponse.data as PreAuthTransaction; - if (preAuthTx && preAuthTx.transaction_type === 1 && preAuthTx.status === 0) { - const capturePayload = { - auth_net_transaction_id: preAuthTx.auth_net_transaction_id, - charge_amount: this.finalChargeAmount.toFixed(2), - }; - const capturePath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/capture/`; - await axios.post(capturePath, capturePayload, { withCredentials: true, headers: authHeader() }); - notify({ title: "Payment Captured", text: `Successfully charged $${capturePayload.charge_amount}.`, type: "success" }); - } else { - notify({ title: "Warning", text: "No valid pre-authorization found to capture. Please charge manually.", type: "warn" }); + try { + const transactionResponse = await axios.get(transactionUrl, { withCredentials: true, headers: authHeader() }); + const preAuthTx = transactionResponse.data as PreAuthTransaction; + if (preAuthTx && preAuthTx.transaction_type === 1 && preAuthTx.status === 0) { + const capturePayload = { + auth_net_transaction_id: preAuthTx.auth_net_transaction_id, + charge_amount: this.finalChargeAmount.toFixed(2), + }; + const capturePath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/capture/`; + await axios.post(capturePath, capturePayload, { withCredentials: true, headers: authHeader() }); + notify({ title: "Payment Captured", text: `Successfully charged $${capturePayload.charge_amount}.`, type: "success" }); + } else { + notify({ title: "Warning", text: "No valid pre-authorization found to capture. Please charge manually.", type: "warn" }); + } + } catch (error: any) { + if (error.response?.status === 404 && error.response?.data?.detail?.includes("No pre-authorization transaction found")) { + notify({ title: "Redirecting", text: "No pre-authorization found. Redirecting to customer profile to update payment method.", type: "info" }); + this.$router.push({ name: "customerProfile", params: { id: this.customer.id } }); + return; + } else { + notify({ title: "Error", text: "Failed to check transaction status.", type: "error" }); + throw error; + } } } this.CreateTransaction(); @@ -423,4 +497,4 @@ export default defineComponent({ }, }, }); - \ No newline at end of file + diff --git a/src/pages/delivery/view.vue b/src/pages/delivery/view.vue index c33b3f8..36d0972 100755 --- a/src/pages/delivery/view.vue +++ b/src/pages/delivery/view.vue @@ -154,15 +154,19 @@
Pre-Auth Amount: - ${{ transaction.pre_auth_amount || '0.00' }} + ${{ transaction.preauthorize_amount || '0.00' }}
Charge Amount: ${{ transaction.charge_amount || '0.00' }}
+
+ Type: + {{ transaction.transaction_type === 0 ? 'Charge' : transaction.transaction_type === 1 ? 'Auth' : 'Capture' }} +
Date: - {{ format_date(transaction.transaction_date) }} + {{ format_date(transaction.created_at) }}
Status: @@ -290,6 +294,9 @@ + + + @@ -305,6 +312,18 @@ import { defineComponent } from 'vue' import axios from 'axios' import authHeader from '../../services/auth.header' + +interface UserCard { + id: number; + last_four: string; + type_of_card: string; + expiration_month: number; + expiration_year: number; + name_on_card: string; + card_number: string; + security_number: string; + main_card?: boolean; +} import Header from '../../layouts/headers/headerauth.vue' import SideBar from '../../layouts/sidebar/sidebar.vue' import Footer from '../../layouts/footers/footer.vue' @@ -335,7 +354,7 @@ export default defineComponent({ total_amount_after_discount: 0, deliveryNotesDriver: [], userCardfound: false, - userCard: null as any, + userCard: {} as UserCard, customer: { account_number: '', id: 0, @@ -438,6 +457,14 @@ export default defineComponent({ return moment(String(value)).format('LLLL') } }, + getTypeColor(transactionType: number) { + switch (transactionType) { + case 1: return 'text-blue-600'; + case 0: return 'text-orange-600'; + case 2: return 'text-green-600'; + default: return 'text-gray-600'; + } + }, deleteCall(delivery_id: any) { let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id; axios({ @@ -527,13 +554,13 @@ export default defineComponent({ this.userCard = response.data; this.userCardfound = true; } else { - this.userCard = null; + this.userCard = {} as UserCard; this.userCardfound = false; } }) .catch((error: any) => { console.error("Error fetching payment card:", error); - this.userCard = null; + this.userCard = {} as UserCard; this.userCardfound = false; }); } else { @@ -689,8 +716,14 @@ getOilOrder(delivery_id: any) { this.transaction = response.data; }) .catch((error: any) => { - console.error("Error fetching transaction:", error); - this.transaction = null; + // Handle 404 gracefully - delivery doesn't have transaction data in authorize system + if (error.response && error.response.status === 404) { + console.log(`No transaction found for delivery ${delivery_id} in authorize system`); + this.transaction = null; + } else { + console.error("Error fetching transaction:", error); + this.transaction = null; + } }); }, }, diff --git a/src/pages/pay/oil/authorize_preauthcharge.vue b/src/pages/pay/oil/authorize_preauthcharge.vue index d40c4e8..e947f23 100644 --- a/src/pages/pay/oil/authorize_preauthcharge.vue +++ b/src/pages/pay/oil/authorize_preauthcharge.vue @@ -505,7 +505,7 @@ export default defineComponent({ }; // Use the correct endpoint for charging a saved card - const chargePath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/charge/saved-card/${this.customer.id}`; + const chargePath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/payments/charge/saved-card/${this.customer.id}`; console.log('=== DEBUG: Charge payload ==='); console.log('Calling endpoint:', chargePath);