mirror of
https://github.com/documenso/documenso.git
synced 2025-11-18 18:51:37 +10:00
fix: migrate billing to RR7
This commit is contained in:
@ -23,6 +23,7 @@ export type SessionUser = Pick<
|
||||
| 'roles'
|
||||
| 'signature'
|
||||
| 'url'
|
||||
| 'customerId'
|
||||
>;
|
||||
|
||||
export type SessionValidationResult =
|
||||
@ -99,6 +100,7 @@ export const validateSessionToken = async (token: string): Promise<SessionValida
|
||||
roles: true,
|
||||
signature: true,
|
||||
url: true,
|
||||
customerId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -31,7 +31,9 @@ export const getStripeCustomerById = async (stripeCustomerId: string) => {
|
||||
*
|
||||
* Will create a Stripe customer and update the relevant user if one does not exist.
|
||||
*/
|
||||
export const getStripeCustomerByUser = async (user: User) => {
|
||||
export const getStripeCustomerByUser = async (
|
||||
user: Pick<User, 'id' | 'customerId' | 'email' | 'name'>,
|
||||
) => {
|
||||
if (user.customerId) {
|
||||
const stripeCustomer = await getStripeCustomerById(user.customerId);
|
||||
|
||||
|
||||
@ -205,7 +205,7 @@ export const createTeamFromPendingTeam = async ({
|
||||
pendingTeamId,
|
||||
subscription,
|
||||
}: CreateTeamFromPendingTeamOptions) => {
|
||||
return await prisma.$transaction(async (tx) => {
|
||||
const createdTeam = await prisma.$transaction(async (tx) => {
|
||||
const pendingTeam = await tx.teamPending.findUniqueOrThrow({
|
||||
where: {
|
||||
id: pendingTeamId,
|
||||
@ -249,19 +249,21 @@ export const createTeamFromPendingTeam = async ({
|
||||
mapStripeSubscriptionToPrismaUpsertAction(subscription, undefined, team.id),
|
||||
);
|
||||
|
||||
// Attach the team ID to the subscription metadata for sanity reasons.
|
||||
await stripe.subscriptions
|
||||
.update(subscription.id, {
|
||||
metadata: {
|
||||
teamId: team.id.toString(),
|
||||
},
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
// Non-critical error, but we want to log it so we can rectify it.
|
||||
// Todo: Teams - Alert us.
|
||||
});
|
||||
|
||||
return team;
|
||||
});
|
||||
|
||||
// Attach the team ID to the subscription metadata for sanity reasons.
|
||||
await stripe.subscriptions
|
||||
.update(subscription.id, {
|
||||
metadata: {
|
||||
teamId: createdTeam.id.toString(),
|
||||
},
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
// Non-critical error, but we want to log it so we can rectify it.
|
||||
// Todo: Teams - Alert us.
|
||||
});
|
||||
|
||||
return createdTeam;
|
||||
};
|
||||
|
||||
22
packages/lib/server-only/user/create-billing-portal.ts
Normal file
22
packages/lib/server-only/user/create-billing-portal.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import type { User } from '@prisma/client';
|
||||
|
||||
import { getStripeCustomerByUser } from '@documenso/ee/server-only/stripe/get-customer';
|
||||
import { getPortalSession } from '@documenso/ee/server-only/stripe/get-portal-session';
|
||||
import { IS_BILLING_ENABLED, NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
|
||||
export type CreateBillingPortalOptions = {
|
||||
user: Pick<User, 'id' | 'customerId' | 'email' | 'name'>;
|
||||
};
|
||||
|
||||
export const createBillingPortal = async ({ user }: CreateBillingPortalOptions) => {
|
||||
if (!IS_BILLING_ENABLED()) {
|
||||
throw new Error('Billing is not enabled');
|
||||
}
|
||||
|
||||
const { stripeCustomer } = await getStripeCustomerByUser(user);
|
||||
|
||||
return getPortalSession({
|
||||
customerId: stripeCustomer.id,
|
||||
returnUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/settings/billing`,
|
||||
});
|
||||
};
|
||||
39
packages/lib/server-only/user/create-checkout-session.ts
Normal file
39
packages/lib/server-only/user/create-checkout-session.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import type { User } from '@prisma/client';
|
||||
|
||||
import { getCheckoutSession } from '@documenso/ee/server-only/stripe/get-checkout-session';
|
||||
import { getStripeCustomerByUser } from '@documenso/ee/server-only/stripe/get-customer';
|
||||
import { getPortalSession } from '@documenso/ee/server-only/stripe/get-portal-session';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
|
||||
import { getSubscriptionsByUserId } from '../subscription/get-subscriptions-by-user-id';
|
||||
|
||||
export type CreateCheckoutSession = {
|
||||
user: Pick<User, 'id' | 'customerId' | 'email' | 'name'>;
|
||||
priceId: string;
|
||||
};
|
||||
|
||||
export const createCheckoutSession = async ({ user, priceId }: CreateCheckoutSession) => {
|
||||
const { stripeCustomer } = await getStripeCustomerByUser(user);
|
||||
|
||||
const existingSubscriptions = await getSubscriptionsByUserId({ userId: user.id });
|
||||
|
||||
const foundSubscription = existingSubscriptions.find(
|
||||
(subscription) =>
|
||||
subscription.priceId === priceId &&
|
||||
subscription.periodEnd &&
|
||||
subscription.periodEnd >= new Date(),
|
||||
);
|
||||
|
||||
if (foundSubscription) {
|
||||
return getPortalSession({
|
||||
customerId: stripeCustomer.id,
|
||||
returnUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/settings/billing`,
|
||||
});
|
||||
}
|
||||
|
||||
return getCheckoutSession({
|
||||
customerId: stripeCustomer.id,
|
||||
priceId,
|
||||
returnUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/settings/billing`,
|
||||
});
|
||||
};
|
||||
@ -4,6 +4,8 @@ import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||
import { AppError } from '@documenso/lib/errors/app-error';
|
||||
import { setAvatarImage } from '@documenso/lib/server-only/profile/set-avatar-image';
|
||||
import { getSubscriptionsByUserId } from '@documenso/lib/server-only/subscription/get-subscriptions-by-user-id';
|
||||
import { createBillingPortal } from '@documenso/lib/server-only/user/create-billing-portal';
|
||||
import { createCheckoutSession } from '@documenso/lib/server-only/user/create-checkout-session';
|
||||
import { deleteUser } from '@documenso/lib/server-only/user/delete-user';
|
||||
import { findUserSecurityAuditLogs } from '@documenso/lib/server-only/user/find-user-security-audit-logs';
|
||||
import { getUserById } from '@documenso/lib/server-only/user/get-user-by-id';
|
||||
@ -12,6 +14,7 @@ import { updatePublicProfile } from '@documenso/lib/server-only/user/update-publ
|
||||
|
||||
import { adminProcedure, authenticatedProcedure, router } from '../trpc';
|
||||
import {
|
||||
ZCreateCheckoutSessionRequestSchema,
|
||||
ZFindUserSecurityAuditLogsSchema,
|
||||
ZRetrieveUserByIdQuerySchema,
|
||||
ZSetProfileImageMutationSchema,
|
||||
@ -35,6 +38,31 @@ export const profileRouter = router({
|
||||
return await getUserById({ id });
|
||||
}),
|
||||
|
||||
createBillingPortal: authenticatedProcedure.mutation(async ({ ctx }) => {
|
||||
return await createBillingPortal({
|
||||
user: {
|
||||
id: ctx.user.id,
|
||||
customerId: ctx.user.customerId,
|
||||
email: ctx.user.email,
|
||||
name: ctx.user.name,
|
||||
},
|
||||
});
|
||||
}),
|
||||
|
||||
createCheckoutSession: authenticatedProcedure
|
||||
.input(ZCreateCheckoutSessionRequestSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
return await createCheckoutSession({
|
||||
user: {
|
||||
id: ctx.user.id,
|
||||
customerId: ctx.user.customerId,
|
||||
email: ctx.user.email,
|
||||
name: ctx.user.name,
|
||||
},
|
||||
priceId: input.priceId,
|
||||
});
|
||||
}),
|
||||
|
||||
updateProfile: authenticatedProcedure
|
||||
.input(ZUpdateProfileMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
|
||||
@ -15,6 +15,10 @@ export const ZRetrieveUserByIdQuerySchema = z.object({
|
||||
|
||||
export type TRetrieveUserByIdQuerySchema = z.infer<typeof ZRetrieveUserByIdQuerySchema>;
|
||||
|
||||
export const ZCreateCheckoutSessionRequestSchema = z.object({
|
||||
priceId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const ZUpdateProfileMutationSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
signature: z.string(),
|
||||
|
||||
Reference in New Issue
Block a user