Skip to content

Commit

Permalink
feat: Better handling of GovPay errors (#3654)
Browse files Browse the repository at this point in the history
  • Loading branch information
DafyddLlyr authored Sep 11, 2024
1 parent 579c7ba commit ec024a8
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 47 deletions.
106 changes: 59 additions & 47 deletions api.planx.uk/modules/pay/controller.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import assert from "assert";
import { Request } from "express";
import { responseInterceptor } from "http-proxy-middleware";
import { logPaymentStatus } from "./helpers.js";
import { handleGovPayErrors, logPaymentStatus } from "./helpers.js";
import { usePayProxy } from "./proxy.js";
import { $api } from "../../client/index.js";
import { ServerError } from "../../errors/index.js";
Expand Down Expand Up @@ -46,9 +46,12 @@ export const makePaymentViaProxy: PaymentProxyController = async (
pathRewrite: (path) => path.replace(/^\/pay.*$/, ""),
selfHandleResponse: true,
onProxyRes: responseInterceptor(
async (responseBuffer, _proxyRes, _req, _res) => {
async (responseBuffer, _proxyRes, _req, { statusCode }) => {
const responseString = responseBuffer.toString("utf8");
const govUkResponse = JSON.parse(responseString);

if (statusCode >= 400) return handleGovPayErrors(govUkResponse);

await logPaymentStatus({
sessionId,
flowId,
Expand Down Expand Up @@ -79,27 +82,32 @@ export const makeInviteToPayPaymentViaProxy: PaymentRequestProxyController = (
{
pathRewrite: (path) => path.replace(/^\/pay.*$/, ""),
selfHandleResponse: true,
onProxyRes: responseInterceptor(async (responseBuffer) => {
const responseString = responseBuffer.toString("utf8");
const govUkResponse = JSON.parse(responseString);
await logPaymentStatus({
sessionId,
flowId,
teamSlug,
govUkResponse,
});

try {
await addGovPayPaymentIdToPaymentRequest(
paymentRequestId,
onProxyRes: responseInterceptor(
async (responseBuffer, _proxyRes, _req, { statusCode }) => {
const responseString = responseBuffer.toString("utf8");
const govUkResponse = JSON.parse(responseString);

if (statusCode >= 400) return handleGovPayErrors(govUkResponse);

await logPaymentStatus({
sessionId,
flowId,
teamSlug,
govUkResponse,
);
} catch (error) {
throw Error(error as string);
}
});

return responseBuffer;
}),
try {
await addGovPayPaymentIdToPaymentRequest(
paymentRequestId,
govUkResponse,
);
} catch (error) {
throw Error(error as string);
}

return responseBuffer;
},
),
},
req,
res,
Expand All @@ -125,32 +133,36 @@ export function fetchPaymentViaProxyWithCallback(
{
pathRewrite: () => `/${req.params.paymentId}`,
selfHandleResponse: true,
onProxyRes: responseInterceptor(async (responseBuffer) => {
const govUkResponse = JSON.parse(responseBuffer.toString("utf8"));

await logPaymentStatus({
sessionId,
flowId,
teamSlug,
govUkResponse,
});

try {
await callback(req, govUkResponse);
} catch (e) {
throw Error(e as string);
}

// only return payment status, filter out PII
return JSON.stringify({
payment_id: govUkResponse.payment_id,
amount: govUkResponse.amount,
state: govUkResponse.state,
_links: {
next_url: govUkResponse._links?.next_url,
},
});
}),
onProxyRes: responseInterceptor(
async (responseBuffer, _proxyRes, _req, { statusCode }) => {
const govUkResponse = JSON.parse(responseBuffer.toString("utf8"));

if (statusCode >= 400) return handleGovPayErrors(govUkResponse);

await logPaymentStatus({
sessionId,
flowId,
teamSlug,
govUkResponse,
});

try {
await callback(req, govUkResponse);
} catch (e) {
throw Error(e as string);
}

// only return payment status, filter out PII
return JSON.stringify({
payment_id: govUkResponse.payment_id,
amount: govUkResponse.amount,
state: govUkResponse.state,
_links: {
next_url: govUkResponse._links?.next_url,
},
});
},
),
},
req,
res,
Expand Down
11 changes: 11 additions & 0 deletions api.planx.uk/modules/pay/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@ import { gql } from "graphql-request";
import airbrake from "../../airbrake.js";
import { $api } from "../../client/index.js";

/**
* Gracefully handle GovPay errors
* Docs: https://docs.payments.service.gov.uk/api_reference/#responses
*/
export const handleGovPayErrors = (res: unknown) =>
JSON.stringify({
message:
"GovPay responded with an error when attempting to proxy to their API",
govPayResponse: res,
});

export async function logPaymentStatus({
sessionId,
flowId,
Expand Down
35 changes: 35 additions & 0 deletions api.planx.uk/modules/pay/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,38 @@ describe("fetching status of a GOV.UK payment", () => {
});
});
});

test("handling GovPay error responses", async () => {
const govUKErrorResponse = {
code: "govUKErrorResponse",
description:
"Account is not fully configured. Please refer to documentation to setup your account or contact support with your error code - https://www.payments.service.gov.uk/support/ .",
};

nock("https://publicapi.payments.service.gov.uk/v1/payments")
.post("")
.reply(400, govUKErrorResponse);

await supertest(app)
.post(
"/pay/southwark?flowId=7cd1c4b4-4229-424f-8d04-c9fdc958ef4e&sessionId=f2d8ca1d-a43b-43ec-b3d9-a9fec63ff19c",
)
.send({
amount: 100,
reference: "12343543",
description: "New application",
return_url: "https://editor.planx.uk",
metadata: {
source: "PlanX",
flow: "apply-for-a-lawful-development-certificate",
inviteToPay: false,
},
})
.expect(400)
.then((res) => {
expect(res.body.message).toMatch(
/GovPay responded with an error when attempting to proxy to their API/,
);
expect(res.body.govPayResponse).toEqual(govUKErrorResponse);
});
});

0 comments on commit ec024a8

Please sign in to comment.