#noorboi_webhooks

1 messages Β· Page 1 of 1 (latest)

jagged bronzeBOT
#

πŸ‘‹ 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/1261383904812601385

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

still summit
#

HI πŸ‘‹

What is the actual error you are receiving and where exactly is it being thrown in your code?

serene root
still summit
#

Okay so you are unable to create a transfer because the funds are not in your available balance.

#

How are you creating charges?

serene root
#

i am using test mode
first thing i create a connect account for the user in the backend when he visit a seller dashboard
then when he checksout i create a session to my main account
and in the webhook i loop over my products get thier sellers stripe id account then i send them with this fn
await stripe.transfers.create({
amount: transferAmount,
currency: "usd",
destination: transfer.stripeAccountId,
transfer_group: session.payment_intent,
});
and he is showing the error ive told u about but it is transfering money to my main account

#

i want money to be distributed to each seller

#

everything is working except for the money itself not being spread and it shows me that error
i tried adding alot of funds to my account with a script
my account already includes alot of fake or test funds

#

when i check each account i find this error in the response of the request i made
note that the account creation function is this
export const CreateStripeAccount = async (email: string) => {
try { await connect(); const account = await stripe.accounts.create({ type: "standard", email, capabilities: { card_payments: { requested: true }, transfers: { requested: true }, }, }); console.log(account); const user = await User.findOne({ email }); user.stripeAccountId = account.id; await user.save(); return { success: "Stripe account created to receive payments" }; } catch (error) { console.log(error); throw new Error("Failed to create Stripe account"); } };

still summit
#

Okay it sounds like you are using our Separate Charge and transfers flow. And in this case the transfers are failing because the funds aren't available in your Stripe balance.

#

We do have a fix for that

serene root
#

should it be like this then ?
await stripe.transfers.create({ amount: transferAmount, currency: "usd", destination: transfer.stripeAccountId, source_transaction : session.payment_intent, });

still summit
#

Not quite, the source_transaction needs to be a Charge. We spell out how to retrieve the charge ID from the payment intent in the doc I linked

serene root
#

sorry
but i did not create a payment intent i create a session
const stripeSession = await stripe.checkout.sessions.create({ //@ts-ignore success_url:${process.env.NEXTAUTH_URL!!}/thank-you?orderId=${order._id}, cancel_url: ${process.env.NEXTAUTH_URL!!}/cart, payment_method_types: ["card"], line_items, mode: "payment", metadata: { orderId: ${order._id}, userId: session?.user.id, }, customer_email: session?.user.email, payment_intent_data: { transfer_data: { destination: process.env.PLATFORM_ACCOUNT_ID, // Your platform account ID }, }, });

#

should icreate both or replace the session ?

still summit
#

Those words make no sense.

If you are using a Checkout Session, then the Checkout Session will create the Payment Intent (and Charge) when the customer completes it.

#

So, looking at your ealier code, if you have the session.payment_intent you should also be able to access the session.payment_intent.latest_charge

jagged bronzeBOT
serene root
#

it is showing me this now
Error creating transfer for seller 6676de14f01cd0caa0073c6f: No such charge: 'pi_3PboXTRpqbf3DcZG0J8w5Svh'
Creating transfer for seller 668e2157c49e222dacb3687f with amount 127900
Error creating transfer for seller 668e2157c49e222dacb3687f: No such charge: 'pi_3PboXTRpqbf3DcZG0J8w5Svh'

#

at least something new πŸ™‚

delicate plover
#

Hi taking over here

#

Give me a bit to catch up

serene root
#

the function in the webhook
await stripe.transfers.create({ amount: transferAmount, currency: "usd", destination: transfer.stripeAccountId, source_transaction: session.payment_intent, });
the function in checkout
const stripeSession = await stripe.checkout.sessions.create({ //@ts-ignore success_url:${process.env.NEXTAUTH_URL!!}/thank-you?orderId=${order._id}, cancel_url: ${process.env.NEXTAUTH_URL!!}/cart, payment_method_types: ["card"], line_items, mode: "payment", metadata: { orderId: ${order._id}, userId: session?.user.id, }, customer_email: session?.user.email, payment_intent_data: { transfer_data: { destination: process.env.PLATFORM_ACCOUNT_ID, // Your platform account ID }, }, });

#

no matter what i do it just returns
You have insufficient available funds in your Stripe account. Try adding funds directly to your available balance by creating Charges using the 4000000000000077 test card. See: https://stripe.com/docs/testing#available-balance

delicate plover
#

You're still passing a payment intent id

#

You need to pass a charge id like my colleague mentioned

#

session.payment_intent.latest_charge

serene root
#

wait for me a bit and i will tell u

delicate plover
#

Tell me what?

serene root
#

tell u whether it worked or not

#

Creating transfer for seller 6676de14f01cd0caa0073c6f with amount 134546
Error creating transfer for seller 6676de14f01cd0caa0073c6f: You have insufficient available funds in your Stripe account. Try adding funds directly to your available balance by creating Charges using the 4000000000000077 test card. See: https://stripe.com/docs/testing#available-balance
Creating transfer for seller 668e2157c49e222dacb3687f with amount 127900

#

same error

#

maybe the problem is in the session
const stripeSession = await stripe.checkout.sessions.create({ //@ts-ignore success_url:${process.env.NEXTAUTH_URL!!}/thank-you?orderId=${order._id}, cancel_url: ${process.env.NEXTAUTH_URL!!}/cart, payment_method_types: ["card"], line_items, mode: "payment", metadata: { orderId: ${order._id}, userId: session?.user.id, }, customer_email: session?.user.email, payment_intent_data: { transfer_data: { destination: "....", // Your platform account ID }, }, });
webhook
await stripe.transfers.create({ amount: transferAmount, currency: "usd", destination: transfer.stripeAccountId, source_transaction: session.payment_intent.latest_charge, });

#

this is so confusing 😦

delicate plover
#

Error creating transfer for seller 6676de14f01cd0caa0073c6f: You have insufficient available funds in your Stripe account.
Can you share the request id where you got this error

serene root
#

req_y6j2g4pjMsXmWc

delicate plover
serene root
#

when i actually pass my acc id to the create session it gives me this error : The 'payment_intent_data[transfer_data][destination]' param cannot be set to your own account
so i leave it empty

serene root
#

this is the function in the web hook i am passing it
await stripe.transfers.create({
amount: transferAmount,
currency: "usd",
destination: transfer.stripeAccountId,
source_transaction: session.payment_intent.latest_charge,
});

delicate plover
#

You didn't though

#

Look at the request I linked on your dashboard above

#

You did not pass source_transaction

#

So you need to add some logging/debugging in your code to make sure you're not passing null for the charge id

#

You need to pass charge id in source transaction

#

You're not doing that, so you get the above errror

serene root
#

yes

#

u are right

#

console.log(session.payment_intent.latest_charge)

#

this is undefined

#

but why

#

` const body = req.body;
const sig = req.headers["stripe-signature"];

let event;

try {
event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET);`

delicate plover
#

oh

#

your checkout session variable is called stripeSession not session in the above code snippet

#

also payment_intent won't be expanded by default

serene root
#

const session = event.data.object;

delicate plover
#

Ah ok

#

But the reason is the same

serene root
#

please explain more

delicate plover
#

payment intent isn't expanded by default

#

just do 1 thing for me so you understand what's happening

serene root
#

so how should i expand it

delicate plover
#

print out the session variable in your webhook handler code and look at the entire event object

#

likely payment_intent is just a string id not an object

serene root
#

i will do it

#

i should JSON.parse it then

delicate plover
delicate plover
serene root
#

1 api request or json.parse ?

#

i will just log it first

#

thank u so much by the way

delicate plover
serene root
#

sesssssssssssssssssssssssssssion { id: 'pi_3PbpP7Rpqbf3DcZG1DvYVCh2', object: 'payment_intent', amount: 262446, amount_capturable: 0, amount_details: { tip: {} }, amount_received: 0, application: null, application_fee_amount: null, automatic_payment_methods: null, canceled_at: null, cancellation_reason: null, capture_method: 'automatic_async', client_secret: 'pi_3PbpP7Rpqbf3DcZG1DvYVCh2_secret_Sg7YnSnqz9axadIXOWPmE0aF1', confirmation_method: 'automatic', created: 1720813517, currency: 'usd', customer: null, description: null, invoice: null, last_payment_error: null, latest_charge: null, livemode: false, metadata: {}, next_action: null, on_behalf_of: null, payment_method: null, payment_method_configuration_details: null, payment_method_options: { card: { installments: null, mandate_options: null, network: null, request_three_d_secure: 'automatic' } }, payment_method_types: [ 'card' ], processing: null, receipt_email: null, review: null, setup_future_usage: null, shipping: null, source: null, statement_descriptor: null, statement_descriptor_suffix: null, status: 'requires_payment_method', transfer_data: null, transfer_group: null }

delicate plover
#

That's a payment intent not a session

#

What event are you listening to?

serene root
#

const body = req.body;
const sig = req.headers["stripe-signature"];

let event;

` try {
event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET);
} catch (error) {
console.error("Error constructing webhook:", error.message);
return new Response(JSON.stringify({ message: "Webhook error" }), { status: 400 });
}

const session = event.data.object;
console.log("sesssssssssssssssssssssssssssion",session);
if (!session?.metadata?.userId || !session?.metadata?.orderId) {
console.error("Invalid session metadata:", session.metadata);
return new Response(JSON.stringify({ message: "Webhook error" }), { status: 400 });
}

if (event.type === "checkout.session.completed") {`

delicate plover
#

wait why are you logging this outside of the checkout session completed block

#

the above just logs any event type

#

what event type are you trying to rely on here?

#

checkout.session.completed?

serene root
#

yes

#

should i add an or statement to listen to the payment intent or what

delicate plover
# serene root yes

then add logging the session within the checkout.session.completed block so that way the event actually is a checkout session. that's what we're trying to test here

delicate plover
serene root
#

i will just add the logging inside and tell u

#

i should log the session inside the if statement ?

#

or log the event itself

delicate plover
#

yes. the whole point of you doing this is so you log only a checkout session completed event so you can see what it looks like and we can go from there

serene root
#

here is the whole object

#

``esssssssssssssssssssssssssssion {
id: 'cs_test_b1zaLJcGgSQeW8X5dHNIpSE9yL6keZiKYvLWwX1rb7ZWolNgQqTmYAf8FY',
object: 'checkout.session',
after_expiration: null,
allow_promotion_codes: null,
amount_subtotal: 262446,
amount_total: 262446,
automatic_tax: { enabled: false, liability: null, status: null },
billing_address_collection: null,
cancel_url: 'http://localhost:3000/cart',
client_reference_id: null,
client_secret: null,
consent: null,
consent_collection: null,
created: 1720813920,
currency: 'usd',
currency_conversion: null,
custom_fields: [],
custom_text: {
after_submit: null,
shipping_address: null,
submit: null,
terms_of_service_acceptance: null
},
customer: null,
customer_creation: 'if_required',
customer_details: {
address: {
city: null,
country: 'EG',
line1: null,
line2: null,
postal_code: null,
state: null
},
email: 'noordragon2004@gmail.com',
name: 'AMR MOSTAFA M ALJANDI',
phone: null,
tax_exempt: 'none',
tax_ids: []
},
customer_email: 'noordragon2004@gmail.com',
expires_at: 1720900320,
invoice: null,
invoice_creation: {
enabled: false,
invoice_data: {
account_tax_ids: null,`

#

custom_fields: null, description: null, footer: null, issuer: null, metadata: {}, rendering_options: null } }, livemode: false,
locale: null,
metadata: {
orderId: '669187b73737123532d727e9',
userId: '6676de14f01cd0caa0073c6f'
},
mode: 'payment',
payment_intent: 'pi_3PbpVoRpqbf3DcZG0tXFmKD9',
payment_link: null,
payment_method_collection: 'if_required',
payment_method_configuration_details: null,
payment_method_options: { card: { request_three_d_secure: 'automatic' } },
payment_method_types: [ 'card' ],
payment_status: 'paid',
phone_number_collection: { enabled: false },
recovered_from: null,
saved_payment_method_options: null,
setup_intent: null,
shipping_address_collection: null,
shipping_cost: null,
shipping_details: null,
shipping_options: [],
status: 'complete',
submit_type: null,
subscription: null,
success_url: 'http://localhost:3000/thank-you?orderId=669187b73737123532d727e9',
total_details: { amount_discount: 0, amount_shipping: 0, amount_tax: 0 },
ui_mode: 'hosted',
url: null
}```

delicate plover
#

it gets cut off, but you should see a payment intent param

#

ok. see how this is just an id? payment_intent: 'pi_3PbpVoRpqbf3DcZG0tXFmKD9',

serene root
#

payment_intent: 'pi_3PbpVoRpqbf3DcZG0tXFmKD9',

#

here is it

#

just an id

delicate plover
#

You need the charge associated with that payment intent

#

then you can assign a variable to its latest_charge

#

That's what you pass to the transfer

#

Make sense?

#

Also, is there a reason you're doing the transfer separately? Why not use destination charges?

serene root
#

ok i will test right away

serene root
#

every product has its own seller

#

i sadly could not find any youtube videos about this topic going in depth

#

but is that a bad pracice ?

delicate plover
#

really we recommend never doing this unless you have to for some reason

#

Most integrations can get away with setting transfer_data on the session and have us take care of the transfer automatically

serene root
#

ok

#

but

#

if i did it with transfer data it will only go to my account right ?

delicate plover
#

it works the same

serene root
#

how can i spread it to multiple accounts then ?

delicate plover
#

Ah do you need to spread it to multiple connect accounts?

serene root
#

yep

delicate plover
#

transfer_data only supports sending money to 2 accounts: your platform and 1 connect account

serene root
#

imagine having an amazon clone

delicate plover
#

If you need to split across your platform and several accounts, then it's not possible

serene root
#

i take the items from the cart of the user check the seller of each product send the money to them

delicate plover
#

With this way it is

serene root
#

am i getting too far with this app ?πŸ˜…

delicate plover
#

Explain to me why you need to transfer to multiple connect accounts though

#

Because with amazon, you'd only need to split money between Amazon (platform account) and the 1 seller (the connect account). Not multiple sellers (connect accounts)

serene root
#

imagine i wanna buy a ps5 and its controller but each from diffirent seller

#

and i checkout for my cart which includes both of them

delicate plover
#

oh gotcha. multiple products in 1 session w/ multiple sellers

#

ok

#

makes sense then

#

yeah separate charges and transfers are the right way

serene root
#

exactly

delicate plover
#

Are you only accepting card payments?

#

Or different payment method types

serene root
#

only card for now

delicate plover
#

ok. just wanted to warn you that you shouldn't use checkout.session.completed if accepting certain other payment method types

serene root
#

oh ok
guess i want add anything except paypal
Is it safe ?

delicate plover
#

not sure off-hand if paypal is async or not

#

I don't think it is

serene root
#

OMG

#

it worked

delicate plover
#

so checkout.session.completed should work for paypal as well

#

nice

#

it's important to know these caveats for what you're implementing

serene root
#

thank u so much i am grateful

delicate plover
#

No problem

#

If you want to avoid having to do an extra api call to retrieve the payment intent, you could listen to payment_intent.succeeded instead of checkout.session.completed

#

That event gives you the full payment intent object, and you can just access its latest_charge param

serene root
#

so i can replace the event name ? and everything will be working ?

delicate plover
#

The bonus is it also works for async payment methods

#

Not exactly

#

You receive a payment intent object not a checkout session object

serene root
#

oh

delicate plover
#

Up to you though. Thought i'd mention it

#

Depends whether or not you actually need info from the checkout session object

#

If you don't, then you can just listen for the payment intent succeeded event and immediately grab its latest charge without having to do the extra api call

serene root
#

yes yes i got u but why there are not many youtube tutorials explaining it ?

delicate plover
#

i can't answer that haha

#

all of this is in our docs though

#

we have guides for these things

serene root
#

well well
thank u could never figure it out myself

delicate plover
#

No problem. That's why we're here in this discord to assist devs