#jco_resending-events-for-testing
1 messages ¡ Page 1 of 1 (latest)
đ Welcome to your new thread!
â˛ď¸ We'll be here soon! Typically we respond in a few minutes, but sometimes we might take a bit longer if the server is busy or if you have a particularly tricky question.
âąď¸ We close idle threads, which makes them read-only. Once a thread is closed it won't be reopened, but you can always 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/1230109264270590034
đ Have more to share? Add more details, code, screenshots, videos, etc. below.
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.
- jco_webhooks, 5 days ago, 13 messages
current state is this code: ```typescript
import { NextRequest, NextResponse } from "next/server";
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: "2024-04-10",
typescript: true,
});
const webhooksecret =
"testsecret"; // will be changed to reflect properly with process.env.STRIPE_WH_SECRET on vercel
export async function POST(req: NextRequest) {
const body = await req.text();
const signature = req.headers.get("stripe-signature") as string;
if (!signature)
return Response.json(
{ message: "Invalid or missing signing secret" },
{ status: 403 }
);
if (!body)
return Response.json(
{ message: "Invalid or missing payload" },
{ status: 400 }
);
let event: Stripe.Event;
try {
event = await stripe.webhooks.constructEventAsync(
body,
signature,
webhooksecret
);
} catch (error) {
console.log(error);
return Response.json(
{ message: "Invalid or missing signing secret" },
{ status: 403 }
);
}
switch (event.type) {
case "checkout.session.completed":
console.log("checkout.session.comleted: " + event.data.object);
if (event.data.object.metadata!.site !== "cleo") {
console.log("Not cleo site");
break;
}
break;
case "invoice.paid":
console.log("invoice.paid: " + event.data.object);
break;
case "invoice.payment_failed":
console.log("invoice.payment_failed: " + event.data.object);
break;
default:
break;
}
return NextResponse.json({ status: 200 });
}
Hello there! The CLI is probably the best method to trigger events when testing webhooks: https://docs.stripe.com/cli/trigger
Will it send with the correct data such as wh-secret and the metadata needed?
The CLI will use a unique secret different to your Dashboard webhook. Prints it out when you use stripe listen
You can set fields like metadata in the event yes: https://docs.stripe.com/cli/trigger#trigger-override
Otherwise if you have an existing checkout.session.completed even you want to just resend, the CLI supports that too:
stripe resend evt_xxx
jco_resending-events-for-testing
ok great thank you, I have the weird beta dashboard with the dev console thing.
It no longer lets me resend events.
You can only resend from Dashboard if they've failed I think
Sorry, stripe events resend: https://docs.stripe.com/cli/events/resend
This isn't apparently what I need for my endpoint...
Where can I get this information? invoice paid?
Or do I have to retrieve the subscription?
Not sure what you mean? By default it sends to a local listener, if you you want to send it to a remote webhook you need to pass the --webhook-endpoint arg: https://docs.stripe.com/cli/events/resend#events_resend-webhook-endpoint
I don't knbow what you're referencing there
No, I mean the data... I need to get those values from the webhook, where can I get the date of the subscription start and end?
i cannot see it in checkout, would it be better to get it from invoice.paid?
Well which event are you listening to?
These three
To get it on checkout.session.completed, you'd need to make an API request to retrieve the full Subscription using the ID from the event
I think i need subscription deleted or cancelled to turn the valid state back to false, but I dont know which one is best, or the best event to get the validTo date.
Would it be better to ignore handling the subscription data in checkout.session.completed and instead handle it in invoice.paid, invoice.payment_failed and customer.subscription.cancelled or customer.subscription.deleted?
I think you need to take a step back and describe to me what it is exactly you're trying to do with an event/webhook
It depends what data you need really. Recommendation is to generally use invoice.paid for most billing provision/fulfilment though yes, but some people rely on checkout.session.completed for the initial invoice payment
The user makes a request on the front end to upgrade their account, it passes in their userID, links it to a premium account if they cont have one, grabs their premiumId (the uuid) and fetches a stripe customer, or makes one if needed. Then it creates a subscription for the site name and premiumId via metadata with the correct customer passed into the checkout session with that metadata and the correct price of monthly or yearly etc. On completion I need to be able to handle the activation and successful payment of the subscription, to activate the plan on their account with a validFrom (start) date and a validTo (end) date. The dates are for UI use to show days remaining out of how many on their subscription. When the user either cancels, or they fail to pay the bill within the leniency window, it cancels or deletes (idk which one stripe sends first or for this) and that cancels it on the backend setting it to valid: false
export const createCheckout = async (id: string, price: string) => {
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: "2024-04-10",
typescript: true,
});
const response = await stripe.customers.search({
query: `metadata["cleoId"]: "${id}"`,
});
if (!response.data[0]) {
const customer = await stripe.customers.create({
metadata: {
cleoId: String(id),
},
});
const checkout = await stripe.checkout.sessions.create({
mode: "subscription",
customer: customer.id,
customer_update: {
name: "auto",
},
line_items: [
{
price,
quantity: 1,
},
],
phone_number_collection: {
enabled: false,
},
success_url: "https://cleo-peach.vercel.app/premium/success",
cancel_url: "https://cleo-peach.vercel.app/premium/cancel",
metadata: {
site: "cleo",
user: String(id),
},
});
return checkout.url;
}
const customer = response.data[0].id;
const checkout = await stripe.checkout.sessions.create({
mode: "subscription",
customer,
customer_update: {
name: "auto",
},
line_items: [
{
price,
quantity: 1,
},
],
phone_number_collection: {
enabled: false,
},
success_url: "https://cleo-peach.vercel.app/premium/success",
cancel_url: "https://cleo-peach.vercel.app/premium/cancel",
metadata: {
site: "cleo",
user: String(id),
},
});
return checkout.url;
};
```This is how I make the checkout
I realise I could make a customer and apply that to the user object now, after thinking, but this was the design I was passed by my colleague.
OK, then you should be able to derive the 'dates' info required from invoice.paid events
Ok, so should I remove the handling of checkout.session.completed as I don't use it?
only issue I have is the metadata isn't correctly passed into the invoice?
Did I do it wrong?
inv id: in_1P6W3QEOJsqRXL0RdYdSB04k
You need to set subscription_data[metadata]
ah thank you
Up to you! As I said, that'll only fire on the initial invoice payment when the customer completes the payment page. You won't get that event for subsequent recurring payments
invoice.paid will fire for all billing cycle payments
So yes, handle only invoice paid as this site only handles subscriptions as of current.
You should see the metadata in the subscription_details hash on the invoice.paid event: https://docs.stripe.com/api/invoices/object#invoice_object-subscription_details
Complete reference documentation for the Stripe API. Includes code snippets and examples for our Python, Java, PHP, Node.js, Go, Ruby, and .NET libraries.
Thank you
{
"id": "il_1P6W3QEOJsqRXL0RDZkrMlu6","object": "line_item","amount": 500,"amount_excluding_tax": 500,"currency": "gbp","description": "1 Ă Cleo Stats (at ÂŁ5.00 / month)","discount_amounts": [],"discountable": true,"discounts": [],"invoice": "in_1P6W3QEOJsqRXL0RdYdSB04k"
,"livemode": false,"metadata": {},
"period": {
"end": 1715942967,"start": 1713350967,
}```For the `period.end` and `period.start` what date format are they in? would a new Date(period.end) work to get the date?
or is it already in the DateTime format?
It's a unix timestamp so you need to format it as you need
console.log({
valid: payload.status === "paid" ? true : false,
validFrom: new Date(subscriptionData.period.start * 1000),
validTo: new Date(subscriptionData.period.end * 1000),
});```So this?
or is times 1000 not the correct way to convert to datetime?
Well our timestamps are seconds, not milliseconds, so the multiplication is redundant I think
let unix_timestamp = 1549312452;
// Create a new JavaScript Date object based on the timestamp
// multiplied by 1000 so that the argument is in milliseconds, not seconds
var date = new Date(unix_timestamp * 1000);
// Hours part from the timestamp
var hours = date.getHours();
// Minutes part from the timestamp
var minutes = "0" + date.getMinutes();
// Seconds part from the timestamp
var seconds = "0" + date.getSeconds();
// Will display time in 10:30:23 format
var formattedTime = hours + ':' + minutes.substr(-2) + ':' + seconds.substr(-2);
console.log(formattedTime);```I was reading this to convert it as an example, is this not correct? Or just a redundant additional step?
Did you try it? Does it return the date you expect?
đ¤ˇââď¸
Without * 1000
It appears that it has to be converted into MS for new Date() to work properly.
Yeah I got confused my bad
What endpoint do I need to use to handle the subscription cancelling or being deleted when the user cancels in portal or fails to pay? is it customer.subscription.deleted that would be best?
because if the user cancels wont it fire the cancel event, so disabling it in that event would result in premature loss of access to their premium account features?
Should be easy enough to test, event behaviour is explained here: https://docs.stripe.com/billing/subscriptions/cancel#events
How the portal cancels depends on your config: https://docs.stripe.com/api/customer_portal/configurations/create#create_portal_configuration-features-subscription_cancel-mode
Complete reference documentation for the Stripe API. Includes code snippets and examples for our Python, Java, PHP, Node.js, Go, Ruby, and .NET libraries.
Thats a cancel terminates at end of subscription period
What is the expected from stripe behaviour of invoice.payment_failed? create a new checkout or nothing?
Do I have to do anything on that? Or is handling subscription.deleted and invoice.paid the bare minimum?
Depends on if it's a recurring payment or the initial payment when customer is on-session
I recommend reading this doc which coves a lot of these questions and how to handle certain scenarios your billing integration may enter
We do not handle user emails ourselves, will stripe send a payment for invoice failed email to them with a link to reattempt this? Or do I have to display a warning on their account to update it in the portal myself?
hi! I'm taking over this thread.
yes I recommend reading the link above. note that this only applies to recurring payment failing, not the very first payment of the subscription
If this fails, the user will be on the success page which will redirect to failure if its still unpaid.
And as we have a discord bot tied to this SaaS, they will likely recieve a notification that premium was unable to be activated, and informed to view the portal.
If this fails, the user will be on the success page which will redirect to failure if its still unpaid.
looks like you are using Checkout Session to create the Subscription? if so, then yes users will stay on the Checkout page if the payment fail so that they can retry.
Then yeah, dont need to handle payment failure as the notification emails link to a hosted page in my config there.
got it. let me know if you have other questions
if the user fails to update, is returning a 500 with a message going to show in dashboard on stripe so I can see there was an issue and go back into logs and see why?
if the user fails to update
what does that mean exactly?
export const updatePremiumAccount = async (
id: string,
data: {
valid: boolean;
validTo: Date | null;
validFrom: Date | null;
}
): Promise<boolean> => {
try {
await db.premiumAccounts.update({
where: {
premId: id,
},
data,
});
db.$disconnect;
return true;
} catch (error) {
console.error(error);
return false;
}
};
Just this
this is not a Stripe API, so no idea.
not sure, I recommend doing some test in test mode.
Im getting this, but its not showing anything run by the CLI, such as resending the event
are you using the CLI or the dashboard to test events?
CLI, but I need to see the events... but apparently response.json didnt send the message body?
you can try with a real webhook endpoint (instead of the CLI), and you might see the reponse
Its a real event I triggered within the dashboard, the cli is just resending it.
can you share the Event ID (evt_xxx)?
sub_1P6WyhEOJsqRXL0RqctaLoj8
evt_1P6XKzEOJsqRXL0Rg5ueoua8
sorry copied the wrong one
NextJS v14.2.1 This is the documented way to handle 500 errors... but its not showing the error message?
Is that a stripe thing?
I see only 200 responses for that specific event you shared in your webhook endpoints.
Does stripe not read json bodies?
None show any data sent back to them in Response.json
do I have to response.send?
I'm not familiar with how the dashboard works, so I recommend doing some tests.
appears theres no data passed.
NextJS might be the issue, as I never had issues with this in express or php.
I'm not really familiar with Next.js, sorry. But using the dashboard for this seems a bit like a hack to me. It would be better to log the errors on your end directly.
They are, but as my customer support colleagues cannot see this, but can see errors on stripe payments, I was trying to see if they could see a "issue updating user" error, before they message the developers to check the host.
etc.
makes sense
testing worked
and cancel worked too. Ok thanks for all your help, now to apply this api endpoint to the frontend.