diff --git a/packages/backend/server/src/__tests__/e2e/workspace/team.spec.ts b/packages/backend/server/src/__tests__/e2e/workspace/team.spec.ts index 5b862d086f..24a0edff72 100644 --- a/packages/backend/server/src/__tests__/e2e/workspace/team.spec.ts +++ b/packages/backend/server/src/__tests__/e2e/workspace/team.spec.ts @@ -145,6 +145,28 @@ e2e('should set new invited users to waiting-seat status', async t => { t.is(invitationInfo.status, WorkspaceMemberStatus.NeedMoreSeat); }); +e2e('should allocate existing team seats for new invited users', async t => { + const { owner, workspace } = await createTeamWorkspace(4); + await app.login(owner); + + const u1 = await app.createUser(); + + const result = await app.gql({ + query: inviteByEmailsMutation, + variables: { + workspaceId: workspace.id, + emails: [u1.email], + }, + }); + + t.not(result.inviteMembers[0].inviteId, null); + + const invitationInfo = await getInvitationInfo( + result.inviteMembers[0].inviteId! + ); + t.is(invitationInfo.status, WorkspaceMemberStatus.Pending); +}); + e2e('should allocate seats', async t => { const { owner, workspace } = await createTeamWorkspace(); await app.login(owner); diff --git a/packages/backend/server/src/__tests__/payment/service.spec.ts b/packages/backend/server/src/__tests__/payment/service.spec.ts index 78deced9d8..fcf1c7afba 100644 --- a/packages/backend/server/src/__tests__/payment/service.spec.ts +++ b/packages/backend/server/src/__tests__/payment/service.spec.ts @@ -1475,6 +1475,41 @@ test('should not be able to checkout for workspace if subscribed', async t => { ); }); +test('should be able to checkout for workspace after canceled subscription', async t => { + const { service, u1, db, stripe } = t.context; + + await db.subscription.create({ + data: { + targetId: 'ws_1', + stripeSubscriptionId: 'sub_1', + plan: SubscriptionPlan.Team, + recurring: SubscriptionRecurring.Monthly, + status: SubscriptionStatus.Canceled, + start: new Date(Date.now() - 100000), + end: new Date(Date.now() - 1000), + quantity: 1, + }, + }); + + await service.checkout( + { + plan: SubscriptionPlan.Team, + recurring: SubscriptionRecurring.Monthly, + variant: null, + successCallbackLink: '', + }, + { + user: u1, + workspaceId: 'ws_1', + } + ); + + t.deepEqual(getLastCheckoutPrice(stripe.checkout.sessions.create), { + price: TEAM_MONTHLY, + coupon: undefined, + }); +}); + const teamSub: Stripe.Subscription = { ...sub, items: { diff --git a/packages/backend/server/src/core/workspaces/resolvers/member.ts b/packages/backend/server/src/core/workspaces/resolvers/member.ts index e52b01d775..593536ba11 100644 --- a/packages/backend/server/src/core/workspaces/resolvers/member.ts +++ b/packages/backend/server/src/core/workspaces/resolvers/member.ts @@ -243,10 +243,8 @@ export class WorkspaceMemberResolver { inviterId: me.id, } ); - results.push({ - email, - inviteId: role.id, - }); + await this.allocateAvailableTeamSeats(workspaceId, quota.memberLimit); + results.push({ email, inviteId: role.id }); } else { const needMoreSeat = quota.memberCount + idx + 1 > quota.memberLimit; if (needMoreSeat) { @@ -404,6 +402,7 @@ export class WorkspaceMemberResolver { inviterId: me.id, } ); + await this.allocateAvailableTeamSeats(workspaceId, quota.memberLimit); } else { if (quota.memberCount >= quota.memberLimit) { throw new NoMoreSeat({ spaceId: workspaceId }); @@ -717,4 +716,9 @@ export class WorkspaceMemberResolver { throw new SpaceAccessDenied({ spaceId: workspaceId }); } } + + private async allocateAvailableTeamSeats(workspaceId: string, limit: number) { + if (limit <= 0) return; + await this.workspaceService.allocateSeats(workspaceId, limit); + } } diff --git a/packages/backend/server/src/plugins/payment/manager/workspace.ts b/packages/backend/server/src/plugins/payment/manager/workspace.ts index b957378b9f..e50f7397ab 100644 --- a/packages/backend/server/src/plugins/payment/manager/workspace.ts +++ b/packages/backend/server/src/plugins/payment/manager/workspace.ts @@ -72,7 +72,7 @@ export class WorkspaceSubscriptionManager extends SubscriptionManager { params: z.infer, args: z.infer ) { - const subscription = await this.getSubscription({ + const subscription = await this.getActiveSubscription({ plan: SubscriptionPlan.Team, workspaceId: args.workspaceId, });