#jco_resending-events-for-testing

1 messages ¡ Page 1 of 1 (latest)

fleet lintelBOT
#

👋 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.

vale sentinelBOT
#

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.

regal marlin
#

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 });

}

wary nimbus
regal marlin
#

Will it send with the correct data such as wh-secret and the metadata needed?

wary nimbus
#

Otherwise if you have an existing checkout.session.completed even you want to just resend, the CLI supports that too:

stripe resend evt_xxx
fleet lintelBOT
#

jco_resending-events-for-testing

regal marlin
#

ok great thank you, I have the weird beta dashboard with the dev console thing.

#

It no longer lets me resend events.

wary nimbus
wary nimbus
regal marlin
#

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?

wary nimbus
wary nimbus
regal marlin
#

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?

wary nimbus
#

Well which event are you listening to?

regal marlin
#

These three

wary nimbus
#

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

regal marlin
#

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?

wary nimbus
#

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

wary nimbus
regal marlin
#

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.

wary nimbus
#

OK, then you should be able to derive the 'dates' info required from invoice.paid events

regal marlin
#

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

wary nimbus
regal marlin
#

ah thank you

wary nimbus
#

invoice.paid will fire for all billing cycle payments

regal marlin
#

So yes, handle only invoice paid as this site only handles subscriptions as of current.

wary nimbus
regal marlin
#

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?

wary nimbus
#

It's a unix timestamp so you need to format it as you need

regal marlin
#
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?

wary nimbus
#

Well our timestamps are seconds, not milliseconds, so the multiplication is redundant I think

regal marlin
#
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?
wary nimbus
#

Did you try it? Does it return the date you expect?

regal marlin
wary nimbus
#

🤷‍♂️

regal marlin
#

Without * 1000

#

It appears that it has to be converted into MS for new Date() to work properly.

wary nimbus
#

Yeah I got confused my bad

regal marlin
#

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?

wary nimbus
regal marlin
#

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?

wary nimbus
#

I recommend reading this doc which coves a lot of these questions and how to handle certain scenarios your billing integration may enter

Learn how subscriptions work within Stripe.

regal marlin
#

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?

magic swan
#

hi! I'm taking over this thread.

wary nimbus
magic swan
#

yes I recommend reading the link above. note that this only applies to recurring payment failing, not the very first payment of the subscription

regal marlin
#

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.

magic swan
#

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.

regal marlin
magic swan
#

got it. let me know if you have other questions

regal marlin
#

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?

magic swan
#

if the user fails to update
what does that mean exactly?

regal marlin
#
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

magic swan
#

this is not a Stripe API, so no idea.

regal marlin
#

Will that message show in the dashboard when I return a 500 response?

magic swan
#

not sure, I recommend doing some test in test mode.

regal marlin
#

Im getting this, but its not showing anything run by the CLI, such as resending the event

magic swan
#

are you using the CLI or the dashboard to test events?

regal marlin
#

CLI, but I need to see the events... but apparently response.json didnt send the message body?

magic swan
#

you can try with a real webhook endpoint (instead of the CLI), and you might see the reponse

regal marlin
#

Its a real event I triggered within the dashboard, the cli is just resending it.

magic swan
#

can you share the Event ID (evt_xxx)?

regal marlin
#

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?

magic swan
#

I see only 200 responses for that specific event you shared in your webhook endpoints.

regal marlin
#

Does stripe not read json bodies?

#

None show any data sent back to them in Response.json

#

do I have to response.send?

magic swan
#

I'm not familiar with how the dashboard works, so I recommend doing some tests.

regal marlin
#

appears theres no data passed.

#

NextJS might be the issue, as I never had issues with this in express or php.

magic swan
#

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.

regal marlin
#

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.

magic swan
#

makes sense

regal marlin
#

testing worked

#

and cancel worked too. Ok thanks for all your help, now to apply this api endpoint to the frontend.

vale sentinelBOT