Compare commits

...

1 Commits

Author SHA1 Message Date
Dan Ribbens
563fc940a3 chore: WIP sentry and stripe esm 2024-03-10 13:31:27 -04:00
22 changed files with 109 additions and 122 deletions

View File

@@ -1,2 +1,2 @@
export { sentry } from './plugin'
export type { PluginOptions } from './types'
export { sentry } from './plugin.js'
export type { PluginOptions } from './types.js'

View File

@@ -1,4 +0,0 @@
module.exports = {
captureException: () => {},
startSentry: () => {},
}

View File

@@ -1,7 +1,7 @@
import type { Config } from 'payload/config'
import { defaults } from 'payload/config'
import { sentry } from './plugin'
import { sentry } from './plugin.js'
describe('plugin', () => {
it('should run the plugin', () => {
@@ -34,13 +34,11 @@ describe('plugin', () => {
})
function assertPluginRan(config: Config) {
expect(config.admin?.webpack).toBeDefined()
expect(config.hooks?.afterError).toBeDefined()
expect(config.onInit).toBeDefined()
}
function assertPluginDidNotRun(config: Config) {
expect(config.admin?.webpack).toBeDefined()
expect(config.hooks?.afterError).toBeUndefined()
expect(config.onInit).toBeUndefined()
}

View File

@@ -1,22 +1,14 @@
/* eslint-disable no-console */
import type { Config } from 'payload/config'
import type { PluginOptions } from './types'
import type { PluginOptions } from './types.js'
import { captureException } from './captureException'
import { startSentry } from './startSentry'
import { extendWebpackConfig } from './webpack'
import { captureException } from './captureException.js'
import { startSentry } from './startSentry.js'
export const sentry =
(pluginOptions: PluginOptions) =>
(incomingConfig: Config): Config => {
const config = { ...incomingConfig }
const webpack = extendWebpackConfig(incomingConfig)
config.admin = {
...(config.admin || {}),
webpack,
}
if (pluginOptions.enabled === false || !pluginOptions.dsn) {
return config

View File

@@ -1,12 +1,10 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import type { NextFunction, Request, Response } from 'express'
import type express from 'express'
import type { Payload } from 'payload'
/* eslint-disable no-console */
import * as Sentry from '@sentry/node'
import type { PluginOptions } from './types'
import type { PluginOptions } from './types.js'
export const startSentry = (pluginOptions: PluginOptions, payload: Payload): void => {
const { dsn, options } = pluginOptions
@@ -17,7 +15,7 @@ export const startSentry = (pluginOptions: PluginOptions, payload: Payload): voi
try {
Sentry.init({
...options?.init,
dsn: dsn,
dsn,
integrations: [
...(options?.init?.integrations || []),
new Sentry.Integrations.Http({ tracing: true }),

View File

@@ -8,7 +8,7 @@
"types": "dist/index.d.ts",
"type": "module",
"scripts": {
"build": "echo \"Build temporarily disabled.\" && exit 0",
"build": "pnpm build:swc && pnpm build:types",
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
"build:types": "tsc --emitDeclarationOnly --outDir dist",
"clean": "rimraf {dist,*.tsbuildinfo}",
@@ -33,13 +33,13 @@
},
"dependencies": {
"@payloadcms/ui": "workspace:*",
"body-parser": "^1.20.2",
"lodash.get": "^4.4.2",
"stripe": "^10.2.0",
"uuid": "^9.0.0"
},
"devDependencies": {
"@payloadcms/eslint-config": "workspace:*",
"@types/express": "^4.17.9",
"@types/lodash.get": "^4.4.7",
"@types/react": "18.0.21",
"@types/uuid": "^9.0.0",

View File

@@ -1,8 +1,8 @@
import type { Config } from 'payload/config'
import type { SanitizedStripeConfig, StripeConfig } from './types'
import type { SanitizedStripeConfig, StripeConfig } from './types.js'
import { getFields } from './fields/getFields'
import { getFields } from './fields/getFields.js'
const stripePlugin =
(incomingStripeConfig: StripeConfig) =>

View File

@@ -1,32 +0,0 @@
import type { Config } from 'payload/config'
import type { Configuration as WebpackConfig } from 'webpack'
import path from 'path'
export const extendWebpackConfig =
(config: Config): ((webpackConfig: WebpackConfig) => WebpackConfig) =>
(webpackConfig) => {
const existingWebpackConfig =
typeof config.admin?.webpack === 'function'
? config.admin.webpack(webpackConfig)
: webpackConfig
const mockModulePath = path.resolve(__dirname, './mocks/mockFile.js')
return {
...existingWebpackConfig,
resolve: {
...(existingWebpackConfig.resolve || {}),
alias: {
...(existingWebpackConfig.resolve?.alias ? existingWebpackConfig.resolve.alias : {}),
'@payloadcms/plugin-stripe': path.resolve(__dirname, './admin.js'),
express: mockModulePath,
[path.resolve(__dirname, './hooks/createNewInStripe')]: mockModulePath,
[path.resolve(__dirname, './hooks/deleteFromStripe')]: mockModulePath,
[path.resolve(__dirname, './hooks/syncExistingWithStripe')]: mockModulePath,
[path.resolve(__dirname, './routes/rest')]: mockModulePath,
[path.resolve(__dirname, './routes/webhooks')]: mockModulePath,
},
},
}
}

View File

@@ -1,8 +1,8 @@
import type { CollectionConfig, Field } from 'payload/types'
import type { SanitizedStripeConfig } from '../types'
import type { SanitizedStripeConfig } from '../types.js'
import { LinkToDoc } from '../ui/LinkToDoc'
import { LinkToDoc } from '../ui/LinkToDoc.js'
interface Args {
collection: CollectionConfig
@@ -15,27 +15,28 @@ interface Args {
export const getFields = ({ collection, stripeConfig, syncConfig }: Args): Field[] => {
const stripeIDField: Field = {
name: 'stripeID',
type: 'text',
admin: {
position: 'sidebar',
readOnly: true,
},
label: 'Stripe ID',
saveToJWT: true,
type: 'text',
}
const skipSyncField: Field = {
name: 'skipSync',
type: 'checkbox',
admin: {
position: 'sidebar',
readOnly: true,
},
label: 'Skip Sync',
type: 'checkbox',
}
const docUrlField: Field = {
name: 'docUrl',
type: 'ui',
admin: {
components: {
Field: (args) =>
@@ -48,7 +49,6 @@ export const getFields = ({ collection, stripeConfig, syncConfig }: Args): Field
},
position: 'sidebar',
},
type: 'ui',
}
const fields = [...collection.fields, stripeIDField, skipSyncField, docUrlField]

View File

@@ -3,9 +3,9 @@ import type { CollectionBeforeValidateHook, CollectionConfig } from 'payload/typ
import { APIError } from 'payload/errors'
import Stripe from 'stripe'
import type { StripeConfig } from '../types'
import type { StripeConfig } from '../types.js'
import { deepen } from '../utilities/deepen'
import { deepen } from '../utilities/deepen.js'
const stripeSecretKey = process.env.STRIPE_SECRET_KEY
const stripe = new Stripe(stripeSecretKey || '', { apiVersion: '2022-08-01' })

View File

@@ -3,7 +3,7 @@ import type { CollectionAfterDeleteHook, CollectionConfig } from 'payload/types'
import { APIError } from 'payload/errors'
import Stripe from 'stripe'
import type { StripeConfig } from '../types'
import type { StripeConfig } from '../types.js'
const stripeSecretKey = process.env.STRIPE_SECRET_KEY
const stripe = new Stripe(stripeSecretKey || '', { apiVersion: '2022-08-01' })

View File

@@ -3,9 +3,9 @@ import type { CollectionBeforeChangeHook, CollectionConfig } from 'payload/types
import { APIError } from 'payload/errors'
import Stripe from 'stripe'
import type { StripeConfig } from '../types'
import type { StripeConfig } from '../types.js'
import { deepen } from '../utilities/deepen'
import { deepen } from '../utilities/deepen.js'
const stripeSecretKey = process.env.STRIPE_SECRET_KEY
const stripe = new Stripe(stripeSecretKey || '', { apiVersion: '2022-08-01' })

View File

@@ -1,18 +1,16 @@
import type { NextFunction, Response } from 'express'
import type { Config, Endpoint } from 'payload/config'
import type { Config } from 'payload/config'
import type { PayloadRequest } from 'payload/types'
import express from 'express'
import bodyParser from 'body-parser'
import type { SanitizedStripeConfig, StripeConfig } from './types'
import type { SanitizedStripeConfig, StripeConfig } from './types.js'
import { extendWebpackConfig } from './extendWebpackConfig'
import { getFields } from './fields/getFields'
import { createNewInStripe } from './hooks/createNewInStripe'
import { deleteFromStripe } from './hooks/deleteFromStripe'
import { syncExistingWithStripe } from './hooks/syncExistingWithStripe'
import { stripeREST } from './routes/rest'
import { stripeWebhooks } from './routes/webhooks'
import { getFields } from './fields/getFields.js'
import { createNewInStripe } from './hooks/createNewInStripe.js'
import { deleteFromStripe } from './hooks/deleteFromStripe.js'
import { syncExistingWithStripe } from './hooks/syncExistingWithStripe.js'
import { stripeREST } from './routes/rest.js'
import { stripeWebhooks } from './routes/webhooks.js'
const stripePlugin =
(incomingStripeConfig: StripeConfig) =>
@@ -33,10 +31,6 @@ const stripePlugin =
return {
...config,
admin: {
...config.admin,
webpack: extendWebpackConfig(config),
},
collections: collections?.map((collection) => {
const { hooks: existingHooks } = collection
@@ -90,7 +84,8 @@ const stripePlugin =
...(config?.endpoints || []),
{
handler: [
express.raw({ type: 'application/json' }),
// TODO: test if this works, was using express.raw() before
bodyParser.raw({ type: 'application/json' }),
async (req, res, next) => {
await stripeWebhooks({
config,
@@ -116,7 +111,7 @@ const stripePlugin =
stripeConfig,
})
},
method: 'post' as Endpoint['method'],
method: 'post',
path: '/stripe/rest',
},
]

View File

@@ -1,9 +0,0 @@
export const createNewInStripe = () => null
export const deleteFromStripe = () => null
export const stripeREST = () => null
export const stripeWebhooks = () => null
export const syncExistingWithStripe = () => null
export default {
raw: () => {}, // mock express fn
}

View File

@@ -1,11 +1,10 @@
import type { Response } from 'express'
import type { PayloadRequest } from 'payload/types'
import { Forbidden } from 'payload/errors'
import type { StripeConfig } from '../types'
import type { StripeConfig } from '../types.js'
import { stripeProxy } from '../utilities/stripeProxy'
import { stripeProxy } from '../utilities/stripeProxy.js'
export const stripeREST = async (args: {
next: any
@@ -16,7 +15,7 @@ export const stripeREST = async (args: {
const { req, res, stripeConfig } = args
const {
body: {
data: {
stripeArgs, // example: ['cus_MGgt3Tuj3D66f2'] or [{ limit: 100 }, { stripeAccount: 'acct_1J9Z4pKZ4Z4Z4Z4Z' }]
stripeMethod, // example: 'subscriptions.list',
},
@@ -38,14 +37,27 @@ export const stripeREST = async (args: {
stripeSecretKey,
})
const { status } = pluginRes
const { data, message, status } = pluginRes
res.status(status).json(pluginRes)
return Response.json(
{
data,
message,
},
{
status,
},
)
} catch (error: unknown) {
const message = `An error has occurred in the Stripe plugin REST handler: '${error}'`
payload.logger.error(message)
return res.status(500).json({
message,
})
return Response.json(
{
message,
},
{
status: 500,
},
)
}
}

View File

@@ -1,21 +1,19 @@
import type { Response } from 'express'
import type { Config as PayloadConfig } from 'payload/config'
import type { PayloadRequest } from 'payload/types'
import Stripe from 'stripe'
import type { StripeConfig } from '../types'
import type { StripeConfig } from '../types.js'
import { handleWebhooks } from '../webhooks'
import { handleWebhooks } from '../webhooks/index.js'
export const stripeWebhooks = async (args: {
config: PayloadConfig
next: any
req: PayloadRequest
res: Response
stripeConfig: StripeConfig
}): Promise<any> => {
const { config, req, res, stripeConfig } = args
const { config, req, stripeConfig } = args
const { stripeSecretKey, stripeWebhooksEndpointSecret, webhooks } = stripeConfig
@@ -32,6 +30,7 @@ export const stripeWebhooks = async (args: {
if (stripeSignature) {
let event: Stripe.Event | undefined
let status: number = 200
try {
event = stripe.webhooks.constructEvent(
@@ -42,7 +41,7 @@ export const stripeWebhooks = async (args: {
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : err
req.payload.logger.error(`Error constructing Stripe event: ${msg}`)
res.status(400)
status = 400
}
if (event) {
@@ -80,6 +79,12 @@ export const stripeWebhooks = async (args: {
}
}
}
res.json({ received: true })
return Response.json(
{
received: true,
},
{
status,
},
)
}

View File

@@ -1,4 +1,4 @@
import type { UIField } from 'payload/dist/fields/config/types'
import type { UIField } from 'payload/types'
import { useFormFields } from '@payloadcms/ui/forms'
// TODO: fix this import to work in dev mode within the monorepo in a way that is backwards compatible with 1.x
@@ -32,7 +32,6 @@ export const LinkToDoc: React.FC<
>
View in Stripe
</span>
{/* @ts-ignore */}
{/* <CopyToClipboard value={href} /> */}
</div>
<div

View File

@@ -1,7 +1,7 @@
import lodashGet from 'lodash.get'
import Stripe from 'stripe'
import type { StripeProxy } from '../types'
import type { StripeProxy } from '../types.js'
export const stripeProxy: StripeProxy = async ({ stripeArgs, stripeMethod, stripeSecretKey }) => {
const stripe = new Stripe(stripeSecretKey, {

View File

@@ -1,8 +1,8 @@
import { v4 as uuid } from 'uuid'
import type { SanitizedStripeConfig, StripeWebhookHandler } from '../types'
import type { SanitizedStripeConfig, StripeWebhookHandler } from '../types.js'
import { deepen } from '../utilities/deepen'
import { deepen } from '../utilities/deepen.js'
type HandleCreatedOrUpdated = (
args: Parameters<StripeWebhookHandler>[0] & {

View File

@@ -1,4 +1,4 @@
import type { SanitizedStripeConfig, StripeWebhookHandler } from '../types'
import type { SanitizedStripeConfig, StripeWebhookHandler } from '../types.js'
type HandleDeleted = (
args: Parameters<StripeWebhookHandler>[0] & {

View File

@@ -1,7 +1,7 @@
import type { StripeWebhookHandler } from '../types'
import type { StripeWebhookHandler } from '../types.js'
import { handleCreatedOrUpdated } from './handleCreatedOrUpdated'
import { handleDeleted } from './handleDeleted'
import { handleCreatedOrUpdated } from './handleCreatedOrUpdated.js'
import { handleDeleted } from './handleDeleted.js'
export const handleWebhooks: StripeWebhookHandler = async (args) => {
const { event, payload, stripeConfig } = args

33
pnpm-lock.yaml generated
View File

@@ -1074,6 +1074,9 @@ importers:
'@payloadcms/ui':
specifier: workspace:*
version: link:../ui
body-parser:
specifier: ^1.20.2
version: 1.20.2
lodash.get:
specifier: ^4.4.2
version: 4.4.2
@@ -7208,6 +7211,26 @@ packages:
- supports-color
dev: false
/body-parser@1.20.2:
resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
dependencies:
bytes: 3.1.2
content-type: 1.0.5
debug: 2.6.9
depd: 2.0.0
destroy: 1.2.0
http-errors: 2.0.0
iconv-lite: 0.4.24
on-finished: 2.4.1
qs: 6.11.0
raw-body: 2.5.2
type-is: 1.6.18
unpipe: 1.0.0
transitivePeerDependencies:
- supports-color
dev: false
/body-scroll-lock@3.1.5:
resolution: {integrity: sha512-Yi1Xaml0EvNA0OYWxXiYNqY24AfWkbA6w5vxE7GWxtKfzIbZM+Qw+aSmkgsbWzbHiy/RCSkUZBplVxTA+E4jJg==}
dev: false
@@ -13865,6 +13888,16 @@ packages:
unpipe: 1.0.0
dev: false
/raw-body@2.5.2:
resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==}
engines: {node: '>= 0.8'}
dependencies:
bytes: 3.1.2
http-errors: 2.0.0
iconv-lite: 0.4.24
unpipe: 1.0.0
dev: false
/rc@1.2.8:
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
hasBin: true