EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
xero.js
Go to the documentation of this file.
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);
6
7// Query integrations table for Xero config for a tenant
8/**
9 *
10 * @param tenantId
11 */
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`,
15 [tenantId]
16 );
17 return result.rows.length ? result.rows[0].config : null;
18}
19
20const { XeroClient } = require('xero-node');
21
22/**
23 *
24 * @param config
25 */
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(' '),
32 });
33}
34
35/**
36 *
37 * @param invoice
38 * @param config
39 */
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];
45
46 // Map your invoice to Xero format
47 const xeroInvoice = {
48 Type: 'ACCREC',
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
55 })),
56 Date: invoice.issued_date,
57 DueDate: invoice.due_date,
58 Status: 'AUTHORISED',
59 LineAmountTypes: 'Exclusive',
60 };
61
62 try {
63 const result = await xero.accountingApi.createInvoices(config.xero_tenant_id, { Invoices: [xeroInvoice] });
64 console.log('[Xero] Invoice synced:', result.body.Invoices[0].InvoiceID);
65 } catch (err) {
66 console.error('[Xero] Invoice sync error:', err);
67 }
68}
69
70/**
71 *
72 * @param customer
73 * @param config
74 */
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];
79
80 const xeroContact = {
81 Name: customer.name,
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 }],
85 };
86
87 try {
88 const result = await xero.accountingApi.createContacts(config.xero_tenant_id, { Contacts: [xeroContact] });
89 console.log('[Xero] Customer synced:', result.body.Contacts[0].ContactID);
90 } catch (err) {
91 console.error('[Xero] Customer sync error:', err);
92 }
93}
94
95redis.subscribe('events', (err, count) => {
96 if (err) console.error('[Xero] Redis subscribe error:', err);
97 else console.log('[Xero] Subscribed to events channel');
98});
99
100redis.on('message', async (channel, message) => {
101 if (channel !== 'events') return;
102 const event = JSON.parse(message);
103
104 if (['invoice.created', 'customer.created'].includes(event.type)) {
105 const { tenant_id, data } = event;
106 const xeroConfig = await getXeroConfigForTenant(tenant_id);
107 if (xeroConfig) {
108 if (event.type === 'invoice.created') {
109 await syncInvoiceToXero(data, xeroConfig);
110 } else if (event.type === 'customer.created') {
111 await syncCustomerToXero(data, xeroConfig);
112 }
113 } else {
114 console.log(`[Xero] Skipping event for tenant ${tenant_id} (Xero not enabled)`);
115 }
116 }
117});
118
119// --- Scaffold: Manual fetch endpoints and audit logging ---
120const express = require('express');
121const xeroRouter = express.Router();
122
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;
126 try {
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);
133 // Audit log
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 });
136 } catch (err) {
137 res.status(500).json({ error: 'Failed to fetch invoices', details: err.message });
138 }
139});
140
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;
144 try {
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);
151 // Audit log
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 });
154 } catch (err) {
155 res.status(500).json({ error: 'Failed to fetch contacts', details: err.message });
156 }
157});
158
159module.exports = { getXeroConfigForTenant, createXeroClient, syncInvoiceToXero, syncCustomerToXero, xeroRouter };