Fixes/stripe improvements (#5698)
* Enables require confirmation after payment its made * Various Stripe improvements * Use constant * Merge branch 'main' into fixes/stripe-improvements * Update getStripeAppKeys.ts * submodule sync * Submodule sync * Revert * Update apps/web/components/booking/BookingListItem.tsx Co-authored-by: alannnc <alannnc@gmail.com> Co-authored-by: Alan <alannnc@gmail.com> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
12 files changed
+77
-21
No files matched your search
@@ -15,6 +15,7 @@ export default function AppCard({
|
||||
switchChecked,
|
||||
children,
|
||||
setAppData,
|
||||
returnTo,
|
||||
}: {
|
||||
app: RouterOutputs["viewer"]["apps"][number];
|
||||
description?: React.ReactNode;
|
||||
@@ -22,6 +23,7 @@ export default function AppCard({
|
||||
switchOnClick?: (e: boolean) => void;
|
||||
children?: React.ReactNode;
|
||||
setAppData: SetAppDataGeneric<typeof eventTypeAppCardZod>;
|
||||
returnTo?: string;
|
||||
}) {
|
||||
const [animationRef] = useAutoAnimate<HTMLDivElement>();
|
||||
|
||||
@@ -55,7 +57,11 @@ export default function AppCard({
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<OmniInstallAppButton className="ml-auto flex items-center" appId={app.slug} />
|
||||
<OmniInstallAppButton
|
||||
className="ml-auto flex items-center"
|
||||
appId={app.slug}
|
||||
returnTo={returnTo}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,12 +11,21 @@ import { InstallAppButton } from "../components";
|
||||
* Use this component to allow installing an app from anywhere on the app.
|
||||
* Use of this component requires you to remove custom InstallAppButtonComponent so that it can manage the redirection itself
|
||||
*/
|
||||
export default function OmniInstallAppButton({ appId, className }: { appId: string; className: string }) {
|
||||
export default function OmniInstallAppButton({
|
||||
appId,
|
||||
className,
|
||||
returnTo,
|
||||
}: {
|
||||
appId: string;
|
||||
className: string;
|
||||
returnTo?: string;
|
||||
}) {
|
||||
const { t } = useLocale();
|
||||
const { data: app } = useApp(appId);
|
||||
const utils = trpc.useContext();
|
||||
|
||||
const mutation = useAddAppMutation(null, {
|
||||
returnTo,
|
||||
onSuccess: (data) => {
|
||||
//TODO: viewer.appById might be replaced with viewer.apps so that a single query needs to be invalidated.
|
||||
utils.viewer.appById.invalidate({ appId });
|
||||
|
||||
@@ -21,9 +21,11 @@ type CustomUseMutationOptions =
|
||||
type AddAppMutationData = { setupPending: boolean } | void;
|
||||
type UseAddAppMutationOptions = CustomUseMutationOptions & {
|
||||
onSuccess: (data: AddAppMutationData) => void;
|
||||
returnTo?: string;
|
||||
};
|
||||
|
||||
function useAddAppMutation(_type: App["type"] | null, options?: UseAddAppMutationOptions) {
|
||||
function useAddAppMutation(_type: App["type"] | null, allOptions?: UseAddAppMutationOptions) {
|
||||
const { returnTo, ...options } = allOptions || {};
|
||||
const mutation = useMutation<
|
||||
AddAppMutationData,
|
||||
Error,
|
||||
@@ -42,11 +44,12 @@ function useAddAppMutation(_type: App["type"] | null, options?: UseAddAppMutatio
|
||||
}
|
||||
const state: IntegrationOAuthCallbackState = {
|
||||
returnTo:
|
||||
returnTo ||
|
||||
WEBAPP_URL +
|
||||
getInstalledAppPath(
|
||||
{ variant: variables && variables.variant, slug: variables && variables.slug },
|
||||
location.search
|
||||
),
|
||||
getInstalledAppPath(
|
||||
{ variant: variables && variables.variant, slug: variables && variables.slug },
|
||||
location.search
|
||||
),
|
||||
};
|
||||
const stateStr = encodeURIComponent(JSON.stringify(state));
|
||||
const searchParams = `?state=${stateStr}`;
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
"@calcom/trpc": "*",
|
||||
"@calcom/ui": "*",
|
||||
"@calcom/zoomvideo": "*",
|
||||
"lodash": "^4.17.21"
|
||||
"lodash": "^4.17.21",
|
||||
"qs-stringify": "^1.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@calcom/types": "*"
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { stringify } from "querystring";
|
||||
import stringify from "qs-stringify";
|
||||
import Stripe from "stripe";
|
||||
import { z } from "zod";
|
||||
|
||||
import { BASE_URL } from "@calcom/lib/constants";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
const client_id = process.env.STRIPE_CLIENT_ID;
|
||||
import { getStripeAppKeys } from "../lib/getStripeAppKeys";
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { client_id } = await getStripeAppKeys();
|
||||
|
||||
if (req.method === "GET") {
|
||||
// Get user
|
||||
const user = await prisma.user.findUnique({
|
||||
@@ -19,18 +23,23 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
},
|
||||
});
|
||||
|
||||
const redirect_uri = encodeURI(BASE_URL + "/api/integrations/stripepayment/callback");
|
||||
const stripeConnectParams = {
|
||||
const redirect_uri = encodeURI(WEBAPP_URL + "/api/integrations/stripepayment/callback");
|
||||
const stripeConnectParams: Stripe.OAuthAuthorizeUrlParams = {
|
||||
client_id,
|
||||
scope: "read_write",
|
||||
response_type: "code",
|
||||
"stripe_user[email]": user?.email,
|
||||
"stripe_user[first_name]": user?.name,
|
||||
/** We need this so E2E don't fail for international users */
|
||||
"stripe_user[country]": process.env.NEXT_PUBLIC_IS_E2E ? "US" : undefined,
|
||||
stripe_user: {
|
||||
email: user?.email,
|
||||
first_name: user?.name || undefined,
|
||||
/** We need this so E2E don't fail for international users */
|
||||
country: process.env.NEXT_PUBLIC_IS_E2E ? "US" : undefined,
|
||||
},
|
||||
redirect_uri,
|
||||
state: typeof req.query.state === "string" ? req.query.state : undefined,
|
||||
};
|
||||
const query = stringify(stripeConnectParams);
|
||||
/** stringify is being dumb here */
|
||||
const params = z.record(z.any()).parse(stripeConnectParams);
|
||||
const query = stringify(params);
|
||||
/**
|
||||
* Choose Express or Standard Stripe accounts
|
||||
* @url https://stripe.com/docs/connect/accounts
|
||||
|
||||
@@ -7,6 +7,16 @@ import prisma from "@calcom/prisma";
|
||||
import getInstalledAppPath from "../../_utils/getInstalledAppPath";
|
||||
import stripe, { StripeData } from "../lib/server";
|
||||
|
||||
function getReturnToValueFromQueryState(req: NextApiRequest) {
|
||||
let returnTo = "";
|
||||
try {
|
||||
returnTo = JSON.parse(`${req.query.state}`).returnTo;
|
||||
} catch (error) {
|
||||
console.info("No 'returnTo' in req.query.state");
|
||||
}
|
||||
return returnTo;
|
||||
}
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { code, error, error_description } = req.query;
|
||||
|
||||
@@ -40,5 +50,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
},
|
||||
});
|
||||
|
||||
res.redirect(getInstalledAppPath({ variant: "payment", slug: "stripe" }));
|
||||
const returnTo = getReturnToValueFromQueryState(req);
|
||||
res.redirect(returnTo || getInstalledAppPath({ variant: "payment", slug: "stripe" }));
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import Stripe from "stripe";
|
||||
|
||||
import { getPremiumMonthlyPlanPriceId } from "@calcom/app-store/stripepayment/lib/utils";
|
||||
import { checkPremiumUsername } from "@calcom/features/ee/common/lib/checkPremiumUsername";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { Prisma } from "@calcom/prisma/client";
|
||||
|
||||
@@ -36,7 +37,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
return;
|
||||
}
|
||||
|
||||
const return_url = `${process.env.NEXT_PUBLIC_WEBAPP_URL}/api/integrations/stripepayment/paymentCallback?checkoutSessionId={CHECKOUT_SESSION_ID}&callbackUrl=${callbackUrl}`;
|
||||
const return_url = `${WEBAPP_URL}/api/integrations/stripepayment/paymentCallback?checkoutSessionId={CHECKOUT_SESSION_ID}&callbackUrl=${callbackUrl}`;
|
||||
const createSessionParams: Stripe.Checkout.SessionCreateParams = {
|
||||
mode: "subscription",
|
||||
payment_method_types: ["card"],
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { useState } from "react";
|
||||
import { FormattedNumber, IntlProvider } from "react-intl";
|
||||
|
||||
import { useAppContextWithSchema } from "@calcom/app-store/EventTypeAppContext";
|
||||
import AppCard from "@calcom/app-store/_components/AppCard";
|
||||
import type { EventTypeAppCardComponent } from "@calcom/app-store/types";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Alert, TextField } from "@calcom/ui";
|
||||
|
||||
import { appDataSchema } from "../zod";
|
||||
|
||||
const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType }) {
|
||||
const { asPath } = useRouter();
|
||||
const [getAppData, setAppData] = useAppContextWithSchema<typeof appDataSchema>();
|
||||
const price = getAppData("price");
|
||||
const currency = getAppData("currency");
|
||||
@@ -28,6 +31,7 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
|
||||
.trim();
|
||||
return (
|
||||
<AppCard
|
||||
returnTo={WEBAPP_URL + asPath}
|
||||
setAppData={setAppData}
|
||||
app={app}
|
||||
switchChecked={requirePayment}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { stringify } from "querystring";
|
||||
|
||||
import { WEBSITE_URL } from "@calcom/lib/constants";
|
||||
|
||||
export type Maybe<T> = T | undefined | null;
|
||||
|
||||
export function createPaymentLink(opts: {
|
||||
@@ -11,7 +13,7 @@ export function createPaymentLink(opts: {
|
||||
}): string {
|
||||
const { paymentUid, name, email, date, absolute = true } = opts;
|
||||
let link = "";
|
||||
if (absolute) link = process.env.NEXT_PUBLIC_WEBSITE_URL!;
|
||||
if (absolute) link = WEBSITE_URL;
|
||||
const query = stringify({ date, name, email });
|
||||
return link + `/payment/${paymentUid}?${query}`;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import getParsedAppKeysFromSlug from "../../_utils/getParsedAppKeysFromSlug";
|
||||
import { appKeysSchema } from "../zod";
|
||||
|
||||
export const getStripeAppKeys = () => getParsedAppKeysFromSlug("stripe", appKeysSchema);
|
||||
@@ -34,6 +34,7 @@ export const stripeDataSchema = stripeOAuthTokenSchema.extend({
|
||||
|
||||
export type StripeData = z.infer<typeof stripeDataSchema>;
|
||||
|
||||
/** Figure out a way to get this from the DB without too much wreckage. */
|
||||
const stripePrivateKey = process.env.STRIPE_PRIVATE_KEY!;
|
||||
const stripe = new Stripe(stripePrivateKey, {
|
||||
apiVersion: "2020-08-27",
|
||||
|
||||
@@ -21971,6 +21971,11 @@ qrcode@^1.5.1:
|
||||
pngjs "^5.0.0"
|
||||
yargs "^15.3.1"
|
||||
|
||||
qs-stringify@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/qs-stringify/-/qs-stringify-1.2.1.tgz#9b39ef6b816bd83309628fc9dad435fc0eccc28b"
|
||||
integrity sha512-2N5xGLGZUxpgAYq1fD1LmBSCbxQVsXYt5JU0nU3FuPWO8PlCnKNFQwXkZgyB6mrTdg7IbexX4wxIR403dJw9pw==
|
||||
|
||||
qs@6.10.3, qs@^6.10.2, qs@^6.10.3, qs@^6.7.0, qs@^6.9.4:
|
||||
version "6.10.3"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e"
|
||||
|
||||
Reference in new issue
Block a user