Skip to content

Commit

Permalink
Properly handle errors for SimpleFIN. (#375)
Browse files Browse the repository at this point in the history
  • Loading branch information
psybers authored Jun 18, 2024
1 parent 925926f commit 73d0f04
Show file tree
Hide file tree
Showing 2 changed files with 174 additions and 120 deletions.
288 changes: 168 additions & 120 deletions src/app-simplefin/app-simplefin.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,150 +2,198 @@ import express from 'express';
import { inspect } from 'util';
import https from 'https';
import { SecretName, secretsService } from '../services/secrets-service.js';
import { handleError } from '../app-gocardless/util/handle-error.js';

const app = express();
export { app as handlers };
app.use(express.json());

app.post('/status', async (req, res) => {
let configured = false;
app.post(
'/status',
handleError(async (req, res) => {
const token = secretsService.get(SecretName.simplefin_token);
const configured = token != null && token !== 'Forbidden';

let token = secretsService.get(SecretName.simplefin_token);
if (token != null && token !== 'Forbidden') {
configured = true;
}

res.send({
status: 'ok',
data: {
configured: configured,
},
});
});

app.post('/accounts', async (req, res) => {
let accessKey = secretsService.get(SecretName.simplefin_accessKey);

if (accessKey == null || accessKey === 'Forbidden') {
let token = secretsService.get(SecretName.simplefin_token);
if (token == null || token === 'Forbidden') {
res.send({
status: 'ok',
data: {
configured: configured,
},
});
}),
);

app.post(
'/accounts',
handleError(async (req, res) => {
let accessKey = secretsService.get(SecretName.simplefin_accessKey);

try {
if (accessKey == null || accessKey === 'Forbidden') {
const token = secretsService.get(SecretName.simplefin_token);
if (token == null || token === 'Forbidden') {
throw new Error('No token');
} else {
accessKey = await getAccessKey(token);
secretsService.set(SecretName.simplefin_accessKey, accessKey);
if (accessKey == null || accessKey === 'Forbidden') {
throw new Error('No access key');
}
}
}
} catch (error) {
invalidToken(res);
return;
} else {
accessKey = await getAccessKey(token);
secretsService.set(SecretName.simplefin_accessKey, accessKey);
}
}

const now = new Date();
let startDate = new Date(now.getFullYear(), now.getMonth(), 1);
let endDate = new Date(now.getFullYear(), now.getMonth() + 1, 1);

let accounts = await getAccounts(accessKey, startDate, endDate);
const now = new Date();
const startDate = new Date(now.getFullYear(), now.getMonth(), 1);
const endDate = new Date(now.getFullYear(), now.getMonth() + 1, 1);

res.send({
status: 'ok',
data: {
accounts: accounts.accounts,
},
});
});

app.post('/transactions', async (req, res) => {
const { accountId, startDate } = req.body;

let accessKey = secretsService.get(SecretName.simplefin_accessKey);

if (accessKey == null || accessKey === 'Forbidden') {
return;
}
const accounts = await getAccounts(accessKey, startDate, endDate);

try {
let results = await getTransactions(accessKey, new Date(startDate));
res.send({
status: 'ok',
data: {
accounts: accounts.accounts,
},
});
}),
);

let account = results.accounts.find((a) => a.id === accountId);
app.post(
'/transactions',
handleError(async (req, res) => {
const { accountId, startDate } = req.body;

let response = {};
const accessKey = secretsService.get(SecretName.simplefin_accessKey);

let balance = parseInt(account.balance.replace('.', ''));
let date = new Date(account['balance-date'] * 1000)
.toISOString()
.split('T')[0];
if (accessKey == null || accessKey === 'Forbidden') {
invalidToken(res);
return;
}

response.balances = [
{
balanceAmount: { amount: account.balance, currency: account.currency },
balanceType: 'expected',
referenceDate: date,
},
{
balanceAmount: { amount: account.balance, currency: account.currency },
balanceType: 'interimAvailable',
referenceDate: date,
},
];
//response.iban = don't have compared to GoCardless
//response.institutionId = don't have compared to GoCardless
response.startingBalance = balance; // could be named differently in this use case.

let allTransactions = [];
let bookedTransactions = [];
let pendingTransactions = [];

for (let trans of account.transactions) {
let newTrans = {};

let dateToUse = 0;

if (trans.posted == 0) {
newTrans.booked = false;
dateToUse = trans.transacted_at;
} else {
newTrans.booked = true;
dateToUse = trans.posted;
try {
const results = await getTransactions(accessKey, new Date(startDate));

const account = results.accounts.find((a) => a.id === accountId);

const needsAttention = results.errors.find(
(e) => e === `Connection to ${account.org.name} may need attention`,
);
if (needsAttention) {
res.send({
status: 'ok',
data: {
error_type: 'ACCOUNT_NEEDS_ATTENTION',
error_code: 'ACCOUNT_NEEDS_ATTENTION',
status: 'rejected',
reason:
'The account needs your attention at <a href="https://beta-bridge.simplefin.org/auth/login">SimpleFIN</a>.',
},
});
}

newTrans.bookingDate = new Date(dateToUse * 1000)
.toISOString()
.split('T')[0];
const response = {};

newTrans.date = new Date(dateToUse * 1000).toISOString().split('T')[0];
newTrans.debtorName = trans.payee;
//newTrans.debtorAccount = don't have compared to GoCardless
newTrans.remittanceInformationUnstructured = trans.description;
newTrans.transactionAmount = { amount: trans.amount, currency: 'USD' };
newTrans.transactionId = trans.id;
newTrans.valueDate = new Date(dateToUse * 1000)
const balance = parseInt(account.balance.replace('.', ''));
const date = new Date(account['balance-date'] * 1000)
.toISOString()
.split('T')[0];

if (newTrans.booked) {
bookedTransactions.push(newTrans);
} else {
pendingTransactions.push(newTrans);
response.balances = [
{
balanceAmount: {
amount: account.balance,
currency: account.currency,
},
balanceType: 'expected',
referenceDate: date,
},
{
balanceAmount: {
amount: account.balance,
currency: account.currency,
},
balanceType: 'interimAvailable',
referenceDate: date,
},
];
response.startingBalance = balance; // could be named differently in this use case.

const allTransactions = [];
const bookedTransactions = [];
const pendingTransactions = [];

for (const trans of account.transactions) {
const newTrans = {};

let dateToUse = 0;

if (trans.posted == 0) {
newTrans.booked = false;
dateToUse = trans.transacted_at;
} else {
newTrans.booked = true;
dateToUse = trans.posted;
}

newTrans.bookingDate = new Date(dateToUse * 1000)
.toISOString()
.split('T')[0];

newTrans.date = new Date(dateToUse * 1000).toISOString().split('T')[0];
newTrans.debtorName = trans.payee;
//newTrans.debtorAccount = don't have compared to GoCardless
newTrans.remittanceInformationUnstructured = trans.description;
newTrans.transactionAmount = { amount: trans.amount, currency: 'USD' };
newTrans.transactionId = trans.id;
newTrans.valueDate = new Date(dateToUse * 1000)
.toISOString()
.split('T')[0];

if (newTrans.booked) {
bookedTransactions.push(newTrans);
} else {
pendingTransactions.push(newTrans);
}
allTransactions.push(newTrans);
}
allTransactions.push(newTrans);
}

response.transactions = {
all: allTransactions,
booked: bookedTransactions,
pending: pendingTransactions,
};
response.transactions = {
all: allTransactions,
booked: bookedTransactions,
pending: pendingTransactions,
};

res.send({
status: 'ok',
data: response,
});
} catch (error) {
const sendErrorResponse = (data) =>
res.send({ status: 'ok', data: { ...data, details: error.details } });
console.log(
'Something went wrong',
inspect(error, { depth: null }),
sendErrorResponse,
);
}
});
res.send({
status: 'ok',
data: response,
});
} catch (error) {
const sendErrorResponse = (data) =>
res.send({ status: 'ok', data: { ...data, details: error.details } });
console.log(
'Something went wrong',
inspect(error, { depth: null }),
sendErrorResponse,
);
}
}),
);

function invalidToken(res) {
res.send({
status: 'ok',
data: {
error_type: 'INVALID_ACCESS_TOKEN',
error_code: 'INVALID_ACCESS_TOKEN',
status: 'rejected',
reason:
'Invalid SimpleFIN access token. Reset the token and re-link any broken accounts.',
},
});
}

function parseAccessKey(accessKey) {
let scheme = null;
Expand Down
6 changes: 6 additions & 0 deletions upcoming-release-notes/375.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
category: Enhancements
authors: [psybers]
---

Properly handle errors for SimpleFIN.

0 comments on commit 73d0f04

Please sign in to comment.