* Add payment option to schema * Add payment option to Stripe zod * Set payment option on event type * Create manual payment intent in Stripe * Set payment option from Stripe app * Add payment option to DB * Pass React.ReactNode to checkbox * Create uncaptured payment intent * WIP * Capture card in setup intent * Show charge card option * Charge card from booking page * Bug fixes * Clean up * Clean up app card * Add no-show fee messaging on booking page * Send payment email on payment & add price * Fix messaging * Create no show fee charged email * Send charge fee collected email * Disable submit on card failure * Clean up * Serverside prevent charging card again if already charged * Only confirm booking if paid for * Type fixes * More type fixes * More type fixes * Type fix * Type fixes * UI changes * Payment component rework * Update apps/web/public/static/locales/en/common.json Co-authored-by: Alex van Andel <me@alexvanandel.com> * Update apps/web/public/static/locales/en/common.json Co-authored-by: Alex van Andel <me@alexvanandel.com> * Update apps/web/components/dialog/ChargeCardDialog.tsx Co-authored-by: Alex van Andel <me@alexvanandel.com> * Update packages/trpc/server/routers/viewer/payments.tsx Co-authored-by: Alex van Andel <me@alexvanandel.com> * Revert GTM config * Adjust payment option dropdown * Show alert when seats are set * Small bug fixes * Create collect card method * clean up * Prevent seats & charge no-show fee to be enabled together * Do not charge no-show fee on unconfirmed bookings * Add check to collect card method * Webhook send request emails * Fix some dark mode colours * Change awaiting payment language * Type fixes * Set height of Select and TextField both to 38px to fix alignment * Fix message seats & payment error message * Type fix --------- Co-authored-by: Alex van Andel <me@alexvanandel.com>
142 lines
3.5 KiB
TypeScript
142 lines
3.5 KiB
TypeScript
import { BookingStatus } from "@prisma/client";
|
|
import type { GetServerSidePropsContext } from "next";
|
|
import { z } from "zod";
|
|
|
|
import type { StripePaymentData, StripeSetupIntentData } from "@calcom/app-store/stripepayment/lib/server";
|
|
import prisma from "@calcom/prisma";
|
|
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
|
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
|
|
|
|
import { ssrInit } from "@server/lib/ssr";
|
|
|
|
export type PaymentPageProps = inferSSRProps<typeof getServerSideProps>;
|
|
|
|
const querySchema = z.object({
|
|
uid: z.string(),
|
|
});
|
|
|
|
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
|
const ssr = await ssrInit(context);
|
|
|
|
const { uid } = querySchema.parse(context.query);
|
|
const rawPayment = await prisma.payment.findFirst({
|
|
where: {
|
|
uid,
|
|
},
|
|
select: {
|
|
data: true,
|
|
success: true,
|
|
uid: true,
|
|
refunded: true,
|
|
bookingId: true,
|
|
appId: true,
|
|
amount: true,
|
|
currency: true,
|
|
paymentOption: true,
|
|
booking: {
|
|
select: {
|
|
id: true,
|
|
uid: true,
|
|
description: true,
|
|
title: true,
|
|
startTime: true,
|
|
attendees: {
|
|
select: {
|
|
email: true,
|
|
name: true,
|
|
},
|
|
},
|
|
eventTypeId: true,
|
|
location: true,
|
|
status: true,
|
|
rejectionReason: true,
|
|
cancellationReason: true,
|
|
eventType: {
|
|
select: {
|
|
id: true,
|
|
title: true,
|
|
description: true,
|
|
length: true,
|
|
eventName: true,
|
|
requiresConfirmation: true,
|
|
userId: true,
|
|
metadata: true,
|
|
users: {
|
|
select: {
|
|
name: true,
|
|
username: true,
|
|
hideBranding: true,
|
|
theme: true,
|
|
},
|
|
},
|
|
team: {
|
|
select: {
|
|
name: true,
|
|
hideBranding: true,
|
|
},
|
|
},
|
|
price: true,
|
|
currency: true,
|
|
successRedirectUrl: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!rawPayment) return { notFound: true };
|
|
|
|
const { data, booking: _booking, ...restPayment } = rawPayment;
|
|
const payment = {
|
|
...restPayment,
|
|
data: data as unknown as StripePaymentData | StripeSetupIntentData,
|
|
};
|
|
|
|
if (!_booking) return { notFound: true };
|
|
|
|
const { startTime, eventType, ...restBooking } = _booking;
|
|
const booking = {
|
|
...restBooking,
|
|
startTime: startTime.toString(),
|
|
};
|
|
|
|
if (!eventType) return { notFound: true };
|
|
|
|
const [user] = eventType.users;
|
|
if (!user) return { notFound: true };
|
|
|
|
const profile = {
|
|
name: eventType.team?.name || user?.name || null,
|
|
theme: (!eventType.team?.name && user?.theme) || null,
|
|
hideBranding: eventType.team?.hideBranding || user?.hideBranding || null,
|
|
};
|
|
|
|
if (
|
|
([BookingStatus.CANCELLED, BookingStatus.REJECTED] as BookingStatus[]).includes(
|
|
booking.status as BookingStatus
|
|
)
|
|
) {
|
|
return {
|
|
redirect: {
|
|
destination: `/booking/${booking.uid}`,
|
|
permanent: false,
|
|
},
|
|
};
|
|
}
|
|
|
|
return {
|
|
props: {
|
|
user,
|
|
eventType: {
|
|
...eventType,
|
|
metadata: EventTypeMetaDataSchema.parse(eventType.metadata),
|
|
},
|
|
booking,
|
|
trpcState: ssr.dehydrate(),
|
|
payment,
|
|
profile,
|
|
},
|
|
};
|
|
};
|