Working flow authorize
This commit is contained in:
@@ -201,6 +201,7 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// --- STEP 2: CRITICAL CALL - SAVE CARD TO LOCAL DATABASE VIA FLASK ---
|
// --- STEP 2: CRITICAL CALL - SAVE CARD TO LOCAL DATABASE VIA FLASK ---
|
||||||
|
let card_id: number;
|
||||||
try {
|
try {
|
||||||
const flaskPath = `${import.meta.env.VITE_BASE_URL}/payment/card/create/${this.customer.id}`;
|
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);
|
console.log("Attempting to save card to local DB via Flask:", flaskPath);
|
||||||
@@ -210,7 +211,8 @@ export default defineComponent({
|
|||||||
// If the primary save fails, stop everything and show an error.
|
// If the primary save fails, stop everything and show an error.
|
||||||
throw new Error(flaskResponse.data.error || "Failed to save card.");
|
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) {
|
} catch (error: any) {
|
||||||
const errorMessage = error.response?.data?.error || "A critical error occurred while saving the card.";
|
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
|
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 {
|
try {
|
||||||
const fastapiPath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/payments/customers/${this.customer.id}/cards`;
|
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);
|
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.");
|
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) {
|
} catch (error: any) {
|
||||||
// If this fails, we just log it for the developers. We DON'T show an error to the user.
|
// 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);
|
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 ---
|
// --- STEP 4: ALWAYS SHOW SUCCESS AND REDIRECT ---
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
<HistoryTabs
|
<HistoryTabs
|
||||||
:deliveries="deliveries"
|
:deliveries="deliveries"
|
||||||
:service-calls="serviceCalls"
|
:service-calls="serviceCalls"
|
||||||
|
:transactions="transactions"
|
||||||
@open-service-modal="openEditModal"
|
@open-service-modal="openEditModal"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -82,6 +83,7 @@
|
|||||||
:cards="credit_cards"
|
:cards="credit_cards"
|
||||||
:count="credit_cards_count"
|
:count="credit_cards_count"
|
||||||
:user_id="customer.user_id"
|
:user_id="customer.user_id"
|
||||||
|
:auth_net_profile_id="customer.auth_net_profile_id"
|
||||||
@edit-card="editCard"
|
@edit-card="editCard"
|
||||||
@remove-card="removeCard"
|
@remove-card="removeCard"
|
||||||
/>
|
/>
|
||||||
@@ -248,10 +250,11 @@ export default defineComponent({
|
|||||||
deliveries: [] as Delivery[],
|
deliveries: [] as Delivery[],
|
||||||
autodeliveries: [] as AutomaticDelivery[],
|
autodeliveries: [] as AutomaticDelivery[],
|
||||||
serviceCalls: [] as ServiceCall[],
|
serviceCalls: [] as ServiceCall[],
|
||||||
|
transactions: [] as any[],
|
||||||
// --- END OF UPDATES ---
|
// --- END OF UPDATES ---
|
||||||
automatic_response: 0,
|
automatic_response: 0,
|
||||||
credit_cards_count: 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_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_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 },
|
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.getServiceCalls(this.customer.id);
|
||||||
this.fetchCustomerParts(this.customer.id);
|
this.fetchCustomerParts(this.customer.id);
|
||||||
this.loadServicePlan(this.customer.id);
|
this.loadServicePlan(this.customer.id);
|
||||||
|
this.getCustomerTransactions(this.customer.id);
|
||||||
|
|
||||||
}).catch((error: any) => {
|
}).catch((error: any) => {
|
||||||
console.error("CRITICAL: Failed to fetch main customer data. Aborting other calls.", error);
|
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);
|
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) {
|
openEditModal(service: ServiceCall) {
|
||||||
this.selectedServiceForEdit = service;
|
this.selectedServiceForEdit = service;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -16,6 +16,9 @@
|
|||||||
<div v-else class="text-success font-semibold">
|
<div v-else class="text-success font-semibold">
|
||||||
{{ count }} card(s) on file.
|
{{ count }} card(s) on file.
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-sm mt-2">
|
||||||
|
<span class="font-semibold">Authorize.net Profile ID:</span> {{ auth_net_profile_id || 'No Authorize.net account' }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4 space-y-4">
|
<div class="mt-4 space-y-4">
|
||||||
@@ -71,6 +74,7 @@ interface Props {
|
|||||||
cards: CreditCard[];
|
cards: CreditCard[];
|
||||||
count: number;
|
count: number;
|
||||||
user_id: number;
|
user_id: number;
|
||||||
|
auth_net_profile_id: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Use the generic defineProps to apply the types
|
// 3. Use the generic defineProps to apply the types
|
||||||
|
|||||||
@@ -12,7 +12,14 @@
|
|||||||
Service History
|
Service History
|
||||||
</a>
|
</a>
|
||||||
<div role="tabpanel" class="tab-content bg-base-100 border-base-300 rounded-box p-4" v-show="activeTab === 'service'">
|
<div role="tabpanel" class="tab-content bg-base-100 border-base-300 rounded-box p-4" v-show="activeTab === 'service'">
|
||||||
<ServiceCallsTable :service-calls="serviceCalls" @open-service-modal="(service) => $emit('openServiceModal', service)" />
|
<ServiceCallsTable :service-calls="serviceCalls" @open-service-modal="(service: ServiceCall) => $emit('openServiceModal', service)" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a role="tab" class="tab [--tab-bg:oklch(var(--b1))] text-base-content" :class="{ 'tab-active': activeTab === 'transactions' }" @click="activeTab = 'transactions'">
|
||||||
|
Transactions
|
||||||
|
</a>
|
||||||
|
<div role="tabpanel" class="tab-content bg-base-100 border-base-300 rounded-box p-4" v-show="activeTab === 'transactions'">
|
||||||
|
<TransactionsTable :transactions="transactions" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -21,6 +28,7 @@
|
|||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import DeliveriesTable from './DeliveriesTable.vue';
|
import DeliveriesTable from './DeliveriesTable.vue';
|
||||||
import ServiceCallsTable from './ServiceCallsTable.vue';
|
import ServiceCallsTable from './ServiceCallsTable.vue';
|
||||||
|
import TransactionsTable from './TransactionsTable.vue';
|
||||||
|
|
||||||
// 1. Define the interfaces for the data this component receives and passes down.
|
// 1. Define the interfaces for the data this component receives and passes down.
|
||||||
// These should match the interfaces in the child components.
|
// These should match the interfaces in the child components.
|
||||||
@@ -37,19 +45,39 @@ interface Delivery {
|
|||||||
interface ServiceCall {
|
interface ServiceCall {
|
||||||
id: number;
|
id: number;
|
||||||
scheduled_date: string;
|
scheduled_date: string;
|
||||||
|
customer_name: string;
|
||||||
|
customer_address: string;
|
||||||
|
customer_town: string;
|
||||||
type_service_call: number;
|
type_service_call: number;
|
||||||
description: string;
|
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
|
// 2. Define the Props interface
|
||||||
interface Props {
|
interface Props {
|
||||||
deliveries: Delivery[];
|
deliveries: Delivery[];
|
||||||
serviceCalls: ServiceCall[];
|
serviceCalls: ServiceCall[];
|
||||||
|
transactions: Transaction[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Use the typed defineProps
|
// 3. Use the typed defineProps
|
||||||
defineProps<Props>();
|
defineProps<Props>();
|
||||||
|
|
||||||
defineEmits(['openServiceModal']);
|
defineEmits<{
|
||||||
|
openServiceModal: [service: ServiceCall];
|
||||||
|
}>();
|
||||||
const activeTab = ref('deliveries');
|
const activeTab = ref('deliveries');
|
||||||
</script>
|
</script>
|
||||||
146
src/pages/customer/profile/profile/TransactionsTable.vue
Normal file
146
src/pages/customer/profile/profile/TransactionsTable.vue
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
<!-- src/pages/customer/profile/profile/TransactionsTable.vue -->
|
||||||
|
<template>
|
||||||
|
<div v-if="!transactions || transactions.length === 0" class="text-center p-10">
|
||||||
|
<p>No transaction history found.</p>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<!-- DESKTOP TABLE -->
|
||||||
|
<div class="overflow-x-auto hidden lg:block">
|
||||||
|
<table class="table w-full">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Transaction Info</th>
|
||||||
|
<th>Amounts & Type</th>
|
||||||
|
<th>Details & Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<template v-for="transaction in transactions" :key="transaction.id">
|
||||||
|
<tr class="hover:bg-blue-600 hover:text-white">
|
||||||
|
<td>
|
||||||
|
<div class="space-y-1">
|
||||||
|
<div><strong>#{{ transaction.id }}</strong></div>
|
||||||
|
<div class="text-xs text-gray-500">{{ formatDate(transaction.created_at) }}</div>
|
||||||
|
<div><span :class="'badge badge-sm ' + getStatusClass(transaction.status)">{{ getStatusText(transaction.status) }}</span></div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="space-y-1">
|
||||||
|
<div class="text-xs">
|
||||||
|
<div><strong>Pre-Auth:</strong> ${{ transaction.preauthorize_amount || '0.00' }}</div>
|
||||||
|
<div><strong>Charge:</strong> ${{ transaction.charge_amount || '0.00' }}</div>
|
||||||
|
</div>
|
||||||
|
<div><strong>Type:</strong> <span :class="getTypeColor(transaction.transaction_type)">{{ transaction.transaction_type === 0 ? 'Charge' : transaction.transaction_type === 1 ? 'Auth' : 'Capture' }}</span></div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div>{{ getSourceText(transaction) }}</div>
|
||||||
|
<div>
|
||||||
|
<router-link v-if="transaction.delivery_id" :to="{ name: 'deliveryOrder', params: { id: transaction.delivery_id } }" class="btn btn-xs btn-info">
|
||||||
|
View Delivery
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Rejection Reason Row (Full Width) -->
|
||||||
|
<tr v-if="transaction.rejection_reason && transaction.rejection_reason.trim()" class="bg-transparent border-t border-gray-300">
|
||||||
|
<td colspan="3" class="px-4 py-3 text-red-800 font-medium">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<svg class="w-5 h-5 mr-2 text-red-500" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path>
|
||||||
|
</svg>
|
||||||
|
<span class="text-red-700">{{ transaction.rejection_reason }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MOBILE CARDS -->
|
||||||
|
<div class="lg:hidden space-y-4">
|
||||||
|
<div v-for="transaction in transactions" :key="transaction.id" class="card bg-base-100 shadow-md">
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<div>
|
||||||
|
<h2 class="card-title text-base">Transaction #{{ transaction.id }}</h2>
|
||||||
|
<p class="text-xs text-gray-400">{{ formatDate(transaction.created_at) }}</p>
|
||||||
|
</div>
|
||||||
|
<div :class="'badge badge-' + getStatusClass(transaction.status)">
|
||||||
|
{{ getStatusText(transaction.status) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-sm mt-2 space-y-1">
|
||||||
|
<p><strong>Transaction ID:</strong> {{ transaction.auth_net_transaction_id || 'N/A' }}</p>
|
||||||
|
<p><strong>Pre-Auth:</strong> ${{ transaction.preauthorize_amount || '0.00' }}</p>
|
||||||
|
<p><strong>Charge:</strong> ${{ transaction.charge_amount || '0.00' }}</p>
|
||||||
|
<p><strong>Type:</strong> <span :class="getTypeColor(transaction.transaction_type)">{{ transaction.transaction_type === 0 ? 'Charge' : transaction.transaction_type === 1 ? 'Auth' : 'Capture' }}</span></p>
|
||||||
|
<p><strong>Source:</strong> {{ getSourceText(transaction) }}</p>
|
||||||
|
<!-- Rejection Reason in Mobile View -->
|
||||||
|
<div v-if="transaction.rejection_reason && transaction.rejection_reason.trim()" class="bg-transparent border border-gray-300 rounded-md p-3 mt-2">
|
||||||
|
<div class="flex items-start">
|
||||||
|
<svg class="w-4 h-4 mr-2 text-red-500 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path>
|
||||||
|
</svg>
|
||||||
|
<span class="text-red-700 text-sm">{{ transaction.rejection_reason }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Action Buttons -->
|
||||||
|
<div class="mt-3">
|
||||||
|
<router-link v-if="transaction.delivery_id" :to="{ name: 'deliveryOrder', params: { id: transaction.delivery_id } }" class="btn btn-xs btn-info btn-block">
|
||||||
|
View Delivery
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
transactions: Transaction[];
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<Props>();
|
||||||
|
|
||||||
|
const getStatusClass = (status: number) => status === 0 ? 'badge-success' : 'badge-error';
|
||||||
|
const getStatusText = (status: number) => status === 0 ? 'Approved' : 'Declined';
|
||||||
|
const getSourceText = (transaction: Transaction) => {
|
||||||
|
if (transaction.delivery_id) {
|
||||||
|
return 'Delivery - ' + transaction.delivery_id;
|
||||||
|
} else if (transaction.service_id) {
|
||||||
|
return 'Service - ' + transaction.service_id;
|
||||||
|
} else {
|
||||||
|
return 'Other';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const formatDate = (dateStr: string) => dateStr.split('T')[0]; // YYYY-MM-DD
|
||||||
|
const 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';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -90,14 +90,45 @@
|
|||||||
<div class="font-bold">When Ordered</div>
|
<div class="font-bold">When Ordered</div>
|
||||||
<div class="opacity-80">{{ deliveryOrder.when_ordered }}</div>
|
<div class="opacity-80">{{ deliveryOrder.when_ordered }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<div class="font-bold">When Delivered</div>
|
|
||||||
<div class="opacity-80">{{ deliveryOrder.when_delivered }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Transaction Summary -->
|
||||||
|
<div v-if="transaction && transaction.auth_net_transaction_id" class="bg-neutral rounded-lg p-5">
|
||||||
|
<h3 class="text-xl font-bold mb-4">Transaction Summary</h3>
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div class="grid grid-cols-2 gap-x-4 gap-y-3 text-sm">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="font-bold">Transaction ID:</span>
|
||||||
|
<span class="font-mono">{{ transaction.auth_net_transaction_id }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="font-bold">Pre-Auth Amount:</span>
|
||||||
|
<span>${{ transaction.preauthorize_amount || '0.00' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="font-bold">Charge Amount:</span>
|
||||||
|
<span>${{ transaction.charge_amount || '0.00' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="font-bold">Type:</span>
|
||||||
|
<span :class="getTypeColor(transaction.transaction_type)">{{ transaction.transaction_type === 0 ? 'Charge' : transaction.transaction_type === 1 ? 'Auth' : 'Capture' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="font-bold">Date:</span>
|
||||||
|
<span>{{ transaction.created_at }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="font-bold">Status:</span>
|
||||||
|
<span :class="transaction.status === 0 ? 'text-success' : 'text-error'">
|
||||||
|
{{ transaction.status === 0 ? 'Approved' : 'Declined' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Financial Summary Card -->
|
<!-- Financial Summary Card -->
|
||||||
<div class="bg-neutral rounded-lg p-5">
|
<div class="bg-neutral rounded-lg p-5">
|
||||||
<h3 class="text-xl font-bold mb-4">Financial Summary</h3>
|
<h3 class="text-xl font-bold mb-4">Financial Summary</h3>
|
||||||
@@ -134,10 +165,12 @@
|
|||||||
<span v-else>No Payment Type Added</span>
|
<span v-else>No Payment Type Added</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="userCardfound && (deliveryOrder.payment_type == 1 || deliveryOrder.payment_type == 2)" class="mt-2 p-3 rounded-lg border bg-accent/20 border-accent">
|
<div v-if="userCardfound && (deliveryOrder.payment_type == 1 || deliveryOrder.payment_type == 2)" class="mt-2 p-3 rounded-lg border bg-accent/20 border-accent">
|
||||||
<!-- --- MODIFICATION --- Safe display of card data -->
|
<!-- --- MODIFICATION --- Full display of card data -->
|
||||||
<div class="font-bold text-sm">{{ userCard.type_of_card }}</div>
|
<div class="font-bold text-sm">{{ userCard.type_of_card }}</div>
|
||||||
<div class="text-sm font-mono tracking-wider">
|
<div class="text-sm font-mono tracking-wider space-y-1">
|
||||||
<p>**** **** **** {{ userCard.last_four }}</p>
|
<p>Name: {{ userCard.name_on_card }}</p>
|
||||||
|
<p>Card: {{ userCard.card_number }}</p>
|
||||||
|
<p>CVV: {{ userCard.security_number }}</p>
|
||||||
<p>Exp: {{ userCard.expiration_month }} / {{ userCard.expiration_year }}</p>
|
<p>Exp: {{ userCard.expiration_month }} / {{ userCard.expiration_year }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -217,6 +250,9 @@ interface UserCard {
|
|||||||
type_of_card: string;
|
type_of_card: string;
|
||||||
expiration_month: number;
|
expiration_month: number;
|
||||||
expiration_year: number;
|
expiration_year: number;
|
||||||
|
name_on_card: string;
|
||||||
|
card_number: string;
|
||||||
|
security_number: string;
|
||||||
}
|
}
|
||||||
interface PreAuthTransaction {
|
interface PreAuthTransaction {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -276,6 +312,7 @@ export default defineComponent({
|
|||||||
price_prime: 0,
|
price_prime: 0,
|
||||||
price_same_day: 0,
|
price_same_day: 0,
|
||||||
},
|
},
|
||||||
|
transaction: null as any,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -297,6 +334,7 @@ export default defineComponent({
|
|||||||
console.log(`[DEBUG] Component Mounted. Fetching data for delivery ID: ${deliveryId}`);
|
console.log(`[DEBUG] Component Mounted. Fetching data for delivery ID: ${deliveryId}`);
|
||||||
this.getOilOrder(deliveryId);
|
this.getOilOrder(deliveryId);
|
||||||
this.getOilPricing();
|
this.getOilPricing();
|
||||||
|
this.getTransaction(deliveryId);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async getOilOrder(delivery_id: any) {
|
async getOilOrder(delivery_id: any) {
|
||||||
@@ -371,6 +409,31 @@ export default defineComponent({
|
|||||||
.then((response: any) => { this.pricing = response.data; })
|
.then((response: any) => { this.pricing = response.data; })
|
||||||
.catch((error: any) => { console.error("[DEBUG] Error fetching oil pricing:", error); });
|
.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() {
|
CreateTransaction() {
|
||||||
const path = `${import.meta.env.VITE_MONEY_URL}/delivery/add/${this.deliveryOrder.id}`;
|
const path = `${import.meta.env.VITE_MONEY_URL}/delivery/add/${this.deliveryOrder.id}`;
|
||||||
axios.post(path, {}, { withCredentials: true, headers: authHeader() })
|
axios.post(path, {}, { withCredentials: true, headers: authHeader() })
|
||||||
@@ -397,18 +460,29 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
if ([1, 2].includes(this.deliveryOrder.payment_type)) {
|
if ([1, 2].includes(this.deliveryOrder.payment_type)) {
|
||||||
const transactionUrl = `${import.meta.env.VITE_AUTHORIZE_URL}/api/transaction/delivery/${this.deliveryOrder.id}`;
|
const transactionUrl = `${import.meta.env.VITE_AUTHORIZE_URL}/api/transaction/delivery/${this.deliveryOrder.id}`;
|
||||||
const transactionResponse = await axios.get(transactionUrl, { withCredentials: true, headers: authHeader() });
|
try {
|
||||||
const preAuthTx = transactionResponse.data as PreAuthTransaction;
|
const transactionResponse = await axios.get(transactionUrl, { withCredentials: true, headers: authHeader() });
|
||||||
if (preAuthTx && preAuthTx.transaction_type === 1 && preAuthTx.status === 0) {
|
const preAuthTx = transactionResponse.data as PreAuthTransaction;
|
||||||
const capturePayload = {
|
if (preAuthTx && preAuthTx.transaction_type === 1 && preAuthTx.status === 0) {
|
||||||
auth_net_transaction_id: preAuthTx.auth_net_transaction_id,
|
const capturePayload = {
|
||||||
charge_amount: this.finalChargeAmount.toFixed(2),
|
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() });
|
const capturePath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/capture/`;
|
||||||
notify({ title: "Payment Captured", text: `Successfully charged $${capturePayload.charge_amount}.`, type: "success" });
|
await axios.post(capturePath, capturePayload, { withCredentials: true, headers: authHeader() });
|
||||||
} else {
|
notify({ title: "Payment Captured", text: `Successfully charged $${capturePayload.charge_amount}.`, type: "success" });
|
||||||
notify({ title: "Warning", text: "No valid pre-authorization found to capture. Please charge manually.", type: "warn" });
|
} 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();
|
this.CreateTransaction();
|
||||||
|
|||||||
@@ -154,15 +154,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<span>Pre-Auth Amount:</span>
|
<span>Pre-Auth Amount:</span>
|
||||||
<span>${{ transaction.pre_auth_amount || '0.00' }}</span>
|
<span>${{ transaction.preauthorize_amount || '0.00' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<span>Charge Amount:</span>
|
<span>Charge Amount:</span>
|
||||||
<span>${{ transaction.charge_amount || '0.00' }}</span>
|
<span>${{ transaction.charge_amount || '0.00' }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span>Type:</span>
|
||||||
|
<span :class="getTypeColor(transaction.transaction_type)">{{ transaction.transaction_type === 0 ? 'Charge' : transaction.transaction_type === 1 ? 'Auth' : 'Capture' }}</span>
|
||||||
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<span>Date:</span>
|
<span>Date:</span>
|
||||||
<span>{{ format_date(transaction.transaction_date) }}</span>
|
<span>{{ format_date(transaction.created_at) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<span>Status:</span>
|
<span>Status:</span>
|
||||||
@@ -290,6 +294,9 @@
|
|||||||
<router-link :to="{ name: 'deliveryEdit', params: { id: deliveryOrder.id } }">
|
<router-link :to="{ name: 'deliveryEdit', params: { id: deliveryOrder.id } }">
|
||||||
<button class="btn btn-secondary btn-sm">Edit Delivery</button>
|
<button class="btn btn-secondary btn-sm">Edit Delivery</button>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
<router-link :to="{ name: 'finalizeTicket', params: { id: deliveryOrder.id } }" v-if="deliveryOrder.delivery_status != 10">
|
||||||
|
<button class="btn btn-accent btn-sm">Finalize</button>
|
||||||
|
</router-link>
|
||||||
<router-link :to="{ name: 'Ticket', params: { id: deliveryOrder.id } }">
|
<router-link :to="{ name: 'Ticket', params: { id: deliveryOrder.id } }">
|
||||||
<button class="btn btn-success btn-sm">Print Ticket</button>
|
<button class="btn btn-success btn-sm">Print Ticket</button>
|
||||||
</router-link>
|
</router-link>
|
||||||
@@ -305,6 +312,18 @@
|
|||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from 'vue'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import authHeader from '../../services/auth.header'
|
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 Header from '../../layouts/headers/headerauth.vue'
|
||||||
import SideBar from '../../layouts/sidebar/sidebar.vue'
|
import SideBar from '../../layouts/sidebar/sidebar.vue'
|
||||||
import Footer from '../../layouts/footers/footer.vue'
|
import Footer from '../../layouts/footers/footer.vue'
|
||||||
@@ -335,7 +354,7 @@ export default defineComponent({
|
|||||||
total_amount_after_discount: 0,
|
total_amount_after_discount: 0,
|
||||||
deliveryNotesDriver: [],
|
deliveryNotesDriver: [],
|
||||||
userCardfound: false,
|
userCardfound: false,
|
||||||
userCard: null as any,
|
userCard: {} as UserCard,
|
||||||
customer: {
|
customer: {
|
||||||
account_number: '',
|
account_number: '',
|
||||||
id: 0,
|
id: 0,
|
||||||
@@ -438,6 +457,14 @@ export default defineComponent({
|
|||||||
return moment(String(value)).format('LLLL')
|
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) {
|
deleteCall(delivery_id: any) {
|
||||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
|
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
|
||||||
axios({
|
axios({
|
||||||
@@ -527,13 +554,13 @@ export default defineComponent({
|
|||||||
this.userCard = response.data;
|
this.userCard = response.data;
|
||||||
this.userCardfound = true;
|
this.userCardfound = true;
|
||||||
} else {
|
} else {
|
||||||
this.userCard = null;
|
this.userCard = {} as UserCard;
|
||||||
this.userCardfound = false;
|
this.userCardfound = false;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
console.error("Error fetching payment card:", error);
|
console.error("Error fetching payment card:", error);
|
||||||
this.userCard = null;
|
this.userCard = {} as UserCard;
|
||||||
this.userCardfound = false;
|
this.userCardfound = false;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -689,8 +716,14 @@ getOilOrder(delivery_id: any) {
|
|||||||
this.transaction = response.data;
|
this.transaction = response.data;
|
||||||
})
|
})
|
||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
console.error("Error fetching transaction:", error);
|
// Handle 404 gracefully - delivery doesn't have transaction data in authorize system
|
||||||
this.transaction = null;
|
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;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -505,7 +505,7 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Use the correct endpoint for charging a saved card
|
// 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('=== DEBUG: Charge payload ===');
|
||||||
console.log('Calling endpoint:', chargePath);
|
console.log('Calling endpoint:', chargePath);
|
||||||
|
|||||||
Reference in New Issue
Block a user