Skip to content

Commit 2b68f83

Browse files
committed
fix(payments): Update buttons to navigate to appropriate pages
Navigate to appropriate pages from the Subscription Management page, per subscription: Stay subscribed flow If the customer is eligible for the churn coupon, the “Stay subscribed” button will navigate to /[locale]/subscriptions/[subscription_id]/loyalty-discount/stay-subscribed If no churn coupon is available, the “Stay subscribed” button will navigate to /[locale]/subscriptions/[subscription_id]/stay-subscribed Cancel subscription flow If customer is eligible for churn coupon, the “Cancel subscription” will navigate to /[locale/subscriptions/[subscription_id]/loyalty-discount/cancel If no churn coupon is available, but there is a cancel interstitial offer, the “Cancel subscription” button will navigate to /[locale]/subscriptions/[subscription_id]/offer If no churn coupon or cancel interstitial offer is available, the “Cancel subscription” button will navigate to /[locale]/subscriptions/[subscription_id]/cancel Closes: PAY-3455
1 parent af25995 commit 2b68f83

8 files changed

Lines changed: 61 additions & 88 deletions

File tree

apps/payments/next/app/[locale]/subscriptions/[subscriptionId]/cancel/page.tsx

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,6 @@ export default async function CancelSubscriptionPage({
4040
notFound();
4141
}
4242

43-
if (pageContent.isEligibleForChurnCancel === true) {
44-
redirect(
45-
`/${locale}/subscriptions/${subscriptionId}/loyalty-discount/cancel`
46-
);
47-
}
48-
49-
if (pageContent.isEligibleForCancelInterstitialOffer === true) {
50-
redirect(`/${locale}/subscriptions/${subscriptionId}/offer`);
51-
}
52-
5343
return (
5444
<CancelSubscription
5545
userId={uid}

apps/payments/next/app/[locale]/subscriptions/[subscriptionId]/stay-subscribed/page.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,6 @@ export default async function StaySubscribedPage({
4141
notFound();
4242
}
4343

44-
if (pageContent.isEligibleforChurnStaySubscribed === true) {
45-
redirect(
46-
`/${locale}/subscriptions/${subscriptionId}/loyalty-discount/stay-subscribed`
47-
);
48-
}
49-
5044
return (
5145
<StaySubscribed
5246
userId={uid}

libs/payments/management/src/lib/factories/subscriptionContent.factory.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@ export const SubscriptionContentFactory = (
8989
nextInvoiceDate: faker.date.future().getDate(),
9090
nextInvoiceTax: faker.number.int({ min: 1, max: 1000 }),
9191
nextInvoiceTotal: faker.number.int({ min: 1, max: 1000 }),
92+
isEligibleForChurnCancel: faker.datatype.boolean(),
9293
isEligibleForChurnStaySubscribed: faker.datatype.boolean(),
94+
isEligibleForOffer: faker.datatype.boolean(),
9395
churnStaySubscribedCtaMessage: faker.string.sample(),
9496
...override,
9597
});

libs/payments/management/src/lib/subscriptionManagement.service.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,14 @@ export class SubscriptionManagementService {
481481
selectedLanguage
482482
);
483483

484+
const cancelIntervention =
485+
await this.churnInterventionService.determineCancellationIntervention({
486+
uid,
487+
subscriptionId: subscription.id,
488+
acceptLanguage,
489+
selectedLanguage,
490+
});
491+
484492
return {
485493
id: subscription.id,
486494
productName,
@@ -511,7 +519,15 @@ export class SubscriptionManagementService {
511519
nextPromotionName,
512520
promotionName,
513521
cancelAtPeriodEnd: subscription.cancel_at_period_end,
522+
isEligibleForChurnCancel:
523+
cancelIntervention.reason === 'eligible' &&
524+
cancelIntervention.cancelChurnInterventionType ===
525+
'cancel_churn_intervention',
514526
isEligibleForChurnStaySubscribed: staySubscribedResult.isEligible,
527+
isEligibleForOffer:
528+
cancelIntervention.reason === 'eligible' &&
529+
cancelIntervention.cancelChurnInterventionType ===
530+
'cancel_interstitial_offer',
515531
churnStaySubscribedCtaMessage:
516532
staySubscribedResult.cmsChurnInterventionEntry?.ctaMessage,
517533
};

libs/payments/management/src/lib/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ export interface SubscriptionContent {
9393
promotionName?: string | null;
9494
isEligibleForChurnStaySubscribed: boolean;
9595
churnStaySubscribedCtaMessage?: string | null;
96+
isEligibleForChurnCancel: boolean;
97+
isEligibleForOffer: boolean;
9698
}
9799

98100
export enum ChurnErrorReason {

libs/payments/ui/src/lib/client/components/SubscriptionContent/index.tsx

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ interface Subscription {
3737
nextInvoiceTotal?: number;
3838
nextPromotionName?: string | null;
3939
promotionName?: string | null;
40+
isEligibleForChurnCancel: boolean;
4041
isEligibleForChurnStaySubscribed: boolean;
42+
isEligibleForOffer: boolean;
4143
churnStaySubscribedCtaMessage?: string | null;
4244
}
4345

@@ -58,6 +60,9 @@ export const SubscriptionContent = ({
5860
currentInvoiceTotal,
5961
currentInvoiceUrl,
6062
currentPeriodEnd,
63+
isEligibleForChurnCancel,
64+
isEligibleForChurnStaySubscribed,
65+
isEligibleForOffer,
6166
nextInvoiceTax,
6267
nextInvoiceTotal,
6368
nextPromotionName,
@@ -250,9 +255,7 @@ export const SubscriptionContent = ({
250255
</div>
251256

252257
<div>
253-
<p>
254-
{subscription.churnStaySubscribedCtaMessage}
255-
</p>
258+
<p>{subscription.churnStaySubscribedCtaMessage}</p>
256259
<LinkExternal
257260
href={`/${locale}/${subscription.offeringApiIdentifier}/${subscription.interval}/stay_subscribed/loyalty-discount/terms`}
258261
className="w-fit text-sm text-blue-500 underline hover:text-blue-600"
@@ -262,7 +265,7 @@ export const SubscriptionContent = ({
262265
'View coupon terms and restrictions'
263266
)}
264267
>
265-
<Localized id='subscription-content-link-churn-intervention-terms-apply'>
268+
<Localized id="subscription-content-link-churn-intervention-terms-apply">
266269
<span>Terms apply</span>
267270
</Localized>
268271
</LinkExternal>
@@ -272,7 +275,11 @@ export const SubscriptionContent = ({
272275
<div className="ms-auto w-full tablet:w-auto">
273276
{canResubscribe ? (
274277
<Link
275-
href={`/${locale}/subscriptions/${subscription.id}/stay-subscribed`}
278+
href={
279+
isEligibleForChurnStaySubscribed
280+
? `/${locale}/subscriptions/${subscription.id}/loyalty-discount/stay-subscribed`
281+
: `/${locale}/subscriptions/${subscription.id}/stay-subscribed`
282+
}
276283
className="border box-border flex font-bold font-header h-10 items-center justify-center rounded-md py-2 px-5 bg-blue-500 hover:bg-blue-700 text-white w-full tablet:w-auto"
277284
aria-label={`Stay subscribed to ${productName}`}
278285
>
@@ -285,7 +292,13 @@ export const SubscriptionContent = ({
285292
</Link>
286293
) : (
287294
<Link
288-
href={`/${locale}/subscriptions/${subscription.id}/cancel`}
295+
href={
296+
isEligibleForChurnCancel
297+
? `/${locale}/subscriptions/${subscription.id}/loyalty-discount/cancel`
298+
: isEligibleForOffer
299+
? `/${locale}/subscriptions/${subscription.id}/offer`
300+
: `/${locale}/subscriptions/${subscription.id}/cancel`
301+
}
289302
className="border border-grey-200 box-border flex font-bold font-header h-10 items-center justify-center rounded-md py-2 px-5 bg-grey-10 hover:bg-grey-50 w-full tablet:w-auto"
290303
aria-label={`Cancel your subscription to ${productName}`}
291304
>

libs/payments/ui/src/lib/nestapp/nextjs-actions.service.ts

Lines changed: 12 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -663,33 +663,12 @@ export class NextJSActionsService {
663663
acceptLanguage?: string | null;
664664
selectedLanguage?: string;
665665
}) {
666-
const result =
667-
await this.subscriptionManagementService.getCancelFlowContent(
668-
args.uid,
669-
args.subscriptionId,
670-
args.acceptLanguage || undefined,
671-
args.selectedLanguage
672-
);
673-
674-
const churnCancelEligibility =
675-
await this.churnInterventionService.determineCancellationIntervention({
676-
uid: args.uid,
677-
subscriptionId: args.subscriptionId,
678-
acceptLanguage: args.acceptLanguage,
679-
selectedLanguage: args.selectedLanguage,
680-
});
681-
682-
return {
683-
...result,
684-
isEligibleForChurnCancel:
685-
churnCancelEligibility.reason === 'eligible' &&
686-
churnCancelEligibility.cancelChurnInterventionType ===
687-
'cancel_churn_intervention',
688-
isEligibleForCancelInterstitialOffer:
689-
churnCancelEligibility.reason === 'eligible' &&
690-
churnCancelEligibility.cancelChurnInterventionType ===
691-
'cancel_interstitial_offer',
692-
};
666+
return await this.subscriptionManagementService.getCancelFlowContent(
667+
args.uid,
668+
args.subscriptionId,
669+
args.acceptLanguage || undefined,
670+
args.selectedLanguage
671+
);
693672
}
694673

695674
@SanitizeExceptions()
@@ -705,27 +684,12 @@ export class NextJSActionsService {
705684
acceptLanguage?: string | null;
706685
selectedLanguage?: string;
707686
}) {
708-
const result =
709-
await this.subscriptionManagementService.getStaySubscribedFlowContent(
710-
args.uid,
711-
args.subscriptionId,
712-
args.acceptLanguage || undefined,
713-
args.selectedLanguage
714-
);
715-
716-
const churnStaySubscribedEligibility =
717-
await this.churnInterventionService.determineStaySubscribedEligibility(
718-
args.uid,
719-
args.subscriptionId,
720-
args.acceptLanguage,
721-
args.selectedLanguage
722-
);
723-
724-
return {
725-
...result,
726-
isEligibleforChurnStaySubscribed:
727-
churnStaySubscribedEligibility.isEligible,
728-
};
687+
return this.subscriptionManagementService.getStaySubscribedFlowContent(
688+
args.uid,
689+
args.subscriptionId,
690+
args.acceptLanguage || undefined,
691+
args.selectedLanguage
692+
);
729693
}
730694

731695
@SanitizeExceptions()

libs/shared/cms/src/lib/queries/cancel-interstitial-offer/util.ts

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,18 @@ import * as Sentry from '@sentry/node';
1010

1111
export class CancelInterstitialOfferUtil {
1212
constructor(private rawResult: CancelInterstitialOfferResult) {}
13-
getTransformedResult(): CancelInterstitialOfferTransformed {
14-
if (this.rawResult.cancelInterstitialOffers.length !== 1) {
13+
getTransformedResult(): CancelInterstitialOfferTransformed | undefined {
14+
const offers = this.rawResult?.cancelInterstitialOffers ?? [];
15+
16+
if (offers.length !== 1) {
1517
Sentry.captureMessage(
1618
'Unexpected number of cancel interstitial offers found for api identifier, intervals, and locale',
17-
{
18-
extra: {
19-
cancelInterstitialOffersCount:
20-
this.rawResult.cancelInterstitialOffers.length,
21-
},
22-
}
19+
{ extra: { cancelInterstitialOffersCount: offers.length } }
2320
);
2421
}
25-
const cancelInterstitialOffer = this.rawResult.cancelInterstitialOffers[0];
22+
23+
const cancelInterstitialOffer = offers.at(0);
24+
if (!cancelInterstitialOffer) return undefined;
2625

2726
return {
2827
...cancelInterstitialOffer,
@@ -39,24 +38,17 @@ export class CancelInterstitialOfferUtil {
3938
cancelInterstitialOffer.localizations.at(0)?.modalMessage ??
4039
cancelInterstitialOffer.modalMessage
4140
),
42-
productPageUrl:
43-
cancelInterstitialOffer.localizations.at(0)?.productPageUrl ??
44-
cancelInterstitialOffer.productPageUrl,
41+
productPageUrl: cancelInterstitialOffer.productPageUrl,
4542
upgradeButtonLabel:
4643
cancelInterstitialOffer.localizations.at(0)?.upgradeButtonLabel ??
4744
cancelInterstitialOffer.upgradeButtonLabel,
48-
upgradeButtonUrl:
49-
cancelInterstitialOffer.localizations.at(0)?.upgradeButtonUrl ??
50-
cancelInterstitialOffer.upgradeButtonUrl,
45+
upgradeButtonUrl: cancelInterstitialOffer.upgradeButtonUrl,
5146
offering: {
5247
...cancelInterstitialOffer.offering,
5348
defaultPurchase: {
5449
purchaseDetails: {
5550
...cancelInterstitialOffer.offering.defaultPurchase.purchaseDetails,
5651
webIcon:
57-
cancelInterstitialOffer.offering.defaultPurchase.purchaseDetails.localizations.at(
58-
0
59-
)?.webIcon ??
6052
cancelInterstitialOffer.offering.defaultPurchase.purchaseDetails
6153
.webIcon,
6254
},

0 commit comments

Comments
 (0)