Skip to content

Commit 7591d88

Browse files
committed
Handles test/live API key
1 parent 41fbf7a commit 7591d88

8 files changed

Lines changed: 55 additions & 42 deletions

File tree

src/config/index.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -268,14 +268,6 @@ export const config = convict({
268268
nullable: true,
269269
default: undefined,
270270
env: 'PAYMENT_PROVIDER_API_KEY_TEST'
271-
} as SchemaObj<string | undefined>,
272-
273-
paymentProviderApiKeyLive: {
274-
doc: 'A live API key for integrating with a payment provider',
275-
format: String,
276-
nullable: true,
277-
default: undefined,
278-
env: 'PAYMENT_PROVIDER_API_KEY_LIVE'
279271
} as SchemaObj<string | undefined>
280272
})
281273

src/server/plugins/engine/components/PaymentField.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
type FormSubmissionError,
2626
type FormSubmissionState
2727
} from '~/src/server/plugins/engine/types.js'
28+
import { getPaymentApiKey } from '~/src/server/plugins/payment/helper.js'
2829
import { PaymentService } from '~/src/server/plugins/payment/service.js'
2930

3031
export class PaymentField extends FormComponent {
@@ -48,7 +49,8 @@ export class PaymentField extends FormComponent {
4849
amount: joi.number().required(),
4950
description: joi.string().required(),
5051
uuid: joi.string().uuid().required(),
51-
isLive: joi.boolean().required(),
52+
formId: joi.string().required(),
53+
isLivePayment: joi.boolean().required(),
5254
preAuth: joi
5355
.object({
5456
status: joi
@@ -157,8 +159,11 @@ export class PaymentField extends FormComponent {
157159
h: FormResponseToolkit,
158160
args: PaymentDispatcherArgs
159161
): Promise<unknown> {
160-
const { isLive } = args
161-
const paymentService = new PaymentService({ isLive })
162+
const isLivePayment = args.isLive && !args.isPreview
163+
const formId = args.controller.model.formId
164+
const apiKeyValue = getPaymentApiKey(isLivePayment, formId)
165+
166+
const paymentService = new PaymentService(apiKeyValue)
162167

163168
// 1. Generate UUID token
164169
const uuid = randomUUID()
@@ -171,7 +176,6 @@ export class PaymentField extends FormComponent {
171176
const amount = options.amount ?? 0
172177
const description = options.description ?? ''
173178

174-
const formId = model.formId
175179
const slug = `/${model.basePath}`
176180

177181
// 2. Build the return URL for GOV.UK Pay
@@ -196,14 +200,15 @@ export class PaymentField extends FormComponent {
196200
// 4. Store session data for the return route to use
197201
const sessionData: PaymentSessionData = {
198202
uuid,
203+
formId,
199204
reference,
200205
amount,
201206
description,
202207
paymentId: payment.paymentId,
203208
componentName,
204209
returnUrl: summaryUrl,
205210
failureUrl: paymentPageUrl,
206-
isLive
211+
isLivePayment
207212
}
208213

209214
request.yar.set(`payment-${uuid}`, sessionData)
@@ -237,8 +242,9 @@ export class PaymentField extends FormComponent {
237242
return
238243
}
239244

240-
const { paymentId, isLive } = paymentState
241-
const paymentService = new PaymentService({ isLive })
245+
const { paymentId, isLivePayment, formId } = paymentState
246+
const apiKey = getPaymentApiKey(isLivePayment, formId)
247+
const paymentService = new PaymentService(apiKey)
242248

243249
// Verify payment is still in capturable state
244250
const status = await paymentService.getPaymentStatus(paymentId)
@@ -309,19 +315,21 @@ export interface PaymentDispatcherArgs {
309315
component: PaymentField
310316
sourceUrl: string
311317
isLive: boolean
318+
isPreview: boolean
312319
}
313320

314321
/**
315322
* Session data stored when dispatching to GOV.UK Pay
316323
*/
317324
export interface PaymentSessionData {
318325
uuid: string
326+
formId: string
319327
reference: string
320328
amount: number
321329
description: string
322330
paymentId: string
323331
componentName: string
324332
returnUrl: string
325333
failureUrl: string
326-
isLive: boolean
334+
isLivePayment: boolean
327335
}

src/server/plugins/engine/components/PaymentField.types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ export interface PaymentState {
77
amount: number
88
description: string
99
uuid: string
10-
isLive: boolean
10+
formId: string
11+
isLivePayment: boolean
1112
capture?: {
1213
status: 'success' | 'failed'
1314
createdAt: string

src/server/plugins/engine/pageControllers/QuestionPageController.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -619,15 +619,16 @@ export class QuestionPageController extends PageController {
619619
request.yar.clear(EXTERNAL_STATE_APPENDAGE)
620620

621621
// Determine if this is a live form (not preview/draft)
622-
const { state } = checkFormStatus(request.params)
622+
const { state, isPreview } = checkFormStatus(request.params)
623623
const isLive = state === FormStatus.Live
624624

625625
return await selectedComponent.dispatcher(request, h, {
626626
component,
627627
controller: this,
628628
sourceUrl: request.url.toString(),
629629
actionArgs: args,
630-
isLive
630+
isLive,
631+
isPreview
631632
})
632633
}
633634

src/server/plugins/engine/routes/payment.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { StatusCodes } from 'http-status-codes'
33
import Joi from 'joi'
44

55
import { EXTERNAL_STATE_APPENDAGE } from '~/src/server/constants.js'
6+
import { getPaymentApiKey } from '~/src/server/plugins/payment/helper.js'
67
import { PaymentService } from '~/src/server/plugins/payment/service.js'
78

89
export const PAYMENT_RETURN_PATH = '/payment-callback'
@@ -22,7 +23,8 @@ function flashComponentState(request, session, paymentId) {
2223
amount: session.amount,
2324
description: session.description,
2425
uuid: session.uuid,
25-
isLive: session.isLive,
26+
formId: session.formId,
27+
isLivePayment: session.isLivePayment,
2628
preAuth: {
2729
status: 'success',
2830
createdAt: new Date().toISOString()
@@ -69,13 +71,14 @@ function getReturnRoute() {
6971
}
7072

7173
// 2. Get payment status from GOV.UK Pay
72-
const { paymentId, isLive } = session
74+
const { paymentId, isLivePayment, formId } = session
7375

7476
if (!paymentId) {
7577
throw Boom.badRequest('No paymentId in session')
7678
}
7779

78-
const paymentService = new PaymentService({ isLive })
80+
const apiKey = getPaymentApiKey(isLivePayment, formId)
81+
const paymentService = new PaymentService(apiKey)
7982
const paymentStatus = await paymentService.getPaymentStatus(paymentId)
8083

8184
// 3. Handle different payment states based on GOV.UK Pay status lifecycle
@@ -151,14 +154,15 @@ function getReturnRoute() {
151154
* Payment session data stored when dispatching to GOV.UK Pay
152155
* @typedef {object} PaymentSessionData
153156
* @property {string} uuid - unique identifier for this payment attempt
157+
* @property {string} formId - id of the form
154158
* @property {string} reference - form reference number
155159
* @property {number} amount - amount in pounds
156160
* @property {string} description - payment description
157161
* @property {string} paymentId - GOV.UK Pay payment ID
158162
* @property {string} componentName - name of the PaymentField component
159163
* @property {string} returnUrl - URL to redirect to after successful payment
160164
* @property {string} failureUrl - URL to redirect to after failed/cancelled payment
161-
* @property {boolean} isLive - whether the payment is using live API key
165+
* @property {boolean} isLivePayment - whether the payment is using live API key
162166
*/
163167

164168
/**

src/server/plugins/engine/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,7 @@ export interface ExternalArgs {
399399
sourceUrl: string
400400
actionArgs: Record<string, string>
401401
isLive: boolean
402+
isPreview: boolean
402403
}
403404

404405
export interface PostcodeLookupExternalArgs extends ExternalArgs {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { config } from '~/src/config/index.js'
2+
3+
/**
4+
* Determine which payment API key value to use.
5+
* If a non-live non-preview form, use the TEST API key value.
6+
* If a live (non-preview) form, read the API key value specific to that form.
7+
* @param {boolean} isLivePayment - true if this is a live payment (as opposed to a test one)
8+
* @param {string} formId - id of the form
9+
* @returns {string}
10+
*/
11+
export function getPaymentApiKey(isLivePayment, formId) {
12+
const apiKeyValue = isLivePayment
13+
? process.env[`PAYMENT_PROVIDER_API_KEY_LIVE_${formId}`]
14+
: config.get('paymentProviderApiKeyTest')
15+
16+
if (!apiKeyValue) {
17+
throw new Error(
18+
`Missing payment api key for ${isLivePayment ? 'live' : 'test'} form id ${formId}`
19+
)
20+
}
21+
return apiKeyValue
22+
}

src/server/plugins/payment/service.js

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { config } from '~/src/config/index.js'
21
import { createLogger } from '~/src/server/common/helpers/logging/logger.js'
32
import { get, post, postJson } from '~/src/server/services/httpService.js'
43

@@ -17,30 +16,15 @@ function getAuthHeaders(apiKey) {
1716
}
1817
}
1918

20-
/**
21-
* Gets the fallback API key from global config
22-
* @param {boolean} isLive
23-
* @returns {string}
24-
*/
25-
function getFallbackApiKey(isLive) {
26-
return /** @type {string} */ (
27-
isLive
28-
? config.get('paymentProviderApiKeyLive')
29-
: config.get('paymentProviderApiKeyTest')
30-
)
31-
}
32-
3319
export class PaymentService {
3420
/** @type {string} */
3521
#apiKey
3622

3723
/**
38-
* @param {object} options
39-
* @param {string} [options.apiKey] - API key to use (if not provided, falls back to global config)
40-
* @param {boolean} [options.isLive] - whether to use live API key (only used if apiKey not provided)
24+
* @param {string} apiKey - API key to use (global config for test value, per-form config for live value)
4125
*/
42-
constructor({ apiKey, isLive = false } = {}) {
43-
this.#apiKey = apiKey ?? getFallbackApiKey(isLive)
26+
constructor(apiKey) {
27+
this.#apiKey = apiKey
4428
}
4529

4630
/**

0 commit comments

Comments
 (0)