Skip to content

Commit 2b5db2a

Browse files
fix(billing): require org owner role for Stripe checkout URL creation (#1092)
2 parents dc756e5 + 9bba7b7 commit 2b5db2a

2 files changed

Lines changed: 27 additions & 9 deletions

File tree

src/routers/organizations/organization-subscription-router.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,32 @@ describe('organizations subscription trpc router', () => {
5757
});
5858
});
5959

60+
describe('getSubscriptionStripeUrl procedure', () => {
61+
it('should throw UNAUTHORIZED error for non-owner members', async () => {
62+
const caller = await createCallerForUser(memberUser.id);
63+
64+
await expect(
65+
caller.organizations.subscription.getSubscriptionStripeUrl({
66+
organizationId: testOrganization.id,
67+
seats: 1,
68+
cancelUrl: 'https://example.com',
69+
})
70+
).rejects.toThrow('You do not have the required organizational role to access this feature');
71+
});
72+
73+
it('should throw UNAUTHORIZED error for non-member users', async () => {
74+
const caller = await createCallerForUser(_nonMemberUser.id);
75+
76+
await expect(
77+
caller.organizations.subscription.getSubscriptionStripeUrl({
78+
organizationId: testOrganization.id,
79+
seats: 1,
80+
cancelUrl: 'https://example.com',
81+
})
82+
).rejects.toThrow('You do not have access to this organization');
83+
});
84+
});
85+
6086
describe('cancel procedure', () => {
6187
it('should throw UNAUTHORIZED error for non-owner users', async () => {
6288
const caller = await createCallerForUser(memberUser.id);

src/routers/organizations/organization-subscription-router.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import { baseProcedure, createTRPCRouter } from '@/lib/trpc/init';
1515
import {
1616
OrganizationIdInputSchema,
1717
organizationOwnerProcedure,
18-
ensureOrganizationAccess,
1918
organizationMemberProcedure,
2019
} from '@/routers/organizations/utils';
2120
import { TRPCError } from '@trpc/server';
@@ -117,7 +116,7 @@ export const organizationsSubscriptionRouter = createTRPCRouter({
117116
return { status: paymentStatus };
118117
}),
119118

120-
getSubscriptionStripeUrl: baseProcedure
119+
getSubscriptionStripeUrl: organizationOwnerProcedure
121120
.input(SubscriptionRequestSchema)
122121
.mutation(async ({ input, ctx }) => {
123122
const { user } = ctx;
@@ -132,20 +131,13 @@ export const organizationsSubscriptionRouter = createTRPCRouter({
132131
const customerId = await getOrCreateStripeCustomerIdForOrganization(org.id);
133132
const subscriptions = await getSubscriptionsForStripeCustomerId(customerId);
134133

135-
// if any subscriptions are not ended, throw bad request error
136134
if (subscriptions.find(sub => sub.ended_at == null)) {
137135
throw new TRPCError({
138136
code: 'BAD_REQUEST',
139137
message: 'Organization has active subscription(s)',
140138
});
141139
}
142140

143-
// if any subscriptions exist we need to enforce security
144-
// otherwise, we can't enforce ownership as the org is still not finished being set up
145-
if (subscriptions.length) {
146-
await ensureOrganizationAccess(ctx, organizationId, ['owner', 'billing_manager']);
147-
}
148-
149141
const result = await getStripeSeatsCheckoutUrl({
150142
kiloUserId: user.id,
151143
stripeCustomerId: customerId,

0 commit comments

Comments
 (0)