mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
* add initial installation flow of stripe sync engine * update docs link * Add supabase_vault extension dep * Add stripe logo to sync engine integration * Move overview content to bottom of integration pages * Add sync state to stripe sync page * only check sync state if stripe integration is installed * Use proper stripe-sync package and setup flows * Improve sync engine installation ux * Remove unused hardcoded dep * Add alpha status to stripe sync engine integration * fix typo * run format * fix types * Rename the stripe-sync path to remove the 'integration". The path needs to have BASE_PATH to work on prod. * Design tidy up (#41337) UI tidy up * update to latest sync engine package * Add stripe key verification * Remove noop try/catch * Add integration isntallation telelemtry * Add basic settings page * Address coderabbit comments * remove unused dep * Remove state setting on render * s/description/comment * Cleanup settings screen UI * Improve settings screen design * update schema test snapshot * Use latest stripe-sync-engine package * Update repo url to new official location * revert marketing change * Update stripe sync engine package * Add link to table from overview page * Add feature flag and improve telemetry * Fix missing useMemo dep * add uninstall telemetry note --------- Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com> Co-authored-by: Saxon Fletcher <saxonafletcher@gmail.com>
142 lines
4.3 KiB
TypeScript
142 lines
4.3 KiB
TypeScript
import { NextApiRequest, NextApiResponse } from 'next'
|
|
import { z } from 'zod'
|
|
import { install, uninstall } from 'stripe-experiment-sync/supabase'
|
|
import { VERSION } from 'stripe-experiment-sync'
|
|
import { waitUntil } from '@vercel/functions'
|
|
|
|
const ENABLE_FLAG_KEY = 'enableStripeSyncEngineIntegration'
|
|
|
|
const InstallBodySchema = z.object({
|
|
projectRef: z.string().min(1),
|
|
stripeSecretKey: z.string().min(1),
|
|
})
|
|
|
|
const UninstallBodySchema = z.object({
|
|
projectRef: z.string().min(1),
|
|
})
|
|
|
|
async function isStripeSyncEnabled() {
|
|
// The ConfigClient doesn't seem to work properly so we'll just gate access from the frontend
|
|
// for now
|
|
return true
|
|
}
|
|
|
|
function getBearerToken(req: NextApiRequest) {
|
|
const authHeader = req.headers.authorization
|
|
if (!authHeader || Array.isArray(authHeader)) return null
|
|
const match = authHeader.match(/^Bearer\s+(.+)$/i)
|
|
return match?.[1]?.trim() ?? null
|
|
}
|
|
|
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|
// Hide endpoint if the integration is disabled.
|
|
if (!(await isStripeSyncEnabled())) {
|
|
return res.status(404).json({ data: null, error: { message: 'Not Found' } })
|
|
}
|
|
|
|
const { method } = req
|
|
switch (method) {
|
|
case 'POST':
|
|
return handleSetupStripeSyncInstall(req, res)
|
|
case 'DELETE':
|
|
return handleDeleteStripeSyncInstall(req, res)
|
|
default:
|
|
return res
|
|
.status(405)
|
|
.json({ data: null, error: { message: `Method ${method} Not Allowed` } })
|
|
}
|
|
}
|
|
|
|
async function handleDeleteStripeSyncInstall(req: NextApiRequest, res: NextApiResponse) {
|
|
const supabaseToken = getBearerToken(req)
|
|
if (!supabaseToken) {
|
|
return res
|
|
.status(401)
|
|
.json({ data: null, error: { message: 'Unauthorized: Invalid Authorization header' } })
|
|
}
|
|
|
|
const parsed = UninstallBodySchema.safeParse(req.body)
|
|
if (!parsed.success) {
|
|
return res
|
|
.status(400)
|
|
.json({ data: null, error: { message: 'Bad Request: Invalid request body' } })
|
|
}
|
|
const { projectRef } = parsed.data
|
|
|
|
waitUntil(
|
|
uninstall({
|
|
supabaseAccessToken: supabaseToken,
|
|
supabaseProjectRef: projectRef,
|
|
baseProjectUrl: process.env.NEXT_PUBLIC_CUSTOMER_DOMAIN,
|
|
supabaseManagementUrl: process.env.NEXT_PUBLIC_API_DOMAIN,
|
|
}).catch((error) => {
|
|
console.error('Stripe Sync Engine uninstallation failed.', error)
|
|
throw error
|
|
})
|
|
)
|
|
|
|
return res
|
|
.status(200)
|
|
.json({ data: { message: 'Stripe Sync uninstallation initiated' }, error: null })
|
|
}
|
|
|
|
async function handleSetupStripeSyncInstall(req: NextApiRequest, res: NextApiResponse) {
|
|
const supabaseToken = getBearerToken(req)
|
|
if (!supabaseToken) {
|
|
return res
|
|
.status(401)
|
|
.json({ data: null, error: { message: 'Unauthorized: Invalid Authorization header' } })
|
|
}
|
|
|
|
const parsed = InstallBodySchema.safeParse(req.body)
|
|
if (!parsed.success) {
|
|
return res
|
|
.status(400)
|
|
.json({ data: null, error: { message: 'Bad Request: Invalid request body' } })
|
|
}
|
|
const { projectRef, stripeSecretKey } = parsed.data
|
|
|
|
// Validate the Stripe API key before proceeding with installation
|
|
try {
|
|
const stripeResponse = await fetch('https://api.stripe.com/v1/account', {
|
|
method: 'GET',
|
|
headers: {
|
|
Authorization: `Bearer ${stripeSecretKey}`,
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
})
|
|
|
|
if (!stripeResponse.ok) {
|
|
const errorData = await stripeResponse.json()
|
|
const errorMessage =
|
|
errorData.error?.message || `Invalid Stripe API key (HTTP ${stripeResponse.status})`
|
|
return res.status(400).json({
|
|
data: null,
|
|
error: { message: `Invalid Stripe API key: ${errorMessage}` },
|
|
})
|
|
}
|
|
} catch (error: any) {
|
|
return res.status(400).json({
|
|
data: null,
|
|
error: { message: `Failed to validate Stripe API key: ${error.message}` },
|
|
})
|
|
}
|
|
waitUntil(
|
|
install({
|
|
supabaseAccessToken: supabaseToken,
|
|
supabaseProjectRef: projectRef,
|
|
stripeKey: stripeSecretKey,
|
|
baseProjectUrl: process.env.NEXT_PUBLIC_CUSTOMER_DOMAIN,
|
|
supabaseManagementUrl: process.env.NEXT_PUBLIC_API_DOMAIN,
|
|
packageVersion: VERSION,
|
|
}).catch((error) => {
|
|
console.error('Stripe Sync Engine installation failed.', error)
|
|
throw error
|
|
})
|
|
)
|
|
|
|
return res
|
|
.status(200)
|
|
.json({ data: { message: 'Stripe Sync setup initiated', version: VERSION }, error: null })
|
|
}
|