diff --git a/api.planx.uk/modules/pay/middleware.ts b/api.planx.uk/modules/pay/middleware.ts index b21a939221..3f687ba710 100644 --- a/api.planx.uk/modules/pay/middleware.ts +++ b/api.planx.uk/modules/pay/middleware.ts @@ -3,6 +3,11 @@ import { gql } from "graphql-request"; import { $api } from "../../client"; import { ServerError } from "../../errors"; import { GovPayMetadata } from "./types"; +import { formatGovPayMetadata } from "@opensystemslab/planx-core"; +import { + GovPayMetadataValue, + Passport, +} from "@opensystemslab/planx-core/types"; /** * Confirm that this local authority (aka team) has a pay token @@ -41,6 +46,7 @@ interface GetPaymentRequestDetails { slug: string; }; }; + passport: Passport; }; } | null; } @@ -63,6 +69,7 @@ export async function fetchPaymentRequestDetails( slug } } + passport: data(path: "$.passport") } } } @@ -92,6 +99,7 @@ export async function fetchPaymentRequestDetails( if (paymentAmount) req.params.paymentAmount = paymentAmount; res.locals.govPayMetadata = paymentRequest.govPayMetadata; + res.locals.passport = paymentRequest.session.passport; next(); } @@ -102,7 +110,7 @@ interface GovPayCreatePayment { reference: string; description: string; return_url: string; - metadata: Record; + metadata: Record; } export async function buildPaymentPayload( @@ -129,28 +137,11 @@ export async function buildPaymentPayload( } // Convert metadata to format required by GovPay - const govPayMetadata = Object.fromEntries( - res.locals.govPayMetadata.map(({ key, value }: GovPayMetadata) => [ - key, - value, - ]), + const metadata = formatGovPayMetadata( + res.locals.govPayMetadata, + res.locals.passport, ); - const defaultGovPayMetadata = { - source: "PlanX", - // Payment requests have /pay path suffix, so get flow-slug from second-to-last position - flow: - (req.query.returnURL as string) - .split("?")?.[0] - ?.split("/") - ?.slice(-2, -1)?.[0] || "Could not parse service name", - inviteToPay: true, - }; - - const metadata = Object.keys(govPayMetadata).length - ? govPayMetadata - : defaultGovPayMetadata; - const createPaymentBody: GovPayCreatePayment = { amount: parseInt(req.params.paymentAmount), reference: req.query.sessionId as string, diff --git a/api.planx.uk/package.json b/api.planx.uk/package.json index 4f8cca4fde..820a7136e3 100644 --- a/api.planx.uk/package.json +++ b/api.planx.uk/package.json @@ -4,7 +4,7 @@ "private": true, "dependencies": { "@airbrake/node": "^2.1.8", - "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#15bde6b", + "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#52f76e7", "@types/isomorphic-fetch": "^0.0.36", "adm-zip": "^0.5.10", "aws-sdk": "^2.1467.0", diff --git a/api.planx.uk/pnpm-lock.yaml b/api.planx.uk/pnpm-lock.yaml index 12362c985b..1ca0966e55 100644 --- a/api.planx.uk/pnpm-lock.yaml +++ b/api.planx.uk/pnpm-lock.yaml @@ -12,8 +12,8 @@ dependencies: specifier: ^2.1.8 version: 2.1.8 '@opensystemslab/planx-core': - specifier: git+https://github.com/theopensystemslab/planx-core#15bde6b - version: github.com/theopensystemslab/planx-core/15bde6b + specifier: git+https://github.com/theopensystemslab/planx-core#52f76e7 + version: github.com/theopensystemslab/planx-core/52f76e7 '@types/isomorphic-fetch': specifier: ^0.0.36 version: 0.0.36 @@ -8411,8 +8411,8 @@ packages: resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} dev: false - github.com/theopensystemslab/planx-core/15bde6b: - resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/15bde6b} + github.com/theopensystemslab/planx-core/52f76e7: + resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/52f76e7} name: '@opensystemslab/planx-core' version: 1.0.0 prepare: true diff --git a/api.planx.uk/tests/mocks/inviteToPayMocks.ts b/api.planx.uk/tests/mocks/inviteToPayMocks.ts index 69bbb9e9d1..705876577e 100644 --- a/api.planx.uk/tests/mocks/inviteToPayMocks.ts +++ b/api.planx.uk/tests/mocks/inviteToPayMocks.ts @@ -127,6 +127,11 @@ export const createPaymentRequestQueryMock = { payeeName: payee.name, payeeEmail: payee.email, sessionPreviewData: sessionPreviewData, + govPayMetadata: [ + { key: "source", value: "PlanX" }, + { key: "isInviteToPay", value: true }, + { key: "flow", value: validSession.flow.slug }, + ], }, }; diff --git a/e2e/package.json b/e2e/package.json index c8f449b52a..c5888413e4 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -30,4 +30,4 @@ "prettier": "^3.2.4", "typescript": "^5.4.3" } -} \ No newline at end of file +} diff --git a/e2e/tests/api-driven/package.json b/e2e/tests/api-driven/package.json index f588363618..928afa4805 100644 --- a/e2e/tests/api-driven/package.json +++ b/e2e/tests/api-driven/package.json @@ -6,7 +6,7 @@ }, "dependencies": { "@cucumber/cucumber": "^9.3.0", - "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#15bde6b", + "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#52f76e7", "axios": "^1.6.8", "dotenv": "^16.3.1", "dotenv-expand": "^10.0.0", diff --git a/e2e/tests/api-driven/pnpm-lock.yaml b/e2e/tests/api-driven/pnpm-lock.yaml index e9f0e220e9..d4153f3fe8 100644 --- a/e2e/tests/api-driven/pnpm-lock.yaml +++ b/e2e/tests/api-driven/pnpm-lock.yaml @@ -9,8 +9,8 @@ dependencies: specifier: ^9.3.0 version: 9.3.0 '@opensystemslab/planx-core': - specifier: git+https://github.com/theopensystemslab/planx-core#15bde6b - version: github.com/theopensystemslab/planx-core/15bde6b + specifier: git+https://github.com/theopensystemslab/planx-core#52f76e7 + version: github.com/theopensystemslab/planx-core/52f76e7 axios: specifier: ^1.6.8 version: 1.6.8 @@ -2943,8 +2943,8 @@ packages: resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} dev: false - github.com/theopensystemslab/planx-core/15bde6b: - resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/15bde6b} + github.com/theopensystemslab/planx-core/52f76e7: + resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/52f76e7} name: '@opensystemslab/planx-core' version: 1.0.0 prepare: true diff --git a/e2e/tests/ui-driven/package.json b/e2e/tests/ui-driven/package.json index c3305e837f..b8a870b394 100644 --- a/e2e/tests/ui-driven/package.json +++ b/e2e/tests/ui-driven/package.json @@ -8,7 +8,7 @@ "postinstall": "./install-dependencies.sh" }, "dependencies": { - "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#15bde6b", + "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#52f76e7", "axios": "^1.6.8", "dotenv": "^16.3.1", "eslint": "^8.56.0", diff --git a/e2e/tests/ui-driven/pnpm-lock.yaml b/e2e/tests/ui-driven/pnpm-lock.yaml index e39ecbf311..c030f168a3 100644 --- a/e2e/tests/ui-driven/pnpm-lock.yaml +++ b/e2e/tests/ui-driven/pnpm-lock.yaml @@ -6,8 +6,8 @@ settings: dependencies: '@opensystemslab/planx-core': - specifier: git+https://github.com/theopensystemslab/planx-core#15bde6b - version: github.com/theopensystemslab/planx-core/15bde6b + specifier: git+https://github.com/theopensystemslab/planx-core#52f76e7 + version: github.com/theopensystemslab/planx-core/52f76e7 axios: specifier: ^1.6.8 version: 1.6.8 @@ -2609,8 +2609,8 @@ packages: resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} dev: false - github.com/theopensystemslab/planx-core/15bde6b: - resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/15bde6b} + github.com/theopensystemslab/planx-core/52f76e7: + resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/52f76e7} name: '@opensystemslab/planx-core' version: 1.0.0 prepare: true diff --git a/editor.planx.uk/package.json b/editor.planx.uk/package.json index d0ca0360f7..86f212585e 100644 --- a/editor.planx.uk/package.json +++ b/editor.planx.uk/package.json @@ -12,7 +12,7 @@ "@mui/material": "^5.15.2", "@mui/utils": "^5.15.2", "@opensystemslab/map": "^0.8.1", - "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#15bde6b", + "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#52f76e7", "@tiptap/core": "^2.0.3", "@tiptap/extension-bold": "^2.0.3", "@tiptap/extension-bubble-menu": "^2.1.13", diff --git a/editor.planx.uk/pnpm-lock.yaml b/editor.planx.uk/pnpm-lock.yaml index 2643798e8f..cf98170858 100644 --- a/editor.planx.uk/pnpm-lock.yaml +++ b/editor.planx.uk/pnpm-lock.yaml @@ -38,8 +38,8 @@ dependencies: specifier: ^0.8.1 version: 0.8.1 '@opensystemslab/planx-core': - specifier: git+https://github.com/theopensystemslab/planx-core#15bde6b - version: github.com/theopensystemslab/planx-core/15bde6b(@types/react@18.2.45) + specifier: git+https://github.com/theopensystemslab/planx-core#52f76e7 + version: github.com/theopensystemslab/planx-core/52f76e7(@types/react@18.2.45) '@tiptap/core': specifier: ^2.0.3 version: 2.0.3(@tiptap/pm@2.0.3) @@ -21139,9 +21139,9 @@ packages: use-sync-external-store: 1.2.0(react@18.2.0) dev: false - github.com/theopensystemslab/planx-core/15bde6b(@types/react@18.2.45): - resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/15bde6b} - id: github.com/theopensystemslab/planx-core/15bde6b + github.com/theopensystemslab/planx-core/52f76e7(@types/react@18.2.45): + resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/52f76e7} + id: github.com/theopensystemslab/planx-core/52f76e7 name: '@opensystemslab/planx-core' version: 1.0.0 prepare: true diff --git a/editor.planx.uk/src/@planx/components/Pay/Editor.tsx b/editor.planx.uk/src/@planx/components/Pay/Editor.tsx index 0cef28938e..3b357ef2de 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Editor.tsx +++ b/editor.planx.uk/src/@planx/components/Pay/Editor.tsx @@ -2,9 +2,8 @@ import DataObjectIcon from "@mui/icons-material/DataObject"; import Box from "@mui/material/Box"; import Link from "@mui/material/Link"; import Typography from "@mui/material/Typography"; -import { ComponentType as TYPES } from "@opensystemslab/planx-core/types"; +import { GovPayMetadata, ComponentType as TYPES } from "@opensystemslab/planx-core/types"; import { - GovPayMetadata, Pay, REQUIRED_GOVPAY_METADATA, validationSchema, @@ -30,7 +29,10 @@ import ErrorWrapper from "ui/shared/ErrorWrapper"; import Input from "ui/shared/Input"; import InputRow from "ui/shared/InputRow"; -type FormikGovPayMetadata = Record[] | string | undefined; +type FormikGovPayMetadata = + | Record[] + | string + | undefined; const GOVPAY_DOCS_URL = "https://docs.payments.service.gov.uk/reporting/#add-more-information-to-a-payment-39-custom-metadata-39-or-39-reporting-columns-39"; @@ -113,6 +115,7 @@ function GovPayMetadataEditor(props: ListManagerEditorProps) { placeholder="key" /> = (props: Props) => { {" "} for more details. + Any values beginning with @ will be dynamically read from data values set throughout the flow. { const payment = await resolvePaymentResponse(res.data); @@ -273,9 +273,9 @@ function Component(props: Props) { return ( <> {state.status === "init" || - state.status === "retry" || - state.status === "unsupported_team" || - state.status === "undefined_fee" ? ( + state.status === "retry" || + state.status === "unsupported_team" || + state.status === "undefined_fee" ? ( { const validate = async (payload: unknown) => @@ -14,7 +14,7 @@ describe("GovPayMetadata Schema", () => { const errors = await validate([]); expect(errors).toHaveLength(1); expect(errors[0]).toMatch( - /Keys flow, source and isInviteToPay must be present/, + /Keys flow, source and isInviteToPay must be present/ ); }); @@ -108,22 +108,3 @@ describe("GovPayMetadata Schema", () => { expect(errors[0]).toMatch(/A maximum of 10 fields can be set as metadata/); }); }); - -describe("formatMetadata() helper", () => { - it("handles empty metadata", () => { - const result = formatMetadata([]); - expect(result).toMatchObject({}); - }); - - it("converts metadata from the format generated in the form, to the format required by GovPay", () => { - const result = formatMetadata([ - { key: "firstKey", value: "firstValue" }, - { key: "secondKey", value: "secondValue" }, - ]); - - expect(result).toMatchObject({ - firstKey: "firstValue", - secondKey: "secondValue", - }); - }); -}); diff --git a/editor.planx.uk/src/@planx/components/Pay/model.ts b/editor.planx.uk/src/@planx/components/Pay/model.ts index 902be86ce4..e042589cab 100644 --- a/editor.planx.uk/src/@planx/components/Pay/model.ts +++ b/editor.planx.uk/src/@planx/components/Pay/model.ts @@ -1,13 +1,14 @@ import { useStore } from "pages/FlowEditor/lib/store"; -import { ApplicationPath } from "types"; +import { ApplicationPath, Passport } from "types"; import { array, boolean, object, string } from "yup"; import type { MoreInformation } from "../shared"; - -export interface GovPayMetadata { - key: string; - value: string | boolean; -} +import { + GovPayMetadata, + GovUKCreatePaymentPayload, + Passport as IPassport, +} from "@opensystemslab/planx-core/types"; +import { formatGovPayMetadata } from "@opensystemslab/planx-core"; export interface Pay extends MoreInformation { title: string; @@ -28,33 +29,12 @@ export interface Pay extends MoreInformation { govPayMetadata: GovPayMetadata[]; } -// https://docs.payments.service.gov.uk/making_payments/#creating-a-payment -export interface GovUKCreatePaymentPayload { - amount: number; - reference: string; - description: string; - return_url: string; - email?: string; - prefilled_cardholder_details?: { - cardholder_name?: string; - billing_address?: { - line1: string; - line2: string; - postcode: string; - city: string; - country: string; - }; - }; - language?: string; - metadata?: Record; -} - export const toPence = (decimal: number) => Math.trunc(decimal * 100); export const toDecimal = (pence: number) => pence / 100; export const formattedPriceWithCurrencySymbol = ( amount: number, - currency = "GBP", + currency = "GBP" ) => new Intl.NumberFormat("en-GB", { style: "currency", @@ -65,19 +45,15 @@ export const createPayload = ( fee: number, reference: string, metadata: GovPayMetadata[], + passport: Passport ): GovUKCreatePaymentPayload => ({ amount: toPence(fee), reference, description: "New application", return_url: getReturnURL(reference), - metadata: formatMetadata(metadata), + metadata: formatGovPayMetadata(metadata, passport as IPassport), }); -export const formatMetadata = ( - metadata: GovPayMetadata[], -): GovUKCreatePaymentPayload["metadata"] => - Object.fromEntries(metadata.map(({ key, value }) => [key, value])); - /** * For Save & Return, include sessionId and email as query params so the session can be picked up */ @@ -106,7 +82,7 @@ export const govPayMetadataSchema = array( value: string() .required("Value is a required field") .max(100, "Value length cannot exceed 100 characters"), - }), + }) ) .max(10, "A maximum of 10 fields can be set as metadata") .test({ @@ -133,7 +109,7 @@ export const govPayMetadataSchema = array( const keys = metadata.map((item) => item.key); const allRequiredKeysPresent = REQUIRED_GOVPAY_METADATA.every( - (requiredKey) => keys.includes(requiredKey), + (requiredKey) => keys.includes(requiredKey) ); return allRequiredKeysPresent; }, diff --git a/editor.planx.uk/src/routes/views/standalone.tsx b/editor.planx.uk/src/routes/views/standalone.tsx index 7eaabb156c..75d17bdcdb 100644 --- a/editor.planx.uk/src/routes/views/standalone.tsx +++ b/editor.planx.uk/src/routes/views/standalone.tsx @@ -38,7 +38,7 @@ const standaloneView = async (req: NaviRequest) => { return ( - + diff --git a/editor.planx.uk/src/ui/shared/ErrorWrapper.tsx b/editor.planx.uk/src/ui/shared/ErrorWrapper.tsx index 52fd064fb2..6e4e7cb1c7 100644 --- a/editor.planx.uk/src/ui/shared/ErrorWrapper.tsx +++ b/editor.planx.uk/src/ui/shared/ErrorWrapper.tsx @@ -18,8 +18,8 @@ export interface Props { const Root = styled(Box, { shouldForwardProp: (prop) => prop !== "error", })(({ theme, error }) => ({ + width: "100%", ...(error && { - width: "100%", paddingLeft: theme.spacing(1.5), borderLeft: `5px solid ${theme.palette.error.main}`, display: "flex", diff --git a/editor.planx.uk/src/ui/shared/Input.tsx b/editor.planx.uk/src/ui/shared/Input.tsx index 03655321e1..4d764401cb 100644 --- a/editor.planx.uk/src/ui/shared/Input.tsx +++ b/editor.planx.uk/src/ui/shared/Input.tsx @@ -65,7 +65,6 @@ const StyledInputBase = styled(InputBase, { backgroundColor: "#f0f0f0", borderColor: "#d3d3d3", fontFamily: `"Source Code Pro", monospace;`, - height: "44px", "& input": { fontSize: theme.typography.body2.fontSize, }, diff --git a/infrastructure/application/Pulumi.staging.yaml b/infrastructure/application/Pulumi.staging.yaml index d59ef2260d..47ce774321 100644 --- a/infrastructure/application/Pulumi.staging.yaml +++ b/infrastructure/application/Pulumi.staging.yaml @@ -29,7 +29,7 @@ config: application:metabase-encryption-secret-key: secure: AAABAGmfVICD8sR+IE6mHC8BNUY1WQXGCbv5F3C1fSgA+1ADiRem3GNrwY0YRZociRYuPIo3MIRS0aIg44jt10SBCE0ik58wHamcKA== application:metabasePgPassword: - secure: AAABAJxtd6OSlOFAyzfOFUU40UYC/CYJet4OeghALRGG2XdjcG/pUvuPGhkxud+tc2I/l65zGVw= + secure: AAABADfCkSRtVTdU8wi1F+nwuHl8OeI3bm9ZzaNkIY4yOGB9dsOqS30tkD8W7GZlNoWDZT2ojoP3zOFPuTLWbQ== application:ordnance-survey-api-key: secure: AAABAKhzYRhAmGmoBY1fPF75JWSWMOTORRCUAPKqvFUJMA60U5IFDUvPZ1Y+FuLtQ6Y1sQ8Q3laXGbp0f9j3Lw== application:session-secret: diff --git a/infrastructure/data/Pulumi.staging.yaml b/infrastructure/data/Pulumi.staging.yaml index a056740a4a..42ae957cac 100644 --- a/infrastructure/data/Pulumi.staging.yaml +++ b/infrastructure/data/Pulumi.staging.yaml @@ -1,3 +1,4 @@ config: + aws:profile: planx-staging-pulumi data:db-password: - secure: AAABAB8n9YndCWHacrUqhkHcPPKh4iWGyB2MT8xYq55zcyp2hCSANG69CWR+6JiehY73+W/eBqA= + secure: AAABAC1IL67K+rI8eqDLfUG/eTuaQlcEOOBUmXWjY8pyrS32fUCm2WQGHCo7uwb1UYREMyYOSiYHolwpcscRLQ== diff --git a/infrastructure/data/index.ts b/infrastructure/data/index.ts index 3b7082e6e4..78fe9270bd 100644 --- a/infrastructure/data/index.ts +++ b/infrastructure/data/index.ts @@ -14,7 +14,7 @@ const db = new aws.rds.Instance("app", { engine: "postgres", // Available versions: // $ aws rds describe-db-engine-versions --default-only --engine postgres - engineVersion: "12.14", + engineVersion: "12.17", // Available instance types: https://aws.amazon.com/rds/instance-types/ instanceClass: env === "production" ? "db.t3.medium" : "db.t3.micro", allocatedStorage: env === "production" ? 100 : 20,