116 lines
3.6 KiB
JavaScript
116 lines
3.6 KiB
JavaScript
require('dotenv').config();
|
|
const path = require('path');
|
|
const express = require('express');
|
|
const { savePayment, listPayments, countPayments } = require('./db');
|
|
const { createPaymentLink } = require('./payment-api');
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 3000;
|
|
const PRODUCTION = process.env.PRODUCTION;
|
|
|
|
app.use(express.json());
|
|
app.use(express.static(path.join(__dirname, 'public')));
|
|
|
|
function toNumber(v) {
|
|
const n = parseFloat(v);
|
|
return Number.isFinite(n) ? n : 0;
|
|
}
|
|
|
|
const ALLOWED_CURRENCIES = ['USD', 'AUD'];
|
|
|
|
// --- Create payment link ---------------------------------------------------
|
|
app.post('/api/payments', async (req, res) => {
|
|
const { customerName, email, phone, address, currency, items } = req.body || {};
|
|
|
|
// All customer fields are required.
|
|
const customerFields = { customerName, email, phone, address };
|
|
const CUSTOMER_LABELS = {
|
|
customerName: 'Customer name',
|
|
email: 'Email',
|
|
phone: 'Phone number',
|
|
address: 'Address',
|
|
};
|
|
const missingCustomer = Object.keys(CUSTOMER_LABELS).filter(
|
|
(k) => !customerFields[k] || !String(customerFields[k]).trim()
|
|
);
|
|
if (missingCustomer.length) {
|
|
return res.status(400).json({
|
|
error: `Missing required field(s): ${missingCustomer.map((k) => CUSTOMER_LABELS[k]).join(', ')}.`,
|
|
});
|
|
}
|
|
|
|
const cleanItems = Array.isArray(items) ? items : [];
|
|
if (cleanItems.length === 0) {
|
|
return res.status(400).json({ error: 'At least one item is required.' });
|
|
}
|
|
const cleanCurrency = ALLOWED_CURRENCIES.includes(currency) ? currency : 'AUD';
|
|
|
|
const normItems = cleanItems.map((it) => ({
|
|
sku: (it.sku || '').trim(),
|
|
name: (it.name || '').trim(),
|
|
price: toNumber(it.price),
|
|
quantity: toNumber(it.quantity),
|
|
condition: (it.condition || '').trim(),
|
|
}));
|
|
|
|
// Each item requires SKU, a positive price, a positive quantity and a condition.
|
|
for (let i = 0; i < normItems.length; i++) {
|
|
const it = normItems[i];
|
|
const missing = [];
|
|
if (!it.sku) missing.push('SKU');
|
|
if (!(it.price > 0)) missing.push('price');
|
|
if (!(it.quantity > 0)) missing.push('quantity');
|
|
if (!it.condition) missing.push('condition');
|
|
if (missing.length) {
|
|
return res
|
|
.status(400)
|
|
.json({ error: `Item ${i + 1} is missing required field(s): ${missing.join(', ')}.` });
|
|
}
|
|
}
|
|
|
|
const total = normItems.reduce((sum, it) => sum + it.price * it.quantity, 0);
|
|
|
|
// Sequential quote number based on how many payments already exist:
|
|
// first ever -> QT000001, when history already has 9 -> QT000010.
|
|
const quoteNumber = `QT${String(countPayments() + 1).padStart(6, '0')}`;
|
|
|
|
try {
|
|
const dataPayment = await createPaymentLink({
|
|
customerName,
|
|
email,
|
|
phone,
|
|
address,
|
|
currency: cleanCurrency,
|
|
quoteNumber,
|
|
items: normItems,
|
|
total,
|
|
});
|
|
|
|
const saved = savePayment({
|
|
customer_name: customerName,
|
|
email: email || '',
|
|
phone: phone || '',
|
|
address: address || '',
|
|
items_json: JSON.stringify(normItems),
|
|
total,
|
|
currency: cleanCurrency,
|
|
payment_link: dataPayment.link,
|
|
status: 'created',
|
|
});
|
|
|
|
res.json({ ok: true, payment: saved, link: dataPayment.link, raw: dataPayment.raw });
|
|
} catch (err) {
|
|
console.error('createPaymentLink error:', err);
|
|
res.status(502).json({ error: err.message || 'Failed to create payment link.' });
|
|
}
|
|
});
|
|
|
|
// --- History ---------------------------------------------------------------
|
|
app.get('/api/payments', (_req, res) => {
|
|
res.json({ ok: true, payments: listPayments() });
|
|
});
|
|
|
|
app.listen(PORT, () => {
|
|
console.log(`Quick Payment server running: http://localhost:${PORT}`);
|
|
});
|