Skip to content

Commit bf6915d

Browse files
committed
feat(payments): Update Cancel and Stay subscribed pages
1 parent 298e753 commit bf6915d

File tree

5 files changed

+121
-50
lines changed

5 files changed

+121
-50
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@ 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+
4353
return (
4454
<CancelSubscription
4555
userId={uid}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ 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+
4450
return (
4551
<StaySubscribed
4652
userId={uid}

libs/payments/management/src/lib/churn-intervention.service.ts

Lines changed: 73 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -327,9 +327,6 @@ export class ChurnInterventionService {
327327
async determineCancellationIntervention(args: {
328328
uid: string;
329329
subscriptionId: string;
330-
offeringApiIdentifier: string;
331-
currentInterval: SubplatInterval;
332-
upgradeInterval: SubplatInterval;
333330
acceptLanguage?: string | null;
334331
selectedLanguage?: string;
335332
}) {
@@ -422,64 +419,105 @@ export class ChurnInterventionService {
422419
async determineCancelInterstitialOfferEligibility(args: {
423420
uid: string;
424421
subscriptionId: string;
425-
offeringApiIdentifier: string;
426-
currentInterval: SubplatInterval;
427-
upgradeInterval: SubplatInterval;
428422
acceptLanguage?: string | null;
429423
selectedLanguage?: string;
430424
}) {
431-
const cmsCancelInterstitialOffer =
432-
await this.productConfigurationManager.getCancelInterstitialOffer(
433-
args.offeringApiIdentifier,
434-
args.currentInterval,
435-
args.upgradeInterval,
436-
args.acceptLanguage || undefined,
437-
args.selectedLanguage
438-
);
425+
const upgradeInterval = SubplatInterval.Yearly;
426+
const subscription = await this.subscriptionManager.retrieve(
427+
args.subscriptionId
428+
);
439429

440-
const cmsCancelInterstitialOfferResult =
441-
cmsCancelInterstitialOffer.getTransformedResult();
442-
if (!cmsCancelInterstitialOfferResult) {
430+
if (!subscription) {
443431
this.statsd.increment('cancel_intervention_decision', {
444432
type: 'none',
445-
reason: 'no_cancel_interstitial_offer_found',
433+
reason: 'subscription_not_found',
446434
});
447435
return {
448436
isEligible: false,
449-
reason: 'no_cancel_interstitial_offer_found',
437+
reason: 'subscription_not_found',
450438
cmsCancelInterstitialOfferResult: null,
451439
};
452440
}
453441

454-
const currentStripeInterval =
442+
const currentInterval =
455443
await this.productConfigurationManager.getSubplatIntervalBySubscription(
456-
args.subscriptionId
444+
subscription
457445
);
458-
if (
459-
!currentStripeInterval ||
460-
currentStripeInterval !== args.currentInterval
461-
) {
446+
447+
if (!currentInterval) {
462448
this.statsd.increment('cancel_intervention_decision', {
463449
type: 'none',
464-
reason: 'current_interval_mismatch',
450+
reason: 'current_interval_not_found',
465451
});
466452
return {
467453
isEligible: false,
468-
reason: 'current_interval_mismatch',
454+
reason: 'current_interval_not_found',
469455
cmsCancelInterstitialOfferResult: null,
470456
};
471457
}
472458

473-
try {
474-
await this.productConfigurationManager.retrieveStripePrice(
475-
args.offeringApiIdentifier,
476-
args.upgradeInterval
459+
const stripePriceId = subscription.items.data.at(0)?.price.id;
460+
461+
if (!stripePriceId) {
462+
this.statsd.increment('cancel_intervention_decision', {
463+
type: 'none',
464+
reason: 'stripe_price_id_not_found',
465+
});
466+
return {
467+
isEligible: false,
468+
reason: 'stripe_price_id_not_found',
469+
cmsCancelInterstitialOfferResult: null,
470+
};
471+
}
472+
473+
const result =
474+
await this.productConfigurationManager.getPageContentByPriceIds([
475+
stripePriceId,
476+
]);
477+
const offeringId =
478+
result.purchaseForPriceId(stripePriceId).offering?.apiIdentifier;
479+
480+
if (!offeringId) {
481+
this.statsd.increment('cancel_intervention_decision', {
482+
type: 'none',
483+
reason: 'offering_id_not_found',
484+
});
485+
return {
486+
isEligible: false,
487+
reason: 'offering_id_not_found',
488+
cmsCancelInterstitialOfferResult: null,
489+
};
490+
}
491+
492+
const cmsCancelInterstitialOffer =
493+
await this.productConfigurationManager.getCancelInterstitialOffer(
494+
offeringId,
495+
currentInterval,
496+
upgradeInterval,
497+
args.acceptLanguage || undefined,
498+
args.selectedLanguage
477499
);
478-
} catch {
500+
const cmsCancelInterstitialOfferResult =
501+
cmsCancelInterstitialOffer.getTransformedResult();
502+
503+
if (!cmsCancelInterstitialOfferResult) {
479504
this.statsd.increment('cancel_intervention_decision', {
480505
type: 'none',
481-
reason: 'no_upgrade_plan_found',
506+
reason: 'no_cancel_interstitial_offer_found',
482507
});
508+
return {
509+
isEligible: false,
510+
reason: 'no_cancel_interstitial_offer_found',
511+
cmsCancelInterstitialOfferResult: null,
512+
};
513+
}
514+
515+
try {
516+
await this.productConfigurationManager.retrieveStripePrice(
517+
offeringId,
518+
upgradeInterval
519+
);
520+
} catch {
483521
return {
484522
isEligible: false,
485523
reason: 'no_upgrade_plan_found',
@@ -488,8 +526,8 @@ export class ChurnInterventionService {
488526
}
489527

490528
const eligibility = await this.eligibilityService.checkEligibility(
491-
args.upgradeInterval,
492-
args.offeringApiIdentifier,
529+
upgradeInterval,
530+
offeringId,
493531
args.uid,
494532
args.subscriptionId
495533
);

libs/payments/ui/src/lib/actions/determineCancellationIntervention.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,16 @@
55
'use server';
66

77
import { getApp } from '../nestapp/app';
8-
import { SubplatInterval } from '@fxa/payments/customer';
98

109
export const determineCancellationInterventionAction = async (args: {
1110
uid: string;
1211
subscriptionId: string;
13-
offeringApiIdentifier: string;
14-
currentInterval: SubplatInterval;
15-
upgradeInterval: SubplatInterval;
1612
acceptLanguage?: string | null;
1713
selectedLanguage?: string;
1814
}) => {
1915
return await getApp().getActionsService().determineCancellationIntervention({
2016
uid: args.uid,
2117
subscriptionId: args.subscriptionId,
22-
offeringApiIdentifier: args.offeringApiIdentifier,
23-
currentInterval: args.currentInterval,
24-
upgradeInterval: args.upgradeInterval,
2518
acceptLanguage: args.acceptLanguage,
2619
selectedLanguage: args.selectedLanguage,
2720
});

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

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -318,19 +318,13 @@ export class NextJSActionsService {
318318
async determineCancellationIntervention(args: {
319319
uid: string;
320320
subscriptionId: string;
321-
offeringApiIdentifier: string;
322-
currentInterval: SubplatInterval;
323-
upgradeInterval: SubplatInterval;
324321
acceptLanguage?: string | null;
325322
selectedLanguage?: string;
326323
}) {
327324
return await this.churnInterventionService.determineCancellationIntervention(
328325
{
329326
uid: args.uid,
330327
subscriptionId: args.subscriptionId,
331-
offeringApiIdentifier: args.offeringApiIdentifier,
332-
currentInterval: args.currentInterval,
333-
upgradeInterval: args.upgradeInterval,
334328
acceptLanguage: args.acceptLanguage,
335329
selectedLanguage: args.selectedLanguage,
336330
}
@@ -677,7 +671,25 @@ export class NextJSActionsService {
677671
args.selectedLanguage
678672
);
679673

680-
return result;
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+
};
681693
}
682694

683695
@SanitizeExceptions()
@@ -701,7 +713,19 @@ export class NextJSActionsService {
701713
args.selectedLanguage
702714
);
703715

704-
return result;
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+
};
705729
}
706730

707731
@SanitizeExceptions()

0 commit comments

Comments
 (0)