mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) link AppSumo activations with stripe, and support upgrades/downgrades
Summary: This links AppSumo sign-ups with Stripe subscriptions and our billing pages. Different AppSumo tiers are supported by different coupons on the standard plan. Configuration of this is in stripe, and then cached in the database. The front end is tweaked just enough to make completing a sign-up possible. It is not yet friendly. Not covered includes: * Streamlining landing page. * Making billing pages git clearer summaries of AppSumo states. * Making flow through Cognito as graceful as possible - default probably doesn't meet AppSumo requirements. * Disabling site on cancellation/refund. * Downgrades when more seats in use than lower tier allows. Test Plan: api-level tests added. No front-end tests yet. Reviewers: dsagal Reviewed By: dsagal Differential Revision: https://phab.getgrist.com/D2878
This commit is contained in:
parent
6240fd6982
commit
1af99e9567
@ -175,6 +175,19 @@ export class BillingModelImpl extends Disposable implements BillingModel {
|
|||||||
}
|
}
|
||||||
// If there is an org update, re-initialize the org in the client.
|
// If there is an org update, re-initialize the org in the client.
|
||||||
if (newSettings) { this._appModel.topAppModel.initialize(); }
|
if (newSettings) { this._appModel.topAppModel.initialize(); }
|
||||||
|
} else if (task === 'signUpLite') {
|
||||||
|
// This is a sign up variant where payment info is handled externally.
|
||||||
|
// All that can change here is company name, and domain.
|
||||||
|
const org = this._appModel.currentOrg;
|
||||||
|
const name = formData.settings && formData.settings.name;
|
||||||
|
const domain = formData.settings && formData.settings.domain;
|
||||||
|
const newSettings = org && (name !== org.name || domain !== org.domain) && formData.settings;
|
||||||
|
// If the address or settings have a new value, run the update.
|
||||||
|
if (newSettings) {
|
||||||
|
await this._billingAPI.updateAddress(undefined, newSettings || undefined);
|
||||||
|
}
|
||||||
|
// If there is an org update, re-initialize the org in the client.
|
||||||
|
if (newSettings) { this._appModel.topAppModel.initialize(); }
|
||||||
} else {
|
} else {
|
||||||
throw new Error('BillingPage _submit error: no task in progress');
|
throw new Error('BillingPage _submit error: no task in progress');
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,8 @@ const taskActions = {
|
|||||||
updatePlan: 'Update Plan',
|
updatePlan: 'Update Plan',
|
||||||
addCard: 'Add Payment Method',
|
addCard: 'Add Payment Method',
|
||||||
updateCard: 'Update Payment Method',
|
updateCard: 'Update Payment Method',
|
||||||
updateAddress: 'Update Address'
|
updateAddress: 'Update Address',
|
||||||
|
signUpLite: 'Complete Sign Up'
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -310,10 +311,10 @@ export class BillingPage extends Disposable {
|
|||||||
// If there is an immediate charge required, require re-entering the card info.
|
// If there is an immediate charge required, require re-entering the card info.
|
||||||
// Show all forms on sign up.
|
// Show all forms on sign up.
|
||||||
this._form = new BillingForm(org, (...args) => this._model.isDomainAvailable(...args), {
|
this._form = new BillingForm(org, (...args) => this._model.isDomainAvailable(...args), {
|
||||||
payment: task !== 'updateAddress',
|
payment: ['signUp', 'updatePlan', 'addCard', 'updateCard'].includes(task),
|
||||||
address: task === 'signUp' || task === 'updateAddress',
|
address: ['signUp', 'updateAddress'].includes(task),
|
||||||
settings: task === 'signUp' || task === 'updateAddress',
|
settings: ['signUp', 'signUpLite', 'updateAddress'].includes(task),
|
||||||
domain: task === 'signUp'
|
domain: ['signUp', 'signUpLite'].includes(task)
|
||||||
}, { address: currentAddress, settings: currentSettings, card: this._formData.card });
|
}, { address: currentAddress, settings: currentSettings, card: this._formData.card });
|
||||||
return dom('div',
|
return dom('div',
|
||||||
dom.onDispose(() => {
|
dom.onDispose(() => {
|
||||||
@ -447,6 +448,8 @@ export class BillingPage extends Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _buildDomainSummary(domain: string|null) {
|
private _buildDomainSummary(domain: string|null) {
|
||||||
|
const task = this._model.currentTask.get();
|
||||||
|
if (task === 'signUpLite') { return null; }
|
||||||
return css.summaryItem(
|
return css.summaryItem(
|
||||||
css.summaryHeader(
|
css.summaryHeader(
|
||||||
css.billingBoldText('Billing Info'),
|
css.billingBoldText('Billing Info'),
|
||||||
|
@ -10,7 +10,7 @@ export type BillingSubPage = typeof BillingSubPage.type;
|
|||||||
export const BillingPage = StringUnion(...BillingSubPage.values, 'billing');
|
export const BillingPage = StringUnion(...BillingSubPage.values, 'billing');
|
||||||
export type BillingPage = typeof BillingPage.type;
|
export type BillingPage = typeof BillingPage.type;
|
||||||
|
|
||||||
export const BillingTask = StringUnion('signUp', 'updatePlan', 'addCard', 'updateCard', 'updateAddress');
|
export const BillingTask = StringUnion('signUp', 'signUpLite', 'updatePlan', 'addCard', 'updateCard', 'updateAddress');
|
||||||
export type BillingTask = typeof BillingTask.type;
|
export type BillingTask = typeof BillingTask.type;
|
||||||
|
|
||||||
// Note that IBillingPlan includes selected fields from the Stripe plan object along with
|
// Note that IBillingPlan includes selected fields from the Stripe plan object along with
|
||||||
|
@ -1673,7 +1673,8 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
// Pick out properties that are allowed to be changed, to prevent accidental updating
|
// Pick out properties that are allowed to be changed, to prevent accidental updating
|
||||||
// of other information.
|
// of other information.
|
||||||
const updated = pick(billingAccountCopy, 'inGoodStanding', 'status', 'stripeCustomerId',
|
const updated = pick(billingAccountCopy, 'inGoodStanding', 'status', 'stripeCustomerId',
|
||||||
'stripeSubscriptionId', 'stripePlanId', 'product');
|
'stripeSubscriptionId', 'stripePlanId', 'product', 'externalId',
|
||||||
|
'externalOptions');
|
||||||
billingAccount.paid = undefined; // workaround for a typeorm bug fixed upstream in
|
billingAccount.paid = undefined; // workaround for a typeorm bug fixed upstream in
|
||||||
// https://github.com/typeorm/typeorm/pull/4035
|
// https://github.com/typeorm/typeorm/pull/4035
|
||||||
await transaction.save(Object.assign(billingAccount, updated));
|
await transaction.save(Object.assign(billingAccount, updated));
|
||||||
|
Loading…
Reference in New Issue
Block a user