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:
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;
|
||||
};
|
||||
@@ -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: {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -117,6 +117,7 @@ export const buildEventType = (eventType?: Partial<EventType>): EventType => {
|
||||
slotInterval: null,
|
||||
metadata: null,
|
||||
successRedirectUrl: null,
|
||||
forwardParamsSuccessRedirect: true,
|
||||
bookingFields: [],
|
||||
parentId: null,
|
||||
profileId: null,
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "EventType" ADD COLUMN "forwardParamsSuccessRedirect" BOOLEAN DEFAULT true;
|
||||
@@ -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?
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user