Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CIV-14068 Additional fee payment confirmation screens #4201

Merged
merged 25 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0cadeca
CIV-14064 Pay additional fee
paul-pearson Jul 11, 2024
5dc9001
CIV-14064 Fix lint issues
paul-pearson Jul 11, 2024
38b152f
CIV-14064 Merge master
paul-pearson Jul 11, 2024
d2f814d
CIV-14064 Fix missing fields
paul-pearson Jul 11, 2024
ea1d897
CIV-14064 Fix test
paul-pearson Jul 11, 2024
42053ff
CIV-14064 Fix test
paul-pearson Jul 11, 2024
2881ce9
CIV-14064 Merge master
paul-pearson Jul 15, 2024
2f3a9ec
CIV-14068 Implement new content for payment confirmation
paul-pearson Jul 16, 2024
4a8419c
Merge branch 'master' into feat/CIV-14068
paul-pearson Jul 16, 2024
0644736
CIV-14068 Fix lint issues
paul-pearson Jul 16, 2024
811f110
CIV-14068 Add missing GA fields
paul-pearson Jul 16, 2024
aa3576c
CIV-14068 Fix payment unsuccessful repayment button
paul-pearson Jul 17, 2024
921f867
CIV-14068 Fix lint
paul-pearson Jul 17, 2024
b24cd57
CIV-13677 Payment sync warning
paul-pearson Jul 18, 2024
a6385c7
CIV-13677 Fix lint
paul-pearson Jul 18, 2024
505b9f5
CIV-13677 Fix test
paul-pearson Jul 18, 2024
ca96eab
CIV-13677 Add test for payment success sync
paul-pearson Jul 18, 2024
123c19c
Merge branch 'master' into feat/CIV-14068
paul-pearson Jul 18, 2024
c5a53f7
CIV-13677 Remove empty lines
paul-pearson Jul 18, 2024
4b78d78
Merge branch 'feat/CIV-14068' of https://github.com/hmcts/civil-citiz…
paul-pearson Jul 18, 2024
6f87ff7
CIV-13677 Fix a11y tests
paul-pearson Jul 18, 2024
d4b711a
CIV-14068 Use additional payment service ref
paul-pearson Jul 18, 2024
69e79cb
Merge branch 'master' into feat/CIV-14068
manish14836 Jul 22, 2024
096a1fd
CIV-14064 Fix typo in text
paul-pearson Jul 23, 2024
3f80b83
Merge branch 'master' into feat/CIV-14068
pliao-hmcts Jul 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
24 changes: 23 additions & 1 deletion 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
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
Loading