(core) suspend a team site after an AppSumo refund

Summary:
This suspends service to a team site for which an AppSumo refund has been made, and nudges users to their free personal account.

I expect that a refund request would fail for a site where user is also paying us for extra seats.

Test Plan: tested manually

Reviewers: dsagal

Reviewed By: dsagal

Differential Revision: https://phab.getgrist.com/D2912
pull/9/head
Paul Fitzpatrick 3 years ago
parent 6e15d44cf6
commit 1ce5e98996

@ -104,7 +104,7 @@ export class TopAppModelImpl extends Disposable implements TopAppModel {
org.billingAccount.product.name === 'suspended') {
this.notifier.createUserError(
'This team site is suspended. Documents can be read, but not modified.',
{actions: ['renew']}
{actions: ['renew', 'personal']}
);
}
}

@ -34,7 +34,7 @@ export interface IProgress extends Expirable {
}
// Identifies supported actions. These are implemented in NotifyUI.
export type NotifyAction = 'upgrade' | 'renew' | 'report-problem' | 'ask-for-help';
export type NotifyAction = 'upgrade' | 'renew' | 'personal' | 'report-problem' | 'ask-for-help';
export interface INotifyOptions {
message: string | (() => DomElementArg); // A string, or a function that builds dom.

@ -146,6 +146,7 @@ export class BillingPage extends Disposable {
// name and minimizing the plan.
const tier = sub.discountName && sub.discountName.includes(' Tier ');
const planName = tier ? sub.discountName! : sub.activePlan.nickname;
const invoiceId = this._appModel.currentOrg?.billingAccount?.externalOptions?.invoiceId;
return [
css.summaryFeatures(
validPlan ? [
@ -182,7 +183,7 @@ export class BillingPage extends Disposable {
makeSummaryFeature([`Your next invoice is `, getPriceString(sub.nextTotal),
' on ', dateFmt(sub.periodEnd)]),
] : null,
tier ? this.buildAppSumoLink(this._appModel.currentOrg?.billingAccount?.externalOptions?.invoiceId) : null,
invoiceId ? this.buildAppSumoLink(invoiceId) : null,
getSubscriptionProblem(sub),
testId('summary')
),
@ -252,8 +253,7 @@ export class BillingPage extends Disposable {
}
// Include a precise link back to AppSumo for changing plans.
public buildAppSumoLink(invoiceId: string | undefined) {
if (!invoiceId) { return null; }
public buildAppSumoLink(invoiceId: string) {
return dom('div',
css.billingTextBtn({ style: 'margin: 10px 0;' },
cssBreadcrumbsLink(

@ -31,6 +31,17 @@ function buildAction(action: NotifyAction, item: Notification, options: IBeaconO
return dom('a', cssToastAction.cls(''), 'Renew', {target: '_blank'},
{href: urlState().makeUrl({billing: 'billing'})});
case 'personal':
if (!appModel) { return null; }
return cssToastAction('Go to your free personal site', dom.on('click', async () => {
const info = await appModel.api.getSessionAll();
const orgs = info.orgs.filter(org => org.owner && org.owner.id === appModel.currentUser?.id);
if (orgs.length !== 1) {
throw new Error('Cannot find personal site, sorry!');
}
window.location.assign(urlState().makeUrl({org: orgs[0].domain || undefined}));
}));
case 'report-problem':
return cssToastAction('Report a problem', testId('toast-report-problem'),
dom.on('click', () => beaconOpenMessage({...options, includeAppErrors: true})));

@ -110,8 +110,9 @@ export function getDefaultProductNames() {
return {
personal: 'starter', // Personal site start off on a functional plan.
teamInitial: 'stub', // Team site starts off on a limited plan, requiring subscription.
team: 'team', // Functional team site
};
teamCancel: 'suspended', // Team site that has been 'turned off'.
team: 'team', // Functional team site.
};
}
/**

Loading…
Cancel
Save