(core) Billing for formula assistant

Summary:
Adding limits for AI calls and connecting those limits with a Stripe Account.

- New table in homedb called `limits`
- All calls to the AI are not routed through DocApi and measured.
- All products now contain a special key `assistantLimit`, with a default value 0
- Limit is reset every time the subscription has changed its period
- The billing page is updated with two new options that describe the AI plan
- There is a new popup that allows the user to upgrade to a higher plan
- Tiers are read directly from the Stripe product with a volume pricing model

Test Plan: Updated and added

Reviewers: georgegevoian, paulfitz

Reviewed By: georgegevoian

Subscribers: dsagal

Differential Revision: https://phab.getgrist.com/D3907
This commit is contained in:
Jarosław Sadziński
2023-07-05 17:36:45 +02:00
parent 75d979abdb
commit d13b9b9019
26 changed files with 501 additions and 106 deletions

View File

@@ -43,6 +43,16 @@ export interface IBillingPlan {
active: boolean;
}
export interface ILimitTier {
name?: string;
volume: number;
price: number;
flatFee: number;
type: string;
planId: string;
interval: string; // probably 'month'|'year';
}
// Utility type that requires all properties to be non-nullish.
// type NonNullableProperties<T> = { [P in keyof T]: Required<NonNullable<T[P]>>; };
@@ -69,6 +79,7 @@ export interface IBillingDiscount {
export interface IBillingSubscription {
// All standard plan options.
plans: IBillingPlan[];
tiers: ILimitTier[];
// Index in the plans array of the plan currently in effect.
planIndex: number;
// Index in the plans array of the plan to be in effect after the current period end.
@@ -111,6 +122,14 @@ export interface IBillingSubscription {
lastInvoiceUrl?: string; // URL of the Stripe-hosted page with the last invoice.
lastChargeError?: string; // The last charge error, if any, to show in case of a bad status.
lastChargeTime?: number; // The time of the last charge attempt.
limit?: ILimit|null;
}
export interface ILimit {
limitValue: number;
currentUsage: number;
type: string; // Limit type, for now only assistant is supported.
price: number; // If this is 0, it means it is a free plan.
}
export interface IBillingOrgSettings {
@@ -139,6 +158,7 @@ export interface BillingAPI {
downgradePlan(planName: string): Promise<void>;
renewPlan(): string;
customerPortal(): string;
updateAssistantPlan(tier: number): Promise<void>;
}
export class BillingAPIImpl extends BaseAPI implements BillingAPI {
@@ -230,6 +250,13 @@ export class BillingAPIImpl extends BaseAPI implements BillingAPI {
return `${this._url}/api/billing/renew`;
}
public async updateAssistantPlan(tier: number): Promise<void> {
await this.request(`${this._url}/api/billing/upgrade-assistant`, {
method: 'POST',
body: JSON.stringify({ tier })
});
}
/**
* Checks if current org has active subscription for a Stripe plan.
*/