You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I am fighting since a couple of days getting the stripe webhooks reliably to work on vercel. Unfortunately, I came to a point where I need some help to debug the issue further.
What is happening:
I make a request to the stripe webhook endpoints
stripe webhooks fire but hang on the first payload.find operation in my custom webhook (see below)
the operation continues when I refresh the admin panel in a collection (normal requests to some endpoints do not resolve the issue)
it looks like the stripe webhooks on the endpoint /api/stripe/webhooks are somehow treated differently from others. I can't say if this is payload or vercel related but going through the source code of the stripe plugin I couldn't really find anything suspicious.
if e.g. deployed freshly or the admin panel has been refreshed, the webhooks ususally work as expected
Other observations which might be related to the issue:
locally in dev mode and the stripe cli, webhooks run without issues
I ran before into another issue related to mongodb (via atlas) and transactions enabled. The stripe webhooks caused transaction conflicts because apparently a running webhook is not awaited before a new incoming webhook is processed. After deactivating transactions I didn't have any database related errors anymore.
other endpoints work as expected
Any help or hints how to debug this any further would be highly appreciated!
Payload Config
// storage-adapter-import-placeholderimport{createPaymentIntent}from'@/endpoints/create-payment-intent'import{customersProxy}from'@/endpoints/customer'import{productsProxy}from'@/endpoints/products'import{defaultLexical}from'@/fields/defaultLexical'import{syncInstagramMedia}from'@/tasks/handleInstagramMedia'import{mongooseAdapter}from'@payloadcms/db-mongodb'importpathfrom'path'import{buildConfig,Payload,TaskConfig}from'payload'importsharpfrom'sharp'// sharp-importimport{fileURLToPath}from'url'import{Categories}from'./collections/Categories'import{InstagramMedia}from'./collections/Media/InstagramMedia/config'import{Media}from'./collections/Media/Media/config'import{Orders}from'./collections/Orders'import{Pages}from'./collections/Pages'import{Posts}from'./collections/Posts'import{ProductCategories}from'./collections/ProductCategories'import{Products}from'./collections/Products'import{ReusableContent}from'./collections/ReusableContent'import{Users}from'./collections/Users'import{Vereinsrollen}from'./collections/Vereinsrollen'import{Footer}from'./Footer/config'import{Header}from'./Header/config'import{plugins}from'./plugins'import{getServerSideURL}from'./utilities/getURL'constfilename=fileURLToPath(import.meta.url)constdirname=path.dirname(filename)exportdefaultbuildConfig({admin: {components: {// The `BeforeLogin` component renders a message that you see while logging into your admin panel.// Feel free to delete this at any time. Simply remove the line below and the import `BeforeLogin` statement on line 15.beforeLogin: ['@/components/BeforeLogin'],// The `BeforeDashboard` component renders the 'welcome' block that you see after logging into your admin panel.// Feel free to delete this at any time. Simply remove the line below and the import `BeforeDashboard` statement on line 15.beforeDashboard: ['@/components/BeforeDashboard'],},importMap: {baseDir: path.resolve(dirname),},user: Users.slug,livePreview: {breakpoints: [{label: 'Mobile',name: 'mobile',width: 375,height: 667,},{label: 'Tablet',name: 'tablet',width: 768,height: 1024,},{label: 'Desktop',name: 'desktop',width: 1440,height: 900,},],},},// This config helps us configure global or default features that the other editors can inheriteditor: defaultLexical,db: mongooseAdapter({url: process.env.DATABASE_URI||'',transactionOptions: false,}),onInit: async(payload: Payload)=>{awaitpayload.jobs.queue({task: 'syncInstagramMedia',// input: {// title: 'New Post',// },})},collections: [Pages,Posts,Media,Categories,Users,Vereinsrollen,Orders,Products,ReusableContent,ProductCategories,InstagramMedia,],endpoints: [{path: '/stripe/products',method: 'get',handler: productsProxy,},{path: '/stripe/customers',method: 'get',handler: customersProxy,},{method: 'post',path: '/create-payment-intent',handler: createPaymentIntent,},],logger: {options: {level: 'debug',},destination: process.stdout,},jobs: {tasks: [{retries: 1,// This is a unique identifier for the taskslug: 'syncInstagramMedia',handler: syncInstagramMedia,}asTaskConfig<'syncInstagramMedia'>,],},cors: [getServerSideURL()].filter(Boolean),globals: [Header,Footer],plugins: [
...plugins,// storage-adapter-placeholder],secret: process.env.PAYLOAD_SECRET,
sharp,typescript: {outputFile: path.resolve(dirname,'payload-types.ts'),},})
importtype{StripeWebhookHandler}from'@payloadcms/plugin-stripe'importtypeStripefrom'stripe'constlogs=trueexportconstchargeSucceeded: StripeWebhookHandler<{data: {object: Stripe.Charge}}>=async(args)=>{const{
event,
stripe,
req,
payload,}=argsconstcharge=event.data.objectconststripeCustomerID=charge.customerconststripePaymentIntentID=charge.payment_intent
let payloadUserID: string|undefined
let payloadOrderID: string|undefinedif(logs)payload.logger.debug(`🪝 Payment succeeded event received with ID ${stripePaymentIntentID} and stripe user ${stripeCustomerID}, now trying to find payload user and existing order..`,)try{constuserRequest=awaitpayload.find({collection: 'users',where: {stripeCustomerID: {equals: stripeCustomerID,},},})payloadUserID=userRequest.docs?.[0]?.idif(payloadUserID===undefined){payload.logger.error(`User not found for stripe customer ID ${stripeCustomerID}`)return}}catch(err: unknown){constmsg=errinstanceofError ? err.message : 'Unknown error'payload.logger.error(`Error finding user ${msg}`)return}try{if(logs)payload.logger.debug(`Searching for order with PaymentIntentID ${stripePaymentIntentID} for user ${payloadUserID}`,)constorderRequest=awaitpayload.find({collection: 'orders',where: {stripePaymentIntentID: {equals: stripePaymentIntentID,},},})payloadOrderID=orderRequest.docs?.[0]?.idif(payloadOrderID===undefined){payload.logger.error(`Order not found for stripe PaymantIntentID ${stripePaymentIntentID}`)return}}catch(err: unknown){constmsg=errinstanceofError ? err.message : 'Unknown error'payload.logger.error(`Error querying PaymentIntentID ${stripePaymentIntentID}`)payload.logger.error(msg)return}// process event payment_intent.createdif(event.type==='charge.succeeded'){if(charge.status!=='succeeded'||charge.paid!==true){payload.logger.error(`PaymentIntent ${stripePaymentIntentID} is not in succeeded status, cannot update order status`,)return}if(logs)payload.logger.debug(`🪝 Updating Order with paymentIntentID ${stripePaymentIntentID}`)try{constorder=awaitpayload.update({collection: 'orders',id: payloadOrderID,data: {total: charge.amount_captured,currency: charge.currency,payment_status: 'paid',paymentMethod: charge.payment_method_details?.type,paymentMethodDetails: charge?.payment_method_details
? JSON.stringify(charge.payment_method_details)
: null,shipping: {address: {street: charge.shipping?.address?.line1,extra: charge.shipping?.address?.line2,city: charge.shipping?.address?.city,country: charge.shipping?.address?.country,zip: charge.shipping?.address?.postal_code,},phone: charge.shipping?.phone,name: charge.shipping?.name,},billing_details: {address: {street: charge.billing_details?.address?.line1,extra: charge.billing_details?.address?.line2,city: charge.billing_details?.address?.city,country: charge.billing_details?.address?.country,zip: charge.billing_details?.address?.postal_code,},phone: charge.billing_details?.phone,name: charge.billing_details?.name,},},depth: 0,})}catch(err: unknown){payload.logger.error(`Error updating order for user ${payloadUserID}`)}try{if(logs)payload.logger.debug(`🪝 Clearing cart for user ${payloadUserID}`)constuser=awaitpayload.update({collection: 'users',id: payloadUserID,data: {cart: {items: [],},},})}catch(err: unknown){constmsg=errinstanceofError ? err.message : 'Unknown error'payload.logger.error(`Error clearing cart for user ${payloadUserID}: ${msg}`)}}else{payload.logger.error(`Unknown event type ${event.type}`)}}
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
Hi,
I am fighting since a couple of days getting the stripe webhooks reliably to work on vercel. Unfortunately, I came to a point where I need some help to debug the issue further.
What is happening:
/api/stripe/webhooks
are somehow treated differently from others. I can't say if this is payload or vercel related but going through the source code of the stripe plugin I couldn't really find anything suspicious.Other observations which might be related to the issue:
Any help or hints how to debug this any further would be highly appreciated!
Payload Config
plugin config
Hook example:
Beta Was this translation helpful? Give feedback.
All reactions