diff --git a/packages/libs/web-common/src/controllers/PaymentController.tsx b/packages/libs/web-common/src/controllers/PaymentController.tsx index a1c7dd7138..61f577a9a9 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); @@ -33,8 +35,14 @@ function AutoSubmitForm(props: AutoSubmitFormProps) { ); } -async function getFormData(amount: string) { - const url = webAppUrl + '/service/payment-form-content?amount=' + amount; +// empty string invoiceNumber is a valid arg +async function getFormData(amount: string, invoiceNumber: string) { + const url = + webAppUrl + + '/service/payment-form-content?amount=' + + amount + + '&invoice_number=' + + invoiceNumber; const response = await fetch(url); if (!response.ok) { throw new Error('Pre-payment form service error'); @@ -44,8 +52,12 @@ async function getFormData(amount: string) { export default function PaymentController() { const [formData, setFormData] = useState(null); - const [amount, setAmount] = useState('0.00'); - const [errorMessage, setErrorMessage] = useState(''); + const [amount, setAmount] = useState(''); + const [invoiceNumber, setInvoiceNumber] = useState(''); + // errorType => errorMessage + const [errors, setErrors] = useState>>( + {} + ); const [isSubmitting, setIsSubmitting] = useState(false); // If we're showing a persisted page from a back-button navigation @@ -66,41 +78,64 @@ 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 : ( + <> + Invoice numbers may contain only A-Z a-z 0-9 and dash ('-') + characters. + + ), + })); + + // 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. - ); - setIsSubmitting(false); - } else { - setErrorMessage(''); - // 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)); + if (amountIsValid && invoiceNumberIsValid) { + // submit to our service + // clear all errors including 'general' + setErrors({}); - getFormData(amountNum.toFixed(2)) + // console.log('Submitting form with payment amount $' + 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); }); } @@ -123,19 +158,41 @@ export default function PaymentController() {

-

{errorMessage}

+

{errors.amount}

-
-

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

+ +
+ + setAmount(e.target.value)} + />
+ +
+

{errors.invoiceNumber}

+
+ +
+ + setInvoiceNumber(e.target.value)} + /> +
+ +
+

{errors.general}

+
+
{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..329aa8947d 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; + } + + &.optional input { + border: 1px solid #eee !important; + + &::placeholder { + color: #666; + } + } - input { - width: 5em; + input { + padding: 0.4em; + width: 12em; + border-radius: 0.25em; + border: 1px solid #666 !important; - &.hasError { - border: 0.5px solid red !important; - } + &.hasError { + border: 0.5px solid red !important; /* more important than the .optional grey */ } } + + input#amount { + text-align: right; + width: 6em; + } + + &.optional label { + color: #666; + } } + div.button { input { width: 12em;