stripe
This commit is contained in:
@@ -184,14 +184,22 @@ function renderInvoiceRow(invoice) {
|
||||
const balance = parseFloat(invoice.balance) ?? ((parseFloat(invoice.total) || 0) - amountPaid);
|
||||
const partial = isPartiallyPaid(invoice);
|
||||
|
||||
const stripeIndicator = invoice.stripe_payment_link_id
|
||||
? (invoice.stripe_payment_status === 'paid'
|
||||
? ' <span title="Stripe payment received" class="text-purple-500 text-xs">💳✓</span>'
|
||||
: ' <span title="Stripe payment link active" class="text-purple-400 text-xs">💳</span>')
|
||||
: '';
|
||||
|
||||
const invNumDisplay = invoice.invoice_number
|
||||
? invoice.invoice_number
|
||||
? invoice.invoice_number + stripeIndicator
|
||||
: `<span class="text-gray-400 italic text-xs">Draft</span>`;
|
||||
|
||||
// Status Badge (left side, next to invoice number)
|
||||
let statusBadge = '';
|
||||
if (paid && invoice.payment_status === 'Deposited') {
|
||||
statusBadge = `<span class="inline-block px-2 py-0.5 text-xs font-semibold rounded-full bg-blue-100 text-blue-800" title="Deposited ${formatDate(invoice.paid_date)}">Deposited</span>`;
|
||||
} else if (paid && invoice.payment_status === 'Stripe') {
|
||||
statusBadge = `<span class="inline-block px-2 py-0.5 text-xs font-semibold rounded-full bg-purple-100 text-purple-800" title="Stripe payment ${formatDate(invoice.paid_date)}">Stripe</span>`;
|
||||
} else if (paid) {
|
||||
statusBadge = `<span class="inline-block px-2 py-0.5 text-xs font-semibold rounded-full bg-green-100 text-green-800" title="Paid ${formatDate(invoice.paid_date)}">Paid</span>`;
|
||||
} else if (partial) {
|
||||
@@ -258,20 +266,29 @@ function renderInvoiceRow(invoice) {
|
||||
|
||||
// Mark Sent button (right side) — only when open, not paid/partial
|
||||
let sendBtn = '';
|
||||
// if (hasQbo && !paid && !overdue && invoice.email_status !== 'sent') {
|
||||
// sendBtn = `<button onclick="window.invoiceView.setEmailStatus(${invoice.id}, 'sent')" class="text-indigo-600 hover:text-indigo-800 text-xs font-medium" title="Mark as sent to customer">📤 Mark Sent</button>`;
|
||||
// }
|
||||
if (hasQbo && !paid && !overdue) {
|
||||
sendBtn = `
|
||||
<button onclick="window.invoiceView.setEmailStatus(${invoice.id}, 'sent')" class="text-gray-600 hover:text-gray-900 text-xs font-medium mr-4" title="Nur Status ändern">
|
||||
✔️ Mark Sent
|
||||
</button>
|
||||
<button onclick="window.emailModal.open(${invoice.id})" class="text-indigo-600 hover:text-indigo-800 text-xs font-medium" title="E-Mail via SES versenden">
|
||||
📧 Send Email
|
||||
</button>
|
||||
`; }
|
||||
if (hasQbo && !paid && !overdue && invoice.email_status !== 'sent') {
|
||||
sendBtn = `<button onclick="window.invoiceView.setEmailStatus(${invoice.id}, 'sent')" class="text-indigo-600 hover:text-indigo-800 text-xs font-medium" title="Mark as sent to customer">📤 Mark Sent</button>`;
|
||||
}
|
||||
// if (hasQbo && !paid && !overdue) {
|
||||
// sendBtn = `
|
||||
// <button onclick="window.invoiceView.setEmailStatus(${invoice.id}, 'sent')" class="text-gray-600 hover:text-gray-900 text-xs font-medium mr-4" title="Nur Status ändern">
|
||||
// ✔️ Mark Sent
|
||||
// </button>
|
||||
// <button onclick="window.emailModal.open(${invoice.id})" class="text-indigo-600 hover:text-indigo-800 text-xs font-medium" title="E-Mail via SES versenden">
|
||||
// 📧 Send Email
|
||||
// </button>
|
||||
// `; }
|
||||
|
||||
const delBtn = `<button onclick="window.invoiceView.remove(${invoice.id})" class="text-red-600 hover:text-red-900">Del</button>`;
|
||||
|
||||
const stripeEmailBtn = (!paid && hasQbo)
|
||||
? `<button onclick="window.emailModal.open(${invoice.id})" title="Email with Stripe Payment Link" class="px-2 py-1 bg-purple-100 text-purple-700 rounded hover:bg-purple-200 text-xs font-semibold">💳 Pay Link</button>`
|
||||
: '';
|
||||
|
||||
const stripeCheckBtn = (invoice.stripe_payment_link_id && !paid)
|
||||
? `<button onclick="window.invoiceView.checkStripePayment(${invoice.id})" title="Check Stripe Payment Status" class="px-2 py-1 bg-purple-50 text-purple-600 rounded hover:bg-purple-100 text-xs font-semibold">🔍 Check</button>`
|
||||
: '';
|
||||
|
||||
const rowClass = paid ? (invoice.payment_status === 'Deposited' ? 'bg-blue-50/50' : 'bg-green-50/50') : partial ? 'bg-yellow-50/30' : overdue ? 'bg-red-50/50' : '';
|
||||
|
||||
return `
|
||||
@@ -283,7 +300,7 @@ function renderInvoiceRow(invoice) {
|
||||
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500">${invoice.terms}</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900 font-semibold">${amountDisplay}</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap text-sm font-medium space-x-1">
|
||||
${editBtn} ${qboBtn} ${pdfBtn} ${htmlBtn} ${sendBtn} ${paidBtn} ${delBtn}
|
||||
${editBtn} ${qboBtn} ${pdfBtn} ${htmlBtn} ${sendBtn} ${stripeEmailBtn} ${stripeCheckBtn} ${paidBtn} ${delBtn}
|
||||
</td>
|
||||
</tr>`;
|
||||
}
|
||||
@@ -518,12 +535,55 @@ export async function remove(id) {
|
||||
try { const r = await fetch(`/api/invoices/${id}`, { method: 'DELETE' }); if (r.ok) loadInvoices(); }
|
||||
catch (e) { console.error(e); }
|
||||
}
|
||||
async function checkStripePayment(invoiceId) {
|
||||
if (typeof showSpinner === 'function') showSpinner('Checking Stripe payment status...');
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/invoices/${invoiceId}/check-payment`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
if (result.paid) {
|
||||
let msg = `✅ ${result.message}`;
|
||||
if (result.qbo) {
|
||||
if (result.qbo.error) {
|
||||
msg += `\n\n⚠️ QBO booking failed: ${result.qbo.error}`;
|
||||
} else {
|
||||
msg += `\n\n📗 QBO Payment recorded (ID: ${result.qbo.paymentId})`;
|
||||
if (result.qbo.feeBooked) msg += '\n📗 Processing fee booked';
|
||||
}
|
||||
}
|
||||
if (!result.fullyPaid) {
|
||||
msg += '\n\n⚠️ Partial payment — invoice is not fully paid yet.';
|
||||
}
|
||||
alert(msg);
|
||||
loadInvoices(); // Refresh the list
|
||||
} else if (result.alreadyProcessed) {
|
||||
alert('ℹ️ Stripe payment was already recorded for this invoice.');
|
||||
} else if (result.status === 'processing') {
|
||||
alert('⏳ ACH payment is processing (typically 3-5 business days).\n\nCheck again later.');
|
||||
} else {
|
||||
alert('ℹ️ No payment received yet.\n\nThe customer may not have clicked the payment link, or the payment is still being processed.');
|
||||
}
|
||||
} else {
|
||||
alert(`❌ Error: ${result.error}`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Check Stripe payment error:', e);
|
||||
alert('Network error checking payment status.');
|
||||
} finally {
|
||||
if (typeof hideSpinner === 'function') hideSpinner();
|
||||
}
|
||||
}
|
||||
// ============================================================
|
||||
// Expose
|
||||
// ============================================================
|
||||
|
||||
window.invoiceView = {
|
||||
viewPDF, viewHTML, syncFromQBO, resetQbo, markPaid, setEmailStatus, edit, remove,
|
||||
loadInvoices, renderInvoiceView, setStatus
|
||||
loadInvoices, renderInvoiceView, setStatus, checkStripePayment
|
||||
};
|
||||
Reference in New Issue
Block a user