Skip to content

Commit

Permalink
CIV-14068 Additional fee payment confirmation screens (#4201)
Browse files Browse the repository at this point in the history
* CIV-14064 Pay additional fee

* CIV-14064 Fix lint issues

* CIV-14064 Fix missing fields

* CIV-14064 Fix test

* CIV-14064 Fix test

* CIV-14068 Implement new content for payment confirmation

* CIV-14068 Fix lint issues

* CIV-14068 Add missing GA fields

* CIV-14068 Fix payment unsuccessful repayment button

* CIV-14068 Fix lint

* CIV-13677 Payment sync warning

Also change additional fee payment urls, and add mock
pages for a11y tests

* CIV-13677 Fix lint

* CIV-13677 Fix test

* CIV-13677 Add test for payment success sync

* CIV-13677 Remove empty lines

* CIV-13677 Fix a11y tests

* CIV-14068 Use additional payment service ref

Use additional payment service ref field to check if
citizen is paying for additional payment

* CIV-14064 Fix typo in text

---------

Co-authored-by: Manish Garg <[email protected]>
Co-authored-by: pliao-hmcts <[email protected]>
  • Loading branch information
3 people authored Jul 24, 2024
1 parent 4fa83dd commit a2d3a1a
Show file tree
Hide file tree
Showing 39 changed files with 2,306 additions and 149 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export interface CcdGeneralApplicationPBADetails {
fee: CcdFee,
paymentDetails: CcdPaymentDetails,
additionalPaymentDetails?: CcdPaymentDetails,
serviceRequestReference: string,
additionalPaymentServiceRef?: string,
}

export interface CcdFee {
code: string,
version: string,
calculatedAmountInPence: string,
}

export interface CcdPaymentDetails {
status: string,
reference: string,
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
CcdGeneralApplicationStatementOfTruth,
} from 'models/ccdGeneralApplication/ccdGeneralApplicationStatementOfTruth';
import {CcdGeneralApplicationAddlDocument} from 'models/ccdGeneralApplication/ccdGeneralApplicationAddlDocument';
import {CcdGeneralApplicationPBADetails} from 'models/ccdGeneralApplication/ccdGeneralApplicationPBADetails';

export class ApplicationResponse {
id: string;
Expand Down Expand Up @@ -52,5 +53,7 @@ export interface CCDApplication extends ApplicationUpdate {
gaAddlDoc: CcdGeneralApplicationAddlDocument[];
generalAppHearingDetails: CcdGeneralApplicationHearingDetails;
generalAppStatementOfTruth: CcdGeneralApplicationStatementOfTruth;
generalAppPBADetails: CcdGeneralApplicationPBADetails;
applicationFeeAmountInPence: string;
parentClaimantIsApplicant: YesNoUpperCamelCase;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
export enum ApplicationState {
AWAITING_RESPONDENT_RESPONSE = 'AWAITING_RESPONDENT_RESPONSE',
AWAITING_APPLICATION_PAYMENT = 'AWAITING_APPLICATION_PAYMENT',
APPLICATION_SUBMITTED_AWAITING_JUDICIAL_DECISION = 'APPLICATION_SUBMITTED_AWAITING_JUDICIAL_DECISION'
APPLICATION_SUBMITTED_AWAITING_JUDICIAL_DECISION = 'APPLICATION_SUBMITTED_AWAITING_JUDICIAL_DECISION',
APPLICATION_ADD_PAYMENT = 'APPLICATION_ADD_PAYMENT',
}

export enum ApplicationStatus {
Expand Down
5 changes: 4 additions & 1 deletion src/main/common/models/generalApplication/gaHelpWithFees.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ export class GaHelpWithFees {
applyHelpWithFees?: YesNo;
helpWithFeesRequested: string;
helpFeeReferenceNumberForm?: ApplyHelpFeesReferenceForm;
applyAdditionalHelpWithFees?: YesNo;

constructor(applyHelpWithFees?: YesNo, helpWithFeesRequested?: string, helpFeeReferenceNumberForm?: ApplyHelpFeesReferenceForm) {
constructor(applyHelpWithFees?: YesNo, helpWithFeesRequested?: string, helpFeeReferenceNumberForm?: ApplyHelpFeesReferenceForm,
applyAdditionalHelpWithFees?: YesNo) {
this.applyHelpWithFees = applyHelpWithFees;
this.helpWithFeesRequested = helpWithFeesRequested;
this.helpFeeReferenceNumberForm = helpFeeReferenceNumberForm;
this.applyAdditionalHelpWithFees = applyAdditionalHelpWithFees;
}
}
4 changes: 4 additions & 0 deletions src/main/common/utils/urlFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ export function constructResponseUrlWithIdParams(id: string, path: string): stri
return path.replace(/(:id)/i, id);
}

export function constructResponseUrlWithIdAndAppIdParams(id: string, appId: string, path: string): string{
return path.replace(/(:id)/i, id).replace(/(:appId)/i, appId);
}

export function constructUrlWithNotEligibleReason(path: string, reason: NotEligibleReason): string {
return `${path}?reason=${reason}`;
}
Expand Down
24 changes: 23 additions & 1 deletion src/main/modules/i18n/locales/cy.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@
"MICRO_TEXT": {
"HEARING_FEE": "Ffi gwrandawiad",
"CLAIM_FEE": "Ffi’r hawliad",
"APPLICATION_FEE": "Application fee"
"APPLICATION_FEE": "Application fee",
"ADDITIONAL_APPLICATION_FEE" : "Additional application fee"
},
"CHECKBOX_FIELDS": {
"COUNCIL_TAX_AND_COMMUNITY_CHARGE": "Treth Cyngor",
Expand Down Expand Up @@ -2575,6 +2576,21 @@
"WHY_NOT_ACCEPT": "Why do you not accept the defendant's offer?",
"WHY_NOT_ACCEPT_2": "Why you do not accept the defendant's offer?"
},
"ADDITIONAL_FEE": {
"CLOSE_AND_RETURN": "Close and return to case details",
"COURT_DECIDED": "The court has decided that your application must be submitted 'with notice'. This means that the other parties will receive a copy of the application and will be able to respond to what you have said.",
"DO_NEXT": "What you need to do next",
"HWF_LINK": "Help with Fees",
"IF_NOT_PAY": "If you do not pay the fee, your application will not be considered.",
"LOW_INCOME": "If you're on a low income, receive benefits or have limited savings, you may be eligible for ",
"MAKE_PAYMENT": "Make the payment",
"MEET_CRITERIA": "If you meet the criteria, you can apply for this support when you make the payment.",
"PAGE_TITLE": "Additional fee",
"PAY_ADDITIONAL_FEE": "Pay the additional fee of {{additionalFee}}.",
"TITLE": "You must pay an additional fee",
"WITH_NOTICE_COST": "A 'with notice' application costs {{withNoticeCost}}. You've already paid {{alreadyPaid}}, but you'll now need to pay an additional fee to make up the full amount.",
"WITHOUT_NOTICE": "Your application was submitted without giving notice to the other parties."
},
"AGREE_TO_ORDER": {
"MADE_AWARE": "If you have not been made aware of this application by the other parties, select 'No'.",
"RESPOND_TO": "Respond to an application to {{applicationType}}",
Expand Down Expand Up @@ -2669,6 +2685,10 @@
},
"UNAVAILABLE_DATES": "Are there any dates when you cannot attend a hearing within the next 3 months"
},
"PAY_ADDITIONAL_FEE": {
"HEADING": "Additional application fee",
"WANT_TO_APPLY_HWF_TITLE": "Pay additional application fee"
},
"PAYMENT_FOR_APPLICATION": {
"APPLICATION_FEE": "Application fee to pay:",
"TITLE": "Paying for your application",
Expand Down Expand Up @@ -2851,6 +2871,7 @@
"TITLE": "Application hearing arrangements"
},
"GA_PAYMENT_SUCCESSFUL" : {
"ACCOUNT_NOT_UPDATED": "Your payment was successful but your account might not have been updated yet. Try refreshing your account later on.",
"APPLICATION_SUBMITTED": "Application Submitted",
"CHOOSEN_NOT_TO_INFORM_OTHER_PARTY_PARA_1": "If you have not provided a valid reason, a judge may order that the other parties should be informed. If this happens, you'll need to pay an additional fee and your application may also be delayed.",
"CHOOSEN_NOT_TO_INFORM_OTHER_PARTY_PARA_2": "If a judge agrees that the other parties should not be informed, the other parties will not be able to respond to the application before the judge makes a decision.",
Expand All @@ -2862,6 +2883,7 @@
"WHAT_HAPPENS_NEXT_PARA_3": "You'll receive a notification letting you know if an application hearing has been scheduled.",
"WHAT_HAPPENS_NEXT_PARA_4": "If you've made an application to change a hearing date, the trial or hearing will go ahead as planned on the original date unless a judge makes an order changing the date of the hearing.",
"WHAT_HAPPENS_NEXT_PARA_5": "The other parties will have 5 working days to respond to your application. If you have a hearing in the next 10 days, your application will be treated urgently.",
"WHAT_HAPPENS_NEXT_PARA_6": "You may be required to attend a hearing so that the judge can consider your application with you and the other parties and decide what the next steps should be.",
"WHAT_HAPPENS_NEXT": "What happens next"
},
"HEARING_SUPPORT": {
Expand Down
26 changes: 24 additions & 2 deletions src/main/modules/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@
"MICRO_TEXT": {
"HEARING_FEE": "Hearing fee",
"CLAIM_FEE": "Claim fee",
"APPLICATION_FEE" : "Application fee"
"APPLICATION_FEE" : "Application fee",
"ADDITIONAL_APPLICATION_FEE" : "Additional application fee"
},
"CHECKBOX_FIELDS": {
"COUNCIL_TAX_AND_COMMUNITY_CHARGE": "Council Tax or Community Charge",
Expand Down Expand Up @@ -2575,6 +2576,21 @@
"WHY_NOT_ACCEPT": "Why do you not accept the defendant's offer?",
"WHY_NOT_ACCEPT_2": "Why you do not accept the defendant's offer?"
},
"ADDITIONAL_FEE": {
"CLOSE_AND_RETURN": "Close and return to case details",
"COURT_DECIDED": "The court has decided that your application must be submitted 'with notice'. This means that the other parties will receive a copy of the application and will be able to respond to what you have said.",
"DO_NEXT": "What you need to do next",
"HWF_LINK": "Help with Fees",
"IF_NOT_PAY": "If you do not pay the fee, your application will not be considered.",
"LOW_INCOME": "If you're on a low income, receive benefits or have limited savings, you may be eligible for ",
"MAKE_PAYMENT": "Make the payment",
"MEET_CRITERIA": "If you meet the criteria, you can apply for this support when you make the payment.",
"PAGE_TITLE": "Additional fee",
"PAY_ADDITIONAL_FEE": "Pay the additional fee of {{additionalFee}}.",
"TITLE": "You must pay an additional fee",
"WITH_NOTICE_COST": "A 'with notice' application costs {{withNoticeCost}}. You've already paid {{alreadyPaid}}, but you'll now need to pay an additional fee to make up the full amount.",
"WITHOUT_NOTICE": "Your application was submitted without giving notice to the other parties."
},
"AGREE_TO_ORDER": {
"MADE_AWARE": "If you have not been made aware of this application by the other parties, select 'No'.",
"RESPOND_TO": "Respond to an application to {{applicationType}}",
Expand Down Expand Up @@ -2669,6 +2685,10 @@
},
"UNAVAILABLE_DATES": "Are there any dates when you cannot attend a hearing within the next 3 months"
},
"PAY_ADDITIONAL_FEE": {
"HEADING": "Additional application fee",
"WANT_TO_APPLY_HWF_TITLE": "Pay additional application fee"
},
"PAYMENT_FOR_APPLICATION": {
"APPLICATION_FEE": "Application fee to pay:",
"TITLE": "Paying for your application",
Expand Down Expand Up @@ -2851,6 +2871,7 @@
"TITLE": "Application hearing arrangements"
},
"GA_PAYMENT_SUCCESSFUL" : {
"ACCOUNT_NOT_UPDATED": "Your payment was successful but your account might not have been updated yet. Try refreshing your account later on.",
"APPLICATION_SUBMITTED":"Application Submitted",
"CHOOSEN_NOT_TO_INFORM_OTHER_PARTY_PARA_1": "If you have not provided a valid reason, a judge may order that the other parties should be informed. If this happens, you'll need to pay an additional fee and your application may also be delayed.",
"CHOOSEN_NOT_TO_INFORM_OTHER_PARTY_PARA_2": "If a judge agrees that the other parties should not be informed, the other parties will not be able to respond to the application before the judge makes a decision.",
Expand All @@ -2862,6 +2883,7 @@
"WHAT_HAPPENS_NEXT_PARA_3": "You'll receive a notification letting you know if an application hearing has been scheduled.",
"WHAT_HAPPENS_NEXT_PARA_4" : "If you've made an application to change a hearing date, the trial or hearing will go ahead as planned on the original date unless a judge makes an order changing the date of the hearing.",
"WHAT_HAPPENS_NEXT_PARA_5": "The other parties will have 5 working days to respond to your application. If you have a hearing in the next 10 days, your application will be treated urgently.",
"WHAT_HAPPENS_NEXT_PARA_6": "You may be required to attend a hearing so that the judge can consider your application with you and the other parties and decide what the next steps should be.",
"WHAT_HAPPENS_NEXT": "What happens next"
},
"HEARING_SUPPORT": {
Expand Down Expand Up @@ -3131,7 +3153,7 @@
"CASE_PROGRESSION": {
"HEARING_FEE": {
"APPLY_HELP_FEE_SELECTION": {
"LINK_BEFORE": "If you're on a low income, have limited savings or are claiming benefits, you may able to get ",
"LINK_BEFORE": "If you're on a low income, have limited savings or are claiming benefits, you may be able to get ",
"LINK_TEXT": "help with fees (opens in a new tab).",
"PARAGRAPH": "If you meet the criteria, you may get support to pay some or all of the fee.",
"QUESTION_TITLE": "Do you want to apply for help with fees?",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {NextFunction, RequestHandler, Response, Router} from 'express';
import {
DASHBOARD_CLAIMANT_URL,
DEFENDANT_SUMMARY_URL,
GA_PAY_ADDITIONAL_FEE_URL,
GA_APPLY_HELP_ADDITIONAL_FEE_SELECTION_URL,
} from 'routes/urls';
import {AppRequest} from 'common/models/AppRequest';
import {getClaimById} from 'modules/utilityService';
import {constructResponseUrlWithIdParams, constructResponseUrlWithIdAndAppIdParams} from 'common/utils/urlFormatter';
import {getApplicationFromGAService} from 'services/features/generalApplication/generalApplicationService';
import {ApplicationResponse} from 'models/generalApplication/applicationResponse';
import {convertToPoundsFilter, currencyFormatWithNoTrailingZeros} from 'common/utils/currencyFormat';

const additionalFeeController = Router();
const viewPath = 'features/generalApplication/additionalFee/additional-fee';

additionalFeeController.get(GA_PAY_ADDITIONAL_FEE_URL, (async (req: AppRequest, res: Response, next: NextFunction) => {
try {
const claimId = req.params.id;
const appId = req.params.appId;
const claim = await getClaimById(claimId, req, true);
const applicationResponse: ApplicationResponse = await getApplicationFromGAService(req, appId);
const alreadyPaidPounds = convertToPoundsFilter(applicationResponse?.case_data?.applicationFeeAmountInPence);
const additionalFeePounds = convertToPoundsFilter(applicationResponse?.case_data?.generalAppPBADetails?.fee?.calculatedAmountInPence);
const alreadyPaid = currencyFormatWithNoTrailingZeros(alreadyPaidPounds);
const additionalFee = currencyFormatWithNoTrailingZeros(additionalFeePounds);
const withNoticeCost = currencyFormatWithNoTrailingZeros(alreadyPaidPounds + additionalFeePounds);
const urlNextView = constructResponseUrlWithIdAndAppIdParams(claimId, appId, GA_APPLY_HELP_ADDITIONAL_FEE_SELECTION_URL);
res.render(viewPath, {
withNoticeCost,
alreadyPaid,
additionalFee,
urlNextView,
dashboardUrl: constructResponseUrlWithIdParams(claimId, claim.isClaimant() ? DASHBOARD_CLAIMANT_URL : DEFENDANT_SUMMARY_URL),
});
} catch (error) {
next(error);
}
}) as RequestHandler);

export default additionalFeeController;
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {NextFunction, Request, RequestHandler, Response, Router} from 'express';
import {DASHBOARD_CLAIMANT_URL, GA_PAY_ADDITIONAL_FEE_URL, GA_APPLY_HELP_ADDITIONAL_FEE_SELECTION_URL} from 'routes/urls';
import {GenericForm} from 'form/models/genericForm';
import {constructResponseUrlWithIdAndAppIdParams, constructResponseUrlWithIdParams} from 'common/utils/urlFormatter';
import {GenericYesNo} from 'form/models/genericYesNo';
import {Claim} from 'models/claim';
import {getRedirectUrl} from 'services/features/generalApplication/fee/helpWithFeeService';
import {getClaimById} from 'modules/utilityService';
import {t} from 'i18next';
import {AppRequest} from 'models/AppRequest';
import {getHelpAdditionalFeeSelectionPageContents, getButtonsContents}
from 'services/features/generalApplication/additionalFee/helpWithAdditionalFeeContent';
import {saveHelpWithFeesDetails} from 'services/features/generalApplication/generalApplicationService';
import {generateRedisKey} from 'modules/draft-store/draftStoreService';

const applyHelpWithApplicationFeeViewPath = 'features/generalApplication/additionalFee/help-with-additional-fee';
const payAdditionalFeeController = Router();
const hwfPropertyName = 'applyAdditionalHelpWithFees';

async function renderView(res: Response, req: AppRequest | Request, form: GenericForm<GenericYesNo>, claimId: string, redirectUrl: string, lng: string) {
const appId = req.params.appId;
if (!form) {
const claim: Claim = await getClaimById(claimId, req, true);
form = new GenericForm(new GenericYesNo(claim.generalApplication?.helpWithFees?.applyAdditionalHelpWithFees));
}
const backLinkUrl = constructResponseUrlWithIdAndAppIdParams(claimId, appId, GA_PAY_ADDITIONAL_FEE_URL);
res.render(applyHelpWithApplicationFeeViewPath,
{
form,
backLinkUrl,
redirectUrl,
applyHelpWithFeeSelectionContents: getHelpAdditionalFeeSelectionPageContents(lng),
applyHelpWithFeeSelectionButtonContents: getButtonsContents(claimId),
});
}

payAdditionalFeeController.get(GA_APPLY_HELP_ADDITIONAL_FEE_SELECTION_URL, (async (req: AppRequest, res: Response, next: NextFunction) => {
try {
const lng = req.query.lang ? req.query.lang : req.cookies.lang;
const claimId = req.params.id;
const redirectUrl = constructResponseUrlWithIdParams(claimId, DASHBOARD_CLAIMANT_URL);
await renderView(res, req, null, claimId, redirectUrl, lng);
}catch (error) {
next(error);
}
}) as RequestHandler);

payAdditionalFeeController.post(GA_APPLY_HELP_ADDITIONAL_FEE_SELECTION_URL, (async (req: AppRequest | Request, res: Response, next: NextFunction) => {
try {
const lng = req.query.lang ? req.query.lang : req.cookies.lang;
const claimId = req.params.id;
const form = new GenericForm(new GenericYesNo(req.body.option, t('ERRORS.VALID_YES_NO_SELECTION_UPPER', { lng })));
await form.validate();
if (form.hasErrors()) {
const redirectUrl = constructResponseUrlWithIdParams(claimId, GA_APPLY_HELP_ADDITIONAL_FEE_SELECTION_URL);
await renderView(res, req, form, claimId, redirectUrl, lng);
} else {
const redisKey = generateRedisKey(<AppRequest>req);
await saveHelpWithFeesDetails(redisKey, req.body.option, hwfPropertyName);
const redirectUrl = await getRedirectUrl(claimId, form.model, <AppRequest>req);
res.redirect(redirectUrl);
}
}catch (error) {
next(error);
}
}) as RequestHandler);
export default payAdditionalFeeController;
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {GenericForm} from 'form/models/genericForm';
import {constructResponseUrlWithIdParams} from 'common/utils/urlFormatter';
import {GenericYesNo} from 'form/models/genericYesNo';
import {Claim} from 'models/claim';
import {getRedirectUrl} from 'services/features/generalApplication/applicationFee/helpWithApplicationFeeService';
import {getRedirectUrl} from 'services/features/generalApplication/fee/helpWithFeeService';
import {getClaimById} from 'modules/utilityService';
import {t} from 'i18next';
import {AppRequest} from 'models/AppRequest';
Expand Down
Loading

0 comments on commit a2d3a1a

Please sign in to comment.