stripe-sync.ts 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. // @ts-nocheck
  2. import { waitUntil } from '@vercel/functions'
  3. import { NextApiRequest, NextApiResponse } from 'next'
  4. import { VERSION } from 'stripe-experiment-sync'
  5. import { install, uninstall } from 'stripe-experiment-sync/supabase'
  6. import { z } from 'zod'
  7. const InstallBodySchema = z.object({
  8. projectRef: z.string().min(1),
  9. stripeSecretKey: z.string().min(1),
  10. startTime: z.number().positive().optional(),
  11. })
  12. const UninstallBodySchema = z.object({
  13. projectRef: z.string().min(1),
  14. startTime: z.number().positive().optional(),
  15. })
  16. async function isStripeSyncEnabled() {
  17. // The ConfigClient doesn't seem to work properly so we'll just gate access from the frontend
  18. // for now
  19. return true
  20. }
  21. function getBearerToken(req: NextApiRequest) {
  22. const authHeader = req.headers.authorization
  23. if (!authHeader || Array.isArray(authHeader)) return null
  24. const match = authHeader.match(/^Bearer\s+(.+)$/i)
  25. return match?.[1]?.trim() ?? null
  26. }
  27. export const config = {
  28. maxDuration: 300, // 5 minutes, since the installation process can take a while even if happening in background
  29. }
  30. export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  31. // Hide endpoint if the integration is disabled.
  32. if (!(await isStripeSyncEnabled())) {
  33. return res.status(404).json({ data: null, error: { message: 'Not Found' } })
  34. }
  35. const { method } = req
  36. switch (method) {
  37. case 'POST':
  38. return handleSetupStripeSyncInstall(req, res)
  39. case 'DELETE':
  40. return handleDeleteStripeSyncInstall(req, res)
  41. default:
  42. return res
  43. .status(405)
  44. .json({ data: null, error: { message: `Method ${method} Not Allowed` } })
  45. }
  46. }
  47. async function handleDeleteStripeSyncInstall(req: NextApiRequest, res: NextApiResponse) {
  48. const brivenToken = getBearerToken(req)
  49. if (!brivenToken) {
  50. return res
  51. .status(401)
  52. .json({ data: null, error: { message: 'Unauthorized: Invalid Authorization header' } })
  53. }
  54. const parsed = UninstallBodySchema.safeParse(req.body)
  55. if (!parsed.success) {
  56. return res
  57. .status(400)
  58. .json({ data: null, error: { message: 'Bad Request: Invalid request body' } })
  59. }
  60. const { projectRef, startTime } = parsed.data
  61. waitUntil(
  62. uninstall({
  63. brivenAccessToken: brivenToken,
  64. brivenProjectRef: projectRef,
  65. baseProjectUrl: process.env.NEXT_PUBLIC_CUSTOMER_DOMAIN,
  66. brivenManagementUrl: process.env.NEXT_PUBLIC_API_DOMAIN,
  67. startTime,
  68. }).catch((error) => {
  69. console.error('Stripe Sync Engine uninstallation failed.', error)
  70. throw error
  71. })
  72. )
  73. return res
  74. .status(200)
  75. .json({ data: { message: 'Stripe Sync uninstallation initiated' }, error: null })
  76. }
  77. async function handleSetupStripeSyncInstall(req: NextApiRequest, res: NextApiResponse) {
  78. const brivenToken = getBearerToken(req)
  79. if (!brivenToken) {
  80. return res
  81. .status(401)
  82. .json({ data: null, error: { message: 'Unauthorized: Invalid Authorization header' } })
  83. }
  84. const parsed = InstallBodySchema.safeParse(req.body)
  85. if (!parsed.success) {
  86. return res
  87. .status(400)
  88. .json({ data: null, error: { message: 'Bad Request: Invalid request body' } })
  89. }
  90. const { projectRef, stripeSecretKey, startTime } = parsed.data
  91. // Validate the Stripe API key before proceeding with installation
  92. try {
  93. const stripeResponse = await fetch('https://api.stripe.com/v1/account', {
  94. method: 'GET',
  95. headers: {
  96. Authorization: `Bearer ${stripeSecretKey}`,
  97. 'Content-Type': 'application/x-www-form-urlencoded',
  98. },
  99. })
  100. if (!stripeResponse.ok) {
  101. const errorData = await stripeResponse.json()
  102. const errorMessage =
  103. errorData.error?.message || `Invalid Stripe API key (HTTP ${stripeResponse.status})`
  104. return res.status(400).json({
  105. data: null,
  106. error: { message: errorMessage },
  107. })
  108. }
  109. } catch (error) {
  110. const normalizedErrorMessage = error instanceof Error ? error.message : String(error)
  111. return res.status(400).json({
  112. data: null,
  113. error: { message: `Failed to validate Stripe API key: ${normalizedErrorMessage}` },
  114. })
  115. }
  116. waitUntil(
  117. install({
  118. brivenAccessToken: brivenToken,
  119. brivenProjectRef: projectRef,
  120. stripeKey: stripeSecretKey,
  121. baseProjectUrl: process.env.NEXT_PUBLIC_CUSTOMER_DOMAIN,
  122. brivenManagementUrl: process.env.NEXT_PUBLIC_API_DOMAIN,
  123. packageVersion: VERSION,
  124. startTime,
  125. }).catch((error) => {
  126. console.error('Stripe Sync Engine installation failed.', error)
  127. throw error
  128. })
  129. )
  130. return res
  131. .status(200)
  132. .json({ data: { message: 'Stripe Sync setup initiated', version: VERSION }, error: null })
  133. }