From fad421b7c06b045cd4eb7b45aca7de4684b66312 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaros=C5=82aw=20Sadzi=C5=84ski?= Date: Wed, 9 Aug 2023 10:35:44 +0200 Subject: [PATCH] (core) Removing temporary pro site Summary: Creating a pro team site after Stripe checkout. Previously a stub site was always created and never removed, even if a user cancels the checkout process, which resulted in multiple 'ghost' sites that can't be removed. Test Plan: Updated and added Reviewers: paulfitz Reviewed By: paulfitz Differential Revision: https://phab.getgrist.com/D3985 --- app/common/BillingAPI.ts | 3 +- app/gen-server/ApiServer.ts | 8 ++--- app/gen-server/lib/HomeDBManager.ts | 49 ++++++++++++++++++++++------- 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/app/common/BillingAPI.ts b/app/common/BillingAPI.ts index 24d1e940..3d1d6408 100644 --- a/app/common/BillingAPI.ts +++ b/app/common/BillingAPI.ts @@ -229,7 +229,8 @@ export class BillingAPIImpl extends BaseAPI implements BillingAPI { body: JSON.stringify({ domain, name, - planType: 'team' + planType: 'team', + next: window.location.href }) }); return data.checkoutUrl; diff --git a/app/gen-server/ApiServer.ts b/app/gen-server/ApiServer.ts index 93738253..6f935431 100644 --- a/app/gen-server/ApiServer.ts +++ b/app/gen-server/ApiServer.ts @@ -7,6 +7,8 @@ import {Request} from 'express'; import {ApiError} from 'app/common/ApiError'; import {FullUser} from 'app/common/LoginSessionAPI'; import {OrganizationProperties} from 'app/common/UserAPI'; +import {User} from 'app/gen-server/entity/User'; +import {BillingOptions, HomeDBManager, QueryResult, Scope} from 'app/gen-server/lib/HomeDBManager'; import {getAuthorizedUserId, getUserId, getUserProfiles, RequestWithLogin} from 'app/server/lib/Authorizer'; import {getSessionUser, linkOrgWithEmail} from 'app/server/lib/BrowserSession'; import {expressWrap} from 'app/server/lib/expressWrap'; @@ -16,9 +18,6 @@ import log from 'app/server/lib/log'; import {addPermit, clearSessionCacheIfNeeded, getDocScope, getScope, integerParam, isParameterOn, optStringParam, sendOkReply, sendReply, stringParam} from 'app/server/lib/requestUtils'; import {IWidgetRepository} from 'app/server/lib/WidgetRepository'; - -import {User} from './entity/User'; -import {HomeDBManager, QueryResult, Scope} from './lib/HomeDBManager'; import {getCookieDomain} from 'app/server/lib/gristSessions'; // exposed for testing purposes @@ -64,7 +63,7 @@ export function getOrgKey(req: Request): string|number { return orgKey; } -// Adds an non-personal org with a new billingAccout, with the given name and domain. +// Adds an non-personal org with a new billingAccount, with the given name and domain. // Returns a QueryResult with the orgId on success. export function addOrg( dbManager: HomeDBManager, @@ -72,6 +71,7 @@ export function addOrg( props: Partial, options?: { planType?: string, + billing?: BillingOptions, } ): Promise { return dbManager.connection.transaction(async manager => { diff --git a/app/gen-server/lib/HomeDBManager.ts b/app/gen-server/lib/HomeDBManager.ts index 38aee356..2e83e327 100644 --- a/app/gen-server/lib/HomeDBManager.ts +++ b/app/gen-server/lib/HomeDBManager.ts @@ -27,7 +27,7 @@ import { } from "app/common/UserAPI"; import {AclRule, AclRuleDoc, AclRuleOrg, AclRuleWs} from "app/gen-server/entity/AclRule"; import {Alias} from "app/gen-server/entity/Alias"; -import {BillingAccount, ExternalBillingOptions} from "app/gen-server/entity/BillingAccount"; +import {BillingAccount} from "app/gen-server/entity/BillingAccount"; import {BillingAccountManager} from "app/gen-server/entity/BillingAccountManager"; import {Document} from "app/gen-server/entity/Document"; import {Group} from "app/gen-server/entity/Group"; @@ -260,6 +260,20 @@ interface CreateWorkspaceOptions { ownerId?: number } +/** + * Available options for creating a new org with a new billing account. + */ +export type BillingOptions = Partial>; + /** * HomeDBManager handles interaction between the ApiServer and the Home database, * encapsulating the typeorm logic. @@ -1348,14 +1362,13 @@ export class HomeDBManager extends EventEmitter { * NOTE: Currently it is always a true - billing account is one to one with org. * @param planType: if set, controls the type of plan used for the org. Only * meaningful for team sites currently. - * + * @param billing: if set, controls the billing account settings for the org. */ public async addOrg(user: User, props: Partial, options: { setUserAsOwner: boolean, useNewPlan: boolean, planType?: string, - externalId?: string, - externalOptions?: ExternalBillingOptions }, + billing?: BillingOptions}, transaction?: EntityManager): Promise> { const notifications: Array<() => void> = []; const name = props.name; @@ -1405,12 +1418,26 @@ export class HomeDBManager extends EventEmitter { billingAccountManager.user = user; billingAccountManager.billingAccount = billingAccount; billingAccountEntities.push(billingAccountManager); - if (options.externalId) { - // save will fail if externalId is a duplicate. - billingAccount.externalId = options.externalId; - } - if (options.externalOptions) { - billingAccount.externalOptions = options.externalOptions; + // Apply billing settings if requested, but not all of them. + if (options.billing) { + const billing = options.billing; + const allowedKeys: Array = [ + 'product', + 'stripeCustomerId', + 'stripeSubscriptionId', + 'stripePlanId', + // save will fail if externalId is a duplicate. + 'externalId', + 'externalOptions', + 'inGoodStanding', + 'status' + ]; + Object.keys(billing).forEach(key => { + if (!allowedKeys.includes(key as any)) { + delete (billing as any)[key]; + } + }); + Object.assign(billingAccount, billing); } } else { log.warn("Creating org with shared billing account"); @@ -1421,7 +1448,7 @@ export class HomeDBManager extends EventEmitter { .leftJoinAndSelect('billing_accounts.orgs', 'orgs') .where('orgs.owner_id = :userId', {userId: user.id}) .getOne(); - if (options.externalId && billingAccount?.externalId !== options.externalId) { + if (options.billing?.externalId && billingAccount?.externalId !== options.billing?.externalId) { throw new ApiError('Conflicting external identifier', 400); } if (!billingAccount) {