diff --git a/apps/api/v2/package.json b/apps/api/v2/package.json
index abf6885712..b0879dc6de 100644
--- a/apps/api/v2/package.json
+++ b/apps/api/v2/package.json
@@ -61,6 +61,7 @@
"next-auth": "^4.22.1",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
+ "qs-stringify": "^1.2.1",
"querystring": "^0.2.1",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1",
diff --git a/apps/api/v2/src/modules/apps/apps.repository.ts b/apps/api/v2/src/modules/apps/apps.repository.ts
index c2a74e1421..0ed0e30c18 100644
--- a/apps/api/v2/src/modules/apps/apps.repository.ts
+++ b/apps/api/v2/src/modules/apps/apps.repository.ts
@@ -1,7 +1,7 @@
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
import { Injectable } from "@nestjs/common";
-import { App } from "@prisma/client";
+import { App, Prisma } from "@prisma/client";
@Injectable()
export class AppsRepository {
@@ -10,4 +10,15 @@ export class AppsRepository {
async getAppBySlug(slug: string): Promise {
return await this.dbRead.prisma.app.findUnique({ where: { slug } });
}
+
+ async createAppCredential(type: string, key: Prisma.InputJsonValue, userId: number, appId: string) {
+ return this.dbWrite.prisma.credential.create({
+ data: {
+ type: type,
+ key: key,
+ userId: userId,
+ appId: appId,
+ },
+ });
+ }
}
diff --git a/apps/api/v2/src/modules/endpoints.module.ts b/apps/api/v2/src/modules/endpoints.module.ts
index eb6ed4876a..6401726b20 100644
--- a/apps/api/v2/src/modules/endpoints.module.ts
+++ b/apps/api/v2/src/modules/endpoints.module.ts
@@ -3,6 +3,7 @@ import { AtomsModule } from "@/modules/atoms/atoms.module";
import { BillingModule } from "@/modules/billing/billing.module";
import { DestinationCalendarsModule } from "@/modules/destination-calendars/destination-calendars.module";
import { OAuthClientModule } from "@/modules/oauth-clients/oauth-client.module";
+import { StripeModule } from "@/modules/stripe/stripe.module";
import { TimezoneModule } from "@/modules/timezones/timezones.module";
import type { MiddlewareConsumer, NestModule } from "@nestjs/common";
import { Module } from "@nestjs/common";
@@ -20,6 +21,7 @@ import { WebhooksModule } from "./webhooks/webhooks.module";
WebhooksModule,
DestinationCalendarsModule,
AtomsModule,
+ StripeModule,
],
})
export class EndpointsModule implements NestModule {
diff --git a/apps/api/v2/src/modules/stripe/controllers/stripe.controller.ts b/apps/api/v2/src/modules/stripe/controllers/stripe.controller.ts
new file mode 100644
index 0000000000..cbf6b1b6e4
--- /dev/null
+++ b/apps/api/v2/src/modules/stripe/controllers/stripe.controller.ts
@@ -0,0 +1,101 @@
+import { API_VERSIONS_VALUES } from "@/lib/api-versions";
+import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
+import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
+import {
+ StripConnectOutputDto,
+ StripConnectOutputResponseDto,
+ StripCredentialsCheckOutputResponseDto,
+ StripCredentialsSaveOutputResponseDto,
+} from "@/modules/stripe/outputs/stripe.output";
+import { StripeService } from "@/modules/stripe/stripe.service";
+import { getOnErrorReturnToValueFromQueryState } from "@/modules/stripe/utils/getReturnToValueFromQueryState";
+import { UserWithProfile } from "@/modules/users/users.repository";
+import {
+ Controller,
+ Query,
+ UseGuards,
+ Get,
+ HttpCode,
+ HttpStatus,
+ Redirect,
+ Req,
+ BadRequestException,
+ Headers,
+} from "@nestjs/common";
+import { ApiTags as DocsTags } from "@nestjs/swagger";
+import { plainToClass } from "class-transformer";
+import { Request } from "express";
+import { stringify } from "querystring";
+
+import { SUCCESS_STATUS } from "@calcom/platform-constants";
+
+@Controller({
+ path: "/v2/stripe",
+ version: API_VERSIONS_VALUES,
+})
+@DocsTags("Stripe")
+export class StripeController {
+ constructor(private readonly stripeService: StripeService) {}
+
+ @Get("/connect")
+ @UseGuards(ApiAuthGuard)
+ @HttpCode(HttpStatus.OK)
+ async redirect(
+ @Req() req: Request,
+ @Headers("Authorization") authorization: string,
+ @GetUser() user: UserWithProfile,
+ @Query("redir") redir?: string | null,
+ @Query("errorRedir") errorRedir?: string | null
+ ): Promise {
+ const origin = req.headers.origin;
+ const accessToken = authorization.replace("Bearer ", "");
+
+ const state = {
+ onErrorReturnTo: !!errorRedir ? errorRedir : origin,
+ fromApp: false,
+ returnTo: !!redir ? redir : origin,
+ accessToken,
+ };
+
+ const stripeRedirectUrl = await this.stripeService.getStripeRedirectUrl(
+ JSON.stringify(state),
+ user.email,
+ user.name
+ );
+
+ return {
+ status: SUCCESS_STATUS,
+ data: plainToClass(StripConnectOutputDto, { authUrl: stripeRedirectUrl }, { strategy: "excludeAll" }),
+ };
+ }
+
+ @Get("/save")
+ @UseGuards()
+ @Redirect(undefined, 301)
+ async save(
+ @Query("state") state: string,
+ @Query("code") code: string,
+ @Query("error") error: string | undefined,
+ @Query("error_description") error_description: string | undefined
+ ): Promise {
+ const accessToken = JSON.parse(state).accessToken;
+
+ // user cancels flow
+ if (error === "access_denied") {
+ return { url: getOnErrorReturnToValueFromQueryState(state) };
+ }
+
+ if (error) {
+ throw new BadRequestException(stringify({ error, error_description }));
+ }
+
+ return await this.stripeService.saveStripeAccount(state, code, accessToken);
+ }
+
+ @Get("/check")
+ @UseGuards(ApiAuthGuard)
+ @HttpCode(HttpStatus.OK)
+ async check(@GetUser() user: UserWithProfile): Promise {
+ return await this.stripeService.checkIfStripeAccountConnected(user.id);
+ }
+}
diff --git a/apps/api/v2/src/modules/stripe/inputs/stripe.input.ts b/apps/api/v2/src/modules/stripe/inputs/stripe.input.ts
new file mode 100644
index 0000000000..0590eff1c2
--- /dev/null
+++ b/apps/api/v2/src/modules/stripe/inputs/stripe.input.ts
@@ -0,0 +1,7 @@
+import { IsString, IsOptional } from "class-validator";
+
+export class StripeConnectQueryParamsInputDto {
+ @IsString()
+ @IsOptional()
+ readonly redir?: string;
+}
diff --git a/apps/api/v2/src/modules/stripe/outputs/stripe.output.ts b/apps/api/v2/src/modules/stripe/outputs/stripe.output.ts
new file mode 100644
index 0000000000..ca9cb0a263
--- /dev/null
+++ b/apps/api/v2/src/modules/stripe/outputs/stripe.output.ts
@@ -0,0 +1,33 @@
+import { ApiProperty } from "@nestjs/swagger";
+import { Expose, Type } from "class-transformer";
+import { IsString, ValidateNested, IsEnum } from "class-validator";
+
+import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants";
+
+export class StripConnectOutputDto {
+ @IsString()
+ @Expose()
+ readonly authUrl!: string;
+}
+
+export class StripConnectOutputResponseDto {
+ @ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
+ @IsEnum([SUCCESS_STATUS, ERROR_STATUS])
+ status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
+
+ @Expose()
+ @ValidateNested()
+ @Type(() => StripConnectOutputDto)
+ data!: StripConnectOutputDto;
+}
+
+export class StripCredentialsCheckOutputResponseDto {
+ @ApiProperty({ example: SUCCESS_STATUS })
+ status!: typeof SUCCESS_STATUS;
+}
+
+export class StripCredentialsSaveOutputResponseDto {
+ @IsString()
+ @Expose()
+ readonly url!: string;
+}
diff --git a/apps/api/v2/src/modules/stripe/stripe.module.ts b/apps/api/v2/src/modules/stripe/stripe.module.ts
index 2452e10b6e..0191c2fad7 100644
--- a/apps/api/v2/src/modules/stripe/stripe.module.ts
+++ b/apps/api/v2/src/modules/stripe/stripe.module.ts
@@ -1,10 +1,16 @@
+import { AppsRepository } from "@/modules/apps/apps.repository";
+import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
+import { PrismaModule } from "@/modules/prisma/prisma.module";
+import { StripeController } from "@/modules/stripe/controllers/stripe.controller";
import { StripeService } from "@/modules/stripe/stripe.service";
+import { TokensRepository } from "@/modules/tokens/tokens.repository";
import { Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
@Module({
- imports: [ConfigModule],
+ imports: [ConfigModule, PrismaModule],
exports: [StripeService],
- providers: [StripeService],
+ providers: [StripeService, AppsRepository, CredentialsRepository, TokensRepository],
+ controllers: [StripeController],
})
export class StripeModule {}
diff --git a/apps/api/v2/src/modules/stripe/stripe.service.ts b/apps/api/v2/src/modules/stripe/stripe.service.ts
index b93f9f5e9a..a1bf625578 100644
--- a/apps/api/v2/src/modules/stripe/stripe.service.ts
+++ b/apps/api/v2/src/modules/stripe/stripe.service.ts
@@ -1,15 +1,130 @@
import { AppConfig } from "@/config/type";
-import { Injectable } from "@nestjs/common";
+import { AppsRepository } from "@/modules/apps/apps.repository";
+import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
+import { getReturnToValueFromQueryState } from "@/modules/stripe/utils/getReturnToValueFromQueryState";
+import { stripeInstance } from "@/modules/stripe/utils/newStripeInstance";
+import { StripeData } from "@/modules/stripe/utils/stripeDataSchemas";
+import { TokensRepository } from "@/modules/tokens/tokens.repository";
+import { Injectable, NotFoundException, BadRequestException, UnauthorizedException } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
+import type { Prisma } from "@prisma/client";
import Stripe from "stripe";
+import { z } from "zod";
+
+import { SUCCESS_STATUS } from "@calcom/platform-constants";
+
+import { stripeKeysResponseSchema } from "./utils/stripeDataSchemas";
+
+import stringify = require("qs-stringify");
@Injectable()
export class StripeService {
public stripe: Stripe;
+ private redirectUri = `${this.config.get("api.url")}/stripe/save`;
- constructor(configService: ConfigService) {
+ constructor(
+ configService: ConfigService,
+ private readonly config: ConfigService,
+ private readonly appsRepository: AppsRepository,
+ private readonly credentialRepository: CredentialsRepository,
+ private readonly tokensRepository: TokensRepository
+ ) {
this.stripe = new Stripe(configService.get("stripe.apiKey", { infer: true }) ?? "", {
apiVersion: "2020-08-27",
});
}
+
+ async getStripeRedirectUrl(state: string, userEmail?: string, userName?: string | null) {
+ const { client_id } = await this.getStripeAppKeys();
+
+ const stripeConnectParams: Stripe.OAuthAuthorizeUrlParams = {
+ client_id,
+ scope: "read_write",
+ response_type: "code",
+ stripe_user: {
+ email: userEmail,
+ first_name: userName || undefined,
+ /** We need this so E2E don't fail for international users */
+ country: process.env.NEXT_PUBLIC_IS_E2E ? "US" : undefined,
+ },
+ redirect_uri: this.redirectUri,
+ state: state,
+ };
+
+ const params = z.record(z.any()).parse(stripeConnectParams);
+ const query = stringify(params);
+ const url = `https://connect.stripe.com/oauth/authorize?${query}`;
+
+ return url;
+ }
+
+ async getStripeAppKeys() {
+ const app = await this.appsRepository.getAppBySlug("stripe");
+
+ const { client_id, client_secret } = stripeKeysResponseSchema.parse(app?.keys);
+
+ if (!client_id) {
+ throw new NotFoundException("Stripe app not found");
+ }
+
+ if (!client_secret) {
+ throw new NotFoundException("Stripe app not found");
+ }
+
+ return { client_id, client_secret };
+ }
+
+ async saveStripeAccount(state: string, code: string, accessToken: string): Promise<{ url: string }> {
+ const userId = await this.tokensRepository.getAccessTokenOwnerId(accessToken);
+
+ if (!userId) {
+ throw new UnauthorizedException("Invalid Access token.");
+ }
+
+ const response = await stripeInstance.oauth.token({
+ grant_type: "authorization_code",
+ code: code?.toString(),
+ });
+
+ const data: StripeData = { ...response, default_currency: "" };
+ if (response["stripe_user_id"]) {
+ const account = await stripeInstance.accounts.retrieve(response["stripe_user_id"]);
+ data["default_currency"] = account.default_currency;
+ }
+
+ await this.appsRepository.createAppCredential(
+ "stripe_payment",
+ data as unknown as Prisma.InputJsonObject,
+ userId,
+ "stripe"
+ );
+
+ return { url: getReturnToValueFromQueryState(state) };
+ }
+
+ async checkIfStripeAccountConnected(userId: number): Promise<{ status: typeof SUCCESS_STATUS }> {
+ const stripeCredentials = await this.credentialRepository.getByTypeAndUserId("stripe_payment", userId);
+
+ if (!stripeCredentials) {
+ throw new NotFoundException("Credentials for stripe not found.");
+ }
+
+ if (stripeCredentials.invalid) {
+ throw new BadRequestException("Invalid stripe credentials.");
+ }
+
+ const stripeKey = JSON.stringify(stripeCredentials.key);
+ const stripeKeyObject = JSON.parse(stripeKey);
+
+ const stripeAccount = await stripeInstance.accounts.retrieve(stripeKeyObject?.stripe_user_id);
+
+ // both of these should be true for an account to be fully active
+ if (!stripeAccount.payouts_enabled || !stripeAccount.charges_enabled) {
+ throw new BadRequestException("Stripe account is not an active account");
+ }
+
+ return {
+ status: SUCCESS_STATUS,
+ };
+ }
}
diff --git a/apps/api/v2/src/modules/stripe/utils/getReturnToValueFromQueryState.ts b/apps/api/v2/src/modules/stripe/utils/getReturnToValueFromQueryState.ts
new file mode 100644
index 0000000000..a905d153b0
--- /dev/null
+++ b/apps/api/v2/src/modules/stripe/utils/getReturnToValueFromQueryState.ts
@@ -0,0 +1,19 @@
+export const getReturnToValueFromQueryState = (queryState: string | string[] | undefined) => {
+ let returnTo = "";
+ try {
+ returnTo = JSON.parse(`${queryState}`).returnTo;
+ } catch (error) {
+ console.info("No 'returnTo' in req.query.state");
+ }
+ return returnTo;
+};
+
+export const getOnErrorReturnToValueFromQueryState = (queryState: string | string[] | undefined) => {
+ let returnTo = "";
+ try {
+ returnTo = JSON.parse(`${queryState}`).onErrorReturnTo;
+ } catch (error) {
+ console.info("No 'onErrorReturnTo' in req.query.state");
+ }
+ return returnTo;
+};
diff --git a/apps/api/v2/src/modules/stripe/utils/newStripeInstance.ts b/apps/api/v2/src/modules/stripe/utils/newStripeInstance.ts
new file mode 100644
index 0000000000..1784b78305
--- /dev/null
+++ b/apps/api/v2/src/modules/stripe/utils/newStripeInstance.ts
@@ -0,0 +1,6 @@
+import Stripe from "stripe";
+
+const stripeApiKey = process.env.STRIPE_API_KEY || "";
+export const stripeInstance = new Stripe(stripeApiKey, {
+ apiVersion: "2020-08-27",
+});
diff --git a/apps/api/v2/src/modules/stripe/utils/stripeDataSchemas.ts b/apps/api/v2/src/modules/stripe/utils/stripeDataSchemas.ts
new file mode 100644
index 0000000000..77fada616c
--- /dev/null
+++ b/apps/api/v2/src/modules/stripe/utils/stripeDataSchemas.ts
@@ -0,0 +1,24 @@
+import { z } from "zod";
+
+export const stripeOAuthTokenSchema = z.object({
+ access_token: z.string().optional(),
+ scope: z.string().optional(),
+ livemode: z.boolean().optional(),
+ token_type: z.literal("bearer").optional(),
+ refresh_token: z.string().optional(),
+ stripe_user_id: z.string().optional(),
+ stripe_publishable_key: z.string().optional(),
+});
+
+export const stripeDataSchema = stripeOAuthTokenSchema.extend({
+ default_currency: z.string(),
+});
+
+export type StripeData = z.infer;
+
+export const stripeKeysResponseSchema = z.object({
+ client_id: z.string().startsWith("ca_").min(1),
+ client_secret: z.string().startsWith("sk_").min(1),
+ public_key: z.string().startsWith("pk_").min(1),
+ webhook_secret: z.string().startsWith("whsec_").min(1),
+});
diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json
index ece75ba18d..bfe7464c1f 100644
--- a/apps/web/public/static/locales/en/common.json
+++ b/apps/web/public/static/locales/en/common.json
@@ -2605,6 +2605,9 @@
"outlook_connect_atom_label": "Connect Outlook Calendar",
"outlook_connect_atom_already_connected_label": "Connected Outlook Calendar",
"outlook_connect_atom_loading_label": "Checking Outlook Calendar",
+ "stripe_connect_atom_label": "Connect to Stripe",
+ "stripe_connect_atom_already_connected_label": "Connected Stripe",
+ "stripe_connect_atom_loading_label": "Checking Stripe credentials",
"booking_question_response_variables": "Booking question response variables",
"managed_by_teamAdmins": "Managed by {{teamAdmins}}",
"number_of_options":"{{count}} options",
diff --git a/packages/platform/atoms/connect/stripe/StripeConnect.tsx b/packages/platform/atoms/connect/stripe/StripeConnect.tsx
new file mode 100644
index 0000000000..ac369a9cc5
--- /dev/null
+++ b/packages/platform/atoms/connect/stripe/StripeConnect.tsx
@@ -0,0 +1,72 @@
+import type { FC } from "react";
+
+import { useLocale } from "@calcom/lib/hooks/useLocale";
+import { Button } from "@calcom/ui";
+
+import type { OnCheckErrorType, UseCheckProps } from "../../hooks/connect/useCheck";
+import { useCheck } from "../../hooks/stripe/useCheck";
+import { useConnect } from "../../hooks/stripe/useConnect";
+import { AtomsWrapper } from "../../src/components/atoms-wrapper";
+import { cn } from "../../src/lib/utils";
+
+type StripeConnectProps = {
+ className?: string;
+ label: string;
+ alreadyConnectedLabel: string;
+ loadingLabel: string;
+ onCheckError?: OnCheckErrorType;
+ redir?: string;
+ errorRedir?: string;
+ initialData: UseCheckProps["initialData"];
+ onCheckSuccess?: () => void;
+};
+
+export const StripeConnect: FC> = ({
+ label,
+ className,
+ loadingLabel,
+ alreadyConnectedLabel,
+ redir,
+ errorRedir,
+ onCheckError,
+ initialData,
+ onCheckSuccess,
+}) => {
+ const { t } = useLocale();
+ const { connect } = useConnect(redir, errorRedir);
+ const { allowConnect, checked } = useCheck({
+ onCheckError,
+ onCheckSuccess,
+ initialData,
+ });
+
+ let displayedLabel = label || t("stripe_connect_atom_label");
+
+ const isChecking = !checked;
+ const isDisabled = isChecking || !allowConnect;
+
+ if (isChecking) {
+ displayedLabel = loadingLabel || t("stripe_connect_atom_loading_label");
+ } else if (!allowConnect) {
+ displayedLabel = alreadyConnectedLabel || t("stripe_connect_atom_already_connected_label");
+ }
+
+ return (
+
+
+
+ );
+};
diff --git a/packages/platform/atoms/hooks/stripe/useCheck.ts b/packages/platform/atoms/hooks/stripe/useCheck.ts
new file mode 100644
index 0000000000..23eda3d77b
--- /dev/null
+++ b/packages/platform/atoms/hooks/stripe/useCheck.ts
@@ -0,0 +1,62 @@
+import { useQuery, useQueryClient } from "@tanstack/react-query";
+
+import type { CALENDARS } from "@calcom/platform-constants";
+import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants";
+import type { ApiErrorResponse, ApiResponse } from "@calcom/platform-types";
+
+import http from "../../lib/http";
+import { useAtomsContext } from "../useAtomsContext";
+
+export interface UseCheckProps {
+ onCheckError?: OnCheckErrorType;
+ onCheckSuccess?: () => void;
+ initialData?: {
+ status: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
+ data: {
+ allowConnect: boolean;
+ checked: boolean;
+ };
+ };
+}
+const stripeQueryKey = ["get-stripe-check"];
+export type OnCheckErrorType = (err: ApiErrorResponse) => void;
+export const getQueryKey = (calendar: (typeof CALENDARS)[number]) => [`get-${calendar}-check`];
+
+export const useCheck = ({ onCheckError, initialData, onCheckSuccess }: UseCheckProps) => {
+ const { isInit, accessToken } = useAtomsContext();
+ const queryClient = useQueryClient();
+
+ const { data: check, refetch } = useQuery({
+ queryKey: stripeQueryKey,
+ staleTime: 6000,
+ enabled: isInit && !!accessToken,
+ queryFn: () => {
+ return http
+ ?.get>(`/stripe/check`)
+ .then(({ data: responseBody }) => {
+ if (responseBody.status === SUCCESS_STATUS) {
+ onCheckSuccess();
+ return { status: SUCCESS_STATUS, data: { allowConnect: false, checked: true } };
+ }
+ onCheckError?.(responseBody);
+ return { status: ERROR_STATUS, data: { allowConnect: true, checked: true } };
+ })
+ .catch((err) => {
+ onCheckError?.(err);
+ return { status: ERROR_STATUS, data: { allowConnect: true, checked: true } };
+ });
+ },
+ initialData,
+ });
+ return {
+ allowConnect: check?.data?.allowConnect ?? false,
+ checked: check?.data?.checked ?? false,
+ refetch: () => {
+ queryClient.setQueryData(stripeQueryKey, {
+ status: SUCCESS_STATUS,
+ data: { allowConnect: false, checked: false },
+ });
+ refetch();
+ },
+ };
+};
diff --git a/packages/platform/atoms/hooks/stripe/useConnect.ts b/packages/platform/atoms/hooks/stripe/useConnect.ts
new file mode 100644
index 0000000000..4102d63dd0
--- /dev/null
+++ b/packages/platform/atoms/hooks/stripe/useConnect.ts
@@ -0,0 +1,45 @@
+import { useQuery } from "@tanstack/react-query";
+
+import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
+import type { ApiResponse } from "@calcom/platform-types";
+
+import http from "../../lib/http";
+
+export const useGetRedirectUrl = (redir?: string, errorRedir?: string) => {
+ const authUrl = useQuery({
+ queryKey: ["get-stripe-connect-redirect-uri"],
+ staleTime: Infinity,
+ enabled: false,
+ queryFn: () => {
+ return http
+ ?.get>(
+ `/stripe/connect${redir ? `?redir=${encodeURIComponent(redir)}` : "?redir="}${
+ errorRedir ? `&errorRedir=${encodeURIComponent(errorRedir)}` : ""
+ }`
+ )
+ .then(({ data: responseBody }) => {
+ if (responseBody.status === SUCCESS_STATUS) {
+ return responseBody.data.authUrl;
+ }
+ if (responseBody.status === ERROR_STATUS) throw new Error(responseBody.error.message);
+ return "";
+ });
+ },
+ });
+
+ return authUrl;
+};
+
+export const useConnect = (redir?: string, errorRedir?: string) => {
+ const { refetch } = useGetRedirectUrl(redir, errorRedir);
+
+ const connect = async () => {
+ const redirectUri = await refetch();
+
+ if (redirectUri.data) {
+ window.location.href = redirectUri.data;
+ }
+ };
+
+ return { connect };
+};
diff --git a/packages/platform/atoms/index.ts b/packages/platform/atoms/index.ts
index 10e469dab0..651ca848f6 100644
--- a/packages/platform/atoms/index.ts
+++ b/packages/platform/atoms/index.ts
@@ -25,3 +25,4 @@ export { DestinationCalendarSettingsPlatformWrapper as DestinationCalendarSettin
export { CalendarSettingsPlatformWrapper as CalendarSettings } from "./calendar-settings/index";
export type { UpdateScheduleInput_2024_06_11 as UpdateScheduleBody } from "@calcom/platform-types";
export { EventTypePlatformWrapper as EventTypeSettings } from "./event-types/wrappers/EventTypePlatformWrapper";
+export { StripeConnect } from "./connect/stripe/StripeConnect";
diff --git a/packages/platform/examples/base/src/pages/index.tsx b/packages/platform/examples/base/src/pages/index.tsx
index a754a5fff4..c30f972654 100644
--- a/packages/platform/examples/base/src/pages/index.tsx
+++ b/packages/platform/examples/base/src/pages/index.tsx
@@ -1,7 +1,7 @@
import { Navbar } from "@/components/Navbar";
import { Inter, Poppins } from "next/font/google";
-import { GcalConnect, Connect } from "@calcom/atoms";
+import { Connect, StripeConnect } from "@calcom/atoms";
const inter = Inter({ subsets: ["latin"] });
const poppins = Poppins({ subsets: ["latin"], weight: ["400", "800"] });
@@ -20,7 +20,7 @@ export default function Home(props: { calUsername: string; calEmail: string }) {
To get started, connect your google calendar.
-
@@ -33,6 +33,13 @@ export default function Home(props: { calUsername: string; calEmail: string }) {
isMultiCalendar={true}
className="h-[40px] bg-gradient-to-r from-[#8A2387] via-[#E94057] to-[#F27121] text-center text-base font-semibold text-transparent text-white hover:bg-orange-700"
/>
+ {
+ console.log("stripe account connected successfully".toLocaleUpperCase());
+ }}
+ />