env param & single source of truth
This commit is contained in:
@@ -14,6 +14,7 @@ const { exportInvoiceToQbo, syncInvoiceToQbo } = require('../services/qbo-servic
|
||||
const { getOAuthClient, getQboBaseUrl, makeQboApiCall } = require('../config/qbo');
|
||||
const { sendInvoiceEmail } = require('../services/email-service');
|
||||
const { createPaymentLink, checkPaymentStatus, deactivatePaymentLink } = require('../services/stripe-service');
|
||||
const { recordStripePaymentInQbo } = require('../services/qbo-service');
|
||||
|
||||
function calculateNextRecurringDate(invoiceDate, interval) {
|
||||
const d = new Date(invoiceDate);
|
||||
@@ -1163,18 +1164,11 @@ router.post('/:id/check-payment', async (req, res) => {
|
||||
await deactivatePaymentLink(invoice.stripe_payment_link_id);
|
||||
|
||||
// 4. QBO: Record Payment + Expense (if QBO-linked)
|
||||
let qboResult = null;
|
||||
if (invoice.qbo_id && invoice.customer_qbo_id) {
|
||||
try {
|
||||
qboResult = await recordStripePaymentInQbo(
|
||||
invoice, amountReceived, methodLabel, stripeFee,
|
||||
result.details.paymentIntentId || ''
|
||||
);
|
||||
} catch (qboErr) {
|
||||
console.error(`⚠️ QBO booking failed for Invoice #${invoice.invoice_number}:`, qboErr.message);
|
||||
qboResult = { error: qboErr.message };
|
||||
}
|
||||
}
|
||||
qboResult = await recordStripePaymentInQbo(
|
||||
invoice, amountReceived, methodLabel, stripeFee,
|
||||
result.details.paymentIntentId || ''
|
||||
// kein source-Parameter — default ist 'manual'
|
||||
);
|
||||
|
||||
await dbClient.query('COMMIT');
|
||||
|
||||
@@ -1198,89 +1192,7 @@ router.post('/:id/check-payment', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Record Stripe payment in QBO: Payment on Invoice + Expense for Stripe Fee.
|
||||
*/
|
||||
async function recordStripePaymentInQbo(invoice, amount, methodLabel, stripeFee, reference) {
|
||||
const oauthClient = getOAuthClient();
|
||||
const companyId = oauthClient.getToken().realmId;
|
||||
const baseUrl = getQboBaseUrl();
|
||||
|
||||
// --- 1. Create QBO Payment ---
|
||||
const paymentPayload = {
|
||||
CustomerRef: { value: invoice.customer_qbo_id },
|
||||
TotalAmt: amount,
|
||||
TxnDate: new Date().toISOString().split('T')[0],
|
||||
PaymentRefNum: reference ? reference.substring(0, 21) : 'Stripe',
|
||||
PrivateNote: `Stripe ${methodLabel} — processed via Payment Link`,
|
||||
Line: [{
|
||||
Amount: amount,
|
||||
LinkedTxn: [{
|
||||
TxnId: invoice.qbo_id,
|
||||
TxnType: 'Invoice'
|
||||
}]
|
||||
}],
|
||||
// Deposit to Undeposited Funds (Stripe will payout to bank later)
|
||||
DepositToAccountRef: { value: '221' } // Undeposited Funds
|
||||
};
|
||||
|
||||
console.log(`📤 QBO: Recording Stripe payment $${amount.toFixed(2)} for Invoice #${invoice.invoice_number}...`);
|
||||
|
||||
const paymentRes = await makeQboApiCall({
|
||||
url: `${baseUrl}/v3/company/${companyId}/payment`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(paymentPayload)
|
||||
});
|
||||
|
||||
const paymentData = paymentRes.getJson ? paymentRes.getJson() : paymentRes.json;
|
||||
if (paymentData.Fault) {
|
||||
const errMsg = paymentData.Fault.Error?.map(e => `${e.Message}: ${e.Detail}`).join('; ');
|
||||
throw new Error('QBO Payment failed: ' + errMsg);
|
||||
}
|
||||
|
||||
console.log(`✅ QBO Payment created: ID ${paymentData.Payment?.Id}`);
|
||||
|
||||
// --- 2. Create QBO Expense for Stripe Fee ---
|
||||
if (stripeFee > 0) {
|
||||
const expensePayload = {
|
||||
AccountRef: { value: '244', name: 'PlainsCapital Bank' }, // Checking
|
||||
TxnDate: new Date().toISOString().split('T')[0],
|
||||
PaymentType: 'Check',
|
||||
PrivateNote: `Stripe processing fee for Invoice #${invoice.invoice_number} (${methodLabel})`,
|
||||
Line: [{
|
||||
DetailType: 'AccountBasedExpenseLineDetail',
|
||||
Amount: stripeFee,
|
||||
AccountBasedExpenseLineDetail: {
|
||||
AccountRef: { value: '1150040001', name: 'Payment Processing Fees' }
|
||||
},
|
||||
Description: `Stripe ${methodLabel} fee — Invoice #${invoice.invoice_number}`
|
||||
}]
|
||||
};
|
||||
|
||||
console.log(`📤 QBO: Booking Stripe fee $${stripeFee.toFixed(2)}...`);
|
||||
|
||||
const expenseRes = await makeQboApiCall({
|
||||
url: `${baseUrl}/v3/company/${companyId}/purchase`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(expensePayload)
|
||||
});
|
||||
|
||||
const expenseData = expenseRes.getJson ? expenseRes.getJson() : expenseRes.json;
|
||||
if (expenseData.Fault) {
|
||||
console.error('⚠️ QBO Expense booking failed:', JSON.stringify(expenseData.Fault));
|
||||
// Don't throw — payment is still valid even if fee booking fails
|
||||
} else {
|
||||
console.log(`✅ QBO Expense created: ID ${expenseData.Purchase?.Id}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
paymentId: paymentData.Payment?.Id,
|
||||
feeBooked: stripeFee > 0
|
||||
};
|
||||
}
|
||||
// PATCH update sent dates only
|
||||
router.patch('/:id/sent-dates', async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
Reference in New Issue
Block a user