mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) move home server into core
Summary: This moves enough server material into core to run a home server. The data engine is not yet incorporated (though in manual testing it works when ported). Test Plan: existing tests pass Reviewers: dsagal Reviewed By: dsagal Differential Revision: https://phab.getgrist.com/D2552
This commit is contained in:
212
app/common/BillingAPI.ts
Normal file
212
app/common/BillingAPI.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
import {BaseAPI, IOptions} from 'app/common/BaseAPI';
|
||||
import {FullUser} from 'app/common/LoginSessionAPI';
|
||||
import {StringUnion} from 'app/common/StringUnion';
|
||||
import {addCurrentOrgToPath} from 'app/common/urlUtils';
|
||||
import {BillingAccount, ManagerDelta, OrganizationWithoutAccessInfo} from 'app/common/UserAPI';
|
||||
|
||||
export const BillingSubPage = StringUnion('payment', 'plans');
|
||||
export type BillingSubPage = typeof BillingSubPage.type;
|
||||
|
||||
export const BillingPage = StringUnion(...BillingSubPage.values, 'billing');
|
||||
export type BillingPage = typeof BillingPage.type;
|
||||
|
||||
export const BillingTask = StringUnion('signUp', 'updatePlan', 'addCard', 'updateCard', 'updateAddress');
|
||||
export type BillingTask = typeof BillingTask.type;
|
||||
|
||||
// Note that IBillingPlan includes selected fields from the Stripe plan object along with
|
||||
// custom metadata fields that are present on plans we store in Stripe.
|
||||
// For reference: https://stripe.com/docs/api/plans/object
|
||||
export interface IBillingPlan {
|
||||
id: string; // the Stripe plan id
|
||||
nickname: string;
|
||||
currency: string; // lowercase three-letter ISO currency code
|
||||
interval: string; // billing frequency - one of day, week, month or year
|
||||
amount: number; // amount in cents charged at each interval
|
||||
metadata: {
|
||||
family?: string; // groups plans for filtering by GRIST_STRIPE_FAMILY env variable
|
||||
isStandard: boolean; // indicates that the plan should be returned by the API to be offered.
|
||||
supportAvailable: boolean;
|
||||
gristProduct: string; // name of grist product that should be used with this plan.
|
||||
unthrottledApi: boolean;
|
||||
customSubdomain: boolean;
|
||||
workspaces: boolean;
|
||||
maxDocs?: number; // if given, limit of docs that can be created
|
||||
maxUsersPerDoc?: number; // if given, limit of users each doc can be shared with
|
||||
};
|
||||
trial_period_days: number|null; // Number of days in the trial period, or null if there is none.
|
||||
product: string; // the Stripe product id.
|
||||
}
|
||||
|
||||
// Stripe customer address information. Used to maintain the company address.
|
||||
// For reference: https://stripe.com/docs/api/customers/object#customer_object-address
|
||||
export interface IBillingAddress {
|
||||
line1: string;
|
||||
line2?: string;
|
||||
city?: string;
|
||||
state?: string;
|
||||
postal_code?: string;
|
||||
country?: string;
|
||||
}
|
||||
|
||||
export interface IBillingCard {
|
||||
funding?: 'credit'|'debit'|'prepaid'|'unknown';
|
||||
brand?: string;
|
||||
country?: string; // uppercase two-letter ISO country code
|
||||
last4?: string; // last 4 digits of the card number
|
||||
name?: string|null;
|
||||
}
|
||||
|
||||
export interface IBillingSubscription {
|
||||
// All standard plan options.
|
||||
plans: IBillingPlan[];
|
||||
// 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.
|
||||
// Equal to the planIndex when the plan has not been downgraded or cancelled.
|
||||
upcomingPlanIndex: number;
|
||||
// Timestamp in milliseconds indicating when the current plan period ends.
|
||||
// Null if the account is not signed up with Stripe.
|
||||
periodEnd: number|null;
|
||||
// Whether the subscription is in the trial period.
|
||||
isInTrial: boolean;
|
||||
// Value in cents remaining for the current subscription. This indicates the amount that
|
||||
// will be discounted from a subscription upgrade.
|
||||
valueRemaining: number;
|
||||
// The payment card, or null if none is attached.
|
||||
card: IBillingCard|null;
|
||||
// The company address.
|
||||
address: IBillingAddress|null;
|
||||
// The effective tax rate of the customer for the given address.
|
||||
taxRate: number;
|
||||
// The current number of users with whom the paid org is shared.
|
||||
userCount: number;
|
||||
// The next total in cents that Stripe is going to charge (includes tax and discount).
|
||||
nextTotal: number;
|
||||
// Name of the discount if any.
|
||||
discountName: string|null;
|
||||
// Last plan we had a subscription for, if any.
|
||||
lastPlanId: string|null;
|
||||
// Whether there is a valid plan in effect
|
||||
isValidPlan: boolean;
|
||||
}
|
||||
|
||||
export interface IBillingOrgSettings {
|
||||
name: string;
|
||||
domain: string;
|
||||
}
|
||||
|
||||
// Full description of billing account, including nested list of orgs and managers.
|
||||
export interface FullBillingAccount extends BillingAccount {
|
||||
orgs: OrganizationWithoutAccessInfo[];
|
||||
managers: FullUser[];
|
||||
}
|
||||
|
||||
export interface BillingAPI {
|
||||
isDomainAvailable(domain: string): Promise<boolean>;
|
||||
getTaxRate(address: IBillingAddress): Promise<number>;
|
||||
getPlans(): Promise<IBillingPlan[]>;
|
||||
getSubscription(): Promise<IBillingSubscription>;
|
||||
getBillingAccount(): Promise<FullBillingAccount>;
|
||||
// The signUp function takes the tokenId generated when card data is submitted to Stripe.
|
||||
// See: https://stripe.com/docs/stripe-js/reference#stripe-create-token
|
||||
signUp(planId: string, tokenId: string, address: IBillingAddress,
|
||||
settings: IBillingOrgSettings): Promise<OrganizationWithoutAccessInfo>;
|
||||
setCard(tokenId: string): Promise<void>;
|
||||
removeCard(): Promise<void>;
|
||||
setSubscription(planId: string, tokenId?: string): Promise<void>;
|
||||
updateAddress(address?: IBillingAddress, settings?: IBillingOrgSettings): Promise<void>;
|
||||
updateBillingManagers(delta: ManagerDelta): Promise<void>;
|
||||
}
|
||||
|
||||
export class BillingAPIImpl extends BaseAPI implements BillingAPI {
|
||||
constructor(private _homeUrl: string, options: IOptions = {}) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
public async isDomainAvailable(domain: string): Promise<boolean> {
|
||||
const resp = await this.request(`${this._url}/api/billing/domain`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ domain })
|
||||
});
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
public async getTaxRate(address: IBillingAddress): Promise<number> {
|
||||
const resp = await this.request(`${this._url}/api/billing/tax`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ address })
|
||||
});
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
public async getPlans(): Promise<IBillingPlan[]> {
|
||||
const resp = await this.request(`${this._url}/api/billing/plans`, {method: 'GET'});
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
// Returns an IBillingSubscription
|
||||
public async getSubscription(): Promise<IBillingSubscription> {
|
||||
const resp = await this.request(`${this._url}/api/billing/subscription`, {method: 'GET'});
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
public async getBillingAccount(): Promise<FullBillingAccount> {
|
||||
const resp = await this.request(`${this._url}/api/billing`, {method: 'GET'});
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
// Returns the new Stripe customerId.
|
||||
public async signUp(
|
||||
planId: string,
|
||||
tokenId: string,
|
||||
address: IBillingAddress,
|
||||
settings: IBillingOrgSettings
|
||||
): Promise<OrganizationWithoutAccessInfo> {
|
||||
const resp = await this.request(`${this._url}/api/billing/signup`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ tokenId, planId, address, settings })
|
||||
});
|
||||
const parsed = await resp.json();
|
||||
return parsed.data;
|
||||
}
|
||||
|
||||
public async setSubscription(planId: string, tokenId?: string): Promise<void> {
|
||||
await this.request(`${this._url}/api/billing/subscription`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ tokenId, planId })
|
||||
});
|
||||
}
|
||||
|
||||
public async removeSubscription(): Promise<void> {
|
||||
await this.request(`${this._url}/api/billing/subscription`, {method: 'DELETE'});
|
||||
}
|
||||
|
||||
public async setCard(tokenId: string): Promise<void> {
|
||||
await this.request(`${this._url}/api/billing/card`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ tokenId })
|
||||
});
|
||||
}
|
||||
|
||||
public async removeCard(): Promise<void> {
|
||||
await this.request(`${this._url}/api/billing/card`, {method: 'DELETE'});
|
||||
}
|
||||
|
||||
public async updateAddress(address?: IBillingAddress, settings?: IBillingOrgSettings): Promise<void> {
|
||||
await this.request(`${this._url}/api/billing/address`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ address, settings })
|
||||
});
|
||||
}
|
||||
|
||||
public async updateBillingManagers(delta: ManagerDelta): Promise<void> {
|
||||
await this.request(`${this._url}/api/billing/managers`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({delta})
|
||||
});
|
||||
}
|
||||
|
||||
private get _url(): string {
|
||||
return addCurrentOrgToPath(this._homeUrl);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user