diff --git a/support-frontend/assets/components/payPalPaymentButton/digiSubPayPalPaymentButton.tsx b/support-frontend/assets/components/payPalPaymentButton/digiSubPayPalPaymentButton.tsx deleted file mode 100644 index e839dfa12a..0000000000 --- a/support-frontend/assets/components/payPalPaymentButton/digiSubPayPalPaymentButton.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { useEffect } from 'react'; -import AnimatedDots from 'components/spinners/animatedDots'; -import { usePayPal } from 'helpers/customHooks/usePayPal'; -import { validateForm } from 'helpers/redux/checkout/checkoutActions'; -import { contributionsFormHasErrors } from 'helpers/redux/selectors/formValidation/contributionFormValidation'; -import { - useContributionsDispatch, - useContributionsSelector, -} from 'helpers/redux/storeHooks'; -import { PayPalButtonRecurringContainer } from './payPalRecurringContainer'; - -// This is a duplicate of the standard S+ PayPal button that only returns the recurring payment Paypal interface -// This should be able to be removed once we can properly separate product type/contribution type from payment frequency -export function DigiSubPayPalPaymentButton(): JSX.Element { - const payPalHasLoaded = usePayPal(); - - const dispatch = useContributionsDispatch(); - const errorsPreventSubmission = useContributionsSelector( - contributionsFormHasErrors, - ); - - useEffect(() => { - dispatch(validateForm('PayPal')); - }, []); - - if (!payPalHasLoaded) { - return ; - } - - return ; -} diff --git a/support-frontend/assets/components/payPalPaymentButton/payPalButtonProps.ts b/support-frontend/assets/components/payPalPaymentButton/payPalButtonProps.ts deleted file mode 100644 index 1c27a41a5f..0000000000 --- a/support-frontend/assets/components/payPalPaymentButton/payPalButtonProps.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { fetchJson } from 'helpers/async/fetch'; -import type { PayPalCheckoutDetails } from 'helpers/forms/paymentIntegrations/payPalRecurringCheckout'; -import type { CsrfState } from 'helpers/redux/checkout/csrf/state'; -import type { - PayPalTokenReject, - PayPalTokenResolve, -} from 'helpers/redux/checkout/payment/payPal/thunks'; -import { routes } from 'helpers/urls/routes'; -import { logException } from 'helpers/utilities/logger'; - -type PayPalButtonControls = { - enable?: () => void; - disable?: () => void; -}; - -export type OnPaypalWindowOpen = ( - resolve: PayPalTokenResolve, - reject: PayPalTokenReject, -) => void; - -type PayPalPropsRequirements = { - csrf: CsrfState; - isTestUser: boolean; - setValidationControls: (controls: PayPalButtonControls) => void; - onClick: () => void; - onWindowOpen: OnPaypalWindowOpen; - onCompletion: (arg0: PayPalCheckoutDetails) => void; -}; - -export function getPayPalButtonProps({ - csrf, - isTestUser, - setValidationControls, - onClick, - onWindowOpen, - onCompletion, -}: PayPalPropsRequirements): PayPalButtonProps { - return { - env: isTestUser - ? window.guardian.payPalEnvironment.test - : window.guardian.payPalEnvironment.default, - style: { - color: 'blue', - size: 'responsive', - label: 'pay', - tagline: false, - layout: 'horizontal', - fundingicons: false, - }, - // Defines whether user sees 'Agree and Continue' or 'Agree and Pay now' in overlay. - commit: true, - validate: setValidationControls, - funding: { - disallowed: [window.paypal.FUNDING.CREDIT], - }, - onClick, - // This function is called when user clicks the PayPal button. - payment: onWindowOpen, - // This function is called when the user finishes with PayPal interface (approves payment). - onAuthorize: async (payPalData: Record) => { - try { - const body = { - token: payPalData.paymentToken, - }; - const csrfToken = csrf.token; - const payPalCheckoutDetails = await fetchJson( - routes.payPalOneClickCheckout, - { - credentials: 'include', - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Csrf-Token': csrfToken ?? '', - }, - body: JSON.stringify(body), - }, - ); - onCompletion(payPalCheckoutDetails as PayPalCheckoutDetails); - } catch (error) { - logException((error as Error).message); - } - }, - onError: () => null, - }; -} diff --git a/support-frontend/assets/components/payPalPaymentButton/payPalRecurringContainer.tsx b/support-frontend/assets/components/payPalPaymentButton/payPalRecurringContainer.tsx deleted file mode 100644 index 0776a60195..0000000000 --- a/support-frontend/assets/components/payPalPaymentButton/payPalRecurringContainer.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { useEffect } from 'preact/hooks'; -import { useState } from 'react'; -import type { PayPalCheckoutDetails } from 'helpers/forms/paymentIntegrations/payPalRecurringCheckout'; -import { PayPal } from 'helpers/forms/paymentMethods'; -import { validateForm } from 'helpers/redux/checkout/checkoutActions'; -import { setUpPayPalPayment } from 'helpers/redux/checkout/payment/payPal/thunks'; -import { - useContributionsDispatch, - useContributionsSelector, -} from 'helpers/redux/storeHooks'; -import { - onThirdPartyPaymentAuthorised, - paymentWaiting, -} from 'pages/supporter-plus-landing/setup/legacyActionCreators'; -import { PayPalButton } from './payPalButton'; -import type { OnPaypalWindowOpen } from './payPalButtonProps'; -import { getPayPalButtonProps } from './payPalButtonProps'; - -type PayPalButtonControls = { - enable?: () => void; - disable?: () => void; -}; - -type PayPalButtonRecuringContainerProps = { - disabled: boolean; -}; - -export function PayPalButtonRecurringContainer({ - disabled, -}: PayPalButtonRecuringContainerProps): JSX.Element { - const [validationControls, setValidationControls] = - useState({}); - - const dispatch = useContributionsDispatch(); - - const { csrf } = useContributionsSelector((state) => state.page.checkoutForm); - const { isTestUser } = useContributionsSelector((state) => state.page.user); - - function onCompletion(payPalCheckoutDetails: PayPalCheckoutDetails) { - dispatch(paymentWaiting(true)); - void dispatch( - onThirdPartyPaymentAuthorised({ - paymentMethod: PayPal, - token: payPalCheckoutDetails.baid, - }), - ); - } - - const onWindowOpen: OnPaypalWindowOpen = (resolve, reject) => { - dispatch(validateForm('PayPal')); - void dispatch(setUpPayPalPayment({ resolve, reject })); - }; - - const buttonProps = getPayPalButtonProps({ - csrf, - isTestUser, - setValidationControls, - onClick: () => {}, - onWindowOpen, - onCompletion, - }); - - useEffect(() => { - if (disabled) { - validationControls.disable?.(); - } else { - validationControls.enable?.(); - } - }, [disabled, validationControls]); - - return ; -} diff --git a/support-frontend/assets/components/paymentButton/defaultPaymentButtonContainer.tsx b/support-frontend/assets/components/paymentButton/defaultPaymentButtonContainer.tsx deleted file mode 100644 index 8a40e63015..0000000000 --- a/support-frontend/assets/components/paymentButton/defaultPaymentButtonContainer.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import type { - ContributionType, - RegularContributionType, -} from 'helpers/contributions'; -import { simpleFormatAmount } from 'helpers/forms/checkouts'; -import { currencies } from 'helpers/internationalisation/currency'; -import { getPromotion } from 'helpers/productPrice/promotions'; -import { isSupporterPlusFromState } from 'helpers/redux/checkout/product/selectors/isSupporterPlus'; -import { - getContributionType, - getMinimumContributionAmount, -} from 'helpers/redux/checkout/product/selectors/productType'; -import { getUserSelectedAmount } from 'helpers/redux/checkout/product/selectors/selectedAmount'; -import { useContributionsSelector } from 'helpers/redux/storeHooks'; -import { getLowerBenefitsThreshold } from 'helpers/supporterPlus/benefitsThreshold'; -import { threeTierCheckoutEnabled } from 'pages/supporter-plus-landing/setup/threeTierChecks'; -import { deleteAbandonedBasketCookie } from '../../helpers/storage/abandonedBasketCookies'; -import { DefaultPaymentButton } from './defaultPaymentButton'; - -const contributionTypeToPaymentInterval: Partial< - Record -> = { - MONTHLY: 'month', - ANNUAL: 'year', -}; - -type ButtonTextCreator = ( - amountWithCurrency: string, - amountIsAboveThreshold: boolean, - paymentInterval?: 'month' | 'year' | undefined, -) => string; - -export type DefaultPaymentContainerProps = { - onClick: (event: React.MouseEvent) => void; - createButtonText?: ButtonTextCreator; - disabled?: boolean; -}; - -function getButtonText( - amountWithCurrency: string, - amountIsAboveThreshold: boolean, - paymentInterval?: 'month' | 'year', -) { - if (paymentInterval) { - return `${ - amountIsAboveThreshold ? 'Pay' : 'Support us with' - } ${amountWithCurrency} per ${paymentInterval}`; - } - - return `Support us with ${amountWithCurrency}`; -} - -export function DefaultPaymentButtonContainer({ - onClick, - createButtonText = getButtonText, -}: DefaultPaymentContainerProps): JSX.Element { - const { currencyId, countryId } = useContributionsSelector( - (state) => state.common.internationalisation, - ); - const contributionType = useContributionsSelector(getContributionType); - const isSupporterPlus = useContributionsSelector(isSupporterPlusFromState); - const selectedAmount = useContributionsSelector((state) => - isSupporterPlus - ? getLowerBenefitsThreshold( - state, - contributionType as RegularContributionType, - ) - : getUserSelectedAmount(state), - ); - const promotion = isSupporterPlus - ? useContributionsSelector((state) => - getPromotion( - state.page.checkoutForm.product.productPrices, - countryId, - state.page.checkoutForm.product.billingPeriod, - ), - ) - : undefined; - const amount = promotion?.discountedPrice ?? selectedAmount; - - const currency = currencies[currencyId]; - const amountWithCurrency = simpleFormatAmount(currency, amount); - - const testId = 'qa-contributions-landing-submit-contribution-button'; - - const amountIsAboveThreshold = useContributionsSelector( - isSupporterPlusFromState, - ); - - const { abParticipations, amounts } = useContributionsSelector( - (state) => state.common, - ); - - const minAmount = useContributionsSelector(getMinimumContributionAmount()); - - const buttonText = - Number.isNaN(selectedAmount) || selectedAmount < minAmount - ? 'Pay now' - : createButtonText( - amountWithCurrency, - amountIsAboveThreshold || - threeTierCheckoutEnabled(abParticipations, amounts), - contributionTypeToPaymentInterval[contributionType], - ); - - return ( - ) => { - deleteAbandonedBasketCookie(); - onClick(event); - }} - /> - ); -} diff --git a/support-frontend/assets/components/paymentButton/digitalSubscriberPaymentButtonContainer.tsx b/support-frontend/assets/components/paymentButton/digitalSubscriberPaymentButtonContainer.tsx deleted file mode 100644 index e196897a09..0000000000 --- a/support-frontend/assets/components/paymentButton/digitalSubscriberPaymentButtonContainer.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import type { BillingPeriod } from 'helpers/productPrice/billingPeriods'; -import { getUserSelectedAmount } from 'helpers/redux/checkout/product/selectors/selectedAmount'; -import { getSubscriptionPriceForBillingPeriod } from 'helpers/redux/checkout/product/selectors/subscriptionPrice'; -import { useContributionsSelector } from 'helpers/redux/storeHooks'; -import { DefaultPaymentButton } from './defaultPaymentButton'; -import type { DefaultPaymentContainerProps } from './defaultPaymentButtonContainer'; - -const billingPeriodToPaymentInterval: Partial< - Record -> = { - Monthly: 'month', - Annual: 'year', -}; - -function getButtonText( - amountWithCurrency: string, - _amountIsAboveThreshold: boolean, - paymentInterval?: 'month' | 'year', -) { - return `Pay ${amountWithCurrency}${ - paymentInterval ? ` per ${paymentInterval}` : '' - }`; -} - -export function DigitalSubscriberPaymentButtonContainer({ - onClick, - createButtonText = getButtonText, -}: DefaultPaymentContainerProps): JSX.Element { - const selectedAmount = useContributionsSelector(getUserSelectedAmount); - const billingPeriod = useContributionsSelector( - (state) => state.page.checkoutForm.product.billingPeriod, - ); - - const amountWithCurrency = useContributionsSelector( - getSubscriptionPriceForBillingPeriod, - ); - - const testId = 'qa-contributions-landing-submit-contribution-button'; - - const buttonText = Number.isNaN(selectedAmount) - ? 'Pay now' - : createButtonText( - amountWithCurrency, - false, - billingPeriodToPaymentInterval[billingPeriod], - ); - - return ( - - ); -} diff --git a/support-frontend/assets/components/paymentButton/directDebitPaymentButton.tsx b/support-frontend/assets/components/paymentButton/directDebitPaymentButton.tsx deleted file mode 100644 index a88f90e36c..0000000000 --- a/support-frontend/assets/components/paymentButton/directDebitPaymentButton.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useDirectDebitValidation } from 'helpers/customHooks/useFormValidation'; -import type { PaymentAuthorisation } from 'helpers/forms/paymentIntegrations/readerRevenueApis'; -import { payWithDirectDebit } from 'helpers/redux/checkout/payment/directDebit/thunks'; -import { useContributionsDispatch } from 'helpers/redux/storeHooks'; -import { onThirdPartyPaymentAuthorised } from 'pages/supporter-plus-landing/setup/legacyActionCreators'; -import type { PaymentButtonComponentProps } from './paymentButtonController'; - -export function DirectDebitPaymentButton({ - DefaultButtonContainer, -}: PaymentButtonComponentProps): JSX.Element { - const dispatch = useContributionsDispatch(); - - function onSubmit() { - void dispatch( - payWithDirectDebit((paymentAuthorisation: PaymentAuthorisation) => { - void dispatch(onThirdPartyPaymentAuthorised(paymentAuthorisation)); - }), - ); - } - - const onClick = useDirectDebitValidation(onSubmit); - - return ; -} diff --git a/support-frontend/assets/components/paymentButton/noPaymentMethodButton.tsx b/support-frontend/assets/components/paymentButton/noPaymentMethodButton.tsx deleted file mode 100644 index b7a88ba102..0000000000 --- a/support-frontend/assets/components/paymentButton/noPaymentMethodButton.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { useFormValidation } from 'helpers/customHooks/useFormValidation'; -import type { PaymentButtonComponentProps } from './paymentButtonController'; - -export function NoPaymentMethodButton({ - DefaultButtonContainer, -}: PaymentButtonComponentProps): JSX.Element { - const onClick = useFormValidation(() => undefined); - - return ; -} diff --git a/support-frontend/assets/components/paymentButton/paymentButtonController.tsx b/support-frontend/assets/components/paymentButton/paymentButtonController.tsx deleted file mode 100644 index f5a7369cf9..0000000000 --- a/support-frontend/assets/components/paymentButton/paymentButtonController.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { css } from '@emotion/react'; -import { space } from '@guardian/source/foundations'; -import type { PaymentMethod } from 'helpers/forms/paymentMethods'; -import { useContributionsSelector } from 'helpers/redux/storeHooks'; -import type { CSSOverridable } from 'helpers/types/cssOverrideable'; -import { DefaultPaymentButtonContainer } from './defaultPaymentButtonContainer'; -import { NoPaymentMethodButton } from './noPaymentMethodButton'; - -const paymentButtonSpacing = css` - margin-top: ${space[9]}px; - margin-bottom: ${space[6]}px; -`; - -export type PaymentButtonComponentProps = { - DefaultButtonContainer: typeof DefaultPaymentButtonContainer; -}; - -interface PaymentButtonControllerProps extends CSSOverridable { - paymentButtons: Partial< - Record> - >; - defaultContainer?: typeof DefaultPaymentButtonContainer; -} - -export function PaymentButtonController({ - paymentButtons, - defaultContainer = DefaultPaymentButtonContainer, - cssOverrides, -}: PaymentButtonControllerProps): JSX.Element { - const paymentMethod = useContributionsSelector( - (state) => state.page.checkoutForm.payment.paymentMethod.name, - ); - const ButtonToRender = paymentButtons[paymentMethod] ?? NoPaymentMethodButton; - - return ( -
- -
- ); -} diff --git a/support-frontend/assets/components/paymentMethodSelector/PaymentMethodSelectorContainer.tsx b/support-frontend/assets/components/paymentMethodSelector/PaymentMethodSelectorContainer.tsx deleted file mode 100644 index fc937698c2..0000000000 --- a/support-frontend/assets/components/paymentMethodSelector/PaymentMethodSelectorContainer.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { useEffect } from 'react'; -import type { ContributionType } from 'helpers/contributions'; -import { getValidPaymentMethods } from 'helpers/forms/checkouts'; -import type { PaymentMethod } from 'helpers/forms/paymentMethods'; -import { setPaymentMethod } from 'helpers/redux/checkout/payment/paymentMethod/actions'; -import { getContributionType } from 'helpers/redux/checkout/product/selectors/productType'; -import { - useContributionsDispatch, - useContributionsSelector, -} from 'helpers/redux/storeHooks'; -import { - trackComponentClick, - trackComponentInsert, -} from 'helpers/tracking/behaviour'; -import { sendEventPaymentMethodSelected } from 'helpers/tracking/quantumMetric'; -import type { PaymentMethodSelectorProps } from './paymentMethodSelector'; - -type PaymentMethodSelectorContainerProps = { - render: ( - paymentMethodSelectorProps: PaymentMethodSelectorProps, - ) => JSX.Element; - contributionTypeOverride?: ContributionType; -}; - -function PaymentMethodSelectorContainer({ - render, - contributionTypeOverride, -}: PaymentMethodSelectorContainerProps): JSX.Element { - const dispatch = useContributionsDispatch(); - const contributionType = - contributionTypeOverride ?? useContributionsSelector(getContributionType); - - const { countryId, countryGroupId } = useContributionsSelector( - (state) => state.common.internationalisation, - ); - - const { name, errors } = useContributionsSelector( - (state) => state.page.checkoutForm.payment.paymentMethod, - ); - - const availablePaymentMethods = getValidPaymentMethods( - contributionType, - countryId, - countryGroupId, - ); - - function onPaymentMethodEvent( - event: 'select' | 'render', - paymentMethod: PaymentMethod, - ): void { - const trackingId = `payment-method-selector-${paymentMethod}`; - - if (event === 'select') { - trackComponentClick(trackingId); - sendEventPaymentMethodSelected(paymentMethod); - dispatch(setPaymentMethod({ paymentMethod })); - } else { - trackComponentInsert(trackingId); - } - } - - useEffect(() => { - availablePaymentMethods.length === 1 && - availablePaymentMethods[0] && - dispatch(setPaymentMethod({ paymentMethod: availablePaymentMethods[0] })); - }, []); - - return render({ - availablePaymentMethods: availablePaymentMethods, - paymentMethod: name, - validationError: errors?.[0], - onPaymentMethodEvent, - }); -} - -export default PaymentMethodSelectorContainer; diff --git a/support-frontend/assets/components/personalDetails/personalDetailsContainer.tsx b/support-frontend/assets/components/personalDetails/personalDetailsContainer.tsx deleted file mode 100644 index 775e197a10..0000000000 --- a/support-frontend/assets/components/personalDetails/personalDetailsContainer.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { TextInput } from '@guardian/source/react-components'; -import Signout from 'components/signout/signout'; -import { - setBillingPostcode, - setBillingState, -} from 'helpers/redux/checkout/address/actions'; -import { - setEmail, - setFirstName, - setLastName, -} from 'helpers/redux/checkout/personalDetails/actions'; -import { getContributionType } from 'helpers/redux/checkout/product/selectors/productType'; -import { - useContributionsDispatch, - useContributionsSelector, -} from 'helpers/redux/storeHooks'; -import type { PersonalDetailsProps } from './personalDetails'; -import { StateSelect } from './stateSelect'; - -type PersonalDetailsContainerProps = { - renderPersonalDetails: (props: PersonalDetailsProps) => JSX.Element; -}; - -export function PersonalDetailsContainer({ - renderPersonalDetails, -}: PersonalDetailsContainerProps): JSX.Element { - const dispatch = useContributionsDispatch(); - - const { email, firstName, lastName, errors } = useContributionsSelector( - (state) => state.page.checkoutForm.personalDetails, - ); - - const contributionType = useContributionsSelector(getContributionType); - const hideNameFields = contributionType === 'ONE_OFF'; - - const { state, postCode, errorObject } = useContributionsSelector( - (state) => state.page.checkoutForm.billingAddress.fields, - ); - const isSignedIn = useContributionsSelector( - (state) => state.page.user.isSignedIn, - ); - const countryId = useContributionsSelector( - (state) => state.common.internationalisation.countryId, - ); - - const showZipCodeField = countryId === 'US'; - const showStateField = - contributionType !== 'ONE_OFF' && - (countryId === 'US' || countryId === 'CA' || countryId === 'AU'); - - function onEmailChange(email: string) { - dispatch(setEmail(email)); - } - - function onFirstNameChange(firstName: string) { - dispatch(setFirstName(firstName)); - } - - function onLastNameChange(lastName: string) { - dispatch(setLastName(lastName)); - } - - function onBillingStateChange(billingState: string) { - dispatch(setBillingState(billingState)); - } - - function onZipCodeChange(newZipCode: string) { - dispatch(setBillingPostcode(newZipCode)); - } - - return renderPersonalDetails({ - email, - firstName, - lastName, - isSignedIn, - onEmailChange, - onFirstNameChange, - onLastNameChange, - errors, - signOutLink: , - contributionState: showStateField && ( - { - onBillingStateChange(event.currentTarget.value); - }} - error={errorObject?.state?.[0]} - /> - ), - contributionZipcode: showZipCodeField ? ( -
- onZipCodeChange(e.target.value)} - /> -
- ) : undefined, - hideNameFields, - }); -} diff --git a/support-frontend/assets/components/stripe/contributionsStripe.tsx b/support-frontend/assets/components/stripe/contributionsStripe.tsx deleted file mode 100644 index c729001a19..0000000000 --- a/support-frontend/assets/components/stripe/contributionsStripe.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { useEffect } from 'react'; -import type { ContributionType } from 'helpers/contributions'; -import { - getStripeKeyForCountry, - stripeAccountForContributionType, -} from 'helpers/forms/stripe'; -import { - setStripeAccountName, - setStripePublicKey, -} from 'helpers/redux/checkout/payment/stripeAccountDetails/actions'; -import { getContributionType } from 'helpers/redux/checkout/product/selectors/productType'; -import { - useContributionsDispatch, - useContributionsSelector, -} from 'helpers/redux/storeHooks'; -import { StripeElements } from './stripeElements'; - -type ContributionsStripeProps = { - children: React.ReactNode; - contributionTypeOverride?: ContributionType; -}; - -export function ContributionsStripe({ - children, - contributionTypeOverride, -}: ContributionsStripeProps): JSX.Element { - const { countryId, currencyId } = useContributionsSelector( - (state) => state.common.internationalisation, - ); - const contributionType = - contributionTypeOverride ?? useContributionsSelector(getContributionType); - const { isTestUser } = useContributionsSelector((state) => state.page.user); - const { publicKey } = useContributionsSelector( - (state) => state.page.checkoutForm.payment.stripeAccountDetails, - ); - - const dispatch = useContributionsDispatch(); - - useEffect(() => { - const stripeAccount = stripeAccountForContributionType[contributionType]; - const publicKey = getStripeKeyForCountry( - stripeAccount, - countryId, - currencyId, - isTestUser, - ); - - dispatch(setStripeAccountName(stripeAccount)); - dispatch(setStripePublicKey(publicKey)); - }, [contributionType]); - - /** - * The `key` attribute is necessary here because you cannot update the stripe object on the Elements. - * Instead, we create separate instances for ONE_OFF and REGULAR - */ - return ( - <> - - {children} - - - ); -} diff --git a/support-frontend/assets/components/stripe/stripeElements.tsx b/support-frontend/assets/components/stripe/stripeElements.tsx deleted file mode 100644 index 68e8f87e09..0000000000 --- a/support-frontend/assets/components/stripe/stripeElements.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { Elements } from '@stripe/react-stripe-js'; -import { useStripeAccount } from 'helpers/forms/stripe'; - -type StripeElementsProps = { - stripeKey: string; - children: React.ReactNode; -}; - -export function StripeElements({ - stripeKey, - children, -}: StripeElementsProps): JSX.Element { - const stripeSdk = useStripeAccount(stripeKey); - - // `options` must be set even if it's empty, otherwise we get 'Unsupported prop change on Elements' warnings - // in the console - const elementsOptions = {}; - - return ( - - {children} - - ); -} diff --git a/support-frontend/assets/components/stripeCardForm/stripePaymentButton.tsx b/support-frontend/assets/components/stripeCardForm/stripePaymentButton.tsx deleted file mode 100644 index 561d14e282..0000000000 --- a/support-frontend/assets/components/stripeCardForm/stripePaymentButton.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import { - CardNumberElement, - useElements, - useStripe, -} from '@stripe/react-stripe-js'; -import type { StripeError } from '@stripe/stripe-js'; -import { useEffect, useState } from 'preact/hooks'; -import type { PaymentButtonComponentProps } from 'components/paymentButton/paymentButtonController'; -import { useFormValidation } from 'helpers/customHooks/useFormValidation'; -import { Stripe } from 'helpers/forms/paymentMethods'; -import { - useContributionsDispatch, - useContributionsSelector, -} from 'helpers/redux/storeHooks'; -import { trackComponentLoad } from 'helpers/tracking/behaviour'; -import { logException } from 'helpers/utilities/logger'; -import { - onThirdPartyPaymentAuthorised, - paymentFailure, - paymentWaiting, -} from 'pages/supporter-plus-landing/setup/legacyActionCreators'; - -export function StripePaymentButton({ - DefaultButtonContainer, -}: PaymentButtonComponentProps): JSX.Element { - const [paymentAwaitingSetupIntent, setPaymentAwaitingSetupIntent] = - useState(false); - - const stripe = useStripe(); - const elements = useElements(); - const dispatch = useContributionsDispatch(); - - const { postCode } = useContributionsSelector( - (state) => state.page.checkoutForm.billingAddress.fields, - ); - const { stripeAccount } = useContributionsSelector( - (state) => state.page.checkoutForm.payment.stripeAccountDetails, - ); - const { setupIntentClientSecret } = useContributionsSelector( - (state) => state.page.checkoutForm.payment.stripe, - ); - - const payWithStripe = useFormValidation(function pay() { - if (stripeAccount === 'ONE_OFF') { - oneOffPayment(); - } else if (setupIntentClientSecret) { - recurringPayment(setupIntentClientSecret); - } else { - // The setupIntentClientSecret is requested asynchronously when the user completes the recaptcha and is - // required to establish our intent to take future card payments. - // Thus it's possible that the user clicks the payment button *before* we have this secret available. - // In this case we record that they *intend* to pay, and then attempt to make the payment via the useEffect hook below - // which will run when the client secret has become available - setPaymentAwaitingSetupIntent(true); - } - }); - - function handleStripeError(errorData: StripeError): void { - dispatch(paymentWaiting(false)); - logException(`Error creating Payment Method: ${JSON.stringify(errorData)}`); - - if (errorData.type === 'validation_error') { - // This shouldn't be possible as we disable the submit button until all fields are valid, but if it does - // happen then display a generic error about card details - dispatch(paymentFailure('payment_details_incorrect')); - } else { - // This is probably a Stripe or network problem - dispatch(paymentFailure('payment_provider_unavailable')); - } - } - - function oneOffPayment() { - const cardElement = elements?.getElement(CardNumberElement); - - if (stripe && cardElement) { - const handle3DS = (clientSecret: string) => { - trackComponentLoad('stripe-3ds'); - return stripe.handleCardAction(clientSecret); - }; - - void stripe - .createPaymentMethod({ - type: 'card', - card: cardElement, - billing_details: { - address: { - postal_code: postCode, - }, - }, - }) - .then((result) => { - if (result.error) { - handleStripeError(result.error); - } else { - void dispatch( - onThirdPartyPaymentAuthorised({ - paymentMethod: Stripe, - stripePaymentMethod: 'StripeCheckout', - paymentMethodId: result.paymentMethod.id, - handle3DS, - }), - ); - } - }); - } - } - - function recurringPayment(clientSecret: string): void { - const cardElement = elements?.getElement(CardNumberElement); - - if (stripe && cardElement) { - void stripe - .confirmCardSetup(clientSecret, { - payment_method: { - card: cardElement, - billing_details: { - address: { - postal_code: postCode, - }, - }, - }, - }) - .then((result) => { - if (result.error) { - handleStripeError(result.error); - } else if (result.setupIntent.payment_method) { - void dispatch( - onThirdPartyPaymentAuthorised({ - paymentMethod: Stripe, - stripePaymentMethod: 'StripeCheckout', - paymentMethodId: result.setupIntent.payment_method, - }), - ); - } - }); - } - } - - useEffect(() => { - if (setupIntentClientSecret && paymentAwaitingSetupIntent) { - recurringPayment(setupIntentClientSecret); - } - }, [setupIntentClientSecret, paymentAwaitingSetupIntent]); - - return ; -} diff --git a/support-frontend/assets/helpers/abTests/abtestDefinitions.ts b/support-frontend/assets/helpers/abTests/abtestDefinitions.ts index 8cedde016e..47c0f8d261 100644 --- a/support-frontend/assets/helpers/abTests/abtestDefinitions.ts +++ b/support-frontend/assets/helpers/abTests/abtestDefinitions.ts @@ -96,27 +96,6 @@ export const tests: Tests = { targetPage: pageUrlRegexes.contributions.allLandingPagesAndThankyouPages, excludeContributionsOnlyCountries: true, }, - digitalEditionCheckout: { - variants: [ - { - id: 'variant', - }, - ], - audiences: { - GBPCountries: { - offset: 0, - size: 1, - }, - }, - isActive: true, - referrerControlled: false, // ab-test name not needed to be in paramURL - seed: 7, - persistPage: - // uk will ensure we match the generic checkout - '/uk/(subscribe/digitaledition$|subscribe/digitaledition/thankyou$|checkout|thank-you)', - targetPage: '/subscribe$', - excludeContributionsOnlyCountries: true, - }, newspaperGenericCheckout: { variants: [ { diff --git a/support-frontend/assets/helpers/contributions.ts b/support-frontend/assets/helpers/contributions.ts index f8c99545d6..c37eb1d8b7 100644 --- a/support-frontend/assets/helpers/contributions.ts +++ b/support-frontend/assets/helpers/contributions.ts @@ -1,13 +1,6 @@ // ----- Imports ----- // -import type { - PaymentMethod, - PaymentMethodMap, -} from 'helpers/forms/paymentMethods'; import type { IsoCountry } from 'helpers/internationalisation/country'; import type { CountryGroupId } from 'helpers/internationalisation/countryGroup'; -import type { BillingPeriod } from 'helpers/productPrice/billingPeriods'; -import { Annual, Monthly } from 'helpers/productPrice/billingPeriods'; -import { logException } from 'helpers/utilities/logger'; import { roundToDecimalPlaces } from 'helpers/utilities/utilities'; // ----- Types ----- // @@ -24,17 +17,6 @@ type ContributionTypeMap = RegularContributionTypeMap & { export type RegularContributionType = keyof RegularContributionTypeMap; export type ContributionType = keyof ContributionTypeMap; -export type PaymentMatrix = ContributionTypeMap>; - -export const logInvalidCombination = ( - contributionType: ContributionType, - paymentMethod: PaymentMethod, -): void => { - logException( - `Invalid combination of contribution type ${contributionType} and payment method ${paymentMethod}`, - ); -}; - export interface AmountValuesObject { amounts: number[]; defaultAmount: number; @@ -72,7 +54,7 @@ export interface SelectedAmountsVariant extends AmountsVariant { testName: string; } -export type ContributionTypeSetting = { +type ContributionTypeSetting = { contributionType: ContributionType; isDefault?: boolean; }; @@ -271,57 +253,6 @@ const config: Record = { }, }; -function toContributionType( - s: string | null | undefined, -): ContributionType | null | undefined { - if (s) { - switch (s.toUpperCase()) { - case 'ANNUAL': - return 'ANNUAL'; - - case 'MONTHLY': - return 'MONTHLY'; - - case 'ONE_OFF': - return 'ONE_OFF'; - - case 'SINGLE': - return 'ONE_OFF'; - - default: - return null; - } - } - - return null; -} - -function generateContributionTypes( - contributionTypes: ContributionTypeSetting[], -): ContributionTypes { - return { - GBPCountries: contributionTypes, - UnitedStates: contributionTypes, - AUDCountries: contributionTypes, - EURCountries: contributionTypes, - NZDCountries: contributionTypes, - Canada: contributionTypes, - International: contributionTypes, - }; -} - -function billingPeriodFromContrib( - contributionType: ContributionType, -): BillingPeriod { - switch (contributionType) { - case 'ANNUAL': - return Annual; - - default: - return Monthly; - } -} - const contributionsOnlyAmountsTestName = 'VAT_COMPLIANCE'; const isContributionsOnlyCountry = ( @@ -334,9 +265,6 @@ const isContributionsOnlyCountry = ( // ----- Exports ----- // export { config, - toContributionType, - generateContributionTypes, - billingPeriodFromContrib, getAmount, contributionsOnlyAmountsTestName, isContributionsOnlyCountry, diff --git a/support-frontend/assets/helpers/customHooks/useFormValidation.ts b/support-frontend/assets/helpers/customHooks/useFormValidation.ts deleted file mode 100644 index 8d47164c79..0000000000 --- a/support-frontend/assets/helpers/customHooks/useFormValidation.ts +++ /dev/null @@ -1,118 +0,0 @@ -import type { PayloadAction } from '@reduxjs/toolkit'; -import { useCallback, useEffect, useState } from 'react'; -import { validateForm } from 'helpers/redux/checkout/checkoutActions'; -import { confirmAccountDetails } from 'helpers/redux/checkout/payment/directDebit/thunks'; -import { contributionsFormHasErrors } from 'helpers/redux/selectors/formValidation/contributionFormValidation'; -import { - useContributionsDispatch, - useContributionsSelector, -} from 'helpers/redux/storeHooks'; -import { paymentWaiting } from 'pages/supporter-plus-landing/setup/legacyActionCreators'; - -type PreventableEvent = { - preventDefault: () => void; -}; - -function useValidation< - EventType extends PreventableEvent = React.MouseEvent, ->( - paymentHandler: (event: EventType) => void, - validationActionCreator: () => PayloadAction, - dispatchPaymentWaiting = true, -): (event: EventType) => void { - const [clickEvent, setClickEvent] = useState(null); - const dispatch = useContributionsDispatch(); - - const errorsPreventSubmission = useContributionsSelector( - contributionsFormHasErrors, - ); - - const validateAndPay = useCallback( - function validateAndPay(event: EventType) { - event.preventDefault(); - dispatch(validationActionCreator()); - setClickEvent(event); - }, - [dispatch], - ); - - useEffect(() => { - if (errorsPreventSubmission) { - setClickEvent(null); - return; - } - if (clickEvent) { - dispatchPaymentWaiting && dispatch(paymentWaiting(true)); - - paymentHandler(clickEvent); - } - }, [clickEvent, errorsPreventSubmission]); - - return validateAndPay; -} - -/** - * A hook to wrap any payment handler function. - * Validates the form, and will only run the handler if the form is valid. - */ -export function useFormValidation< - EventType extends PreventableEvent = React.MouseEvent, ->( - paymentHandler: (event: EventType) => void, - dispatchPaymentWaiting = true, -): (event: EventType) => void { - const { paymentMethod } = useContributionsSelector( - (state) => state.page.checkoutForm.payment, - ); - - const validateAndPay = useValidation( - paymentHandler, - () => validateForm(paymentMethod.name), - dispatchPaymentWaiting, - ); - - return validateAndPay; -} - -export function useDirectDebitValidation< - EventType extends PreventableEvent = React.MouseEvent, ->( - paymentHandler: (event: EventType) => void, - dispatchPaymentWaiting = true, -): (event: EventType) => void { - const [clickEvent, setClickEvent] = useState(null); - - const dispatch = useContributionsDispatch(); - - const errorsPreventSubmission = useContributionsSelector( - contributionsFormHasErrors, - ); - - const { phase } = useContributionsSelector( - (state) => state.page.checkoutForm.payment.directDebit, - ); - - const validateAndPay = useCallback( - function validateAndPay(event: EventType) { - dispatchPaymentWaiting && dispatch(paymentWaiting(true)); - event.preventDefault(); - dispatch(validateForm('DirectDebit')); - void dispatch(confirmAccountDetails()); - setClickEvent(event); - }, - [dispatch], - ); - - useEffect(() => { - if (errorsPreventSubmission) { - dispatch(paymentWaiting(false)); - setClickEvent(null); - return; - } - if (clickEvent && phase === 'confirmation') { - paymentHandler(clickEvent); - } - }, [clickEvent, errorsPreventSubmission, phase]); - - return validateAndPay; -} diff --git a/support-frontend/assets/helpers/customHooks/usePayPal.ts b/support-frontend/assets/helpers/customHooks/usePayPal.ts deleted file mode 100644 index 91b24820eb..0000000000 --- a/support-frontend/assets/helpers/customHooks/usePayPal.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { useEffect } from 'preact/hooks'; -import { loadPayPalExpressSdk } from 'helpers/redux/checkout/payment/payPal/thunks'; -import { - useContributionsDispatch, - useContributionsSelector, -} from 'helpers/redux/storeHooks'; - -export function usePayPal(): boolean { - const dispatch = useContributionsDispatch(); - const { hasLoaded, hasBegunLoading } = useContributionsSelector( - (state) => state.page.checkoutForm.payment.payPal, - ); - - useEffect(() => { - if (!hasBegunLoading) { - void dispatch(loadPayPalExpressSdk()); - } - }, []); - - return hasLoaded; -} diff --git a/support-frontend/assets/helpers/forms/checkouts.ts b/support-frontend/assets/helpers/forms/checkouts.ts index 6b2402d07f..e7936521f7 100644 --- a/support-frontend/assets/helpers/forms/checkouts.ts +++ b/support-frontend/assets/helpers/forms/checkouts.ts @@ -1,14 +1,6 @@ // ----- Imports ----- // -import { - generateContributionTypes, - toContributionType, -} from 'helpers/contributions'; -import type { - ContributionType, - ContributionTypes, - ContributionTypeSetting, -} from 'helpers/contributions'; import 'helpers/globalsAndSwitches/settings'; +import type { ContributionType } from 'helpers/contributions'; import type { PaymentMethod } from 'helpers/forms/paymentMethods'; import { DirectDebit, @@ -24,8 +16,6 @@ import type { Currency, SpokenCurrency, } from 'helpers/internationalisation/currency'; -import * as storage from 'helpers/storage/storage'; -import { getQueryParameter } from 'helpers/urls/url'; // ----- Types ----- // export type PaymentMethodSwitch = @@ -60,50 +50,6 @@ function toPaymentMethodSwitchNaming( } } -function getValidContributionTypesFromUrlOrElse( - fallback: ContributionTypes, -): ContributionTypes { - const contributionTypesFromUrl = getQueryParameter('contribution-types'); - - if (contributionTypesFromUrl) { - return generateContributionTypes( - contributionTypesFromUrl - .split(',') - .map(toContributionType) - .filter(Boolean) - .map((contributionType) => ({ - contributionType, - })) as ContributionTypeSetting[], - ); - } - - return fallback; -} - -function getContributionTypeFromSession(): ContributionType | null | undefined { - return toContributionType(storage.getSession('selectedContributionType')); -} - -function getContributionTypeFromUrl(): ContributionType | null | undefined { - return toContributionType(getQueryParameter('selected-contribution-type')); -} - -function getAmountFromUrl(): number | 'other' | null { - const selected = getQueryParameter('selected-amount'); - - if (selected === 'other') { - return 'other'; - } - - const amount = parseInt(selected, 10); - - if (!Number.isNaN(amount)) { - return amount; - } - - return null; -} - // Returns an array of Payment Methods, in the order of preference, // i.e the first element in the array will be the default option function getPaymentMethods( @@ -147,19 +93,6 @@ function getValidPaymentMethods( ); } -function getPaymentMethodFromSession(): PaymentMethod | null | undefined { - const pm: string | null | undefined = storage.getSession( - 'selectedPaymentMethod', - ); - const paymentMethodNames = ['DirectDebit', 'Stripe', 'PayPal']; - - if (pm && paymentMethodNames.includes(pm)) { - return pm as PaymentMethod; - } - - return null; -} - function round(amount: number) { /** * This rounds a `number` to the second decimal. @@ -205,13 +138,4 @@ const formatAmount = ( }; // ----- Exports ----- // -export { - simpleFormatAmount, - formatAmount, - getValidContributionTypesFromUrlOrElse, - getContributionTypeFromSession, - getContributionTypeFromUrl, - getAmountFromUrl, - getValidPaymentMethods, - getPaymentMethodFromSession, -}; +export { simpleFormatAmount, formatAmount, getValidPaymentMethods }; diff --git a/support-frontend/assets/helpers/forms/paymentIntegrations/oneOffContributions.ts b/support-frontend/assets/helpers/forms/paymentIntegrations/oneOffContributions.ts index eaea9f1518..450e67ed9a 100644 --- a/support-frontend/assets/helpers/forms/paymentIntegrations/oneOffContributions.ts +++ b/support-frontend/assets/helpers/forms/paymentIntegrations/oneOffContributions.ts @@ -61,7 +61,7 @@ export type CreatePayPalPaymentResponse = PaymentApiResponse< // Data that should be posted to the payment API to create a Stripe charge. // https://github.com/guardian/payment-api/blob/master/src/main/scala/model/stripe/StripeChargeData.scala#L82 // TODO: are we deprecating signed-in email? -export type StripeChargeData = { +type StripeChargeData = { paymentData: { currency: IsoCurrency; amount: number; diff --git a/support-frontend/assets/helpers/forms/paymentIntegrations/readerRevenueApis.ts b/support-frontend/assets/helpers/forms/paymentIntegrations/readerRevenueApis.ts index 55ec1b6031..5357d774fb 100644 --- a/support-frontend/assets/helpers/forms/paymentIntegrations/readerRevenueApis.ts +++ b/support-frontend/assets/helpers/forms/paymentIntegrations/readerRevenueApis.ts @@ -188,7 +188,7 @@ export type RegularPaymentRequest = { recaptchaToken?: string; debugInfo: string; }; -export type StripePaymentIntentAuthorisation = { +type StripePaymentIntentAuthorisation = { paymentMethod: typeof Stripe; stripePaymentMethod: StripePaymentMethod; paymentMethodId: string | PaymentMethod; diff --git a/support-frontend/assets/helpers/forms/paymentMethods.ts b/support-frontend/assets/helpers/forms/paymentMethods.ts index 3f362dc0f1..55cf35502d 100644 --- a/support-frontend/assets/helpers/forms/paymentMethods.ts +++ b/support-frontend/assets/helpers/forms/paymentMethods.ts @@ -11,15 +11,6 @@ const None = 'None'; const Success = 'success'; const Pending = 'pending'; -export type PaymentMethodMap = { - Stripe: T; - PayPal: T; - DirectDebit: T; - StripeHostedCheckout: T; - Sepa: T; - None: T; -}; - export type PaymentMethod = | typeof Stripe | typeof PayPal diff --git a/support-frontend/assets/helpers/forms/stripe.ts b/support-frontend/assets/helpers/forms/stripe.ts index b74e93dda4..2096be9c7a 100644 --- a/support-frontend/assets/helpers/forms/stripe.ts +++ b/support-frontend/assets/helpers/forms/stripe.ts @@ -1,8 +1,4 @@ import type { ActiveProductKey } from '@guardian/support-service-lambdas/modules/product-catalog/src/productCatalog'; -import type { Stripe as StripeJs } from '@stripe/stripe-js'; -import { loadStripe } from '@stripe/stripe-js/pure'; -import { useEffect, useState } from 'react'; -import type { ContributionType } from 'helpers/contributions'; import type { IsoCountry } from 'helpers/internationalisation/country'; import type { IsoCurrency } from '../internationalisation/currency'; @@ -12,15 +8,6 @@ export type StripePaymentIntentResult = { client_secret?: string; }; -const stripeAccountForContributionType: Record< - ContributionType, - StripeAccountType -> = { - ONE_OFF: 'ONE_OFF', - MONTHLY: 'REGULAR', - ANNUAL: 'REGULAR', -}; - function getStripeKeyForCountry( stripeAccountType: StripeAccountType, country: IsoCountry, @@ -64,33 +51,4 @@ function getStripeKeyForProduct( return; } -// this is required as useStripeAccount is used in multiple components -// but we only want to call setLoadParameters once. -const stripeScriptHasBeenAddedToPage = (): boolean => - !!document.querySelector("script[src^='https://js.stripe.com']"); - -export function useStripeAccount(stripeKey: string): StripeJs | null { - const [stripeSdk, setStripeSdk] = useState(null); - - useEffect(() => { - if (stripeSdk === null && stripeKey) { - if (!stripeScriptHasBeenAddedToPage()) { - loadStripe.setLoadParameters({ - advancedFraudSignals: false, - }); - } - - void loadStripe(stripeKey).then((newStripe) => { - setStripeSdk(newStripe); - }); - } - }, [stripeKey]); - - return stripeSdk; -} - -export { - stripeAccountForContributionType, - getStripeKeyForCountry, - getStripeKeyForProduct, -}; +export { getStripeKeyForCountry, getStripeKeyForProduct }; diff --git a/support-frontend/assets/helpers/internationalisation/shouldCollectStateForContribs.ts b/support-frontend/assets/helpers/internationalisation/shouldCollectStateForContribs.ts deleted file mode 100644 index 7210bf18d1..0000000000 --- a/support-frontend/assets/helpers/internationalisation/shouldCollectStateForContribs.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { ContributionType } from 'helpers/contributions'; -import type { IsoCountry } from './country'; - -export function shouldCollectStateForContributions( - countryId: IsoCountry, - contributionType: ContributionType, -): boolean { - if (contributionType === 'ONE_OFF') { - return false; - } - if (countryId === 'US' || countryId === 'CA' || countryId === 'AU') { - return true; - } - return false; -} diff --git a/support-frontend/assets/helpers/polyfills/details.ts b/support-frontend/assets/helpers/polyfills/details.ts deleted file mode 100644 index d8723f2fb7..0000000000 --- a/support-frontend/assets/helpers/polyfills/details.ts +++ /dev/null @@ -1,56 +0,0 @@ -const isDetailsSupported: boolean = 'open' in document.createElement('details'); - -function toggleDetailsState(details: Element) { - if (details.hasAttribute('open')) { - details.removeAttribute('open'); - } else { - details.setAttribute('open', 'open'); - } -} - -function isNode(element: EventTarget): element is Node { - return 'nodeName' in element && 'parentElement' in element; -} - -function findSummaryNode(maybeSummary: Node): Node { - let targetNode = maybeSummary; - while ( - targetNode !== document && - targetNode.nodeName.toLowerCase() !== 'summary' && - targetNode.parentElement - ) { - targetNode = targetNode.parentElement; - } - return targetNode; -} - -function handleEvent(event: UIEvent) { - if (event.target && isNode(event.target)) { - const targetNode = findSummaryNode(event.target); - - if ( - targetNode.nodeName.toLowerCase() === 'summary' && - targetNode.parentElement && - targetNode.parentElement.nodeName.toLowerCase() === 'details' - ) { - toggleDetailsState(targetNode.parentElement); - } - } -} - -function polyfillDetails(): void { - document.addEventListener('click', handleEvent, true); - document.addEventListener( - 'keypress', - (event: KeyboardEvent) => { - if (event.key && (event.key === ' ' || event.key === 'Enter')) { - handleEvent(event); - } else if (event.keyCode === 0x20 || event.keyCode === 0x0d) { - handleEvent(event); - } - }, - true, - ); -} - -export { isDetailsSupported, polyfillDetails }; diff --git a/support-frontend/assets/helpers/redux/checkout/payment/payPal/thunks.ts b/support-frontend/assets/helpers/redux/checkout/payment/payPal/thunks.ts index 45737314c0..94830c1335 100644 --- a/support-frontend/assets/helpers/redux/checkout/payment/payPal/thunks.ts +++ b/support-frontend/assets/helpers/redux/checkout/payment/payPal/thunks.ts @@ -1,91 +1,5 @@ import { createAsyncThunk } from '@reduxjs/toolkit'; -import { fetchJson } from 'helpers/async/fetch'; -import { billingPeriodFromContrib, getAmount } from 'helpers/contributions'; import { loadPayPalRecurring } from 'helpers/forms/paymentIntegrations/payPalRecurringCheckout'; -import { getProductPrice } from 'helpers/productPrice/productPrices'; -import { getPromotion } from 'helpers/productPrice/promotions'; -import type { ContributionsState } from 'helpers/redux/contributionsStore'; -import { contributionsFormHasErrors } from 'helpers/redux/selectors/formValidation/contributionFormValidation'; -import { routes } from 'helpers/urls/routes'; -import { logException } from 'helpers/utilities/logger'; -import { getContributionType } from '../../product/selectors/productType'; - -export type PayPalTokenResolve = (token: string) => void; -export type PayPalTokenReject = (err: Error) => void; - -type PayPalLoadFns = { - resolve: PayPalTokenResolve; - reject: PayPalTokenReject; -}; - -export const setUpPayPalPayment = createAsyncThunk< - unknown, - PayPalLoadFns, - { - state: ContributionsState; - } ->('paypal/setUpPayment', async function setUp({ resolve, reject }, thunkApi) { - try { - const state = thunkApi.getState(); - const errorsPreventOpening = contributionsFormHasErrors(state); - - if (errorsPreventOpening) { - reject(new Error('form invalid')); - } - const isDigitalPack = - state.page.checkoutForm.product.productType === 'DigitalPack'; - - const { currencyId, countryId } = state.common.internationalisation; - const csrfToken = state.page.checkoutForm.csrf.token ?? ''; - const contributionType = getContributionType(state); - const { productPrices } = state.page.checkoutForm.product; - const billingPeriod = isDigitalPack - ? state.page.checkoutForm.product.billingPeriod - : billingPeriodFromContrib(contributionType); - const promotion = getPromotion(productPrices, countryId, billingPeriod); - const amount = isDigitalPack - ? getProductPrice( - state.page.checkoutForm.product.productPrices, - countryId, - billingPeriod === 'Annual' ? 'Annual' : 'Monthly', - ).price - : getAmount( - state.page.checkoutForm.product.selectedAmounts, - state.page.checkoutForm.product.otherAmounts, - contributionType, - ); - const finalAmount = promotion?.discountedPrice ?? amount; - - const requestBody = { - amount: finalAmount, - billingPeriod, - currency: currencyId, - requireShippingAddress: false, - }; - - const payPalResponse = await fetchJson<{ token?: string }>( - routes.payPalSetupPayment, - { - credentials: 'include', - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Csrf-Token': csrfToken, - }, - body: JSON.stringify(requestBody), - }, - ); - - if (payPalResponse.token) { - resolve(payPalResponse.token); - } else { - throw new Error('PayPal token came back blank'); - } - } catch (error) { - logException((error as Error).message); - reject(error as Error); - } -}); export const loadPayPalExpressSdk = createAsyncThunk( 'paypal/loadPayPalExpressSdk', diff --git a/support-frontend/assets/helpers/redux/checkout/payment/paymentRequestButton/actions.ts b/support-frontend/assets/helpers/redux/checkout/payment/paymentRequestButton/actions.ts deleted file mode 100644 index 752f6493e7..0000000000 --- a/support-frontend/assets/helpers/redux/checkout/payment/paymentRequestButton/actions.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { paymentRequestButtonSlice } from './reducer'; - -export const { setPaymentRequestError } = paymentRequestButtonSlice.actions; diff --git a/support-frontend/assets/helpers/redux/checkout/payment/paymentRequestButton/reducer.ts b/support-frontend/assets/helpers/redux/checkout/payment/paymentRequestButton/reducer.ts index 0278161c17..7665e86092 100644 --- a/support-frontend/assets/helpers/redux/checkout/payment/paymentRequestButton/reducer.ts +++ b/support-frontend/assets/helpers/redux/checkout/payment/paymentRequestButton/reducer.ts @@ -5,7 +5,7 @@ import { resetValidation, validateForm } from '../../checkoutActions'; import type { PaymentRequestError } from './state'; import { initialPaymentRequestButtonState } from './state'; -export const paymentRequestButtonSlice = createSlice({ +const paymentRequestButtonSlice = createSlice({ name: 'paymentRequestButton', initialState: initialPaymentRequestButtonState, reducers: { diff --git a/support-frontend/assets/helpers/redux/checkout/payment/paymentRequestButton/selectors.ts b/support-frontend/assets/helpers/redux/checkout/payment/paymentRequestButton/selectors.ts deleted file mode 100644 index d6e0e2b660..0000000000 --- a/support-frontend/assets/helpers/redux/checkout/payment/paymentRequestButton/selectors.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { ContributionsState } from 'helpers/redux/contributionsStore'; - -export function hasPaymentRequestButtonBeenClicked( - state: ContributionsState, -): boolean { - const { paymentRequestButton } = state.page.checkoutForm.payment; - return ( - paymentRequestButton.ONE_OFF.buttonClicked || - paymentRequestButton.REGULAR.buttonClicked - ); -} - -export function hasPaymentRequestInterfaceClosed( - state: ContributionsState, -): boolean { - const { paymentRequestButton } = state.page.checkoutForm.payment; - return ( - paymentRequestButton.ONE_OFF.completed || - paymentRequestButton.REGULAR.completed - ); -} - -export function hasPaymentRequestPaymentFailed( - state: ContributionsState, -): boolean { - const { paymentRequestButton } = state.page.checkoutForm.payment; - return ( - !!paymentRequestButton.ONE_OFF.paymentError || - !!paymentRequestButton.REGULAR.paymentError - ); -} diff --git a/support-frontend/assets/helpers/redux/checkout/payment/stripeAccountDetails/actions.ts b/support-frontend/assets/helpers/redux/checkout/payment/stripeAccountDetails/actions.ts index 54a8f8b72f..f63a8ac08b 100644 --- a/support-frontend/assets/helpers/redux/checkout/payment/stripeAccountDetails/actions.ts +++ b/support-frontend/assets/helpers/redux/checkout/payment/stripeAccountDetails/actions.ts @@ -1,4 +1,3 @@ import { stripeAccountDetailsSlice } from './reducer'; -export const { setStripeAccountName, setStripePublicKey } = - stripeAccountDetailsSlice.actions; +export const { setStripePublicKey } = stripeAccountDetailsSlice.actions; diff --git a/support-frontend/assets/helpers/redux/checkout/product/actions.ts b/support-frontend/assets/helpers/redux/checkout/product/actions.ts index 762f7adea4..695024fa84 100644 --- a/support-frontend/assets/helpers/redux/checkout/product/actions.ts +++ b/support-frontend/assets/helpers/redux/checkout/product/actions.ts @@ -9,7 +9,6 @@ export const { setProductPrices, setAllAmounts, setSelectedAmount, - setOtherAmount, setCurrency, setOrderIsAGift, setStartDate, diff --git a/support-frontend/assets/helpers/redux/checkout/product/selectors/isSupporterPlus.ts b/support-frontend/assets/helpers/redux/checkout/product/selectors/isSupporterPlus.ts deleted file mode 100644 index 6f4aa4cb3c..0000000000 --- a/support-frontend/assets/helpers/redux/checkout/product/selectors/isSupporterPlus.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { getAmount, isContributionsOnlyCountry } from 'helpers/contributions'; -import { getContributionType } from 'helpers/redux/checkout/product/selectors/productType'; -import type { ContributionsState } from 'helpers/redux/contributionsStore'; -import { getThresholdPrice } from 'helpers/supporterPlus/benefitsThreshold'; -import { isOneOff } from 'helpers/supporterPlus/isContributionRecurring'; - -export function isSupporterPlusFromState(state: ContributionsState): boolean { - const contributionType = getContributionType(state); - const countryIsAffectedByVATStatus = isContributionsOnlyCountry( - state.common.amounts, - ); - - if (isOneOff(contributionType) || countryIsAffectedByVATStatus) { - return false; - } - const thresholdPrice = getThresholdPrice(contributionType, state); - const selectedAmount = getAmount( - state.page.checkoutForm.product.selectedAmounts, - state.page.checkoutForm.product.otherAmounts, - contributionType, - ); - - return selectedAmount >= thresholdPrice; -} diff --git a/support-frontend/assets/helpers/redux/checkout/product/selectors/selectedAmount.ts b/support-frontend/assets/helpers/redux/checkout/product/selectors/selectedAmount.ts deleted file mode 100644 index 6ff824e80f..0000000000 --- a/support-frontend/assets/helpers/redux/checkout/product/selectors/selectedAmount.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { getAmount } from 'helpers/contributions'; -import type { ContributionsState } from 'helpers/redux/contributionsStore'; -import { getContributionType } from './productType'; - -export function getUserSelectedAmount(state: ContributionsState): number { - const contributionType = getContributionType(state); - const { coverTransactionCost, selectedAmounts, otherAmounts } = - state.page.checkoutForm.product; - - return getAmount( - selectedAmounts, - otherAmounts, - contributionType, - coverTransactionCost, - ); -} diff --git a/support-frontend/assets/helpers/redux/checkout/product/selectors/subscriptionPrice.ts b/support-frontend/assets/helpers/redux/checkout/product/selectors/subscriptionPrice.ts deleted file mode 100644 index 0a5dd5edc5..0000000000 --- a/support-frontend/assets/helpers/redux/checkout/product/selectors/subscriptionPrice.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { getProductPrice, showPrice } from 'helpers/productPrice/productPrices'; -import type { Promotion } from 'helpers/productPrice/promotions'; -import { - finalPrice, - getAppliedPromo, - getPromotion, -} from 'helpers/productPrice/promotions'; -import type { ContributionsState } from 'helpers/redux/contributionsStore'; -import { isSupporterPlusFromState } from './isSupporterPlus'; - -export function getSubscriptionPrices( - state: ContributionsState, -): Record<'monthlyPrice' | 'annualPrice', string> { - const { countryId } = state.common.internationalisation; - const { productPrices, fulfilmentOption, productOption } = - state.page.checkoutForm.product; - - return { - monthlyPrice: showPrice( - finalPrice( - productPrices, - countryId, - 'Monthly', - fulfilmentOption, - productOption, - ), - false, - ), - annualPrice: showPrice( - finalPrice( - productPrices, - countryId, - 'Annual', - fulfilmentOption, - productOption, - ), - false, - ), - }; -} - -export function getSubscriptionPricesBeforeDiscount( - state: ContributionsState, -): Record<'monthlyPrice' | 'annualPrice', string> { - const { countryId } = state.common.internationalisation; - const { productPrices, fulfilmentOption, productOption } = - state.page.checkoutForm.product; - - return { - monthlyPrice: showPrice( - getProductPrice( - productPrices, - countryId, - 'Monthly', - fulfilmentOption, - productOption, - ), - false, - ), - annualPrice: showPrice( - getProductPrice( - productPrices, - countryId, - 'Annual', - fulfilmentOption, - productOption, - ), - false, - ), - }; -} - -export function getSubscriptionPromotions( - state: ContributionsState, -): Partial> { - const { countryId } = state.common.internationalisation; - const { productPrices, fulfilmentOption, productOption } = - state.page.checkoutForm.product; - - return { - monthlyPrice: getPromotion( - productPrices, - countryId, - 'Monthly', - fulfilmentOption, - productOption, - ), - annualPrice: getPromotion( - productPrices, - countryId, - 'Annual', - fulfilmentOption, - productOption, - ), - }; -} - -export function getSubscriptionPromotionForBillingPeriod( - state: ContributionsState, -): Promotion | undefined { - const { countryId } = state.common.internationalisation; - const { - productPrices, - fulfilmentOption, - productOption, - billingPeriod, - productType, - } = state.page.checkoutForm.product; - - if (productType === 'DigitalPack' || isSupporterPlusFromState(state)) { - return getAppliedPromo( - getProductPrice( - productPrices, - countryId, - billingPeriod, - fulfilmentOption, - productOption, - ).promotions, - ); - } - - return; -} - -export function getSubscriptionPriceForBillingPeriod( - state: ContributionsState, -): string { - const { countryId } = state.common.internationalisation; - const { productPrices, fulfilmentOption, productOption, billingPeriod } = - state.page.checkoutForm.product; - - return showPrice( - finalPrice( - productPrices, - countryId, - billingPeriod, - fulfilmentOption, - productOption, - ), - false, - ); -} diff --git a/support-frontend/assets/helpers/redux/commonState/actions.ts b/support-frontend/assets/helpers/redux/commonState/actions.ts index 55565182ec..c7798f1677 100644 --- a/support-frontend/assets/helpers/redux/commonState/actions.ts +++ b/support-frontend/assets/helpers/redux/commonState/actions.ts @@ -1,7 +1,4 @@ import { commonSlice } from './reducer'; -export const { - setInitialCommonState, - setCountryInternationalisation, - setContributionTypes, -} = commonSlice.actions; +export const { setInitialCommonState, setCountryInternationalisation } = + commonSlice.actions; diff --git a/support-frontend/assets/helpers/redux/selectors/formValidation/contributionFormValidation.ts b/support-frontend/assets/helpers/redux/selectors/formValidation/contributionFormValidation.ts deleted file mode 100644 index 82d3dd0508..0000000000 --- a/support-frontend/assets/helpers/redux/selectors/formValidation/contributionFormValidation.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { getOtherAmountErrors } from 'helpers/redux/selectors/formValidation/otherAmountValidation'; -import type { ContributionsState } from '../../contributionsStore'; -import { - getPaymentMethodErrors, - getPaymentRequestButtonErrors, -} from './paymentValidation'; -import { getPersonalDetailsErrors } from './personalDetailsValidation'; -import type { ErrorCollection } from './utils'; -import { errorCollectionHasErrors } from './utils'; - -function getAllErrorsForContributions( - state: ContributionsState, -): ErrorCollection { - // The payment request button- Apple/Google Pay- has a different validation pattern- we validate any custom amount, - // then check the user details returned from Stripe against our schema. - // Thus if the user is paying with the PRB we need to bail out early and not try to validate input fields they won't use. - const prbErrors = getPaymentRequestButtonErrors(state); - - if (prbErrors) { - return prbErrors; - } - - const personalDetailsErrors = getPersonalDetailsErrors(state); - - const otherAmount = getOtherAmountErrors(state); - const paymentMethod = state.page.checkoutForm.payment.paymentMethod.errors; - - const paymentErrors = { - paymentMethod, - ...getPaymentMethodErrors(state), - }; - - return { - otherAmount, - ...personalDetailsErrors, - ...paymentErrors, - }; -} - -export function contributionsFormHasErrors(state: ContributionsState): boolean { - return errorCollectionHasErrors(getAllErrorsForContributions(state)); -} diff --git a/support-frontend/assets/helpers/redux/selectors/formValidation/otherAmountValidation.ts b/support-frontend/assets/helpers/redux/selectors/formValidation/otherAmountValidation.ts deleted file mode 100644 index f35c844f37..0000000000 --- a/support-frontend/assets/helpers/redux/selectors/formValidation/otherAmountValidation.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { getContributionType } from 'helpers/redux/checkout/product/selectors/productType'; -import type { ContributionsState } from 'helpers/redux/contributionsStore'; - -export function getOtherAmountErrors( - state: ContributionsState, -): string[] | undefined { - const constributionType = getContributionType(state); - const { errors, selectedAmounts } = state.page.checkoutForm.product; - - if (selectedAmounts[constributionType] !== 'other') { - return []; - } - - if (errors.otherAmount?.length) { - return errors.otherAmount; - } - - return; -} diff --git a/support-frontend/assets/helpers/redux/selectors/formValidation/paymentValidation.ts b/support-frontend/assets/helpers/redux/selectors/formValidation/paymentValidation.ts index 7a403e161d..01217d3294 100644 --- a/support-frontend/assets/helpers/redux/selectors/formValidation/paymentValidation.ts +++ b/support-frontend/assets/helpers/redux/selectors/formValidation/paymentValidation.ts @@ -1,14 +1,6 @@ import { recaptchaRequiredPaymentMethods } from 'helpers/forms/paymentMethods'; -import { - hasPaymentRequestButtonBeenClicked, - hasPaymentRequestInterfaceClosed, - hasPaymentRequestPaymentFailed, -} from 'helpers/redux/checkout/payment/paymentRequestButton/selectors'; import type { ContributionsState } from 'helpers/redux/contributionsStore'; -import { getOtherAmountErrors } from 'helpers/redux/selectors/formValidation/otherAmountValidation'; -import { getPersonalDetailsErrors } from './personalDetailsValidation'; import type { ErrorCollection } from './utils'; -import { errorCollectionHasErrors } from './utils'; export function getStripeFormErrors( state: ContributionsState, @@ -22,44 +14,6 @@ export function getStripeFormErrors( return { ...errors, robot_checkbox: recaptchaErrors }; } -function getDirectDebitFormErrors(state: ContributionsState): ErrorCollection { - const { errors, formError } = state.page.checkoutForm.payment.directDebit; - const recaptchaErrors = getRecaptchaError(state); - - if (formError) { - return { - ...errors, - robot_checkbox: recaptchaErrors, - directDebitDetails: [formError], - }; - } - - return { - ...errors, - robot_checkbox: recaptchaErrors, - }; -} - -export function getPaymentMethodErrors( - state: ContributionsState, -): ErrorCollection { - const { payment } = state.page.checkoutForm; - - switch (payment.paymentMethod.name) { - case 'Stripe': - return getStripeFormErrors(state); - - case 'DirectDebit': - return getDirectDebitFormErrors(state); - - case 'Sepa': - return payment.sepa.errors; - - default: - return {}; - } -} - function getRecaptchaError(state: ContributionsState): string[] | undefined { const { paymentMethod } = state.page.checkoutForm.payment; @@ -69,34 +23,3 @@ function getRecaptchaError(state: ContributionsState): string[] | undefined { return; } - -export function getPaymentRequestButtonErrors( - state: ContributionsState, -): ErrorCollection | null { - const hasBeenClicked = hasPaymentRequestButtonBeenClicked(state); - const hasBeenCompleted = hasPaymentRequestInterfaceClosed(state); - - const otherAmount = getOtherAmountErrors(state); - - if (hasBeenClicked && hasBeenCompleted) { - const hasFailed = hasPaymentRequestPaymentFailed(state); - const personalDetailsErrors = getPersonalDetailsErrors(state); - - // Either the payment itself has failed, or the personal details on the user's Apple/Google Pay account failed validation- - // eg. they signed up with an emoji in their name - // We can't meaningfully recover from this, so the best option is to try another payment method - if (hasFailed || errorCollectionHasErrors(personalDetailsErrors)) { - return { - maincontent: [ - 'Sorry, something went wrong. Please try another payment method', - ], - }; - } - } - - if (hasBeenClicked) { - return { otherAmount }; - } - - return null; -} diff --git a/support-frontend/assets/helpers/redux/selectors/formValidation/personalDetailsValidation.ts b/support-frontend/assets/helpers/redux/selectors/formValidation/personalDetailsValidation.ts deleted file mode 100644 index fbf081cdb8..0000000000 --- a/support-frontend/assets/helpers/redux/selectors/formValidation/personalDetailsValidation.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { shouldCollectStateForContributions } from 'helpers/internationalisation/shouldCollectStateForContribs'; -import { getContributionType } from 'helpers/redux/checkout/product/selectors/productType'; -import type { ContributionsState } from 'helpers/redux/contributionsStore'; -import { getBillingCountryAndState } from 'pages/supporter-plus-landing/setup/legacyActionCreators'; -import type { ErrorCollection } from './utils'; - -function getStateOrProvinceError(state: ContributionsState): ErrorCollection { - const contributionType = getContributionType(state); - const billingCountry = getBillingCountryAndState(state).billingCountry; - if (shouldCollectStateForContributions(billingCountry, contributionType)) { - return { - state: state.page.checkoutForm.billingAddress.fields.errorObject?.state, - }; - } - return {}; -} - -export function getPersonalDetailsErrors( - state: ContributionsState, -): ErrorCollection { - const contributionType = getContributionType(state); - - const { firstName, lastName, email } = - state.page.checkoutForm.personalDetails.errors ?? {}; - - const stateOrProvinceErrors = getStateOrProvinceError(state); - - if (contributionType === 'ONE_OFF') { - return { - email, - ...stateOrProvinceErrors, - }; - } - return { - email, - firstName, - lastName, - ...stateOrProvinceErrors, - }; -} diff --git a/support-frontend/assets/helpers/redux/selectors/formValidation/utils.ts b/support-frontend/assets/helpers/redux/selectors/formValidation/utils.ts index c2bf42069d..39367f6e39 100644 --- a/support-frontend/assets/helpers/redux/selectors/formValidation/utils.ts +++ b/support-frontend/assets/helpers/redux/selectors/formValidation/utils.ts @@ -1,7 +1 @@ export type ErrorCollection = Partial>; - -export function errorCollectionHasErrors( - errorCollection: ErrorCollection, -): boolean { - return Object.values(errorCollection).some((errorList) => errorList?.length); -} diff --git a/support-frontend/assets/helpers/storage/abandonedBasketCookies.ts b/support-frontend/assets/helpers/storage/abandonedBasketCookies.ts index 27bf7f8dd9..ddf40b0510 100644 --- a/support-frontend/assets/helpers/storage/abandonedBasketCookies.ts +++ b/support-frontend/assets/helpers/storage/abandonedBasketCookies.ts @@ -90,7 +90,3 @@ export function updateAbandonedBasketCookie(amount: string) { ); } } - -export function deleteAbandonedBasketCookie() { - cookie.remove(ABANDONED_BASKET_COOKIE_NAME); -} diff --git a/support-frontend/assets/helpers/storage/cookie.ts b/support-frontend/assets/helpers/storage/cookie.ts index 81d7b5d43f..b885df834a 100644 --- a/support-frontend/assets/helpers/storage/cookie.ts +++ b/support-frontend/assets/helpers/storage/cookie.ts @@ -40,9 +40,3 @@ export function set( document.cookie = `${name}=${value}; path=/; secure; expires=${expires.toUTCString()};${getDomainAttribute()}`; } -export function remove(name: string): void { - const expires = 'expires=Thu, 01 Jan 1970 00:00:01 GMT;'; - const path = 'path=/;'; - // Remove cookie, implicitly using the document's domain. - document.cookie = `${name}=;${path}${expires} domain=${getShortDomain()};`; -} diff --git a/support-frontend/assets/helpers/supporterPlus/benefitsThreshold.ts b/support-frontend/assets/helpers/supporterPlus/benefitsThreshold.ts index 768565fe8e..e557f5e90b 100644 --- a/support-frontend/assets/helpers/supporterPlus/benefitsThreshold.ts +++ b/support-frontend/assets/helpers/supporterPlus/benefitsThreshold.ts @@ -1,38 +1,8 @@ -import type { - ContributionType, - RegularContributionType, -} from 'helpers/contributions'; +import type { ContributionType } from 'helpers/contributions'; import type { CountryGroup } from 'helpers/internationalisation/classes/countryGroup'; import type { IsoCurrency } from 'helpers/internationalisation/currency'; import type { ActiveProductKey } from 'helpers/productCatalog'; import { productCatalog } from 'helpers/productCatalog'; -import { getContributionType } from 'helpers/redux/checkout/product/selectors/productType'; -import type { ContributionsState } from 'helpers/redux/contributionsStore'; -import { isRecurring } from './isContributionRecurring'; - -export function getLowerBenefitsThreshold( - state: ContributionsState, - regularContributionType?: RegularContributionType, - currencyId?: IsoCurrency, -): number { - const contributionType = - regularContributionType ?? getContributionType(state); - const currency = currencyId ?? state.common.internationalisation.currencyId; - return getLowerBenefitThreshold(contributionType, currency); -} - -function getLowerBenefitThreshold( - contributionType: ContributionType, - currencyId: IsoCurrency, -): number { - const supporterPlusRatePlan = - contributionType === 'ANNUAL' ? 'Annual' : 'Monthly'; - return ( - productCatalog.SupporterPlus?.ratePlans[supporterPlusRatePlan]?.pricing[ - currencyId - ] ?? 0 - ); -} export function getLowerProductBenefitThreshold( contributionType: ContributionType, @@ -56,32 +26,3 @@ export function getLowerProductBenefitThreshold( ]?.pricing[currencyId] ?? 0 ); } - -// This is a function overload that means if the caller has already determined that contributionType is recurring -// they do not have to handle an undefined return type from getThresholdPrice -// cf. https://www.typescriptlang.org/docs/handbook/2/functions.html#overload-signatures-and-the-implementation-signature - -// Signatures -export function getThresholdPrice( - contributionType: 'ONE_OFF', - state: ContributionsState, -): undefined; -export function getThresholdPrice( - contributionType: RegularContributionType, - state: ContributionsState, -): number; -export function getThresholdPrice( - contributionType: ContributionType, - state: ContributionsState, -): number | undefined; -// Implementation -export function getThresholdPrice( - contributionType: ContributionType, - state: ContributionsState, -): number | undefined { - if (isRecurring(contributionType)) { - return getLowerBenefitsThreshold(state); - } - - return; -} diff --git a/support-frontend/assets/helpers/supporterPlus/isContributionRecurring.ts b/support-frontend/assets/helpers/supporterPlus/isContributionRecurring.ts deleted file mode 100644 index 57b8714844..0000000000 --- a/support-frontend/assets/helpers/supporterPlus/isContributionRecurring.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { - ContributionType, - RegularContributionType, -} from 'helpers/contributions'; - -export function isOneOff( - contributionType: ContributionType, -): contributionType is 'ONE_OFF' { - return contributionType === 'ONE_OFF'; -} - -export function isRecurring( - contributionType: ContributionType, -): contributionType is RegularContributionType { - return contributionType !== 'ONE_OFF'; -} diff --git a/support-frontend/assets/helpers/tracking/behaviour.ts b/support-frontend/assets/helpers/tracking/behaviour.ts index cf630136b0..a8921179b8 100644 --- a/support-frontend/assets/helpers/tracking/behaviour.ts +++ b/support-frontend/assets/helpers/tracking/behaviour.ts @@ -54,20 +54,9 @@ const trackComponentLoad = (componentId: string): void => { }); }; -const trackComponentInsert = (componentId: string): void => { - trackComponentEvents({ - component: { - componentType: 'ACQUISITIONS_OTHER', - id: componentId, - }, - action: 'INSERT', - }); -}; - export { trackThankYouPageLoaded, trackComponentClick, trackCheckoutSubmitAttempt, trackComponentLoad, - trackComponentInsert, }; diff --git a/support-frontend/assets/helpers/tracking/quantumMetric.ts b/support-frontend/assets/helpers/tracking/quantumMetric.ts index f800aa1739..138ef2d87f 100644 --- a/support-frontend/assets/helpers/tracking/quantumMetric.ts +++ b/support-frontend/assets/helpers/tracking/quantumMetric.ts @@ -416,18 +416,6 @@ function sendEventPaymentMethodSelected( } } -function sendEventConversionPaymentMethod(paymentMethod: PaymentMethod): void { - void ifQmPermitted(() => { - sendEventWhenReadyTrigger(() => - sendEvent( - SendEventContributionPaymentMethodUpdate.PaymentMethodAtConversion, - false, - paymentMethod.toString(), - ), - ); - }); -} - function sendEventABTestParticipations(participations: Participations): void { const sendEventABTestId: SendEventTestParticipationId = 30; const valueQueue: string[] = []; @@ -513,7 +501,6 @@ export { sendEventContributionCheckoutConversion, sendEventContributionCartValue, sendEventPaymentMethodSelected, - sendEventConversionPaymentMethod, sendEventCheckoutValue, sendEventOneTimeCheckoutValue, }; diff --git a/support-frontend/assets/helpers/urls/routes.ts b/support-frontend/assets/helpers/urls/routes.ts index 0590fc8f88..111f3a8505 100644 --- a/support-frontend/assets/helpers/urls/routes.ts +++ b/support-frontend/assets/helpers/urls/routes.ts @@ -87,9 +87,10 @@ function digitalSubscriptionLanding( countryGroupId: CountryGroupId, billingPeriod?: BillingPeriod, ) { - const routeDigitalSubscription = billingPeriod - ? `${routes.checkout}?product=DigitalSubscription&ratePlan=${billingPeriod}` - : routes.digitalSubscriptionLanding; + const routeDigitalSubscription = `${ + routes.checkout + }?product=DigitalSubscription&ratePlan=${billingPeriod ?? 'Monthly'}`; + return `${getOrigin()}/${countryPath( countryGroupId, )}${routeDigitalSubscription}`; diff --git a/support-frontend/assets/helpers/user/user.ts b/support-frontend/assets/helpers/user/user.ts index 88dd4bb8e8..c655fc7ae9 100644 --- a/support-frontend/assets/helpers/user/user.ts +++ b/support-frontend/assets/helpers/user/user.ts @@ -1,5 +1,4 @@ // ----- Imports ----- // -import { getGlobal } from 'helpers/globalsAndSwitches/globals'; import * as cookie from 'helpers/storage/cookie'; import { getSignoutUrl } from 'helpers/urls/externalLinks'; @@ -37,11 +36,6 @@ function isTestUser(): boolean { return isDefined(testMode) || isDefined(testCookie); } -function getUserStateField(): string | undefined { - const user = getGlobal('user'); - return user?.address4; -} - const isPostDeployUser = (): boolean => cookie.get('_post_deploy_user') === 'true'; @@ -50,4 +44,4 @@ const signOut = (): void => { }; // ----- Exports ----- // -export { getUser, isTestUser, isPostDeployUser, getUserStateField, signOut }; +export { getUser, isTestUser, isPostDeployUser, signOut }; diff --git a/support-frontend/assets/pages/digital-subscriber-checkout/components/billingPeriodSelector.tsx b/support-frontend/assets/pages/digital-subscriber-checkout/components/billingPeriodSelector.tsx deleted file mode 100644 index e3aa5c06dc..0000000000 --- a/support-frontend/assets/pages/digital-subscriber-checkout/components/billingPeriodSelector.tsx +++ /dev/null @@ -1,196 +0,0 @@ -import { css } from '@emotion/react'; -import { - from, - headlineBold28, - palette, - space, - textSans12, - textSans17, - textSansBold14, - until, -} from '@guardian/source/foundations'; -import { ChoiceCard, ChoiceCardGroup } from '@guardian/source/react-components'; -import { CheckoutBenefitsList } from 'components/checkoutBenefits/checkoutBenefitsList'; -import { BoxContents } from 'components/checkoutBox/checkoutBox'; -import { setBillingPeriod } from 'helpers/redux/checkout/product/actions'; -import { - getSubscriptionPrices, - getSubscriptionPricesBeforeDiscount, - getSubscriptionPromotions, -} from 'helpers/redux/checkout/product/selectors/subscriptionPrice'; -import { - useContributionsDispatch, - useContributionsSelector, -} from 'helpers/redux/storeHooks'; - -const cardsContainer = css` - position: relative; - padding: ${space[2]}px 0; - - ${from.mobileLandscape} { - padding: ${space[3]}px 0; - } - - ${from.tablet} { - padding: ${space[2]}px 0; - } - - :not(:last-child) { - ${until.mobileLandscape} { - padding-bottom: 10px; - } - padding-bottom: ${space[4]}px; - } -`; - -const headingContainer = css` - margin-bottom: ${space[2]}px; -`; - -const heading = css` - ${headlineBold28} -`; - -const subheading = css` - ${textSans17}; -`; - -const choiceCardContainer = css` - div { - ${from.mobileLandscape} { - column-gap: ${space[1]}px; - } - ${from.tablet} { - column-gap: ${space[2]}px; - } - } -`; - -const choiceCardWrapper = css` - width: 100%; -`; - -const offerText = css` - ${textSansBold14}; - color: ${palette.brand[500]}; -`; - -const offerDetails = css` - ${textSans12}; - color: #606060; - width: 90%; -`; - -const boldText = css` - font-weight: bold; -`; - -export function BillingPeriodSelector(): JSX.Element { - const dispatch = useContributionsDispatch(); - - const { billingPeriod } = useContributionsSelector( - (state) => state.page.checkoutForm.product, - ); - - const { monthlyPrice, annualPrice } = useContributionsSelector( - getSubscriptionPrices, - ); - - const basePrices = useContributionsSelector( - getSubscriptionPricesBeforeDiscount, - ); - const promotions = useContributionsSelector(getSubscriptionPromotions); - - return ( - -
-

The Guardian Digital Edition

-

Subscribe below to unlock the following benefits

-
-
- -
-

- {promotions.monthlyPrice?.discount?.amount - ? `${promotions.monthlyPrice.discount.amount}% off regular monthly price` - : ''} -

- dispatch(setBillingPeriod('Monthly'))} - /> - {promotions.monthlyPrice?.discount?.amount && ( -

- {monthlyPrice} per month for the first{' '} - {promotions.monthlyPrice.discount.durationMonths} months. Then{' '} - {basePrices.monthlyPrice} per month. -

- )} -
-
-

- {promotions.annualPrice?.discount?.amount - ? `${promotions.annualPrice.discount.amount}% off regular annual price` - : ''} -

- dispatch(setBillingPeriod('Annual'))} - /> - {promotions.annualPrice?.discount?.amount && ( -

- {annualPrice} for{' '} - {promotions.annualPrice.numberOfDiscountedPeriods} year. Then{' '} - {basePrices.annualPrice} per year. -

- )} -
-
-
- - undefined} - checkListData={[ - { - isChecked: true, - text: ( -

- The Digital Edition app. Enjoy the - Guardian and Observer newspaper, available for mobile and tablet -

- ), - }, - { - isChecked: true, - text: ( -

- Full access to the Guardian app. - Read our reporting on the go -

- ), - }, - { - isChecked: true, - text: ( -

- Free 14 day trial. Enjoy a free - trial of your subscription, before you pay -

- ), - }, - ]} - /> -
- ); -} diff --git a/support-frontend/assets/pages/digital-subscriber-checkout/components/landingPageHeading.tsx b/support-frontend/assets/pages/digital-subscriber-checkout/components/landingPageHeading.tsx deleted file mode 100644 index 5bbbbae77c..0000000000 --- a/support-frontend/assets/pages/digital-subscriber-checkout/components/landingPageHeading.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { css } from '@emotion/react'; -import { - from, - headlineBold42, - neutral, - space, -} from '@guardian/source/foundations'; - -const headingStyles = css` - display: none; - - color: ${neutral[100]}; - max-width: 480px; - ${headlineBold42}; - margin-bottom: ${space[3]}px; - - ${from.desktop} { - display: inline-block; - } -`; - -interface LandingHeadingProps { - heading: string | JSX.Element; -} -export function LandingPageHeading({ - heading, -}: LandingHeadingProps): JSX.Element { - return

{heading}

; -} diff --git a/support-frontend/assets/pages/digital-subscriber-checkout/components/paymentTsAndCs.tsx b/support-frontend/assets/pages/digital-subscriber-checkout/components/paymentTsAndCs.tsx deleted file mode 100644 index f32223df98..0000000000 --- a/support-frontend/assets/pages/digital-subscriber-checkout/components/paymentTsAndCs.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { css } from '@emotion/react'; -import { neutral, textSans12 } from '@guardian/source/foundations'; -import { StripeDisclaimer } from 'components/stripe/stripeDisclaimer'; -import { privacyLink } from 'helpers/legal'; - -const marginTop = css` - margin-top: 4px; -`; - -const container = css` - ${textSans12}; - color: ${neutral[20]}; - - & a { - color: ${neutral[20]}; - } -`; - -export function PaymentTsAndCs(): JSX.Element { - return ( -
- Payment taken after the first 14 day free trial. At the end of the free - trial period your subscription will auto-renew, and you will be charged, - each month at the full price of £14.99 per month or £149 per year unless - you cancel. You can cancel at any time before your next renewal date. - Cancellation will take effect at the end of your current subscription - month. To cancel, go to{' '} - Manage My Account or see our{' '} - - Terms - - . -
- By proceeding, you are agreeing to our{' '} - - Terms and Conditions - - .{' '} -

- To find out what personal data we collect and how we use it, please - visit our Privacy Policy. -

-

- -

-
-
- ); -} diff --git a/support-frontend/assets/pages/digital-subscriber-checkout/digitalSubscriptionLandingPage.tsx b/support-frontend/assets/pages/digital-subscriber-checkout/digitalSubscriptionLandingPage.tsx deleted file mode 100644 index daa804a9a9..0000000000 --- a/support-frontend/assets/pages/digital-subscriber-checkout/digitalSubscriptionLandingPage.tsx +++ /dev/null @@ -1,233 +0,0 @@ -import { css } from '@emotion/react'; -import { from, neutral, space, textSans17 } from '@guardian/source/foundations'; -import { Column, Columns, Hide } from '@guardian/source/react-components'; -import { - Divider, - FooterLinks, - FooterWithContents, -} from '@guardian/source-development-kitchen/react-components'; -import { useEffect } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { Box, BoxContents } from 'components/checkoutBox/checkoutBox'; -import { CheckoutHeading } from 'components/checkoutHeading/checkoutHeading'; -import type { CountryGroupSwitcherProps } from 'components/countryGroupSwitcher/countryGroupSwitcher'; -import CountryGroupSwitcher from 'components/countryGroupSwitcher/countryGroupSwitcher'; -import GridImage from 'components/gridImage/gridImage'; -import { CountrySwitcherContainer } from 'components/headers/simpleHeader/countrySwitcherContainer'; -import { Header } from 'components/headers/simpleHeader/simpleHeader'; -import { Container } from 'components/layout/container'; -import { LoadingOverlay } from 'components/loadingOverlay/loadingOverlay'; -import Nav from 'components/nav/nav'; -import { PageScaffold } from 'components/page/pageScaffold'; -import { DigitalSubscriberPaymentButtonContainer } from 'components/paymentButton/digitalSubscriberPaymentButtonContainer'; -import { PaymentButtonController } from 'components/paymentButton/paymentButtonController'; -import { PaymentMethodSelector } from 'components/paymentMethodSelector/paymentMethodSelector'; -import PaymentMethodSelectorContainer from 'components/paymentMethodSelector/PaymentMethodSelectorContainer'; -import { PersonalDetails } from 'components/personalDetails/personalDetails'; -import { PersonalDetailsContainer } from 'components/personalDetails/personalDetailsContainer'; -import { SecureTransactionIndicator } from 'components/secureTransactionIndicator/secureTransactionIndicator'; -import { ContributionsStripe } from 'components/stripe/contributionsStripe'; -import { - AUDCountries, - Canada, - EURCountries, - GBPCountries, - International, - NZDCountries, - UnitedStates, -} from 'helpers/internationalisation/countryGroup'; -import { useContributionsSelector } from 'helpers/redux/storeHooks'; -import { LandingPageHeading } from 'pages/digital-subscriber-checkout/components/landingPageHeading'; -import { getPaymentMethodButtons } from 'pages/digital-subscriber-checkout/paymentButtons'; -import { PaymentFailureMessage } from 'pages/supporter-plus-landing/components/paymentFailure'; -import { BillingPeriodSelector } from './components/billingPeriodSelector'; -import { PaymentTsAndCs } from './components/paymentTsAndCs'; - -const checkoutContainer = css` - position: relative; - color: ${neutral[7]}; - ${textSans17}; - - padding-top: ${space[3]}px; - padding-bottom: ${space[9]}px; - - ${from.tablet} { - padding-bottom: ${space[12]}px; - } - - ${from.desktop} { - padding-top: ${space[6]}px; - } -`; - -const divider = css` - max-width: 100%; - margin: 40px 0 ${space[6]}px; -`; - -const subheading = css` - font-weight: normal; - padding-right: ${space[2]}px; -`; - -const cancelAnytime = css` - ${textSans17}; - color: ${neutral[7]}; - margin-bottom: ${space[3]}px; - margin-left: ${space[5]}px; - font-weight: 800; - /* We use negative margin here as BillingPeriodSelector, - which this is below has a tonne of margin on it. - It felt better to do this than change that component - as it's used elsewhere. */ - margin-top: -${space[4]}px; - ${from.tablet} { - margin-top: -${space[4] * 2}px; - } -`; - -const leftColImageEditions = css` - height: 129px; - - img { - max-width: 100%; - } -`; - -export function DigitalSubscriptionLandingPage({ - thankYouRoute, -}: { - thankYouRoute: string; -}): JSX.Element { - const { countryGroupId, countryId } = useContributionsSelector( - (state) => state.common.internationalisation, - ); - - const contributionType = 'MONTHLY'; - - const { paymentComplete, isWaiting } = useContributionsSelector( - (state) => state.page.form, - ); - - const navigate = useNavigate(); - - const countrySwitcherProps: CountryGroupSwitcherProps = { - countryGroupIds: [ - GBPCountries, - UnitedStates, - AUDCountries, - EURCountries, - NZDCountries, - Canada, - International, - ], - selectedCountryGroup: countryGroupId, - subPath: '/subscribe/digitaledition', - }; - const heading = ( - - ); - - useEffect(() => { - if (paymentComplete) { - navigate(thankYouRoute, { replace: true }); - } - }, [paymentComplete]); - - return ( - -
- - - - - -
-