Create billing-service/index.js
Browse files- billing-service/index.js +70 -0
billing-service/index.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const express = require('express');
|
| 2 |
+
const bodyParser = require('body-parser');
|
| 3 |
+
const { Pool } = require('pg');
|
| 4 |
+
const Redis = require('ioredis');
|
| 5 |
+
const fetch = require('node-fetch');
|
| 6 |
+
|
| 7 |
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL || 'postgresql://localhost/integral' });
|
| 8 |
+
const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379');
|
| 9 |
+
|
| 10 |
+
const app = express();
|
| 11 |
+
app.use(bodyParser.json());
|
| 12 |
+
|
| 13 |
+
redis.subscribe('payouts:created', () => console.log('listening for payouts'));
|
| 14 |
+
|
| 15 |
+
redis.on('message', async (channel, msg) => {
|
| 16 |
+
if (channel !== 'payouts:created') return;
|
| 17 |
+
const { payoutId } = JSON.parse(msg);
|
| 18 |
+
console.log('handle payout', payoutId);
|
| 19 |
+
// naive: call settle endpoint
|
| 20 |
+
try {
|
| 21 |
+
await settlePayout(payoutId);
|
| 22 |
+
} catch (err) {
|
| 23 |
+
console.error(err);
|
| 24 |
+
}
|
| 25 |
+
});
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
async function settlePayout(payoutId) {
|
| 29 |
+
// fetch payout
|
| 30 |
+
const r = await pool.query('SELECT * FROM payouts WHERE id=$1 FOR UPDATE', [payoutId]);
|
| 31 |
+
if (r.rowCount === 0) throw new Error('payout not found');
|
| 32 |
+
const payout = r.rows[0];
|
| 33 |
+
if (payout.status !== 'created') return;
|
| 34 |
+
|
| 35 |
+
// mark queued
|
| 36 |
+
await pool.query('UPDATE payouts SET status=$1 WHERE id=$2', ['processing', payoutId]);
|
| 37 |
+
|
| 38 |
+
// Example: call external TMS / Accounting system / Payment Gateway
|
| 39 |
+
// This is a stub — replace with real API call (e.g. TMS settlement API, or bank transfer).
|
| 40 |
+
const remote = await fakePaymentGateway(payout);
|
| 41 |
+
|
| 42 |
+
if (remote.success) {
|
| 43 |
+
const txRef = remote.txRef;
|
| 44 |
+
await pool.query('UPDATE payouts SET status=$1, tx_ref=$2, settled_at=now() WHERE id=$3',
|
| 45 |
+
['settled', txRef, payoutId]);
|
| 46 |
+
await redis.publish('payouts:settled', JSON.stringify({ payoutId, txRef }));
|
| 47 |
+
} else {
|
| 48 |
+
await pool.query('UPDATE payouts SET status=$1 WHERE id=$2', ['failed', payoutId]);
|
| 49 |
+
await redis.publish('payouts:failed', JSON.stringify({ payoutId }));
|
| 50 |
+
}
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
async function fakePaymentGateway(payout) {
|
| 54 |
+
// fake delay
|
| 55 |
+
await new Promise(r => setTimeout(r, 600));
|
| 56 |
+
return { success: true, txRef: `TX-${Date.now()}-${Math.floor(Math.random()*1000)}` };
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
app.post('/settle/:payoutId', async (req, res) => {
|
| 60 |
+
try {
|
| 61 |
+
const { payoutId } = req.params;
|
| 62 |
+
await settlePayout(payoutId);
|
| 63 |
+
res.json({ success: true });
|
| 64 |
+
} catch (err) {
|
| 65 |
+
console.error(err);
|
| 66 |
+
res.status(500).json({ success: false, error: err.message });
|
| 67 |
+
}
|
| 68 |
+
});
|
| 69 |
+
|
| 70 |
+
app.listen(process.env.PORT || 3010, () => console.log('billing-service listening 3010'));
|