Cancellation & Reactivation

A supplier can request deactivation from account settings. Because Payability advances against future receipts, cancellation is not immediate — each connected marketplace has an opt-out date after which funding stops, and the supplier may carry a residual liability until then.

Status model

export enum CancelationStatus {
    Canceled = 'canceled',
    Pending  = 'pending',
}

null

Not cancelling.

Pending

Cancellation requested; opt-out dates are running.

Canceled

Cancellation complete. Combined with supplierStatus === Suspended, selectIsSupplierCancelled returns true.

Availability

CancellationWorkflowService decides which action to offer by reading cancellationPermissions off the supplier status:

this.store.select(selectCurrentUserId).pipe(
    filter(supplierKey => coerceBooleanProperty(supplierKey)),
    take(1),
    switchMap(supplierKey => this.suppliersService.getSupplierStatus(supplierKey)),
    pluck('cancellationPermissions'),
).subscribe(v => { this.showBtn(v); this.loadingActualStatusSubject$.next(false); });
export enum CancellationWorkflowOptionAvailableEnum {
    DEACTIVATE = 'DEACTIVATE',
    REACTIVATE = 'REACTIVATE',
    NONE       = 'NONE',
}

canReactivate takes precedence over canCancel; if neither is set the section renders nothing. The service exposes activeOption$, loadingActualStatus$ and showLoaderOnButton$ so the UI can show a skeleton while the status resolves.

The service calls getSupplierStatus() directly rather than reading the auth slice, because cancellationPermissions is not carried on the Status model.

Deactivation

supplier clicks "Deactivate account"
  → track(CancellationWorkflowEventType.DeactivateAccountStartBtnClicked)
  → startDeactivateAccountProcess()
  → confirmation modal (MessageModalService)
  → cancellation survey
        CancellationSurveyShowWhenDeactivatingAccountService
        → track(CancellationWorkflowEventType.SubmittingTheCancellationSurvey)
  → CancellationWorkflowApiService submits the request
  → success toast (PybToastService)
  → cancelationStatus becomes Pending

Reactivation

supplier clicks "Reactivate account"
  → track(CancellationWorkflowEventType.ReactivateAccountStartBtnClicked)
  → confirmation modal
  → track(CancellationWorkflowEventType.ReactivateAccountConfirmBtnClicked)
  → CancellationWorkflowApiService reactivation call

The cancellation survey

views/partials/modals/cancellation-survey/, registered globally by CancellationSurveyModule in AppModule so it can be opened from anywhere.

Fields:

export enum CancellationSurveyFormFieldsTypes {
    REASON         = 'reasonCode',
    COMMENT        = 'comment',
    RECOMMENDATION = 'rating',
}

Reason options arrive on the cancellation_survey dashboard notification as DashboardNotificationCancellationSurveyOptionDataDto[], so the option list is server-controlled and can change without a release.

The survey is also reachable from the notification itself, not only from the deactivation flow.

Cancellation banners

CancellationBannerInitializerService runs from GlobalSubscriptionsService.init() and only acts when cancelationStatus === Pending. It fetches the supplier’s marketplaces and partitions them by opt-out date.

private isOptOutDateSet(optOutDate: string): boolean {
    return optOutDate && new PybDate(optOutDate, this.rawDateFormat).get('year') !== 9999;   (1)
}

private pendingCancellationFilter = (mp: SupplierMarketplacesModel): boolean => {
    const optOutDate = new PybDate(mp.fundingParticulars.lastOptOutDate, this.rawDateFormat);
    return new PybDate().isBefore(optOutDate, 'd');                                          (2)
};

private reachedCancellationFilter = (mp) => !this.pendingCancellationFilter(mp);
1 Year 9999 is the sentinel for "no opt-out date" — such marketplaces are excluded entirely.
2 Opt-out still in the future → pending. Otherwise → reached.

Pending banner

Composed from three translation keys:

BANNERS.CANCELLATION.PENDING.YOUR_ACCOUNT

Lead sentence.

BANNERS.CANCELLATION.PENDING.LAST_DAY

One per marketplace, interpolating marketplaceName and optOutDate (reformatted yyyy-MM-dd → MM/dd/yyyy).

BANNERS.CANCELLATION.READ_MORE

Trailing link.

Reached banner

Lists the marketplaces whose opt-out date has passed, then branches on the supplier’s potential liability, read from the balance slice:

potentialLiability > 0

BANNERS.CANCELLATION.REACHED.CANCELLATION_LIABILITY with the amount, plus READ_MORE.

otherwise

BANNERS.CANCELLATION.REACHED.CANCELLATION_COMPLETE.

Both banners are BannerTypes.WARNING with hideClose: false, and previous banners are cleared before new ones are shown so repeated evaluation cannot stack duplicates.

Account-cancelled banner

InternalMessagesService.initAccountCancelledBanner(), also called from GlobalSubscriptionsService.init(), shows the terminal banner once the account is fully cancelled.

Analytics

Constant Segment event

DeactivateAccountStartBtnClicked

Clicked_Deactivate_Account_Button

ReactivateAccountStartBtnClicked

Clicked_Reactivate_Account_Button

ReactivateAccountConfirmBtnClicked

Clicked_Reactivate_Account_Confirm_Button

SubmittingTheCancellationSurvey

Submitting_The_Cancellation_Survey

Deactivation has a start event but no confirm event, while reactivation has both. Funnel analysis of completed deactivations must use the survey-submission event as the proxy.

FeedbackService

An unrelated small service despite the name — it opens SwitchCdDialogComponent (the checking/deposit account switch dialog) with an optional revert flag. It is not part of the cancellation survey.