#nikivi_webhooks

1 messages ยท Page 1 of 1 (latest)

pseudo lindenBOT
pallid pewterBOT
#

Below are links to other discussions we've had with you in the past week in case you want to review that information. If your question is related to one of these previous discussions, please provide a comprehensive summary of the current state and what you need help with now. We help many users simultaneously, so a summary allows us to resolve your issue as soon as possible.

pseudo lindenBOT
#

๐Ÿ‘‹ Welcome to your new thread!

โฒ๏ธ We'll be here soon! We typically respond in a few minutes, but in some cases we might need a bit more time (e.g., server's busy, you've got a complex question, etc.).

โฑ๏ธ We close idle threads, which makes them read-only. Once a thread is closed it won't be reopened, but you can start a new thread if you have another question.

๐Ÿ”— This thread will always be available, even after it's closed. You can find it again using Discord's search, or you can save this link: https://discord.com/channels/841573134531821608/1254804551354028102

๐Ÿ“ Have more to share? Add details, code, screenshots, videos, etc. below.

grim agate
#
    .get('/webhooks/stripe/payment-success', async ({ headers, error, request }) => {
        let event: Stripe.Event

        assertString(webhookSecret)

        const signature = headers['stripe-signature']

        if (!signature) {
            return error(400, 'missing signature')
        }

        const payload = await request.text()

        try {
            event = await stripe.webhooks.constructEventAsync(payload, signature, webhookSecret)
        } catch (err: unknown) {
            const errorMessage = err instanceof Error ? err.message : 'Unknown error'
            console.error(err)
            console.error(`[stripe-webhook] Error message: ${errorMessage}`)

            return error(400, `Webhook Error: ${errorMessage}`)
        }

        // Successfully constructed event.
        console.log('[stripe-webhook] Success:', event.id)

        try {
            console.log(event, 'event')
        } catch (err) {
            console.error('[stripe-webhook] Error handling event:', err)
            return error(500, 'Error handling event')
        }

        return { message: 'Received' }
    })
#

is my code for webhook

#

I ran stripe listen --forward-to localhost:8787/webhooks/stripe/payment-success

#

in terminal

heavy cloud
#

Hello

grim agate
#

hey

#

trying to make sense as for how to make sure stripe checkouts get completed

#

and I need to get productId from metadata

#
    .post(
        '/create-product',
        async ({ body }) => {
            const { productTitle, productDescription, productPrice, productRoninId } = body
            const session = await stripe.checkout.sessions.create({
                success_url: 'https://solbond.co/payment-success',
                line_items: [
                    {
                        price_data: {
                            currency: 'usd',
                            product_data: {
                                name: productTitle,
                                description: productDescription
                            },
                            unit_amount: Math.round(Number(productPrice) * 100)
                        },
                        quantity: 1
                    }
                ],
                mode: 'payment',
                metadata: {
                    productRoninId: productRoninId
                }
            })
            const checkoutSessionUrl = session.url
            return checkoutSessionUrl
        },
        {
            body: t.Object({
                productTitle: t.String(),
                productRoninId: t.String(),
                productDescription: t.String(),
                productPrice: t.String()
            })
        }
    )
#

i have this endpoint to create checkout sessions

#

I get back url

heavy cloud
#

I'm seeing that the Event you provided was indeed received by your endpoint and your endpoint returned a 200, however it also returned a response body of Error: NOT_FOUND

#

So you'll want to look in your code for where you send that response

grim agate
#
    .get('/webhooks/stripe/payment-success', async ({ headers, error, request }) => {
        let event: Stripe.Event

        assertString(webhookSecret)

        const signature = headers['stripe-signature']

        if (!signature) {
            return error(400, 'missing signature')
        }

        const payload = await request.text()

        try {
            event = await stripe.webhooks.constructEventAsync(payload, signature, webhookSecret)
        } catch (err: unknown) {
            const errorMessage = err instanceof Error ? err.message : 'Unknown error'
            console.error(err)
            console.error(`[stripe-webhook] Error message: ${errorMessage}`)

            return error(400, `Webhook Error: ${errorMessage}`)
        }

        // Successfully constructed event.
        console.log('[stripe-webhook] Success:', event.id)

        try {
            console.log(event, 'event')
        } catch (err) {
            console.error('[stripe-webhook] Error handling event:', err)
            return error(500, 'Error handling event')
        }

        return { message: 'Received' }
    })
#

is my web hook code

#

don't see NOT_FOUND mm

heavy cloud
#

Alright well first thing I would do is add a log right at the start of your endpoint

#

And then re-test

grim agate
#
        console.log(webhookSecret, 'secret!!')

        const signature = headers['stripe-signature']

        if (!signature) {
            return error(400, 'missing signature')
        }

        const payload = await request.text()

        console.log(payload, 'payload')
#

ok trying with

#

trying it out

heavy cloud
grim agate
#

oh i see

#

ok let me try switch

#

oh i see where error comes from

heavy cloud
#

Yep

grim agate
#
        assertString(webhookSecret)
#

seems webhookSecret is not passed

heavy cloud
#

So now you want to debug from there

grim agate
#

ok fixed the assert thing

#

i do pass the secret 100% now

#
        const signature = headers['stripe-signature']

        if (!signature) {
            return error(400, 'missing signature')
        }
#

can I test this in my http gui app

#

i don't want to fill in the stripe checkout every time

#

its too long ๐Ÿ˜ฆ

heavy cloud
#

You can use the CLI to trigger Events to test

#

stripe trigger checkout.session.completed

grim agate
#

@heavy cloud how can this be

#

if i hit it myself it works

#

but if i do it with stripe

#

its error

#

like its calling wrong url

#

stripe listen --forward-to localhost:8787/webhooks/stripe/payment-success

#

like there is something off with

heavy cloud
#

Hmmm so the endpoint isn't hit at all?

grim agate
#

yes

heavy cloud
#

Your framework is returning the not found for that endpoint?

grim agate
#

its hitting wrong endpoint

#

stripe is trying to hit wrong endpoint

#

that endpoint is there

#
localhost:8787/webhooks/stripe/payment-success
#
stripe listen --forward-to localhost:8787/webhooks/stripe/payment-success
#

should work

#

in my eyes

heavy cloud
#

Ah you are GETting, it should be a POST request

#

Try that

grim agate
#

oh wait

#

making it post

#

ok yea post worked

heavy cloud
#

๐Ÿ‘

grim agate
#
        console.log(payload, 'payload')

        try {
            event = await stripe.webhooks.constructEventAsync(payload, signature, webhookSecret)
        } catch (err: unknown) {
            const errorMessage = err instanceof Error ? err.message : 'Unknown error'
            console.error(err)
            console.error(`[stripe-webhook] Error message: ${errorMessage}`)

            return error(400, `Webhook Error: ${errorMessage}`)
        }

        // Successfully constructed event.
        console.log('[stripe-webhook] Success:', event.id)

        try {
            switch (event.type) {
                case 'payment_intent.succeeded':
                    const paymentIntent = event.data.object
                    console.log(`PaymentIntent for ${paymentIntent.amount} was successful!`)
                    // Then define and call a method to handle the successful payment intent.
                    // handlePaymentIntentSucceeded(paymentIntent);
                    break
                case 'payment_method.attached':
                    const paymentMethod = event.data.object
                    // Then define and call a method to handle the successful attachment of a PaymentMethod.
                    // handlePaymentMethodAttached(paymentMethod);
                    break
                default:
                    // Unexpected event type
                    console.log(`Unhandled event type ${event.type}.`)
            }
        } catch (err) {
#

i added switch

#

trying to get what event I want

#

i just want payment succeeded

heavy cloud
#

Where are you setting metadata?

grim agate
#

and be able to read the

#
                metadata: {
                    productRoninId: productRoninId
                }
#
            const session = await stripe.checkout.sessions.create({
                success_url: 'https://solbond.co/payment-success',
                line_items: [
                    {
                        price_data: {
                            currency: 'usd',
                            product_data: {
                                name: productTitle,
                                description: productDescription
                            },
                            unit_amount: Math.round(Number(productPrice) * 100)
                        },
                        quantity: 1
                    }
                ],
                mode: 'payment',
                metadata: {
                    productRoninId: productRoninId
                }
            })
            const checkoutSessionUrl = session.url
            return checkoutSessionUrl
#

its like this

#
    .post(
        '/create-product',
heavy cloud
#

Okay so that metadata will be on the Checkout Session itself

grim agate
#

payment_intent.succeeded

heavy cloud
#

But you could pass metadata down to the PaymentIntent if you use payment_intent_data.metadata

grim agate
#

this is one i care about?

heavy cloud
#

Then you can just listen for payment_intent.succeeded

grim agate
#
const paymentIntent = event.data.object
#

i get this

#

so the session is on that object?

heavy cloud
#

No

#

Checkout Sessions are "high-level" objects

#

So PaymentIntents live "below" Checkout Sessions

#

You would go Checkout Session --> PaymentIntent

#

Not the other way around

grim agate
#

so given payment intent

#

i can somehow find the right session?

#
    .post('/webhooks/stripe/payment-success', async ({ headers, error, request }) => {
        let event: Stripe.Event

        assertString(webhookSecret)

        const signature = headers['stripe-signature']

        if (!signature) {
            return error(400, 'missing signature')
        }

        const payload = await request.text()

        console.log(payload, 'payload')

        try {
            event = await stripe.webhooks.constructEventAsync(payload, signature, webhookSecret)
        } catch (err: unknown) {
            const errorMessage = err instanceof Error ? err.message : 'Unknown error'
            console.error(err)
            console.error(`[stripe-webhook] Error message: ${errorMessage}`)

            return error(400, `Webhook Error: ${errorMessage}`)
        }

        // Successfully constructed event.
        console.log('[stripe-webhook] Success:', event.id)

        try {
            switch (event.type) {
                case 'payment_intent.succeeded':
                    const paymentIntent = event.data.object
                    console.log(`PaymentIntent for ${paymentIntent.amount} was successful!`)
#

cause at this point

#

i only have paymentIntent

#

i think

#

and payload i guess

#

which has the events i parse

#
                case 'payment_intent.succeeded':
                    const paymentIntent = event.data.object as Stripe.PaymentIntent
                    console.log(`PaymentIntent for ${paymentIntent.amount} was successful!`)

                    // Retrieve the Checkout Session associated with this PaymentIntent
                    const sessions = await stripe.checkout.sessions.list({
                        payment_intent: paymentIntent.id
                    })

                    if (sessions.data.length > 0) {
                        const session = sessions.data[0]
                        console.log('Associated Checkout Session:', session.id)
                        console.log('Checkout Session metadata:', session.metadata)

                        // Here you can access session details like:
                        // session.customer_details
                        // session.line_items (you might need to expand this when retrieving the session)
                        // session.metadata (where you stored custom data)
                    } else {
                        console.log('No associated Checkout Session found')
                    }

                    break
#

trying this

heavy cloud
#

What information do you care about in the Checkout Session object?

#

Just the metadata?

#

Or other info as well?

grim agate
#
                metadata: {
                    productRoninId,
                    userBuyingId
                }
#

i need this

#

to make a db

#

entry

#

i think just that is enough for me

heavy cloud
#

Okay so then when you create your Checkout Session you should use payment_intent_data.metadata instead of metadata

grim agate
#

mm

heavy cloud
#

Then in your Webhook handler you can just listen for payment_intent.succeeded and that metadata will be set on the PaymentIntent object

grim agate
#
            const session = await stripe.checkout.sessions.create({
                success_url: 'https://solbond.co/payment-success',
                line_items: [
                    {
                        price_data: {
                            currency: 'usd',
                            product_data: {
                                name: productTitle,
                                description: productDescription
                            },
                            unit_amount: Math.round(Number(productPrice) * 100)
                        },
                        quantity: 1
                    }
                ],
                mode: 'payment',
                metadata: {
                    productRoninId,
                    userBuyingId
                }
            })
#

ok trying to get where payment_intent.succeeded goes

heavy cloud
#
   metadata: {
      productRoninId,
                    userBuyingId
   }
}```
#

Put that in place of metadata

grim agate
#

getting this weird error

#

i pass correct details I think mm

#

not sure about stripe using url-encoded post bodies

heavy cloud
#

That error is coming from a library you are working with, not the Stripe API

grim agate
#

ok turned it into json with this

#

and it works

#

ok testing checkout

#

๐Ÿ™

#

i get empty Checkout Session metadata

#

oh wait

#
                case 'payment_intent.succeeded':
                    const paymentIntent = event.data.object as Stripe.PaymentIntent
                    console.log(`PaymentIntent for ${paymentIntent.amount} was successful!`)

                    // Retrieve the Checkout Session associated with this PaymentIntent
                    const sessions = await stripe.checkout.sessions.list({
                        payment_intent: paymentIntent.id
                    })

                    if (sessions.data.length > 0) {
                        const session = sessions.data[0]
                        console.log('Associated Checkout Session:', session.id)
                        console.log('Checkout Session metadata:', session.metadata)

                        // Here you can access session details like:
                        // session.customer_details
                        // session.line_items (you might need to expand this when retrieving the session)
                        // session.metadata (where you stored custom data)
                    } else {
                        console.log('No associated Checkout Session found')
                    }

                    break
#

this code should get the metadata from intent itself now

#
paymentIntent.metadata
#

i guess

heavy cloud
#

Yep

grim agate
#
                case 'payment_intent.succeeded':
                    const paymentIntent = event.data.object as Stripe.PaymentIntent
                    console.log(`PaymentIntent for ${paymentIntent.amount} was successful!`)

                    console.log('---------------')
                    console.log(paymentIntent.metadata, 'metadata')
                    console.log('---------------')
#

ok trying

#

stripe trigger checkout.session.completed

#

this triggers intent.succeeded too right?

heavy cloud
#

Yes

grim agate
#

yea its empty

heavy cloud
#

Can you give me that Event ID

grim agate
#
    .post(
        '/create-product',
        async ({ body }) => {
            const { productTitle, productDescription, productPrice, productRoninId, userBuyingId } = body
            const session = await stripe.checkout.sessions.create({
                success_url: 'https://solbond.co/payment-success',
                line_items: [
                    {
                        price_data: {
                            currency: 'usd',
                            product_data: {
                                name: productTitle,
                                description: productDescription
                            },
                            unit_amount: Math.round(Number(productPrice) * 100)
                        },
                        quantity: 1
                    }
                ],
                mode: 'payment',
                payment_intent_data: {
                    metadata: {
                        productRoninId,
                        userBuyingId
                    }
                }
            })
            const checkoutSessionUrl = session.url
            return checkoutSessionUrl
        },
        {
            body: t.Object({
                productTitle: t.String(),
                productRoninId: t.String(),
                productDescription: t.String(),
                productPrice: t.String(),
                userBuyingId: t.String()
            })
        }
    )
#

1 sec

#

evt_1PVEdZEApj6WQ8u04U35kX61

#

i think

#

thats the completed one

#

productRoninId should be rec_l4l501bmvf8lswr6

#

and userBuyingId 300

heavy cloud
#

Oh sorry you are using the CLI here?

grim agate
#

yes

heavy cloud
#

You need to actually create the Checkout Session yourself

grim agate
heavy cloud
#

The CLI won't set this metadata for you

grim agate
#

so this gives me

#
https://checkout.stripe.com/c/pay/cs_test_a1hn5jLXEIDnNm8E6MfXeqvFqmRklgFaZqon2gEUUHYAqT8i3kdVLVofN4#fidkdWxOYHwnPyd1blpxYHZxWjA0VUpLVDZARHVvM1JUPXA1Y1J3fFBVamt9SF9jNlFXSVRxdmxcb1V2cGdNfXFnZEBuakhjSUBWXWBwYU83UFdpNXx8UTxObDJnfDVSUVc8NlFXSEdSb2JUNTV%2FX25LVVZyTycpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBabHFgaCcpJ2BrZGdpYFVpZGZgbWppYWB3dic%2FcXdwYHgl
#

this is what i will open for users for the product

#

so they buy it

#

let me try complete it

heavy cloud
#

๐Ÿ‘

grim agate
#

i expect that i get event with the metadata

#

oh ok this worked

heavy cloud
#

๐Ÿ‘

grim agate
#

I can't seem to find how to pass cover image

#

to product

heavy cloud
#

Hmm don't think you can do that inline

grim agate
#
            const session = await stripe.checkout.sessions.create({
                success_url: 'https://solbond.co/payment-success',
                line_items: [
                    {
                        price_data: {
                            currency: 'usd',
                            product_data: {
                                name: productTitle,
                                description: productDescription
                            },
                            unit_amount: Math.round(Number(productPrice) * 100)
                        },
                        quantity: 1
                    }
                ],
                mode: 'payment',
                payment_intent_data: {
                    metadata: {
                        productRoninId,
                        userBuyingId
                    }
                }
            })
heavy cloud
#

Ah nvm

grim agate
#

so I can't pass image

heavy cloud
#

You can

grim agate
#

?

grim agate
#

oo