mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(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:
@@ -3,12 +3,13 @@ import {BillingAccountManager} from 'app/gen-server/entity/BillingAccountManager
|
||||
import {Organization} from 'app/gen-server/entity/Organization';
|
||||
import {Product} from 'app/gen-server/entity/Product';
|
||||
import {nativeValues} from 'app/gen-server/lib/values';
|
||||
import {Limit} from 'app/gen-server/entity/Limit';
|
||||
|
||||
// This type is for billing account status information. Intended for stuff
|
||||
// like "free trial running out in N days".
|
||||
interface BillingAccountStatus {
|
||||
export interface BillingAccountStatus {
|
||||
stripeStatus?: string;
|
||||
currentPeriodEnd?: Date;
|
||||
currentPeriodEnd?: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
@@ -68,6 +69,9 @@ export class BillingAccount extends BaseEntity {
|
||||
@OneToMany(type => Organization, org => org.billingAccount)
|
||||
public orgs: Organization[];
|
||||
|
||||
@OneToMany(type => Limit, limit => limit.billingAccount)
|
||||
public limits: Limit[];
|
||||
|
||||
// A calculated column that is true if it looks like there is a paid plan.
|
||||
@Column({name: 'paid', type: 'boolean', insert: false, select: false})
|
||||
public paid?: boolean;
|
||||
|
||||
46
app/gen-server/entity/Limit.ts
Normal file
46
app/gen-server/entity/Limit.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import {BaseEntity, Column, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn} from 'typeorm';
|
||||
import {BillingAccount} from 'app/gen-server/entity/BillingAccount';
|
||||
import {nativeValues} from 'app/gen-server/lib/values';
|
||||
|
||||
@Entity('limits')
|
||||
export class Limit extends BaseEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id: number;
|
||||
|
||||
@Column()
|
||||
public limit: number;
|
||||
|
||||
@Column()
|
||||
public usage: number;
|
||||
|
||||
@Column()
|
||||
public type: string;
|
||||
|
||||
@Column({name: 'billing_account_id'})
|
||||
public billingAccountId: number;
|
||||
|
||||
@ManyToOne(type => BillingAccount)
|
||||
@JoinColumn({name: 'billing_account_id'})
|
||||
public billingAccount: BillingAccount;
|
||||
|
||||
@Column({name: 'created_at', default: () => "CURRENT_TIMESTAMP"})
|
||||
public createdAt: Date;
|
||||
|
||||
/**
|
||||
* Last time the Limit.limit value was changed, by an upgrade or downgrade. Null if it has never been changed.
|
||||
*/
|
||||
@Column({name: 'changed_at', type: nativeValues.dateTimeType, nullable: true})
|
||||
public changedAt: Date|null;
|
||||
|
||||
/**
|
||||
* Last time the Limit.usage was used (by sending a request to the model). Null if it has never been used.
|
||||
*/
|
||||
@Column({name: 'used_at', type: nativeValues.dateTimeType, nullable: true})
|
||||
public usedAt: Date|null;
|
||||
|
||||
/**
|
||||
* Last time the Limit.usage was reset, probably by billing cycle change. Null if it has never been reset.
|
||||
*/
|
||||
@Column({name: 'reset_at', type: nativeValues.dateTimeType, nullable: true})
|
||||
public resetAt: Date|null;
|
||||
}
|
||||
@@ -13,7 +13,11 @@ export const personalLegacyFeatures: Features = {
|
||||
// no vanity domain
|
||||
maxDocsPerOrg: 10,
|
||||
maxSharesPerDoc: 2,
|
||||
maxWorkspacesPerOrg: 1
|
||||
maxWorkspacesPerOrg: 1,
|
||||
/**
|
||||
* One time limit of 100 requests.
|
||||
*/
|
||||
baseMaxAssistantCalls: 100,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -23,7 +27,12 @@ export const teamFeatures: Features = {
|
||||
workspaces: true,
|
||||
vanityDomain: true,
|
||||
maxSharesPerWorkspace: 0, // all workspace shares need to be org members.
|
||||
maxSharesPerDoc: 2
|
||||
maxSharesPerDoc: 2,
|
||||
/**
|
||||
* Limit of 100 requests, but unlike for personal/free orgs the usage for this limit is reset at every billing cycle
|
||||
* through Stripe webhook. For canceled subscription the usage is not reset, as the billing cycle is not changed.
|
||||
*/
|
||||
baseMaxAssistantCalls: 100,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -40,6 +49,10 @@ export const teamFreeFeatures: Features = {
|
||||
baseMaxDataSizePerDocument: 5000 * 2 * 1024, // 2KB per row
|
||||
baseMaxAttachmentsBytesPerDocument: 1 * 1024 * 1024 * 1024, // 1GB
|
||||
gracePeriodDays: 14,
|
||||
/**
|
||||
* One time limit of 100 requests.
|
||||
*/
|
||||
baseMaxAssistantCalls: 100,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -55,6 +68,7 @@ export const teamFreeFeatures: Features = {
|
||||
baseMaxDataSizePerDocument: 5000 * 2 * 1024, // 2KB per row
|
||||
baseMaxAttachmentsBytesPerDocument: 1 * 1024 * 1024 * 1024, // 1GB
|
||||
gracePeriodDays: 14,
|
||||
baseMaxAssistantCalls: 100,
|
||||
};
|
||||
|
||||
export const testDailyApiLimitFeatures = {
|
||||
@@ -79,6 +93,7 @@ export const suspendedFeatures: Features = {
|
||||
maxDocsPerOrg: 0,
|
||||
maxSharesPerDoc: 0,
|
||||
maxWorkspacesPerOrg: 0,
|
||||
baseMaxAssistantCalls: 0,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user