#noorboi_webhooks
1 messages Β· Page 1 of 1 (latest)
π 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.
HI π
What is the actual error you are receiving and where exactly is it being thrown in your code?
the error says 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
Okay so you are unable to create a transfer because the funds are not in your available balance.
How are you creating charges?
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"); } };
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
Rather than the transfer_group you will want to use the source_transaction paramenter. We detail this here: https://docs.stripe.com/connect/separate-charges-and-transfers?platform=android#transfer-availability
should it be like this then ?
await stripe.transfers.create({ amount: transferAmount, currency: "usd", destination: transfer.stripeAccountId, source_transaction : session.payment_intent, });
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
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 ?
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
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 π
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
You're still passing a payment intent id
You need to pass a charge id like my colleague mentioned
session.payment_intent.latest_charge
wait for me a bit and i will tell u
Tell me what?
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 π¦
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
req_y6j2g4pjMsXmWc
...you didn't pass source_transaction: https://dashboard.stripe.com/test/logs/req_y6j2g4pjMsXmWc
Sign in to the Stripe Dashboard to manage business payments and operations in your account. Manage payments and refunds, respond to disputes and more.
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
i mean that makes sense
i know but can u please explain more how it works do i accept money to my main acc then spread it to other sellers or what happens
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,
});
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
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);`
oh
your checkout session variable is called stripeSession not session in the above code snippet
also payment_intent won't be expanded by default
Complete reference documentation for the Stripe API. Includes code snippets and examples for our Python, Java, PHP, Node.js, Go, Ruby, and .NET libraries.
const session = event.data.object;
please explain more
payment intent isn't expanded by default
just do 1 thing for me so you understand what's happening
so how should i expand it
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
So, in order to get the payment intent object to access the latest_charge param: https://docs.stripe.com/api/payment_intents/object#payment_intent_object-latest_charge, you'll need 1 more api request: retrieve the payment intent via: https://docs.stripe.com/api/payment_intents/retrieve
yeah
no problem
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 }
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") {`
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?
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
idk what this means
i will just add the logging inside and tell u
i should log the session inside the if statement ?
or log the event itself
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
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
}```
it gets cut off, but you should see a payment intent param
ok. see how this is just an id? payment_intent: 'pi_3PbpVoRpqbf3DcZG0tXFmKD9',
You need the charge associated with that payment intent
so you need to retrieve the payment intent object via this api call: https://docs.stripe.com/api/payment_intents/retrieve
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?
ok i will test right away
i have an amazon clone
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 ?
it's just the most complicated way to do this
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
no
it works the same
how can i spread it to multiple accounts then ?
Ah do you need to spread it to multiple connect accounts?
yep
transfer_data only supports sending money to 2 accounts: your platform and 1 connect account
imagine having an amazon clone
If you need to split across your platform and several accounts, then it's not possible
i take the items from the cart of the user check the seller of each product send the money to them
even with this way ??
With this way it is
am i getting too far with this app ?π
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)
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
oh gotcha. multiple products in 1 session w/ multiple sellers
ok
makes sense then
yeah separate charges and transfers are the right way
exactly
only card for now
ok. just wanted to warn you that you shouldn't use checkout.session.completed if accepting certain other payment method types
oh ok
guess i want add anything except paypal
Is it safe ?
we call this out here: https://docs.stripe.com/checkout/fulfillment#create-payment-event-handler
not sure off-hand if paypal is async or not
I don't think it is
ah it's not. it's immediate. says so here: https://docs.stripe.com/payments/paypal
so checkout.session.completed should work for paypal as well
nice
make sure to read this section: https://docs.stripe.com/connect/separate-charges-and-transfers?platform=web&ui=stripe-hosted#transfer-availability
it's important to know these caveats for what you're implementing
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
so i can replace the event name ? and everything will be working ?
The bonus is it also works for async payment methods
Not exactly
You receive a payment intent object not a checkout session object
oh
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
yes yes i got u but why there are not many youtube tutorials explaining it ?
i can't answer that haha
all of this is in our docs though
we have guides for these things
well well
thank u could never figure it out myself
No problem. That's why we're here in this discord to assist devs