From bd971c043924cbd9b2632212593a190f2a86d7e8 Mon Sep 17 00:00:00 2001 From: Richard Bangay Date: Fri, 26 Jul 2024 13:10:23 +0100 Subject: [PATCH 1/5] copy changes for top tier (digital + print) product. Minor bug fixes. New cypress test. --- .../contributionUpdateAmount.test.tsx | 3 +- .../supporterPlusUpdateAmount.test.tsx | 3 +- .../mma/cancel/CancellationSummary.tsx | 54 ++++---- .../mma/cancel/stages/ExecuteCancellation.tsx | 4 +- .../TierThreeCancellationFlowStart.tsx | 51 ++++++++ .../mma/holiday/HolidayQuestionsModal.tsx | 2 +- .../mma/holiday/HolidaysOverview.tsx | 2 +- client/utilities/productUtils.ts | 26 ++-- .../mocked/parallel-2/cancelTierThree.cy.ts | 121 ++++++++++++++++++ shared/dates.ts | 5 +- shared/productResponse.ts | 3 +- shared/productTypes.ts | 18 ++- 12 files changed, 241 insertions(+), 51 deletions(-) create mode 100644 client/components/mma/cancel/tierThree/TierThreeCancellationFlowStart.tsx create mode 100644 cypress/tests/mocked/parallel-2/cancelTierThree.cy.ts diff --git a/client/__tests__/components/updateAmount/contributionUpdateAmount.test.tsx b/client/__tests__/components/updateAmount/contributionUpdateAmount.test.tsx index 113d27c2a..6b2736736 100644 --- a/client/__tests__/components/updateAmount/contributionUpdateAmount.test.tsx +++ b/client/__tests__/components/updateAmount/contributionUpdateAmount.test.tsx @@ -1,4 +1,5 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import type { CurrencyIso } from '@/client/utilities/currencyIso'; import { PRODUCT_TYPES } from '../../../../shared/productTypes'; import { UpdateAmount } from '../../../components/mma/accountoverview/updateAmount/UpdateAmount'; @@ -9,7 +10,7 @@ const mainPlan = (billingPeriod: string) => ({ name: '', shouldBeVisible: false, currency: '£', - currencyISO: 'GBP', + currencyISO: 'GBP' as CurrencyIso, billingPeriod, features: '', }); diff --git a/client/__tests__/components/updateAmount/supporterPlusUpdateAmount.test.tsx b/client/__tests__/components/updateAmount/supporterPlusUpdateAmount.test.tsx index 2c592b4b9..a00aa4b7b 100644 --- a/client/__tests__/components/updateAmount/supporterPlusUpdateAmount.test.tsx +++ b/client/__tests__/components/updateAmount/supporterPlusUpdateAmount.test.tsx @@ -1,4 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react'; +import type { CurrencyIso } from '@/client/utilities/currencyIso'; import { PRODUCT_TYPES } from '../../../../shared/productTypes'; import { UpdateAmount } from '../../../components/mma/accountoverview/updateAmount/UpdateAmount'; @@ -8,7 +9,7 @@ const mainPlan = (billingPeriod: string) => ({ name: '', shouldBeVisible: false, currency: '£', - currencyISO: 'GBP', + currencyISO: 'GBP' as CurrencyIso, billingPeriod, price: 500, features: '', diff --git a/client/components/mma/cancel/CancellationSummary.tsx b/client/components/mma/cancel/CancellationSummary.tsx index c11e94df1..4dcd4a999 100644 --- a/client/components/mma/cancel/CancellationSummary.tsx +++ b/client/components/mma/cancel/CancellationSummary.tsx @@ -1,11 +1,17 @@ import { css } from '@emotion/react'; -import { palette, space } from '@guardian/source/foundations'; +import { + palette, + space, + textEgyptianBold17, +} from '@guardian/source/foundations'; import { Link } from 'react-router-dom'; import { cancellationFormatDate } from '../../../../shared/dates'; import type { + PaidSubscriptionPlan, ProductDetail, Subscription, } from '../../../../shared/productResponse'; +import { getMainPlan } from '../../../../shared/productResponse'; import type { ProductType } from '../../../../shared/productTypes'; import { measure } from '../../../styles/typography'; import { hasDeliveryRecordsFlow } from '../../../utilities/productUtils'; @@ -21,16 +27,16 @@ import { ResubscribeThrasher } from './ResubscribeThrasher'; const actuallyCancelled = ( productType: ProductType, productDetail: ProductDetail, - cancelledProductDetail: ProductDetail, ) => { const deliveryRecordsLink: string = `/delivery/${productType.urlPart}/records`; const subscription = productDetail.subscription; + const mainPlan = getMainPlan( + productDetail.subscription, + ) as PaidSubscriptionPlan; const headingCopy = productType.productType === 'supporterplus' ? 'Your subscription has been cancelled' - : `Your ${productType.friendlyName( - cancelledProductDetail, - )} is cancelled`; + : `Your ${productType.friendlyName(productDetail)} is cancelled`; return ( <> @@ -54,12 +60,13 @@ const actuallyCancelled = ( You will continue to receive the benefits of your{' '} {productType.friendlyName( - cancelledProductDetail, + productDetail, )}{' '} until{' '} {cancellationFormatDate( - subscription.cancellationEffectiveDate, + productDetail.subscription + .cancellationEffectiveDate, )} . You will not be charged again. If you @@ -130,18 +137,21 @@ const actuallyCancelled = ( reason, )) && ( <> +

+ Support us another way +

- {productType.cancellation && - productType.cancellation - .summaryReasonSpecificPara && - productType.cancellation.summaryReasonSpecificPara( + {productType?.cancellation?.summaryReasonSpecificPara( reason, - ) - ? productType.cancellation.summaryReasonSpecificPara( - reason, - ) - : 'If you are interested in supporting our journalism in other ways, ' + - 'please consider either a contribution or a subscription.'} + mainPlan.currencyISO, + ) || + 'If you are interested in supporting our journalism in other ways, ' + + 'please consider either a contribution or a subscription.'}

Object.keys(subscription).length === 0 || subscription.cancelledAt; export const getCancellationSummary = - (productType: ProductType, cancelledProductDetail: ProductDetail) => + (productType: ProductType, preCancelledProductDetail: ProductDetail) => (productDetail: ProductDetail) => isCancelled(productDetail.subscription) ? ( - actuallyCancelled( - productType, - productDetail, - cancelledProductDetail, - ) + actuallyCancelled(productType, productDetail) ) : ( ); diff --git a/client/components/mma/cancel/stages/ExecuteCancellation.tsx b/client/components/mma/cancel/stages/ExecuteCancellation.tsx index 3c56b0347..732b302b6 100644 --- a/client/components/mma/cancel/stages/ExecuteCancellation.tsx +++ b/client/components/mma/cancel/stages/ExecuteCancellation.tsx @@ -118,7 +118,7 @@ const getCaseUpdatingCancellationSummary = ( caseId: string, productType: ProductTypeWithCancellationFlow, - cancelledProductDetail: ProductDetail, + preCancelledProductDetail: ProductDetail, ) => (mdapiResponse: MembersDataApiResponse) => { const productDetail = (mdapiResponse.products[0] as ProductDetail) || { @@ -128,7 +128,7 @@ const getCaseUpdatingCancellationSummary = const render = getCancellationSummaryWithReturnButton( getCancellationSummary( productType, - cancelledProductDetail, + preCancelledProductDetail, )(productDetail), ); return caseId ? ( diff --git a/client/components/mma/cancel/tierThree/TierThreeCancellationFlowStart.tsx b/client/components/mma/cancel/tierThree/TierThreeCancellationFlowStart.tsx new file mode 100644 index 000000000..e701e5915 --- /dev/null +++ b/client/components/mma/cancel/tierThree/TierThreeCancellationFlowStart.tsx @@ -0,0 +1,51 @@ +import { Stack } from '@guardian/source/react-components'; +import { PRODUCT_TYPES } from '@/shared/productTypes'; +import { measure } from '../../../../styles/typography'; +import { trackEvent } from '../../../../utilities/analytics'; +import { Heading } from '../../shared/Heading'; +import { hrefStyle } from '../cancellationConstants'; + +const trackCancellationClickEvent = (eventLabel: string) => () => + trackEvent({ + eventCategory: 'cancellation', + eventAction: 'click', + eventLabel, + }); + +export const tierThreeCancellationFlowStart = () => ( + + + We’re sorry to hear you’re thinking of cancelling your{' '} + {PRODUCT_TYPES.tierthree.friendlyName()} + + +

+ With your vital support, the Guardian can remain editorially + independent, free from the influence of billionaire owners and + politicians. This enables us to challenge and hold the powerful to + account, and to fearlessly pursue the truth. The support from our + readers helps us keep our journalism without a paywall, open and + accessible to all. +

+ +

+ If you’re looking to take a break, it’s possible to suspend your + subscription to the Guardian Weekly – reducing the cost of your + total subscription. You can suspend up to six issues per year. This + pauses delivery and you will receive the refund for any suspended + issues off your next bill.{' '} + + Suspend your subscription here + + . +

+ +

Could you please take a moment to tell us why you want to cancel?

+
+); diff --git a/client/components/mma/holiday/HolidayQuestionsModal.tsx b/client/components/mma/holiday/HolidayQuestionsModal.tsx index 95c14f340..4e67c6f03 100644 --- a/client/components/mma/holiday/HolidayQuestionsModal.tsx +++ b/client/components/mma/holiday/HolidayQuestionsModal.tsx @@ -5,7 +5,7 @@ import { InfoIcon } from '../shared/assets/InfoIcon'; import { Modal } from './Modal'; export const creditExplainerSentence = (issueKeyword: string) => - `You will be credited for each suspended ${issueKeyword} on the next bill after the ${issueKeyword} date.`; + `You will be credited for each suspended ${issueKeyword} on your next bill after the ${issueKeyword} date.`; interface HolidayQuestionsModalProps { annualIssueLimit: number; diff --git a/client/components/mma/holiday/HolidaysOverview.tsx b/client/components/mma/holiday/HolidaysOverview.tsx index b09b593e9..35f674789 100644 --- a/client/components/mma/holiday/HolidaysOverview.tsx +++ b/client/components/mma/holiday/HolidaysOverview.tsx @@ -113,7 +113,7 @@ export const HolidaysOverview = () => { {holidayStopResponse.annualIssueLimit}{' '} {productType.holidayStops.issueKeyword}s {' '} - per year of your subscription.
+ per year on your subscription.
{productType.holidayStops.alternateNoticeString && (
diff --git a/client/utilities/productUtils.ts b/client/utilities/productUtils.ts index e6f984bf3..cc096adf3 100644 --- a/client/utilities/productUtils.ts +++ b/client/utilities/productUtils.ts @@ -42,21 +42,21 @@ export const createProductDetailFetcher = productTypeFilter: AllProductsProductTypeFilterString, subscriptionName?: string, ) => - () => - fetchWithDefaultParameters( + () => { + const apiUrl = '/api/me/mma' + - (subscriptionName - ? `/${subscriptionName}` - : `?productType=${productTypeFilter}`), - { - headers: { - [X_GU_ID_FORWARDED_SCOPE]: - getScopeFromRequestPathOrEmptyString( - window.location.href, - ), - }, + (subscriptionName + ? `/${subscriptionName}` + : `?productType=${productTypeFilter}`); + + return fetchWithDefaultParameters(apiUrl, { + headers: { + [X_GU_ID_FORWARDED_SCOPE]: getScopeFromRequestPathOrEmptyString( + window.location.href, + ), }, - ); + }); + }; export const createProductDetailFetch = ( productTypeFilter: AllProductsProductTypeFilterString, diff --git a/cypress/tests/mocked/parallel-2/cancelTierThree.cy.ts b/cypress/tests/mocked/parallel-2/cancelTierThree.cy.ts new file mode 100644 index 000000000..5df355773 --- /dev/null +++ b/cypress/tests/mocked/parallel-2/cancelTierThree.cy.ts @@ -0,0 +1,121 @@ +import { tierThree } from '../../../../client/fixtures/productBuilder/testProducts'; +import { toMembersDataApiResponse } from '../../../../client/fixtures/mdapiResponse'; +import { signInAndAcceptCookies } from '../../../lib/signInAndAcceptCookies'; + +describe('Cancel tier three', () => { + beforeEach(() => { + signInAndAcceptCookies(); + + const tierThreeWithSelfCancelEnabled = JSON.parse( + JSON.stringify(tierThree()), + ); + tierThreeWithSelfCancelEnabled.selfServiceCancellation.isAllowed = true; + + const postCancelTierThree = JSON.parse(JSON.stringify(tierThree())); + postCancelTierThree.subscription.cancelledAt = true; + postCancelTierThree.subscription.cancellationEffectiveDate = + '2024-07-25'; + + cy.intercept('POST', '/api/case', { + statusCode: 200, + body: { + id: 'caseId', + }, + }).as('get_case'); + + cy.intercept('PATCH', '/api/case/**', { + statusCode: 200, + body: { message: 'success' }, + }).as('create_case_in_salesforce'); + + cy.intercept('GET', '/api/me/mma?productType=TierThree', { + statusCode: 200, + body: toMembersDataApiResponse(tierThreeWithSelfCancelEnabled), + }); + + cy.intercept('GET', '/api/me/mma', { + statusCode: 200, + body: toMembersDataApiResponse(tierThreeWithSelfCancelEnabled), + }); + + cy.intercept('GET', '/mpapi/user/mobile-subscriptions', { + statusCode: 200, + body: { subscriptions: [] }, + }); + + cy.intercept('GET', '/api/me/one-off-contributions', { + statusCode: 200, + body: [], + }).as('single_contributions'); + + cy.intercept('GET', '/api/me/mma/**', { + statusCode: 200, + body: toMembersDataApiResponse(postCancelTierThree), + }).as('new_product_detail'); + + cy.intercept('GET', '/api/cancelled/', { + statusCode: 200, + body: [], + }).as('cancelled'); + + cy.intercept('GET', 'api/cancellation-date/**', { + statusCode: 200, + body: { cancellationEffectiveDate: '2022-02-05' }, + }).as('get_cancellation_date'); + + cy.intercept('POST', 'api/cancel/**', { + statusCode: 200, + }).as('cancel_gw'); + + cy.intercept('GET', 'api/holidays/**', { + publicationsToRefund: [], + }).as('cancel_gw_holidays'); + + cy.intercept('GET', 'api/delivery-records/**', { + results: [], + deliveryProblemMap: {}, + contactPhoneNumbers: { + Id: 'yo', + }, + }).as('cancel_gw_deliveryrecords'); + + cy.intercept('GET', 'api/existing-payment-options', { + statusCode: 200, + body: [], + }); + }); + + it('cancels tier three (reason: I dont have time to use my subscription, effective: next billing date)', () => { + cy.visit('/'); + + cy.findByText('Manage subscription').click(); + cy.wait('@cancelled'); + + cy.findByRole('link', { + name: 'Cancel subscription', + }).click(); + + cy.findByText( + 'We’re sorry to hear you’re thinking of cancelling your digital + print subscription', + ).should('exist'); + + cy.findAllByRole('radio').eq(6).click(); + + cy.findAllByRole('radio').check('Today'); + cy.findByRole('button', { name: 'Continue' }).click(); + + cy.wait('@get_case'); + + cy.findByRole('button', { name: 'Confirm cancellation' }).click(); + + cy.wait('@cancel_gw_holidays'); + cy.wait('@cancel_gw_deliveryrecords'); + cy.wait('@create_case_in_salesforce'); + + cy.findByText( + 'Your cancellation request has been successfully submitted. Our customer service team will try their best to contact you as soon as possible to confirm the cancellation and refund any credit you are owed.', + ).should('exist'); + + cy.get('@get_cancellation_date.all').should('have.length', 1); + }); +}); diff --git a/shared/dates.ts b/shared/dates.ts index be3b28197..c17a8406f 100644 --- a/shared/dates.ts +++ b/shared/dates.ts @@ -8,11 +8,10 @@ export const DATE_FNS_SHORT_OUTPUT_FORMAT = 'd MMM yyyy'; // example: 5 Jan 2019 export const cancellationFormatDate = ( cancellationEffectiveDate?: string, outputFormat: string = DATE_FNS_SHORT_OUTPUT_FORMAT, -) => { - return cancellationEffectiveDate === undefined +) => + cancellationEffectiveDate === undefined ? 'today' : parseDate(cancellationEffectiveDate).dateStr(outputFormat); -}; export interface ParsedDate { date: Date; diff --git a/shared/productResponse.ts b/shared/productResponse.ts index e077bda5e..bcb5df480 100644 --- a/shared/productResponse.ts +++ b/shared/productResponse.ts @@ -1,5 +1,6 @@ import * as Sentry from '@sentry/browser'; import * as React from 'react'; +import type { CurrencyIso } from '@/client/utilities/currencyIso'; import type { DeliveryRecordDetail } from '../client/components/mma/delivery/records/deliveryRecordsApi'; import { AsyncLoader } from '../client/components/mma/shared/AsyncLoader'; import type { CardProps } from '../client/components/mma/shared/CardDisplay'; @@ -119,7 +120,7 @@ interface SepaDetails { interface CurrencyAndBillingPeriodDetail { currency: string; - currencyISO: string; + currencyISO: CurrencyIso; billingPeriod: string; } diff --git a/shared/productTypes.ts b/shared/productTypes.ts index b12ac8a9f..73215c424 100644 --- a/shared/productTypes.ts +++ b/shared/productTypes.ts @@ -1,4 +1,7 @@ import type { ReactNode } from 'react'; +import { tierThreeCancellationFlowStart } from '@/client/components/mma/cancel/tierThree/TierThreeCancellationFlowStart'; +import type { CurrencyIso } from '@/client/utilities/currencyIso'; +import { convertCurrencyIsoToSymbol } from '@/client/utilities/currencyIso'; import type { CancellationReason, OptionalCancellationReasonId, @@ -79,7 +82,8 @@ export type AllProductsProductTypeFilterString = | 'Digipack' | 'SupporterPlus' | 'ContentSubscription' - | 'GuardianPatron'; + | 'GuardianPatron' + | 'TierThree'; interface CancellationFlowProperties { reasons: CancellationReason[]; @@ -97,6 +101,7 @@ interface CancellationFlowProperties { shouldHideSummaryMainPara?: true; summaryReasonSpecificPara: ( reasonId: OptionalCancellationReasonId, + currencyISO?: CurrencyIso, ) => string | undefined; onlyShowSupportSectionIfAlternateText: boolean; alternateSupportButtonText: ( @@ -600,7 +605,7 @@ export const PRODUCT_TYPES: { [productKey in ProductTypeKeys]: ProductType } = { friendlyName: () => 'digital + print subscription', productType: 'tierthree', groupedProductType: 'recurringSupport', - allProductsProductTypeFilterString: 'Weekly', + allProductsProductTypeFilterString: 'TierThree', urlPart: 'digital+print', softOptInIDs: [ SoftOptInIDs.SupportOnboarding, @@ -628,9 +633,14 @@ export const PRODUCT_TYPES: { [productKey in ProductTypeKeys]: ProductType } = { sfCaseProduct: 'Tier Three', checkForOutstandingCredits: true, flowWrapper: physicalSubsCancellationFlowWrapper, - startPageBody: gwCancellationFlowStart, + startPageBody: tierThreeCancellationFlowStart, startPageOfferEffectiveDateOptions: true, - summaryReasonSpecificPara: () => undefined, + summaryReasonSpecificPara: (_, currencyISO?: CurrencyIso) => { + const currencySymbol = convertCurrencyIsoToSymbol( + currencyISO || 'USD', + ); + return `Your support, no matter what amount, allows us to fund independent Guardian journalism. You can support us from as little as ${currencySymbol}1. It only takes a minute but makes a big difference.`; + }, onlyShowSupportSectionIfAlternateText: false, alternateSupportButtonText: () => undefined, alternateSupportButtonUrlSuffix: () => undefined, From 15fc1f29952d2c773d2747513e217615690656e4 Mon Sep 17 00:00:00 2001 From: Richard Bangay Date: Mon, 29 Jul 2024 22:48:41 +0100 Subject: [PATCH 2/5] remove unnecessary productDetail providing closure --- .../mma/cancel/Cancellation.stories.tsx | 10 ++++---- .../mma/cancel/CancellationSummary.tsx | 25 ++++++++++--------- .../mma/cancel/stages/ExecuteCancellation.tsx | 12 ++------- 3 files changed, 20 insertions(+), 27 deletions(-) diff --git a/client/components/mma/cancel/Cancellation.stories.tsx b/client/components/mma/cancel/Cancellation.stories.tsx index 0c157d728..870b5d84c 100644 --- a/client/components/mma/cancel/Cancellation.stories.tsx +++ b/client/components/mma/cancel/Cancellation.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryFn, StoryObj } from '@storybook/react'; -import {http, HttpResponse} from 'msw'; +import { http, HttpResponse } from 'msw'; import { ReactRouterDecorator } from '@/.storybook/ReactRouterDecorator'; import { PRODUCT_TYPES } from '@/shared/productTypes'; import { @@ -66,7 +66,7 @@ export const Review: StoryObj = { parameters: { msw: [ http.post('/api/case', () => { - return HttpResponse.json({ id: 'caseId' }) + return HttpResponse.json({ id: 'caseId' }); }), ], reactRouter: { @@ -87,7 +87,7 @@ export const Offer: StoryObj = { parameters: { msw: [ http.post('/api/case', () => { - return HttpResponse.json({ id: 'caseId' }) + return HttpResponse.json({ id: 'caseId' }); }), ], reactRouter: { @@ -122,7 +122,7 @@ export const OfferReview: StoryObj = { http.post('/api/discounts/apply-discount', () => { return new HttpResponse(null, { status: 201, - }) + }); }), ], }, @@ -168,5 +168,5 @@ export const Confirmation: StoryFn = () => { return getCancellationSummary( PRODUCT_TYPES.contributions, contributionCancelled(), - )(contributionCancelled()); + ); }; diff --git a/client/components/mma/cancel/CancellationSummary.tsx b/client/components/mma/cancel/CancellationSummary.tsx index 4dcd4a999..858a3abf1 100644 --- a/client/components/mma/cancel/CancellationSummary.tsx +++ b/client/components/mma/cancel/CancellationSummary.tsx @@ -193,15 +193,16 @@ const actuallyCancelled = ( export const isCancelled = (subscription: Subscription) => Object.keys(subscription).length === 0 || subscription.cancelledAt; -export const getCancellationSummary = - (productType: ProductType, preCancelledProductDetail: ProductDetail) => - (productDetail: ProductDetail) => - isCancelled(productDetail.subscription) ? ( - actuallyCancelled(productType, productDetail) - ) : ( - - ); +export const getCancellationSummary = ( + productType: ProductType, + productDetail: ProductDetail, +) => + isCancelled(productDetail.subscription) ? ( + actuallyCancelled(productType, productDetail) + ) : ( + + ); diff --git a/client/components/mma/cancel/stages/ExecuteCancellation.tsx b/client/components/mma/cancel/stages/ExecuteCancellation.tsx index 732b302b6..1b2e57f44 100644 --- a/client/components/mma/cancel/stages/ExecuteCancellation.tsx +++ b/client/components/mma/cancel/stages/ExecuteCancellation.tsx @@ -115,21 +115,14 @@ const getCancellationSummaryWithReturnButton = (body: ReactNode) => () => ); const getCaseUpdatingCancellationSummary = - ( - caseId: string, - productType: ProductTypeWithCancellationFlow, - preCancelledProductDetail: ProductDetail, - ) => + (caseId: string, productType: ProductTypeWithCancellationFlow) => (mdapiResponse: MembersDataApiResponse) => { const productDetail = (mdapiResponse.products[0] as ProductDetail) || { subscription: {}, }; const render = getCancellationSummaryWithReturnButton( - getCancellationSummary( - productType, - preCancelledProductDetail, - )(productDetail), + getCancellationSummary(productType, productDetail), ); return caseId ? ( { render={getCaseUpdatingCancellationSummary( caseId, productType, - productDetail, )} loadingMessage="Performing your cancellation..." /> From d83e0d1e30afdff50f6948bea3d71e476d53fc60 Mon Sep 17 00:00:00 2001 From: Richard Bangay Date: Tue, 30 Jul 2024 12:13:17 +0100 Subject: [PATCH 3/5] fix unnecessary type assertions --- .../updateAmount/SupporterPlusUpdateAmountForm.tsx | 3 +-- client/components/mma/switch/SwitchContainer.tsx | 8 ++------ client/components/mma/upgrade/UpgradeSupport.tsx | 3 +-- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/client/components/mma/accountoverview/updateAmount/SupporterPlusUpdateAmountForm.tsx b/client/components/mma/accountoverview/updateAmount/SupporterPlusUpdateAmountForm.tsx index 132406fed..8a554e846 100644 --- a/client/components/mma/accountoverview/updateAmount/SupporterPlusUpdateAmountForm.tsx +++ b/client/components/mma/accountoverview/updateAmount/SupporterPlusUpdateAmountForm.tsx @@ -19,7 +19,6 @@ import { import { useEffect, useState } from 'react'; import type { PaidSubscriptionPlan } from '../../../../../shared/productResponse'; import { getBillingPeriodAdjective } from '../../../../../shared/productTypes'; -import type { CurrencyIso } from '../../../../utilities/currencyIso'; import { fetchWithDefaultParameters } from '../../../../utilities/fetch'; import { getSupporterPlusSuggestedAmountsFromMainPlan } from '../../../../utilities/pricingConfig/suggestedAmounts'; import { supporterPlusPriceConfigByCountryGroup } from '../../../../utilities/pricingConfig/supporterPlusPricing'; @@ -106,7 +105,7 @@ export const SupporterPlusUpdateAmountForm = ( props: SupporterPlusUpdateAmountFormProps, ) => { const priceConfig = (supporterPlusPriceConfigByCountryGroup[ - props.mainPlan.currencyISO as CurrencyIso + props.mainPlan.currencyISO ] || supporterPlusPriceConfigByCountryGroup.international)[ props.mainPlan.billingPeriod ]; diff --git a/client/components/mma/switch/SwitchContainer.tsx b/client/components/mma/switch/SwitchContainer.tsx index 17e42feef..0dd2f9747 100644 --- a/client/components/mma/switch/SwitchContainer.tsx +++ b/client/components/mma/switch/SwitchContainer.tsx @@ -12,7 +12,6 @@ import { getBillingPeriodAdjective, PRODUCT_TYPES, } from '../../../../shared/productTypes'; -import type { CurrencyIso } from '../../../utilities/currencyIso'; import { LoadingState, useAsyncLoader, @@ -183,13 +182,10 @@ function getThresholds( monthly: boolean, ): Thresholds { const monthlyThreshold = getBenefitsThreshold( - mainPlan.currencyISO as CurrencyIso, + mainPlan.currencyISO, 'month', ); - const annualThreshold = getBenefitsThreshold( - mainPlan.currencyISO as CurrencyIso, - 'year', - ); + const annualThreshold = getBenefitsThreshold(mainPlan.currencyISO, 'year'); const thresholdForBillingPeriod = monthly ? monthlyThreshold : annualThreshold; diff --git a/client/components/mma/upgrade/UpgradeSupport.tsx b/client/components/mma/upgrade/UpgradeSupport.tsx index ab4d44bf7..0004d0bbe 100644 --- a/client/components/mma/upgrade/UpgradeSupport.tsx +++ b/client/components/mma/upgrade/UpgradeSupport.tsx @@ -10,7 +10,6 @@ import { Stack } from '@guardian/source/react-components'; import { useContext, useState } from 'react'; import { formatAmount } from '@/client/utilities/utils'; import type { PreviewResponse } from '../../../../shared/productSwitchTypes'; -import type { CurrencyIso } from '../../../utilities/currencyIso'; import { useAsyncLoader } from '../../../utilities/hooks/useAsyncLoader'; import { getContributionSuggestedAmounts } from '../../../utilities/pricingConfig/suggestedAmounts'; import { getBenefitsThreshold } from '../../../utilities/pricingConfig/supporterPlusPricing'; @@ -36,7 +35,7 @@ export const UpgradeSupport = () => { const currentAmount = mainPlan.price / 100; const threshold = getBenefitsThreshold( - mainPlan.currencyISO as CurrencyIso, + mainPlan.currencyISO, mainPlan.billingPeriod as 'month' | 'year', ); From 70c8f0735058b395b1d2a6be0e37e37e9ac044db Mon Sep 17 00:00:00 2001 From: Richard Bangay Date: Tue, 30 Jul 2024 21:59:15 +0100 Subject: [PATCH 4/5] fix tier three mocked cypress tests --- .../fixtures/productBuilder/baseProducts.ts | 14 ++++++------ .../parallel-2/cancelContribution.cy.ts | 8 ++++++- .../tests/mocked/parallel-2/cancelGW.cy.ts | 22 ++++++++++++++----- .../parallel-2/cancelSupporterPlus.cy.ts | 4 ++-- .../mocked/parallel-3/holidayStops.cy.ts | 9 +++++++- 5 files changed, 40 insertions(+), 17 deletions(-) diff --git a/client/fixtures/productBuilder/baseProducts.ts b/client/fixtures/productBuilder/baseProducts.ts index d6a7b3afb..9a67fade0 100644 --- a/client/fixtures/productBuilder/baseProducts.ts +++ b/client/fixtures/productBuilder/baseProducts.ts @@ -512,7 +512,7 @@ export function baseTierThree(): ProductDetail { phoneRegionsToDisplay: ['UK & ROW', 'US', 'AUS'], }, billingCountry: 'United Kingdom', - joinDate: '2024-06-13', + joinDate: '2021-11-29', optIn: true, subscription: { paymentMethod: 'Card', @@ -532,15 +532,15 @@ export function baseTierThree(): ProductDetail { country: 'United Kingdom', }, safeToUpdatePaymentMethod: true, - start: '2024-06-28', - end: '2025-06-13', + start: '2021-12-24', + end: '2022-12-15', nextPaymentPrice: 2500, nextPaymentDate: '2024-06-28', lastPaymentDate: null, potentialCancellationDate: null, chargedThroughDate: null, - renewalDate: '2025-06-13', - anniversaryDate: '2025-06-28', + renewalDate: '2022-12-15', + anniversaryDate: '2022-12-24', cancelledAt: false, subscriptionId: 'A-S00897035', trialLength: 4, @@ -560,8 +560,8 @@ export function baseTierThree(): ProductDetail { futurePlans: [ { name: null, - start: '2024-06-28', - end: '2025-06-13', + start: '2021-12-10', + end: '2022-11-29', shouldBeVisible: true, chargedThrough: null, price: 2500, diff --git a/cypress/tests/mocked/parallel-2/cancelContribution.cy.ts b/cypress/tests/mocked/parallel-2/cancelContribution.cy.ts index dadfd93e7..f39c4249a 100644 --- a/cypress/tests/mocked/parallel-2/cancelContribution.cy.ts +++ b/cypress/tests/mocked/parallel-2/cancelContribution.cy.ts @@ -28,6 +28,11 @@ describe('Cancel contribution', () => { }; beforeEach(() => { + const contributionCancelled = JSON.parse( + JSON.stringify(contributionPaidByCard()), + ); + contributionCancelled.subscription.cancelledAt = true; + cy.setCookie('GU_mvt_id', '0'); signInAndAcceptCookies(); @@ -66,8 +71,9 @@ describe('Cancel contribution', () => { cy.intercept('GET', '/api/me/mma/**', { statusCode: 200, - body: toMembersDataApiResponse(), + body: toMembersDataApiResponse(contributionCancelled), }).as('new_product_detail'); + cy.intercept('GET', '/api/cancelled/', { statusCode: 200, body: [], diff --git a/cypress/tests/mocked/parallel-2/cancelGW.cy.ts b/cypress/tests/mocked/parallel-2/cancelGW.cy.ts index bda8b3ec7..333d26e73 100644 --- a/cypress/tests/mocked/parallel-2/cancelGW.cy.ts +++ b/cypress/tests/mocked/parallel-2/cancelGW.cy.ts @@ -3,14 +3,19 @@ import { toMembersDataApiResponse } from '../../../../client/fixtures/mdapiRespo import { signInAndAcceptCookies } from '../../../lib/signInAndAcceptCookies'; describe('Cancel guardian weekly', () => { + const GWwithSelfCancelEnabled = JSON.parse( + JSON.stringify(guardianWeeklyPaidByCard()), + ); + GWwithSelfCancelEnabled.selfServiceCancellation.isAllowed = true; + + const GWSelfCancelEnabledAndCancelled = JSON.parse( + JSON.stringify(guardianWeeklyPaidByCard()), + ); + GWSelfCancelEnabledAndCancelled.subscription.cancelledAt = true; + beforeEach(() => { signInAndAcceptCookies(); - const GWwithSelfCancelEnabled = JSON.parse( - JSON.stringify(guardianWeeklyPaidByCard()), - ); - GWwithSelfCancelEnabled.selfServiceCancellation.isAllowed = true; - cy.intercept('POST', '/api/case', { statusCode: 200, body: { @@ -45,7 +50,7 @@ describe('Cancel guardian weekly', () => { cy.intercept('GET', '/api/me/mma/**', { statusCode: 200, - body: toMembersDataApiResponse(), + body: toMembersDataApiResponse(GWwithSelfCancelEnabled), }).as('new_product_detail'); cy.intercept('GET', '/api/cancelled/', { @@ -105,6 +110,11 @@ describe('Cancel guardian weekly', () => { }); it('cancels Guardian Weekly (reason: I dont have time to use my subscription, effective: next billing date)', () => { + cy.intercept('GET', '/api/me/mma/**', { + statusCode: 200, + body: toMembersDataApiResponse(GWSelfCancelEnabledAndCancelled), + }).as('new_product_detail'); + cy.visit('/'); cy.findByText('Manage subscription').click(); diff --git a/cypress/tests/mocked/parallel-2/cancelSupporterPlus.cy.ts b/cypress/tests/mocked/parallel-2/cancelSupporterPlus.cy.ts index 350c60e0c..1fb1e8609 100644 --- a/cypress/tests/mocked/parallel-2/cancelSupporterPlus.cy.ts +++ b/cypress/tests/mocked/parallel-2/cancelSupporterPlus.cy.ts @@ -62,7 +62,7 @@ describe('Cancel Supporter Plus', () => { cy.intercept('GET', '/api/me/mma/**', { statusCode: 200, - body: toMembersDataApiResponse(), + body: toMembersDataApiResponse(supporterPlus()), }).as('new_product_detail'); cy.intercept('GET', '/api/cancelled/', { @@ -115,7 +115,7 @@ describe('Cancel Supporter Plus', () => { cy.intercept('GET', '/api/me/mma/**', { statusCode: 200, - body: toMembersDataApiResponse(), + body: toMembersDataApiResponse(supporterPlusCancelled()), }).as('get_cancelled_product'); cy.findByRole('radio', { diff --git a/cypress/tests/mocked/parallel-3/holidayStops.cy.ts b/cypress/tests/mocked/parallel-3/holidayStops.cy.ts index 9276c455e..8e0922659 100644 --- a/cypress/tests/mocked/parallel-3/holidayStops.cy.ts +++ b/cypress/tests/mocked/parallel-3/holidayStops.cy.ts @@ -8,7 +8,10 @@ import { existingHolidaysFirstIssueDecember, yearSpanningPotentialDeliveries, } from '../../../../client/fixtures/holidays'; -import { guardianWeeklyPaidByCard } from '../../../../client/fixtures/productBuilder/testProducts'; +import { + guardianWeeklyPaidByCard, + tierThree, +} from '../../../../client/fixtures/productBuilder/testProducts'; import { signInAndAcceptCookies } from '../../../lib/signInAndAcceptCookies'; describe('Holiday stops', () => { @@ -86,6 +89,10 @@ describe('Holiday stops', () => { }); it('can add a new holiday stop for Tier Three', () => { + cy.intercept('GET', '/api/me/mma?productType=TierThree', { + statusCode: 200, + body: toMembersDataApiResponse(tierThree()), + }).as('product_detail'); cy.visit('/suspend/digital+print'); cy.wait('@fetch_existing_holidays'); cy.wait('@product_detail'); From 7f0997b8530e3b1c1c7c2492594a702b65f2fc21 Mon Sep 17 00:00:00 2001 From: Richard Bangay Date: Wed, 31 Jul 2024 22:06:27 +0100 Subject: [PATCH 5/5] fix contributions mocked api call response post cancellation. only call 'getMainPlan' function on the cancellation summary page if the required properties on the subscription object exist --- .../components/mma/cancel/CancellationSummary.tsx | 14 ++++++++++---- .../mocked/parallel-2/cancelContribution.cy.ts | 7 +------ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/client/components/mma/cancel/CancellationSummary.tsx b/client/components/mma/cancel/CancellationSummary.tsx index 858a3abf1..80b2ba700 100644 --- a/client/components/mma/cancel/CancellationSummary.tsx +++ b/client/components/mma/cancel/CancellationSummary.tsx @@ -5,6 +5,7 @@ import { textEgyptianBold17, } from '@guardian/source/foundations'; import { Link } from 'react-router-dom'; +import type { CurrencyIso } from '@/client/utilities/currencyIso'; import { cancellationFormatDate } from '../../../../shared/dates'; import type { PaidSubscriptionPlan, @@ -30,9 +31,14 @@ const actuallyCancelled = ( ) => { const deliveryRecordsLink: string = `/delivery/${productType.urlPart}/records`; const subscription = productDetail.subscription; - const mainPlan = getMainPlan( - productDetail.subscription, - ) as PaidSubscriptionPlan; + let currencySymbol: undefined | CurrencyIso; + if (Object.keys(subscription).length) { + const mainPlan = getMainPlan( + productDetail.subscription, + ) as PaidSubscriptionPlan; + currencySymbol = mainPlan.currencyISO; + } + const headingCopy = productType.productType === 'supporterplus' ? 'Your subscription has been cancelled' @@ -148,7 +154,7 @@ const actuallyCancelled = (

{productType?.cancellation?.summaryReasonSpecificPara( reason, - mainPlan.currencyISO, + currencySymbol, ) || 'If you are interested in supporting our journalism in other ways, ' + 'please consider either a contribution or a subscription.'} diff --git a/cypress/tests/mocked/parallel-2/cancelContribution.cy.ts b/cypress/tests/mocked/parallel-2/cancelContribution.cy.ts index f39c4249a..ce42d4a1f 100644 --- a/cypress/tests/mocked/parallel-2/cancelContribution.cy.ts +++ b/cypress/tests/mocked/parallel-2/cancelContribution.cy.ts @@ -28,11 +28,6 @@ describe('Cancel contribution', () => { }; beforeEach(() => { - const contributionCancelled = JSON.parse( - JSON.stringify(contributionPaidByCard()), - ); - contributionCancelled.subscription.cancelledAt = true; - cy.setCookie('GU_mvt_id', '0'); signInAndAcceptCookies(); @@ -71,7 +66,7 @@ describe('Cancel contribution', () => { cy.intercept('GET', '/api/me/mma/**', { statusCode: 200, - body: toMembersDataApiResponse(contributionCancelled), + body: toMembersDataApiResponse(), }).as('new_product_detail'); cy.intercept('GET', '/api/cancelled/', {