From 0c3e29de7eb42fa04b6a7fe05e57a171e0041ea5 Mon Sep 17 00:00:00 2001 From: Bob Date: Thu, 1 May 2025 22:19:44 +0100 Subject: [PATCH 1/5] added invoice input field, changed styling, added placeholders and handled service call --- .../src/controllers/PaymentController.tsx | 47 +++++++++++++------ .../libs/web-common/src/styles/Payment.scss | 47 +++++++++++++++---- 2 files changed, 72 insertions(+), 22 deletions(-) diff --git a/packages/libs/web-common/src/controllers/PaymentController.tsx b/packages/libs/web-common/src/controllers/PaymentController.tsx index a1c7dd7138..dd054fe7e8 100644 --- a/packages/libs/web-common/src/controllers/PaymentController.tsx +++ b/packages/libs/web-common/src/controllers/PaymentController.tsx @@ -33,8 +33,12 @@ function AutoSubmitForm(props: AutoSubmitFormProps) { ); } -async function getFormData(amount: string) { - const url = webAppUrl + '/service/payment-form-content?amount=' + amount; +async function getFormData(amount: string, invoiceNumber?: string) { + const url = + webAppUrl + + '/service/payment-form-content?amount=' + + amount + + (invoiceNumber != null ? '&invoice_number=' + invoiceNumber : ''); const response = await fetch(url); if (!response.ok) { throw new Error('Pre-payment form service error'); @@ -44,7 +48,8 @@ async function getFormData(amount: string) { export default function PaymentController() { const [formData, setFormData] = useState(null); - const [amount, setAmount] = useState('0.00'); + const [amount, setAmount] = useState(''); + const [invoiceNumber, setInvoiceNumber] = useState(''); const [errorMessage, setErrorMessage] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); @@ -85,7 +90,7 @@ export default function PaymentController() { // (will only be visible for a short time, so potentially panic-inducing?) // setAmount(amountNum.toFixed(2)); - getFormData(amountNum.toFixed(2)) + getFormData(amountNum.toFixed(2), invoiceNumber) .then((formData) => { setFormData(formData); }) @@ -125,17 +130,30 @@ export default function PaymentController() {

{errorMessage}

-
-

- Please enter the amount from your invoice in USD:   - setAmount(e.target.value)} - /> -

+ +
+ + setAmount(e.target.value)} + />
+ +
+ + setInvoiceNumber(e.target.value)} + /> +
+
{isSubmitting && } +

* indicates required field

(Clicking the button will take you to secure.cybersource.com.)

diff --git a/packages/libs/web-common/src/styles/Payment.scss b/packages/libs/web-common/src/styles/Payment.scss index 4cc78e7ac1..166e62b2be 100644 --- a/packages/libs/web-common/src/styles/Payment.scss +++ b/packages/libs/web-common/src/styles/Payment.scss @@ -11,19 +11,50 @@ div.payment-container { margin-top: 3.5em; text-align: center; - div.amount { - p { - font-size: 142%; + div.form-row { + display: flex; + flex-direction: row; + justify-content: space-between; + width: 20em; + margin-left: auto; + margin-right: auto; + align-items: center; + margin-bottom: 1em; + font-size: 1.5em; + + label { + margin-bottom: 0.3em; + } + + input { + padding: 0.4em; + width: 12em; + border-radius: 0.25em; + border: 1px solid #666 !important; + + &.hasError { + border: 0.5px solid red !important; + } + } - input { - width: 5em; + input#amount { + text-align: right; + width: 6em; + } - &.hasError { - border: 0.5px solid red !important; - } + &.optional label { + color: #666; + } + + &.optional input { + border: 1px solid #eee !important; + + &::placeholder { + color: #666; } } } + div.button { input { width: 12em; From 84be415a177e6453b28dad791cfe0c1c2d7768ff Mon Sep 17 00:00:00 2001 From: Bob Date: Thu, 1 May 2025 22:27:52 +0100 Subject: [PATCH 2/5] simplify url building --- .../libs/web-common/src/controllers/PaymentController.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/libs/web-common/src/controllers/PaymentController.tsx b/packages/libs/web-common/src/controllers/PaymentController.tsx index dd054fe7e8..0b0d8c41bb 100644 --- a/packages/libs/web-common/src/controllers/PaymentController.tsx +++ b/packages/libs/web-common/src/controllers/PaymentController.tsx @@ -33,12 +33,14 @@ function AutoSubmitForm(props: AutoSubmitFormProps) { ); } -async function getFormData(amount: string, invoiceNumber?: string) { +// empty string invoiceNumber is a valid arg +async function getFormData(amount: string, invoiceNumber: string) { const url = webAppUrl + '/service/payment-form-content?amount=' + amount + - (invoiceNumber != null ? '&invoice_number=' + invoiceNumber : ''); + '&invoice_number=' + + invoiceNumber; const response = await fetch(url); if (!response.ok) { throw new Error('Pre-payment form service error'); From 790d512ba9208f89a1ecf121eb7c3d5c61ed956c Mon Sep 17 00:00:00 2001 From: Bob Date: Fri, 2 May 2025 11:31:00 +0100 Subject: [PATCH 3/5] generalise error message state and validate invoiceNumber and move 'general' errors to above submit button --- .../src/controllers/PaymentController.tsx | 84 +++++++++++++------ 1 file changed, 58 insertions(+), 26 deletions(-) diff --git a/packages/libs/web-common/src/controllers/PaymentController.tsx b/packages/libs/web-common/src/controllers/PaymentController.tsx index 0b0d8c41bb..413298d027 100644 --- a/packages/libs/web-common/src/controllers/PaymentController.tsx +++ b/packages/libs/web-common/src/controllers/PaymentController.tsx @@ -9,6 +9,8 @@ interface AutoSubmitFormProps { params: any; } +type ErrorKey = 'amount' | 'invoiceNumber' | 'general'; + function AutoSubmitForm(props: AutoSubmitFormProps) { // submit form as soon as it is rendered const formRef = useRef(null); @@ -52,7 +54,10 @@ export default function PaymentController() { const [formData, setFormData] = useState(null); const [amount, setAmount] = useState(''); const [invoiceNumber, setInvoiceNumber] = useState(''); - const [errorMessage, setErrorMessage] = useState(''); + // errorType => errorMessage + const [errors, setErrors] = useState>>( + {} + ); const [isSubmitting, setIsSubmitting] = useState(false); // If we're showing a persisted page from a back-button navigation @@ -73,41 +78,60 @@ export default function PaymentController() { const handleUserSubmit = () => { if (isSubmitting) return; - setIsSubmitting(true); - var amountNum: number = Number(removeCommaThousandSeparators(amount)); - if (isNaN(amountNum) || amountNum < 0.01) { - setErrorMessage( + // invoiceNumber validation + // see https://github.com/VEuPathDB/EbrcWebsiteCommon/blob/baf3e3c6936b309b6d677019351c1a5fe06c08f0/Model/src/main/java/org/eupathdb/common/service/CyberSourceFormService.java#L58 + + const invoiceNumberIsValid = + invoiceNumber === '' || invoiceNumber.match(/^[0-9a-zA-Z\-]+$/); + setErrors((errors) => ({ + ...errors, + invoiceNumber: invoiceNumberIsValid ? undefined : ( <> - You must enter a positive dollar amount.
- Do not use commas for decimals. + Invoice numbers may contain only A-Z a-z 0-9 and dash ('-') + characters. - ); + ), + })); + + // amount validation + var amountNum: number = Number(removeCommaThousandSeparators(amount)); + if (isNaN(amountNum) || amountNum < 0.01) { + setErrors((errors) => ({ + ...errors, + amount: ( + <> + You must enter a positive dollar amount.
+ Do not use commas for decimals. + + ), + })); setIsSubmitting(false); - } else { - setErrorMessage(''); + } else if (invoiceNumberIsValid) { + // submit to our service + setErrors({}); // console.log('Submitting form with payment amount $' + amountNum.toFixed(2)); - // optionally update UI with trimmed amount - // (will only be visible for a short time, so potentially panic-inducing?) - // setAmount(amountNum.toFixed(2)); - + setIsSubmitting(true); getFormData(amountNum.toFixed(2), invoiceNumber) .then((formData) => { setFormData(formData); }) .catch((error) => { console.error(error); - setErrorMessage( - <> - Cannot connect to payment system.
- Please{' '} - - let us know - {' '} - about this. - - ); + setErrors((errors) => ({ + ...errors, + general: ( + <> + Cannot connect to payment system.
+ Please{' '} + + let us know + {' '} + about this. + + ), + })); setIsSubmitting(false); }); } @@ -130,14 +154,14 @@ export default function PaymentController() {

-

{errorMessage}

+

{errors['amount']}

+
+

{errors['invoiceNumber']}

+
+
+
+

{errors['general']}

+
+
{isSubmitting && } Date: Fri, 2 May 2025 11:38:12 +0100 Subject: [PATCH 4/5] make sure invoice input gets red border --- .../src/controllers/PaymentController.tsx | 9 +++++---- .../libs/web-common/src/styles/Payment.scss | 18 +++++++++--------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/packages/libs/web-common/src/controllers/PaymentController.tsx b/packages/libs/web-common/src/controllers/PaymentController.tsx index 413298d027..866fd3228f 100644 --- a/packages/libs/web-common/src/controllers/PaymentController.tsx +++ b/packages/libs/web-common/src/controllers/PaymentController.tsx @@ -154,14 +154,14 @@ export default function PaymentController() {

-

{errors['amount']}

+

{errors.amount}

-

{errors['invoiceNumber']}

+

{errors.invoiceNumber}

-

{errors['general']}

+

{errors.general}

diff --git a/packages/libs/web-common/src/styles/Payment.scss b/packages/libs/web-common/src/styles/Payment.scss index 166e62b2be..329aa8947d 100644 --- a/packages/libs/web-common/src/styles/Payment.scss +++ b/packages/libs/web-common/src/styles/Payment.scss @@ -26,6 +26,14 @@ div.payment-container { margin-bottom: 0.3em; } + &.optional input { + border: 1px solid #eee !important; + + &::placeholder { + color: #666; + } + } + input { padding: 0.4em; width: 12em; @@ -33,7 +41,7 @@ div.payment-container { border: 1px solid #666 !important; &.hasError { - border: 0.5px solid red !important; + border: 0.5px solid red !important; /* more important than the .optional grey */ } } @@ -45,14 +53,6 @@ div.payment-container { &.optional label { color: #666; } - - &.optional input { - border: 1px solid #eee !important; - - &::placeholder { - color: #666; - } - } } div.button { From 059c6fbdb0c14b288d076f8610065229591779f3 Mon Sep 17 00:00:00 2001 From: Bob Date: Fri, 2 May 2025 12:24:36 +0100 Subject: [PATCH 5/5] error cancellation logic corrected; dollar symbol removed --- .../src/controllers/PaymentController.tsx | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/packages/libs/web-common/src/controllers/PaymentController.tsx b/packages/libs/web-common/src/controllers/PaymentController.tsx index 866fd3228f..61f577a9a9 100644 --- a/packages/libs/web-common/src/controllers/PaymentController.tsx +++ b/packages/libs/web-common/src/controllers/PaymentController.tsx @@ -94,24 +94,28 @@ export default function PaymentController() { ), })); - // amount validation - var amountNum: number = Number(removeCommaThousandSeparators(amount)); - if (isNaN(amountNum) || amountNum < 0.01) { - setErrors((errors) => ({ - ...errors, - amount: ( - <> - You must enter a positive dollar amount.
- Do not use commas for decimals. - - ), - })); - setIsSubmitting(false); - } else if (invoiceNumberIsValid) { + // amount validation and remove any leading dollar sign + const amountNum: number = Number( + removeCommaThousandSeparators(amount).replace(/^\$/, '') + ); + const amountIsValid = !isNaN(amountNum) && amountNum >= 0.01; + + setErrors((errors) => ({ + ...errors, + amount: amountIsValid ? undefined : ( + <> + You must enter a positive dollar amount.
+ Do not use commas for decimals. + + ), + })); + + if (amountIsValid && invoiceNumberIsValid) { // submit to our service + // clear all errors including 'general' setErrors({}); - // console.log('Submitting form with payment amount $' + amountNum.toFixed(2)); + // console.log('Submitting form with payment amount $' + amountNum.toFixed(2)); setIsSubmitting(true); getFormData(amountNum.toFixed(2), invoiceNumber) .then((formData) => {