Skip to content

Commit

Permalink
fix gocardless error handling (#439)
Browse files Browse the repository at this point in the history
* fix gocardless error handling

* release note

* remove redundant tests

* tcrasset feedback
  • Loading branch information
matt-fidd authored Aug 25, 2024
1 parent 6a41d28 commit 2b37d5a
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 181 deletions.
11 changes: 10 additions & 1 deletion src/app-gocardless/app-gocardless.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import { inspect } from 'util';

import { goCardlessService } from './services/gocardless-service.js';
import {
RequisitionNotLinked,
AccountNotLinedToRequisition,
GenericGoCardlessError,
RateLimitError,
RequisitionNotLinked,
} from './errors.js';
import { handleError } from './util/handle-error.js';
import { sha256String } from '../util/hash.js';
Expand Down Expand Up @@ -191,6 +192,14 @@ app.post(
reason: 'Account not linked with this requisition',
});
break;
case error instanceof RateLimitError:
sendErrorResponse({
error_type: 'RATE_LIMIT_EXCEEDED',
error_code: 'NORDIGEN_ERROR',
status: 'rejected',
reason: 'Rate limit exceeded',
});
break;
case error instanceof GenericGoCardlessError:
console.log('Something went wrong', inspect(error, { depth: null }));
sendErrorResponse({
Expand Down
164 changes: 99 additions & 65 deletions src/app-gocardless/services/gocardless-service.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import BankFactory, { BANKS_WITH_LIMITED_HISTORY } from '../bank-factory.js';
import {
RequisitionNotLinked,
AccessDeniedError,
AccountNotLinedToRequisition,
GenericGoCardlessError,
InvalidInputDataError,
InvalidGoCardlessTokenError,
AccessDeniedError,
NotFoundError,
ResourceSuspended,
RateLimitError,
UnknownError,
ResourceSuspended,
RequisitionNotLinked,
ServiceError,
UnknownError,
} from '../errors.js';
import * as nordigenNode from 'nordigen-node';
import * as uuid from 'uuid';
Expand All @@ -35,26 +36,28 @@ const getGocardlessClient = () => {
return clients.get(hash);
};

export const handleGoCardlessError = (response) => {
switch (response.status_code) {
export const handleGoCardlessError = (error) => {
const status = error?.response?.status;

switch (status) {
case 400:
throw new InvalidInputDataError(response);
throw new InvalidInputDataError(error);
case 401:
throw new InvalidGoCardlessTokenError(response);
throw new InvalidGoCardlessTokenError(error);
case 403:
throw new AccessDeniedError(response);
throw new AccessDeniedError(error);
case 404:
throw new NotFoundError(response);
throw new NotFoundError(error);
case 409:
throw new ResourceSuspended(response);
throw new ResourceSuspended(error);
case 429:
throw new RateLimitError(response);
throw new RateLimitError(error);
case 500:
throw new UnknownError(response);
throw new UnknownError(error);
case 503:
throw new ServiceError(response);
throw new ServiceError(error);
default:
return;
throw new GenericGoCardlessError(error);
}
};

Expand Down Expand Up @@ -87,8 +90,11 @@ export const goCardlessService = {
if (isExpiredJwtToken(getGocardlessClient().token)) {
// Generate new access token. Token is valid for 24 hours
// Note: access_token is automatically injected to other requests after you successfully obtain it
const tokenData = await client.generateToken();
handleGoCardlessError(tokenData);
try {
await client.generateToken();
} catch (error) {
handleGoCardlessError(error);
}
}
},

Expand Down Expand Up @@ -261,23 +267,26 @@ export const goCardlessService = {
const institution = await goCardlessService.getInstitution(institutionId);
const bank = BankFactory(institutionId);

const response = await client.initSession({
redirectUrl: host + '/gocardless/link',
institutionId,
referenceId: uuid.v4(),
accessValidForDays: bank.accessValidForDays,
maxHistoricalDays: BANKS_WITH_LIMITED_HISTORY.includes(institutionId)
? Number(institution.transaction_total_days) >= 90
? '89'
: institution.transaction_total_days
: institution.transaction_total_days,
userLanguage: 'en',
ssn: null,
redirectImmediate: false,
accountSelection: false,
});

handleGoCardlessError(response);
let response;
try {
response = await client.initSession({
redirectUrl: host + '/gocardless/link',
institutionId,
referenceId: uuid.v4(),
accessValidForDays: bank.accessValidForDays,
maxHistoricalDays: BANKS_WITH_LIMITED_HISTORY.includes(institutionId)
? Number(institution.transaction_total_days) >= 90
? '89'
: institution.transaction_total_days
: institution.transaction_total_days,
userLanguage: 'en',
ssn: null,
redirectImmediate: false,
accountSelection: false,
});
} catch (error) {
handleGoCardlessError(error);
}

const { link, id: requisitionId } = response;

Expand All @@ -302,9 +311,14 @@ export const goCardlessService = {
*/
deleteRequisition: async (requisitionId) => {
await goCardlessService.getRequisition(requisitionId);
const response = client.deleteRequisition(requisitionId);

handleGoCardlessError(response);
let response;
try {
response = client.deleteRequisition(requisitionId);
} catch (error) {
handleGoCardlessError(error);
}

return response;
},

Expand All @@ -325,9 +339,12 @@ export const goCardlessService = {
getRequisition: async (requisitionId) => {
await goCardlessService.setToken();

const response = client.getRequisitionById(requisitionId);

handleGoCardlessError(response);
let response;
try {
response = client.getRequisitionById(requisitionId);
} catch (error) {
handleGoCardlessError(error);
}

return response;
},
Expand All @@ -338,13 +355,15 @@ export const goCardlessService = {
* @returns {Promise<import('../gocardless.types.js').DetailedAccount>}
*/
getDetailedAccount: async (accountId) => {
const [detailedAccount, metadataAccount] = await Promise.all([
client.getDetails(accountId),
client.getMetadata(accountId),
]);

handleGoCardlessError(detailedAccount);
handleGoCardlessError(metadataAccount);
let detailedAccount, metadataAccount;
try {
[detailedAccount, metadataAccount] = await Promise.all([
client.getDetails(accountId),
client.getMetadata(accountId),
]);
} catch (error) {
handleGoCardlessError(error);
}

return {
...detailedAccount.account,
Expand All @@ -361,9 +380,12 @@ export const goCardlessService = {
* @returns {Promise<import('../gocardless-node.types.js').GoCardlessAccountMetadata>}
*/
getAccountMetadata: async (accountId) => {
const response = await client.getMetadata(accountId);

handleGoCardlessError(response);
let response;
try {
response = await client.getMetadata(accountId);
} catch (error) {
handleGoCardlessError(error);
}

return response;
},
Expand All @@ -382,9 +404,12 @@ export const goCardlessService = {
* @returns {Promise<Array<import('../gocardless-node.types.js').Institution>>}
*/
getInstitutions: async (country) => {
const response = await client.getInstitutions(country);

handleGoCardlessError(response);
let response;
try {
response = await client.getInstitutions(country);
} catch (error) {
handleGoCardlessError(error);
}

return response;
},
Expand All @@ -403,9 +428,12 @@ export const goCardlessService = {
* @returns {Promise<import('../gocardless-node.types.js').Institution>}
*/
getInstitution: async (institutionId) => {
const response = await client.getInstitutionById(institutionId);

handleGoCardlessError(response);
let response;
try {
response = await client.getInstitutionById(institutionId);
} catch (error) {
handleGoCardlessError(error);
}

return response;
},
Expand Down Expand Up @@ -444,13 +472,16 @@ export const goCardlessService = {
* @returns {Promise<import('../gocardless.types.js').GetTransactionsResponse>}
*/
getTransactions: async ({ institutionId, accountId, startDate, endDate }) => {
const response = await client.getTransactions({
accountId,
dateFrom: startDate,
dateTo: endDate,
});

handleGoCardlessError(response);
let response;
try {
response = await client.getTransactions({
accountId,
dateFrom: startDate,
dateTo: endDate,
});
} catch (error) {
handleGoCardlessError(error);
}

const bank = BankFactory(institutionId);
response.transactions.booked = response.transactions.booked
Expand All @@ -477,9 +508,12 @@ export const goCardlessService = {
* @returns {Promise<import('../gocardless.types.js').GetBalances>}
*/
getBalances: async (accountId) => {
const response = await client.getBalances(accountId);

handleGoCardlessError(response);
let response;
try {
response = await client.getBalances(accountId);
} catch (error) {
handleGoCardlessError(error);
}

return response;
},
Expand Down
Loading

0 comments on commit 2b37d5a

Please sign in to comment.