feat: redirect after booking: option to forward parameters (#14235)

* feat: redirect after booking: option to forward parameters

* fix: unit test

* fix: stripe passing its own params

* fix: manually add stripe params

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2024-05-07 12:15:20 -04:00
1 parent 7cf840723b
commit b6a57381c7
16 files changed
+60 -26

No files matched your search

@@ -321,6 +321,20 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
type="text"
{...formMethods.register("successRedirectUrl")}
/>
<div className="mt-4">
<Controller
name="forwardParamsSuccessRedirect"
render={({ field: { value, onChange } }) => (
<CheckboxField
description={t("forward_params_redirect")}
disabled={successRedirectUrlLocked.disabled}
onChange={(e) => onChange(e)}
checked={value}
/>
)}
/>
</div>
<div
className={classNames(
"p-1 text-sm text-orange-600",
@@ -265,6 +265,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
metadata,
hosts: eventType.hosts,
successRedirectUrl: eventType.successRedirectUrl || "",
forwardParamsSuccessRedirect: eventType.forwardParamsSuccessRedirect,
users: eventType.users,
useEventTypeDestinationCalendarEmail: eventType.useEventTypeDestinationCalendarEmail,
secondaryEmailId: eventType?.secondaryEmailId || -1,
@@ -2314,6 +2314,7 @@
"redirect_to": "Redirect to",
"having_trouble_finding_time": "Having trouble finding a time?",
"show_more": "Show more",
"forward_params_redirect": "Forward parameters such as ?email=...&name=.... and more",
"assignment_description": "Schedule meetings when everyone is available or rotate through members on your team",
"lowest": "lowest",
"low": "low",
@@ -167,6 +167,7 @@ function PaymentChecker(props: PaymentCheckerProps) {
successRedirectUrl: props.eventType.successRedirectUrl,
query: params,
booking: props.booking,
forwardParamsSuccessRedirect: props.eventType.forwardParamsSuccessRedirect,
});
}
})();
@@ -180,6 +181,7 @@ function PaymentChecker(props: PaymentCheckerProps) {
props.booking.status,
props.eventType.id,
props.eventType.successRedirectUrl,
props.eventType.forwardParamsSuccessRedirect,
props.payment.success,
searchParams,
t,
@@ -163,6 +163,10 @@ export const useBookings = ({ event, hashedLink, bookingForm, metadata }: IUseBo
successRedirectUrl: event?.data?.successRedirectUrl || "",
query,
booking: responseData,
forwardParamsSuccessRedirect:
event?.data?.forwardParamsSuccessRedirect === undefined
? true
: event?.data?.forwardParamsSuccessRedirect,
});
},
onError: (err, _, ctx) => {
@@ -212,6 +216,10 @@ export const useBookings = ({ event, hashedLink, bookingForm, metadata }: IUseBo
successRedirectUrl: event?.data?.successRedirectUrl || "",
query,
booking,
forwardParamsSuccessRedirect:
event?.data?.forwardParamsSuccessRedirect === undefined
? true
: event?.data?.forwardParamsSuccessRedirect,
});
},
});
@@ -7,8 +7,7 @@ import type { SyntheticEvent } from "react";
import { useEffect, useState } from "react";
import getStripe from "@calcom/app-store/stripepayment/lib/client";
import { getBookingRedirectExtraParams, useBookingSuccessRedirect } from "@calcom/lib/bookingSuccessRedirect";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useBookingSuccessRedirect } from "@calcom/lib/bookingSuccessRedirect";
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, CheckboxField } from "@calcom/ui";
@@ -22,6 +21,7 @@ type Props = {
eventType: {
id: number;
successRedirectUrl: EventType["successRedirectUrl"];
forwardParamsSuccessRedirect: EventType["forwardParamsSuccessRedirect"];
};
user: {
username: string | null;
@@ -46,24 +46,6 @@ type States =
status: "ok";
};
const getReturnUrl = (props: Props) => {
if (!props.eventType.successRedirectUrl) {
return `${WEBAPP_URL}/booking/${props.booking.uid}`;
}
const returnUrl = new URL(props.eventType.successRedirectUrl);
const queryParams = getBookingRedirectExtraParams(props.booking);
Object.entries(queryParams).forEach(([key, value]) => {
if (value === null || value === undefined) {
return;
}
returnUrl.searchParams.append(key, String(value));
});
return returnUrl.toString();
};
const PaymentForm = (props: Props) => {
const {
user: { username },
@@ -96,6 +78,9 @@ const PaymentForm = (props: Props) => {
uid: string;
email: string | null;
location?: string;
payment_intent?: string;
payment_intent_client_secret?: string;
redirect_status?: string;
} = {
uid: props.booking.uid,
email: searchParams?.get("email"),
@@ -103,17 +88,23 @@ const PaymentForm = (props: Props) => {
if (paymentOption === "HOLD" && "setupIntent" in props.payment.data) {
payload = await stripe.confirmSetup({
elements,
confirmParams: {
return_url: getReturnUrl(props),
},
redirect: "if_required",
});
if (payload.setupIntent) {
params.payment_intent = payload.setupIntent.id;
params.payment_intent_client_secret = payload.setupIntent.client_secret || undefined;
params.redirect_status = payload.setupIntent.status;
}
} else if (paymentOption === "ON_BOOKING") {
payload = await stripe.confirmPayment({
elements,
confirmParams: {
return_url: getReturnUrl(props),
},
redirect: "if_required",
});
if (payload.paymentIntent) {
params.payment_intent = payload.paymentIntent.id;
params.payment_intent_client_secret = payload.paymentIntent.client_secret || undefined;
params.redirect_status = payload.paymentIntent.status;
}
}
if (payload?.error) {
@@ -134,6 +125,7 @@ const PaymentForm = (props: Props) => {
successRedirectUrl: props.eventType.successRedirectUrl,
query: params,
booking: props.booking,
forwardParamsSuccessRedirect: props.eventType.forwardParamsSuccessRedirect,
});
}
};
@@ -79,6 +79,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
price: true,
currency: true,
successRedirectUrl: true,
forwardParamsSuccessRedirect: true,
},
},
},
@@ -92,6 +92,7 @@ const publicEventSelect = Prisma.validator<Prisma.EventTypeSelect>()({
},
},
successRedirectUrl: true,
forwardParamsSuccessRedirect: true,
workflows: {
include: {
workflow: {
@@ -102,5 +102,6 @@ export type FormValues = {
users: EventTypeSetup["users"];
assignAllTeamMembers: boolean;
useEventTypeDestinationCalendarEmail: boolean;
forwardParamsSuccessRedirect: boolean | null;
secondaryEmailId?: number;
};
+6
View File
@@ -47,14 +47,20 @@ export const useBookingSuccessRedirect = () => {
successRedirectUrl,
query,
booking,
forwardParamsSuccessRedirect,
}: {
successRedirectUrl: EventType["successRedirectUrl"];
forwardParamsSuccessRedirect: EventType["forwardParamsSuccessRedirect"];
query: Record<string, string | null | undefined | boolean>;
booking: SuccessRedirectBookingType;
}) => {
if (successRedirectUrl) {
const url = new URL(successRedirectUrl);
// Using parent ensures, Embed iframe would redirect outside of the iframe.
if (!forwardParamsSuccessRedirect) {
window.parent.location.href = url.toString();
return;
}
const bookingExtraParams = getBookingRedirectExtraParams(booking);
const newSearchParams = getNewSeachParams({
query: {
+1
View File
@@ -72,6 +72,7 @@ const commons = {
schedule: null,
timeZone: null,
successRedirectUrl: "",
forwardParamsSuccessRedirect: true,
teamId: null,
scheduleId: null,
availability: [],
@@ -113,6 +113,7 @@ export const getEventTypeById = async ({
durationLimits: true,
assignAllTeamMembers: true,
successRedirectUrl: true,
forwardParamsSuccessRedirect: true,
currency: true,
bookingFields: true,
useEventTypeDestinationCalendarEmail: true,
+1
View File
@@ -117,6 +117,7 @@ export const buildEventType = (eventType?: Partial<EventType>): EventType => {
slotInterval: null,
metadata: null,
successRedirectUrl: null,
forwardParamsSuccessRedirect: true,
bookingFields: [],
parentId: null,
profileId: null,
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "EventType" ADD COLUMN "forwardParamsSuccessRedirect" BOOLEAN DEFAULT true;
+1
View File
@@ -125,6 +125,7 @@ model EventType {
metadata Json?
/// @zod.custom(imports.successRedirectUrl)
successRedirectUrl String?
forwardParamsSuccessRedirect Boolean? @default(true)
workflows WorkflowsOnEventTypes[]
/// @zod.custom(imports.intervalLimitsType)
bookingLimits Json?
+1
View File
@@ -624,6 +624,7 @@ export const allManagedEventTypeProps: { [k in keyof Omit<Prisma.EventTypeSelect
seatsPerTimeSlot: true,
seatsShowAttendees: true,
seatsShowAvailabilityCount: true,
forwardParamsSuccessRedirect: true,
periodType: true,
hashedLink: true,
webhooks: true,