* refactor: move repositories from lib to features domain folders - Move HolidayRepository to features/holidays/repositories - Move PrismaTrackingRepository to features/bookings/repositories - Move PrismaBookingPaymentRepository to features/bookings/repositories - Move PrismaRoutingFormResponseRepository to features/routing-forms/repositories - Move PrismaAssignmentReasonRepository to features/assignment-reason/repositories - Move VerificationTokenRepository to features/auth/repositories - Move WorkspacePlatformRepository to features/workspace-platform/repositories - Move DTO files to their respective feature domains - Merge lib DestinationCalendarRepository into features version - Merge lib SelectedCalendarRepository into features version - Update all import paths across the codebase This follows the vertical slice architecture pattern by organizing repositories by domain rather than by technical layer. Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: update VerificationTokenService import path to new location Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: update test file imports to use new repository locations Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * mv * fix structure * fix * refactor: merge unit tests for SelectedCalendarRepository into single file Co-Authored-By: benny@cal.com <sldisek783@gmail.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
147 lines
4.5 KiB
TypeScript
147 lines
4.5 KiB
TypeScript
import type { NextApiRequest, NextApiResponse } from "next";
|
|
import z from "zod";
|
|
|
|
import { getCustomerAndCheckoutSession } from "@calcom/app-store/stripepayment/lib/getCustomerAndCheckoutSession";
|
|
import sendVerificationRequest from "@calcom/features/auth/lib/sendVerificationRequest";
|
|
import { WEBAPP_URL } from "@calcom/lib/constants";
|
|
import { HttpError } from "@calcom/lib/http-error";
|
|
import logger from "@calcom/lib/logger";
|
|
import { defaultHandler } from "@calcom/lib/server/defaultHandler";
|
|
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
|
|
import { VerificationTokenService } from "../lib/VerificationTokenService";
|
|
import { prisma } from "@calcom/prisma";
|
|
import type { Prisma } from "@calcom/prisma/client";
|
|
|
|
const querySchema = z.object({
|
|
callbackUrl: z.string().transform((url) => {
|
|
if (url.search(/^https?:\/\//) === -1) {
|
|
url = `${WEBAPP_URL}${url}`;
|
|
}
|
|
return new URL(url);
|
|
}),
|
|
checkoutSessionId: z.string(),
|
|
});
|
|
|
|
// It handles premium user payment success/failure
|
|
async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
|
const { callbackUrl, checkoutSessionId } = querySchema.parse(req.query);
|
|
const log = logger.getSubLogger({ prefix: [`[paymentCallback] checkoutSessionId: ${checkoutSessionId}`] });
|
|
const { stripeCustomer, checkoutSession } = await getCustomerAndCheckoutSession(checkoutSessionId);
|
|
|
|
if (!stripeCustomer) {
|
|
log.error("Could not find stripeCustomer");
|
|
throw new HttpError({
|
|
statusCode: 404,
|
|
message:
|
|
"Stripe customer not found or deleted. Please contact support@cal.com and mention your premium username",
|
|
url: req.url,
|
|
method: req.method,
|
|
});
|
|
}
|
|
|
|
let user;
|
|
|
|
if (stripeCustomer?.email) {
|
|
user = await prisma.user.findFirst({
|
|
where: {
|
|
email: stripeCustomer.email,
|
|
},
|
|
select: {
|
|
id: true,
|
|
email: true,
|
|
locale: true,
|
|
metadata: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
// If we cannot find the user via email, query via stripeCustomerId
|
|
if (!user) {
|
|
user = await prisma.user.findFirst({
|
|
where: {
|
|
metadata: {
|
|
path: ["stripeCustomerId"],
|
|
equals: stripeCustomer.id,
|
|
},
|
|
},
|
|
select: {
|
|
id: true,
|
|
email: true,
|
|
locale: true,
|
|
metadata: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
if (!user) {
|
|
log.error("Could not find user");
|
|
throw new HttpError({ statusCode: 404, message: "User not found", url: req.url, method: req.method });
|
|
}
|
|
|
|
const username = stripeCustomer.metadata.username;
|
|
const email = user.email ?? stripeCustomer.email;
|
|
|
|
// If payment hasn't been completed, redirect to verify page with failure status
|
|
if (checkoutSession.payment_status !== "paid") {
|
|
log.warn("Payment not completed", { paymentStatus: checkoutSession.payment_status });
|
|
callbackUrl.searchParams.set("email", email || "");
|
|
callbackUrl.searchParams.set("username", username || "");
|
|
callbackUrl.searchParams.set("paymentStatus", checkoutSession.payment_status);
|
|
return res.redirect(callbackUrl.toString()).end();
|
|
}
|
|
|
|
if (stripeCustomer.metadata.username) {
|
|
try {
|
|
await prisma.user.update({
|
|
data: {
|
|
username,
|
|
metadata: {
|
|
...(user.metadata as Prisma.JsonObject),
|
|
isPremium: true,
|
|
},
|
|
},
|
|
where: {
|
|
id: user.id,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
log.error(error);
|
|
throw new HttpError({
|
|
statusCode: 400,
|
|
url: req.url,
|
|
method: req.method,
|
|
message:
|
|
"We have received your payment. Your premium username could still not be reserved. Please contact support@cal.com and mention your premium username",
|
|
});
|
|
}
|
|
}
|
|
|
|
const expires = new Date(Date.now() + 86400 * 1000); // 1 day
|
|
|
|
const token = await VerificationTokenService.create({ identifier: email, expires });
|
|
|
|
// Generate the callback URL with token
|
|
const params = new URLSearchParams({
|
|
callbackUrl: WEBAPP_URL || "https://app.cal.com",
|
|
token,
|
|
email,
|
|
});
|
|
const url = `${WEBAPP_URL}/api/auth/callback/email?${params.toString()}`;
|
|
|
|
await sendVerificationRequest({
|
|
identifier: email,
|
|
url,
|
|
});
|
|
|
|
// Pass email, username, and payment status in the redirect URL
|
|
callbackUrl.searchParams.set("email", email || "");
|
|
callbackUrl.searchParams.set("username", username);
|
|
callbackUrl.searchParams.set("paymentStatus", checkoutSession.payment_status);
|
|
|
|
return res.redirect(callbackUrl.toString()).end();
|
|
}
|
|
|
|
export default defaultHandler({
|
|
GET: Promise.resolve({ default: defaultResponder(getHandler) }),
|
|
});
|