@@ -48,6 +48,61 @@ export function getRoutes() {
4848 return [ getReturnRoute ( ) ]
4949}
5050
51+ /**
52+ * Validates session data and retrieves payment status
53+ * @param {Request } request - the request
54+ * @param {string } uuid - the payment UUID
55+ * @returns {Promise<{ session: PaymentSessionData, sessionKey: string, paymentStatus: GetPaymentResponse }> }
56+ */
57+ async function getPaymentContext ( request , uuid ) {
58+ const sessionKey = `${ PAYMENT_SESSION_PREFIX } ${ uuid } `
59+ const session = /** @type {PaymentSessionData | null } */ (
60+ request . yar . get ( sessionKey )
61+ )
62+
63+ if ( ! session ) {
64+ throw Boom . badRequest ( `No payment session found for uuid=${ uuid } ` )
65+ }
66+
67+ const { paymentId, isLivePayment, formId } = session
68+
69+ if ( ! paymentId ) {
70+ throw Boom . badRequest ( 'No paymentId in session' )
71+ }
72+
73+ const apiKey = getPaymentApiKey ( isLivePayment , formId )
74+ const paymentService = new PaymentService ( apiKey )
75+ const paymentStatus = await paymentService . getPaymentStatus ( paymentId )
76+
77+ return { session, sessionKey, paymentStatus }
78+ }
79+
80+ /**
81+ * Handles successful payment states (capturable/success)
82+ * @param {Request } request - the request
83+ * @param {ResponseToolkit } h - the response toolkit
84+ * @param {PaymentSessionData } session - the session data
85+ * @param {string } sessionKey - the session key
86+ * @param {string } paymentId - the payment id
87+ */
88+ function handlePaymentSuccess ( request , h , session , sessionKey , paymentId ) {
89+ flashComponentState ( request , session , paymentId )
90+ request . yar . clear ( sessionKey )
91+ return h . redirect ( session . returnUrl ) . code ( StatusCodes . SEE_OTHER )
92+ }
93+
94+ /**
95+ * Handles failed/cancelled/error payment states
96+ * @param {Request } request - the request
97+ * @param {ResponseToolkit } h - the response toolkit
98+ * @param {PaymentSessionData } session - the session data
99+ * @param {string } sessionKey - the session key
100+ */
101+ function handlePaymentFailure ( request , h , session , sessionKey ) {
102+ request . yar . clear ( sessionKey )
103+ return h . redirect ( session . failureUrl ) . code ( StatusCodes . SEE_OTHER )
104+ }
105+
51106/**
52107 * Route handler for payment return URL
53108 * This is called when GOV.UK Pay redirects the user back after payment
@@ -59,83 +114,50 @@ function getReturnRoute() {
59114 path : PAYMENT_RETURN_PATH ,
60115 async handler ( request , h ) {
61116 const { uuid } = /** @type {{ uuid: string } } */ ( request . query )
62-
63- // 1. Get session data using the UUID as the key
64- const sessionKey = `${ PAYMENT_SESSION_PREFIX } ${ uuid } `
65- const session = /** @type {PaymentSessionData | null } */ (
66- request . yar . get ( sessionKey )
117+ const { session, sessionKey, paymentStatus } = await getPaymentContext (
118+ request ,
119+ uuid
67120 )
68121
69- if ( ! session ) {
70- throw Boom . badRequest ( `No payment session found for uuid=${ uuid } ` )
71- }
72-
73- // 2. Get payment status from GOV.UK Pay
74- const { paymentId, isLivePayment, formId } = session
75-
76- if ( ! paymentId ) {
77- throw Boom . badRequest ( 'No paymentId in session' )
78- }
79-
80- const apiKey = getPaymentApiKey ( isLivePayment , formId )
81- const paymentService = new PaymentService ( apiKey )
82- const paymentStatus = await paymentService . getPaymentStatus ( paymentId )
83-
84- // 3. Handle different payment states based on GOV.UK Pay status lifecycle
122+ // Handle different payment states based on GOV.UK Pay status lifecycle
85123 // @see https://docs.payments.service.gov.uk/api_reference/#payment-status-lifecycle
86124 const { status } = paymentStatus . state
87125
88126 switch ( status ) {
127+ // Pre-auth successful or already captured
89128 case 'capturable' :
90- // Pre-auth successful - flash the state and redirect to summary
91- flashComponentState ( request , session , paymentId )
92- request . yar . clear ( sessionKey )
93- return h . redirect ( session . returnUrl ) . code ( StatusCodes . SEE_OTHER )
94-
95129 case 'success' :
96- // Payment already captured (shouldn't happen with delayed_capture: true)
97- flashComponentState ( request , session , paymentId )
98- request . yar . clear ( sessionKey )
99- return h . redirect ( session . returnUrl ) . code ( StatusCodes . SEE_OTHER )
130+ return handlePaymentSuccess (
131+ request ,
132+ h ,
133+ session ,
134+ sessionKey ,
135+ session . paymentId
136+ )
100137
138+ // Payment failed, cancelled, or errored - redirect to retry
101139 case 'cancelled' :
102- // User cancelled payment (P0030) - redirect to payment page to retry
103- request . yar . clear ( sessionKey )
104- return h . redirect ( session . failureUrl ) . code ( StatusCodes . SEE_OTHER )
105-
106140 case 'failed' :
107- // Payment failed (P0010 rejected, P0020 expired, P0040 service cancelled, P0050 provider error)
108- // Redirect to payment page to retry
109- request . yar . clear ( sessionKey )
110- return h . redirect ( session . failureUrl ) . code ( StatusCodes . SEE_OTHER )
111-
112141 case 'error' :
113- // Technical error on GOV.UK Pay side - no funds taken
114- // Redirect to payment page to retry
115- request . yar . clear ( sessionKey )
116- return h . redirect ( session . failureUrl ) . code ( StatusCodes . SEE_OTHER )
142+ return handlePaymentFailure ( request , h , session , sessionKey )
117143
144+ // User came back too early - redirect back to GOV.UK Pay
118145 case 'created' :
119146 case 'started' :
120147 case 'submitted' : {
121- // User came back too early or payment still processing
122- // Redirect back to GOV.UK Pay to continue
123148 const nextUrl = paymentStatus . _links . next_url ?. href
124149
125- if ( nextUrl ) {
126- return h . redirect ( nextUrl ) . code ( StatusCodes . SEE_OTHER )
150+ if ( ! nextUrl ) {
151+ throw Boom . badRequest (
152+ `Payment in state '${ status } ' but no next_url available`
153+ )
127154 }
128155
129- throw Boom . badRequest (
130- `Payment in state '${ status } ' but no next_url available`
131- )
156+ return h . redirect ( nextUrl ) . code ( StatusCodes . SEE_OTHER )
132157 }
133158
134- default : {
135- // this should never be reached but Sonar will complain
136- const unknownStatus = /** @type {string } */ ( status )
137- throw Boom . internal ( `Unknown payment status: ${ unknownStatus } ` )
138- }
159+ default :
160+ throw Boom . internal ( `Unknown payment status: ${ String ( status ) } ` )
139161 }
140162 } ,
141163 options : {
@@ -166,7 +188,8 @@ function getReturnRoute() {
166188 */
167189
168190/**
169- * @import { Request, ServerRoute } from '@hapi/hapi'
191+ * @import { Request, ResponseToolkit, ServerRoute } from '@hapi/hapi'
170192 * @import { PaymentState } from '~/src/server/plugins/engine/components/PaymentField.types.js'
193+ * @import { GetPaymentResponse } from '~/src/server/plugins/payment/types.js'
171194 * @import { ExternalStateAppendage, FormState } from '~/src/server/plugins/engine/types.js'
172195 */
0 commit comments