1// Xero Backend Plugin: Sync invoices and customers via Redis events
2const Redis = require('ioredis');
3const pool = require('../services/db');
4const redisConfig = require('../config/redis');
5const redis = new Redis(redisConfig);
7// Query integrations table for Xero config for a tenant
12async function getXeroConfigForTenant(tenantId) {
13 const result = await pool.query(
14 `SELECT config FROM integrations WHERE tenant_id = $1 AND integration_type = 'xero' AND is_active = true LIMIT 1`,
17 return result.rows.length ? result.rows[0].config : null;
20const { XeroClient } = require('xero-node');
26function createXeroClient(config) {
27 return new XeroClient({
28 clientId: config.client_id,
29 clientSecret: config.client_secret,
30 redirectUris: ['http://localhost'], // adjust as needed
31 scopes: 'openid profile email accounting.transactions accounting.contacts'.split(' '),
40async function syncInvoiceToXero(invoice, config) {
41 const xero = createXeroClient(config);
42 // You must have a valid access token and tenantId in config
43 await xero.setTokenSet({ access_token: config.access_token, id_token: '', expires_at: 0 });
44 xero.tenantIds = [config.xero_tenant_id];
46 // Map your invoice to Xero format
49 Contact: { ContactID: invoice.customer_id }, // map to Xero contact
50 LineItems: invoice.items.map(item => ({
51 Description: item.description,
52 Quantity: item.quantity,
53 UnitAmount: item.unit_price,
54 AccountCode: '200', // adjust as needed
56 Date: invoice.issued_date,
57 DueDate: invoice.due_date,
59 LineAmountTypes: 'Exclusive',
63 const result = await xero.accountingApi.createInvoices(config.xero_tenant_id, { Invoices: [xeroInvoice] });
64 console.log('[Xero] Invoice synced:', result.body.Invoices[0].InvoiceID);
66 console.error('[Xero] Invoice sync error:', err);
75async function syncCustomerToXero(customer, config) {
76 const xero = createXeroClient(config);
77 await xero.setTokenSet({ access_token: config.access_token, id_token: '', expires_at: 0 });
78 xero.tenantIds = [config.xero_tenant_id];
82 EmailAddress: customer.email,
83 Phones: [{ PhoneType: 'DEFAULT', PhoneNumber: customer.phone }],
84 Addresses: [{ AddressType: 'STREET', AddressLine1: customer.address, City: customer.city, PostalCode: customer.postal_code, Country: customer.country }],
88 const result = await xero.accountingApi.createContacts(config.xero_tenant_id, { Contacts: [xeroContact] });
89 console.log('[Xero] Customer synced:', result.body.Contacts[0].ContactID);
91 console.error('[Xero] Customer sync error:', err);
95redis.subscribe('events', (err, count) => {
96 if (err) console.error('[Xero] Redis subscribe error:', err);
97 else console.log('[Xero] Subscribed to events channel');
100redis.on('message', async (channel, message) => {
101 if (channel !== 'events') return;
102 const event = JSON.parse(message);
104 if (['invoice.created', 'customer.created'].includes(event.type)) {
105 const { tenant_id, data } = event;
106 const xeroConfig = await getXeroConfigForTenant(tenant_id);
108 if (event.type === 'invoice.created') {
109 await syncInvoiceToXero(data, xeroConfig);
110 } else if (event.type === 'customer.created') {
111 await syncCustomerToXero(data, xeroConfig);
114 console.log(`[Xero] Skipping event for tenant ${tenant_id} (Xero not enabled)`);
119// --- Scaffold: Manual fetch endpoints and audit logging ---
120const express = require('express');
121const xeroRouter = express.Router();
123// GET /api/xero/invoices/:tenant_id - Fetch invoices from Xero for tenant
124xeroRouter.get('/invoices/:tenant_id', async (req, res) => {
125 const { tenant_id } = req.params;
127 const xeroConfig = await getXeroConfigForTenant(tenant_id);
128 if (!xeroConfig) return res.status(404).json({ error: 'Xero not configured for tenant' });
129 const xero = createXeroClient(xeroConfig);
130 await xero.setTokenSet({ access_token: xeroConfig.access_token, id_token: '', expires_at: 0 });
131 xero.tenantIds = [xeroConfig.xero_tenant_id];
132 const result = await xero.accountingApi.getInvoices(xeroConfig.xero_tenant_id);
134 await pool.query('INSERT INTO audit_log (tenant_id, action, details) VALUES ($1, $2, $3)', [tenant_id, 'xero.fetch.invoices', JSON.stringify({ count: result.body.Invoices.length })]);
135 res.json({ invoices: result.body.Invoices });
137 res.status(500).json({ error: 'Failed to fetch invoices', details: err.message });
141// GET /api/xero/contacts/:tenant_id - Fetch contacts from Xero for tenant
142xeroRouter.get('/contacts/:tenant_id', async (req, res) => {
143 const { tenant_id } = req.params;
145 const xeroConfig = await getXeroConfigForTenant(tenant_id);
146 if (!xeroConfig) return res.status(404).json({ error: 'Xero not configured for tenant' });
147 const xero = createXeroClient(xeroConfig);
148 await xero.setTokenSet({ access_token: xeroConfig.access_token, id_token: '', expires_at: 0 });
149 xero.tenantIds = [xeroConfig.xero_tenant_id];
150 const result = await xero.accountingApi.getContacts(xeroConfig.xero_tenant_id);
152 await pool.query('INSERT INTO audit_log (tenant_id, action, details) VALUES ($1, $2, $3)', [tenant_id, 'xero.fetch.contacts', JSON.stringify({ count: result.body.Contacts.length })]);
153 res.json({ contacts: result.body.Contacts });
155 res.status(500).json({ error: 'Failed to fetch contacts', details: err.message });
159module.exports = { getXeroConfigForTenant, createXeroClient, syncInvoiceToXero, syncCustomerToXero, xeroRouter };