import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router'; import { AuthService } from '../services/auth.service'; import { SubscriptionService } from '../services/subscription.service'; import { catchError, map } from 'rxjs/operators'; import { Observable, of } from 'rxjs'; import { StripeSubscription } from '../models/subscription.model'; import { SUB, SubStripe } from '@app/profile/common'; import { AppMessageService } from '@app/shared/app-message.service'; import { AC, globals } from '@app/shared/global'; import { RouterUtilsService } from '@app/shared/router-utils.service'; /** This guards against unresolved subscriptions when the user is subscribing to a new subscription in the main flow (services <->billing-detail<->checkout<->checkout-review<->checkout-confirm). When the user has an unresolved subscription, the guard forces user to enter the resolving subscription flow. */ @Injectable({ providedIn: 'root' }) export class SubscriptionGuard implements CanActivate { constructor( private readonly authSvc: AuthService, private readonly subSvc: SubscriptionService, private readonly router: Router, private readonly msgSvc: AppMessageService, private readonly routerUtils: RouterUtilsService ) { } canActivate(activatedRoute: ActivatedRouteSnapshot): Observable { return this.subSvc.fetchSubscriptions(this.authSvc.user?.membership?.custId).pipe( map((subs) => { const fromPath = this.routerUtils.getCurrentUrl().split('/').pop(); const toPath = activatedRoute.url[0]?.path; const hasLatestSubs = subs?.length > 0; const hasUnresolvedSubs = this.hasUnresolvedSubs(subs); const hasAllActiveSubs = hasLatestSubs && !hasUnresolvedSubs; const isSubscribing = (!hasLatestSubs || hasAllActiveSubs) && (this.isInMainFlow(fromPath, toPath) || this.isPageReload(fromPath, toPath)); const isResolvingSubs = hasUnresolvedSubs && (this.isInResolvingFlow(fromPath, toPath) || this.isPageReload(fromPath, toPath)); const viewPayment = this.isPaymentPage(toPath); const viewPaymentMethod = this.isPaymentMethodPage(toPath); const viewAC = this.isACPage(toPath) && !hasUnresolvedSubs; const canNavigate = isSubscribing || viewPayment || viewPaymentMethod || viewAC || true; const shouldNavigateToMyServices = hasUnresolvedSubs && !isResolvingSubs; if (canNavigate) { return true; } else if (shouldNavigateToMyServices) { this.router.navigate([SUB.PROFILE, SUB.MY_SERVICES]); } else if (isResolvingSubs) { return true; } else { this.router.navigate(['/', SUB.HOME]); } }), catchError(err => { this.msgSvc.addFailedMsg(globals.doThingsFailed.replace('#do#', globals.load).replace('#thing#', globals.subscription)); return of(false); }) ); } private isInMainFlow(fromPath: string, toPath: string): boolean { return (fromPath === SUB.SERVICES && toPath === SUB.BILL_ADR) || (fromPath === SUB.BILL_ADR && toPath === SUB.CHKOUT) || (fromPath === SUB.CHKOUT && toPath === SUB.BILL_ADR) || (fromPath === SUB.CHKOUT && toPath === SUB.CHKOUT_REV) || (fromPath === SUB.CHKOUT_REV && toPath === SUB.CHKOUT_CONF) || (fromPath === SUB.CHKOUT_REV && toPath === SUB.CHKOUT) || this.isTrialFLow(fromPath, toPath); } private isInResolvingFlow(fromPath: string, toPath: string): boolean { return (fromPath === SUB.UNPAID_SUB && toPath === SUB.BILL_ADR) || (fromPath === SUB.BILL_ADR && toPath === SUB.CHKOUT) || (fromPath === SUB.BILL_ADR && toPath === SUB.UNPAID_SUB) || (fromPath === SUB.CHKOUT && toPath === SUB.BILL_ADR) || (fromPath === SUB.CHKOUT && toPath === SUB.CHKOUT_REV) || (fromPath === SUB.CHKOUT_REV && toPath === SUB.CHKOUT) || (fromPath === SUB.CHKOUT_REV && toPath === SUB.CHKOUT_CONF) || (fromPath === SUB.MY_SERVICES && toPath === SUB.CHKOUT_REV) || (fromPath === SUB.MY_SERVICES && toPath === SUB.UNPAID_SUB); } private isTrialFLow(fromPath: string, toPath: string): boolean { return (fromPath === SUB.CHKOUT && toPath === SUB.CHKOUT_CONF) || (fromPath === SUB.MY_SERVICES && toPath === SUB.BILL_ADR); } private isPageReload(fromPath: string, toPath: string): boolean { return (!fromPath && (toPath === SUB.BILL_ADR || toPath === SUB.CHKOUT || toPath === SUB.CHKOUT_REV || toPath === SUB.CHKOUT_CONF)); } private hasUnresolvedSubs(subs: StripeSubscription[]): boolean { return subs?.some(sub => sub.status === SubStripe.PAST_DUE || sub.status === SubStripe.OVERDUE || sub.status === SubStripe.UNPAID || sub.status === SubStripe.INCOMPLETE); } private isPaymentPage(path: string): boolean { return path === SUB.PM_HISTORY || path === SUB.PM_DETAIL; } private isPaymentMethodPage(path: string): boolean { return path === SUB.PM_LIST; } private isACPage(path: string): boolean { return path === AC; } }