From b7aadd81bedfa381ad4c3f80f581920299bfa606 Mon Sep 17 00:00:00 2001 From: Kiran K Date: Fri, 17 Jan 2025 19:15:55 +0530 Subject: [PATCH 1/3] Remove unused Stripe integration files and refactor webhook handling for account deauthorization. Update workspace management in the app settings and streamline API calls for better efficiency. --- .../api/stripe/integration/callback/route.ts | 81 ------------------ .../account-application-deauthorized.ts | 15 +++- packages/stripe-app/src/utils/create-link.ts | 37 --------- packages/stripe-app/src/utils/dub.ts | 21 ++--- packages/stripe-app/src/views/AppSettings.tsx | 7 -- packages/stripe-app/src/views/create-link.tsx | 82 ------------------- 6 files changed, 22 insertions(+), 221 deletions(-) delete mode 100644 apps/web/app/api/stripe/integration/callback/route.ts delete mode 100644 packages/stripe-app/src/utils/create-link.ts delete mode 100644 packages/stripe-app/src/views/create-link.tsx diff --git a/apps/web/app/api/stripe/integration/callback/route.ts b/apps/web/app/api/stripe/integration/callback/route.ts deleted file mode 100644 index 4ca9489d4f..0000000000 --- a/apps/web/app/api/stripe/integration/callback/route.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { getSession } from "@/lib/auth"; -import { installIntegration } from "@/lib/integrations/install"; -import { redis } from "@/lib/upstash"; -import z from "@/lib/zod"; -import { prisma } from "@dub/prisma"; -import { APP_DOMAIN, getSearchParams, STRIPE_INTEGRATION_ID } from "@dub/utils"; -import { redirect } from "next/navigation"; -import { NextRequest } from "next/server"; - -const schema = z.object({ - state: z.string(), - stripe_user_id: z.string().optional(), - error: z.string().optional(), - error_description: z.string().optional(), -}); - -export const GET = async (req: NextRequest) => { - const session = await getSession(); - - if (!session?.user.id) { - return new Response("Unauthorized", { status: 401 }); - } - - const parsed = schema.safeParse(getSearchParams(req.url)); - - if (!parsed.success) { - console.error("[Stripe OAuth callback] Error", parsed.error); - return new Response("Invalid request", { status: 400 }); - } - - const { - state, - stripe_user_id: stripeAccountId, - error, - error_description, - } = parsed.data; - - // Find workspace that initiated the Stripe app install - const workspaceId = await redis.get(`stripe:install:state:${state}`); - - if (!workspaceId) { - redirect(APP_DOMAIN); - } - - // Delete the state key from Redis - await redis.del(`stripe:install:state:${state}`); - - if (error) { - const workspace = await prisma.project.findUnique({ - where: { - id: workspaceId, - }, - }); - if (!workspace) { - redirect(APP_DOMAIN); - } - redirect( - `${APP_DOMAIN}/${workspace.slug}/settings/integrations/stripe?stripeConnectError=${error_description}`, - ); - } else if (stripeAccountId) { - // Update the workspace with the Stripe Connect ID - const workspace = await prisma.project.update({ - where: { - id: workspaceId, - }, - data: { - stripeConnectId: stripeAccountId, - }, - }); - - await installIntegration({ - integrationId: STRIPE_INTEGRATION_ID, - userId: session.user.id, - workspaceId: workspace.id, - }); - - redirect(`${APP_DOMAIN}/${workspace.slug}/settings/integrations/stripe`); - } - - return new Response("Invalid request", { status: 400 }); -}; diff --git a/apps/web/app/api/stripe/integration/webhook/account-application-deauthorized.ts b/apps/web/app/api/stripe/integration/webhook/account-application-deauthorized.ts index f87fe33f18..607124b29c 100644 --- a/apps/web/app/api/stripe/integration/webhook/account-application-deauthorized.ts +++ b/apps/web/app/api/stripe/integration/webhook/account-application-deauthorized.ts @@ -6,7 +6,20 @@ import type Stripe from "stripe"; export async function accountApplicationDeauthorized(event: Stripe.Event) { const stripeAccountId = event.account; - const workspace = await prisma.project.update({ + const workspace = await prisma.project.findUnique({ + where: { + stripeConnectId: stripeAccountId, + }, + select: { + id: true, + }, + }); + + if (!workspace) { + return `Stripe Connect account ${stripeAccountId} deauthorized.`; + } + + await prisma.project.update({ where: { stripeConnectId: stripeAccountId, }, diff --git a/packages/stripe-app/src/utils/create-link.ts b/packages/stripe-app/src/utils/create-link.ts deleted file mode 100644 index 235493c754..0000000000 --- a/packages/stripe-app/src/utils/create-link.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { DUB_API_HOST } from "./constants"; -import { getValidToken } from "./oauth"; -import { stripe } from "./stripe"; - -export async function createLink({ - url, - isPublic, -}: { - url: string; - isPublic: boolean; -}) { - const token = isPublic ? null : await getValidToken({ stripe }); - - const response = await fetch(`${DUB_API_HOST}/links`, { - method: "POST", - body: JSON.stringify({ - url, - ...(token ? { trackConversion: true } : {}), - }), - headers: { - "Content-Type": "application/json", - ...(!token - ? { "dub-anonymous-link-creation": "1" } - : { Authorization: `Bearer ${token.access_token}` }), - }, - }); - - const data = await response.json(); - - if (!response.ok) { - throw new Error("Failed to create link.", { - cause: data.error, - }); - } - - return data; -} diff --git a/packages/stripe-app/src/utils/dub.ts b/packages/stripe-app/src/utils/dub.ts index b07c60e988..ac4ccb77ec 100644 --- a/packages/stripe-app/src/utils/dub.ts +++ b/packages/stripe-app/src/utils/dub.ts @@ -4,25 +4,20 @@ import { Token } from "./types"; // Update the workspace with stripeAccountId export async function updateWorkspace({ token, - workspaceId, accountId, }: { token: Token; - workspaceId: string; accountId: string | null; }) { - const response = await fetch( - `${DUB_API_HOST}/stripe/integration?workspaceId=${workspaceId}`, - { - method: "PATCH", - headers: { - Authorization: `Bearer ${token.access_token}`, - }, - body: JSON.stringify({ - stripeAccountId: accountId, - }), + const response = await fetch(`${DUB_API_HOST}/stripe/integration`, { + method: "PATCH", + headers: { + Authorization: `Bearer ${token.access_token}`, }, - ); + body: JSON.stringify({ + stripeAccountId: accountId, + }), + }); if (!response.ok) { const data = await response.json(); diff --git a/packages/stripe-app/src/views/AppSettings.tsx b/packages/stripe-app/src/views/AppSettings.tsx index 997c3395a5..6c019b1302 100644 --- a/packages/stripe-app/src/views/AppSettings.tsx +++ b/packages/stripe-app/src/views/AppSettings.tsx @@ -22,9 +22,6 @@ import { deleteSecret, setSecret } from "../utils/secrets"; import { stripe } from "../utils/stripe"; import { Workspace } from "../utils/types"; -// TODO: -// Handle errors and display them to the user - const AppSettings = ({ userContext, oauthContext }: ExtensionContextValue) => { const credentialsUsed = useRef(false); const [oauthState, setOAuthState] = useState(""); @@ -48,7 +45,6 @@ const AppSettings = ({ userContext, oauthContext }: ExtensionContextValue) => { await updateWorkspace({ token, - workspaceId: workspace.id, accountId: null, }); @@ -99,7 +95,6 @@ const AppSettings = ({ userContext, oauthContext }: ExtensionContextValue) => { await updateWorkspace({ token, - workspaceId: workspace.id, accountId: userContext.account.id, }); @@ -189,8 +184,6 @@ const AppSettings = ({ userContext, oauthContext }: ExtensionContextValue) => { brandIcon={appIcon} /> )} - - {/* */} ); }; diff --git a/packages/stripe-app/src/views/create-link.tsx b/packages/stripe-app/src/views/create-link.tsx deleted file mode 100644 index a242cfb7a7..0000000000 --- a/packages/stripe-app/src/views/create-link.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { - Box, - Button, - FormFieldGroup, - Link, - TextField, -} from "@stripe/ui-extension-sdk/ui"; -import { showToast } from "@stripe/ui-extension-sdk/utils"; -import { useState } from "react"; -import { createLink } from "../utils/create-link"; -import { Workspace } from "../utils/types"; - -export const CreateLink = ({ workspace }: { workspace: Workspace | null }) => { - const [url, setUrl] = useState(""); - const [shortLink, setShortLink] = useState(""); - const [isSubmitting, setIsSubmitting] = useState(false); - - const handleCreateLink = async () => { - setIsSubmitting(true); - - try { - const link = await createLink({ - url, - isPublic: !workspace, - }); - - showToast("Link created", { type: "success" }); // TODO: This is not working - setShortLink(link.shortLink); - setUrl(""); - } catch (error: any) { - console.error(error); - showToast(error.message, { type: "caution" }); - } finally { - setIsSubmitting(false); - } - }; - - return ( - - - setUrl(e.target.value)} - value={url} - /> - - - {shortLink && ( - - {shortLink} - - )} - - Want to claim your links and track conversions for them? Connect your - Dub workspace. - - - ); -}; From 76a5be7fe5bf4e377526ee86311edb75851fa8af Mon Sep 17 00:00:00 2001 From: Kiran K Date: Fri, 17 Jan 2025 19:21:47 +0530 Subject: [PATCH 2/3] Update oauth.ts --- packages/stripe-app/src/utils/oauth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stripe-app/src/utils/oauth.ts b/packages/stripe-app/src/utils/oauth.ts index ecc855a221..755afda3b0 100644 --- a/packages/stripe-app/src/utils/oauth.ts +++ b/packages/stripe-app/src/utils/oauth.ts @@ -23,7 +23,7 @@ export function getOAuthUrl({ state: string; challenge: string; }) { - return `${DUB_HOST}/oauth/authorize?client_id=${DUB_CLIENT_ID}&redirect_uri=${getRedirectURL()}&response_type=code&scope=workspaces.write,links.write&state=${state}&code_challenge=${challenge}&code_challenge_method=S256`; + return `${DUB_HOST}/oauth/authorize?client_id=${DUB_CLIENT_ID}&redirect_uri=${getRedirectURL()}&response_type=code&scope=workspaces.write&state=${state}&code_challenge=${challenge}&code_challenge_method=S256`; } // Exchanges the authorization code for an access token From 8f6d3445f3cd1bc7f50b7b437c224a886826f5b9 Mon Sep 17 00:00:00 2001 From: Kiran K Date: Fri, 17 Jan 2025 22:50:51 +0530 Subject: [PATCH 3/3] Create route.ts --- .../api/stripe/integration/callback/route.ts | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 apps/web/app/api/stripe/integration/callback/route.ts diff --git a/apps/web/app/api/stripe/integration/callback/route.ts b/apps/web/app/api/stripe/integration/callback/route.ts new file mode 100644 index 0000000000..4ca9489d4f --- /dev/null +++ b/apps/web/app/api/stripe/integration/callback/route.ts @@ -0,0 +1,81 @@ +import { getSession } from "@/lib/auth"; +import { installIntegration } from "@/lib/integrations/install"; +import { redis } from "@/lib/upstash"; +import z from "@/lib/zod"; +import { prisma } from "@dub/prisma"; +import { APP_DOMAIN, getSearchParams, STRIPE_INTEGRATION_ID } from "@dub/utils"; +import { redirect } from "next/navigation"; +import { NextRequest } from "next/server"; + +const schema = z.object({ + state: z.string(), + stripe_user_id: z.string().optional(), + error: z.string().optional(), + error_description: z.string().optional(), +}); + +export const GET = async (req: NextRequest) => { + const session = await getSession(); + + if (!session?.user.id) { + return new Response("Unauthorized", { status: 401 }); + } + + const parsed = schema.safeParse(getSearchParams(req.url)); + + if (!parsed.success) { + console.error("[Stripe OAuth callback] Error", parsed.error); + return new Response("Invalid request", { status: 400 }); + } + + const { + state, + stripe_user_id: stripeAccountId, + error, + error_description, + } = parsed.data; + + // Find workspace that initiated the Stripe app install + const workspaceId = await redis.get(`stripe:install:state:${state}`); + + if (!workspaceId) { + redirect(APP_DOMAIN); + } + + // Delete the state key from Redis + await redis.del(`stripe:install:state:${state}`); + + if (error) { + const workspace = await prisma.project.findUnique({ + where: { + id: workspaceId, + }, + }); + if (!workspace) { + redirect(APP_DOMAIN); + } + redirect( + `${APP_DOMAIN}/${workspace.slug}/settings/integrations/stripe?stripeConnectError=${error_description}`, + ); + } else if (stripeAccountId) { + // Update the workspace with the Stripe Connect ID + const workspace = await prisma.project.update({ + where: { + id: workspaceId, + }, + data: { + stripeConnectId: stripeAccountId, + }, + }); + + await installIntegration({ + integrationId: STRIPE_INTEGRATION_ID, + userId: session.user.id, + workspaceId: workspace.id, + }); + + redirect(`${APP_DOMAIN}/${workspace.slug}/settings/integrations/stripe`); + } + + return new Response("Invalid request", { status: 400 }); +};