W
Wasp•2mo ago
Sven

Production payment doesn't give user subscription

When i try to do a payment in production mode i dont get the subscriptoin status in the database. I complete the payment and get redirected but i think something goes wrong with the webhook. The webhook only notices the following events (see image.) . I think it might be due to version mismatch but i am not sure what version to use and what date to fill in because when i search the dates don't match up (see image). If this is not the problem what else could it be? Stripe client:
export const stripe = new Stripe(requireNodeEnvVar('STRIPE_API_KEY'), {
// NOTE:
// API version below should ideally match the API version in your Stripe dashboard.
// If that is not the case, you will most likely want to (up/down)grade the `stripe`
// npm package to the API version that matches your Stripe dashboard's one.
// For more details and alternative setups check
// https://docs.stripe.com/api/versioning .
apiVersion: '2024-04-10',
});
export const stripe = new Stripe(requireNodeEnvVar('STRIPE_API_KEY'), {
// NOTE:
// API version below should ideally match the API version in your Stripe dashboard.
// If that is not the case, you will most likely want to (up/down)grade the `stripe`
// npm package to the API version that matches your Stripe dashboard's one.
// For more details and alternative setups check
// https://docs.stripe.com/api/versioning .
apiVersion: '2024-04-10',
});
No description
No description
33 Replies
kapa.ai
kapa.ai•2mo ago
Hi @Sven. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- Based on the information provided, it seems you're experiencing issues with your Stripe webhook in production, which is preventing the user's subscription status from being updated in the database after payment. Let's address this problem step by step. 1. Webhook Events: The image you mentioned (but I can't see) apparently shows that your webhook is only receiving certain events. For a subscription to be properly processed, you typically need to receive events like checkout.session.completed and customer.subscription.created. If these are missing, it could explain why the subscription status isn't being updated. 2. API Version Mismatch: You're correct in suspecting a version mismatch. The API version you're using in your code (2024-04-10) is actually a future date, which is not a valid Stripe API version. This could definitely cause issues with webhook processing. To fix this: 1. Go to your Stripe Dashboard and check the API version you're currently using. 2. Update your code to match this version. For example, if your dashboard shows 2023-10-16, your code should look like this:
export const stripe = new Stripe(requireNodeEnvVar('STRIPE_API_KEY'), {
apiVersion: '2023-10-16',
});
export const stripe = new Stripe(requireNodeEnvVar('STRIPE_API_KEY'), {
apiVersion: '2023-10-16',
});
3. Webhook Configuration: Make sure your webhook is properly configured in your Stripe Dashboard to send events to your production server. The endpoint should look something like {your-production-domain}/stripe-webhook. As mentioned in the OpenSaaS documentation Setting up your Stripe webhook (https://docs.opensaas.sh/guides/deploying/#setting-up-your-stripe-webhook): 1. Go to the Stripe Dashboard > Developers > Webhooks 2. Add an endpoint that points to your production URL + /stripe-webhook 3. Select the events you want to listen for (at minimum: checkout.session.completed, customer.subscription.updated, customer.subscription.deleted) 4. Copy the Signing Secret and add it to your production environment variables as STRIPE_WEBHOOK_SECRET 4. Environment Variables: Ensure all necessary environment variables are correctly set in your production environment, including STRIPE_API_KEY, STRIPE_WEBHOOK_SECRET, and any other Stripe-related variables your app uses. 5. Webhook Testing: After making these changes, you can use the Stripe CLI to test your webhook and ensure it's receiving and processing events correctly, as described in the Stripe testing documentation (https://docs.opensaas.sh/guides/stripe-testing/#testing-webhooks-via-the-stripe-cli). If you've made these changes and are still experiencing issues, you may need to dive deeper into your server logs to see if there are any errors being thrown when processing webhook events. Also, double-check that your database update logic in the webhook handler is correct and not silently failing. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @Sven, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
Sven
SvenOP•2mo ago
Could anyone possibly give any recomendation on what strip versoin to install and what date to put into stripeClient.ts
miho
miho•2mo ago
@Vinny (@Wasp) one for you
Vinny (@Wasp)
Vinny (@Wasp)•2mo ago
stripe versions are backwards compatible so I don't believe that's the issue. When you're in the stripe webhook dashboard, do you see any failed webhook events? if so, what are they ?
Vinny (@Wasp)
Vinny (@Wasp)•2mo ago
when you go to https://dashboard.stripe.com/webhooks, click on your webhook, then click the menu (the three dots in the top right corner, e.g. ...) and update details. Are you sending all the proper events?
Stripe Login | Sign in to the Stripe Dashboard
Sign in to the Stripe Dashboard to manage business payments and operations in your account. Manage payments and refunds, respond to disputes and more.
No description
Vinny (@Wasp)
Vinny (@Wasp)•2mo ago
Also, make sure you're following all the steps in the "deploying your webhook" docs https://docs.opensaas.sh/guides/deploying/#setting-up-your-stripe-webhook
OpenSaaS.sh
Deploying
Open SaaS is a free, open-source, full-stack SaaS starter kit for React + NodeJS.
Sven
SvenOP•2mo ago
no failed ones failperecentage 0%
Sven
SvenOP•2mo ago
as stated in the guide it listens to these: - account.updated - checkout.session.completed - customer.subscription.deleted - customer.subscription.updated - invoice.paid
No description
Vinny (@Wasp)
Vinny (@Wasp)•2mo ago
are you actually paying for a product or is the product price $0 @Sven ?
Sven
SvenOP•2mo ago
0 yeah not paying 0$ indeed
Vinny (@Wasp)
Vinny (@Wasp)•2mo ago
that's the problem the stripe webhook will not send the customer.subscription.updated event if you're not actually paying for something which is why subscription.status never gets updated on your user in the DB
Sven
SvenOP•2mo ago
Thanks!! How could i test this easily without having to bother paying and fixing my business admistration Or do i really need to pay and get paid out by stripe
Vinny (@Wasp)
Vinny (@Wasp)•2mo ago
it looks like it will work fine if you're able to checkout. just update the price on that product to be a real amount and you'll be good to go. you'll find out I guess if users can't pay for your product 🙂 but, like i said, if it worked locally, and you've set all your env vars correctly (which it seems like you have) then no worries or just set the product price to a low amount and test it out yourself if you want to be really sure
Sven
SvenOP•2mo ago
when i sign up in my saas as a user, and want to redo the signup is it a bad practise to remove the user row from the table (to be able to sign up agai) just for now to do a last check
Sven
SvenOP•2mo ago
okay i tried doing it by paying 1$, now it still only recognized: (i see on my creditcard that the 1$ is "reserved", maybe that has to do something?
No description
Sven
SvenOP•2mo ago
and thus it did not update the fields in the database it works in dev mode btw ( i follow the exact steps i did in dev mode in the production mode...) 1. set api key 1.1 create products and set the ids (which works because i can checkout) 2. set customer portal 3. create webhook + 5 events, set webhook secret I am checking put out with admin email tho would really appreciate help on this not sure why it still does not work still did not fix it Would you know why?
Vinny (@Wasp)
Vinny (@Wasp)•2mo ago
Hm, yeah, if the money is "reserved" but not "paid" then that would be the issue? Can you click on the customer.subscription.updated event and see what ti says what's the status?
Sven
SvenOP•2mo ago
I will check, how come it that it is even reserved in the first place? Will that happen to my customers
Vinny (@Wasp)
Vinny (@Wasp)•2mo ago
Stripe and Banks sometimes "flag" suspicious looking payments incorrectly that could be whats going on, but it's hard for me to say.
Sven
SvenOP•2mo ago
Okay hmm i see like the response idk if i can paste it here and also i tried paying with a different card, where it actualy got written of my card so no reserve and it still did not work
Sven
SvenOP•2mo ago
it still only notices these three, see how i set the events properly.... (ignore upper one that is where i remove the subscription and refund)
No description
Vinny (@Wasp)
Vinny (@Wasp)•2mo ago
Can you share the webhook code where the customer subscriptionStatus should get updated but doesnt?
Sven
SvenOP•2mo ago
where is that located?
export const stripeWebhook: PaymentsWebhook = async (request, response, context) => {
const secret = requireNodeEnvVar('STRIPE_WEBHOOK_SECRET');
const sig = request.headers['stripe-signature'];
if (!sig) {
throw new HttpError(400, 'Stripe Webhook Signature Not Provided');
}
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(request.body, sig, secret);
} catch (err) {
throw new HttpError(400, 'Error Constructing Stripe Webhook Event');
}
const prismaUserDelegate = context.entities.User;
switch (event.type) {
case 'checkout.session.completed':
const session = event.data.object as Stripe.Checkout.Session;
await handleCheckoutSessionCompleted(session, prismaUserDelegate);
break;
case 'invoice.paid':
const invoice = event.data.object as Stripe.Invoice;
await handleInvoicePaid(invoice, prismaUserDelegate);
break;
case 'customer.subscription.updated':
const updatedSubscription = event.data.object as Stripe.Subscription;
await handleCustomerSubscriptionUpdated(updatedSubscription, prismaUserDelegate);
break;
case 'customer.subscription.deleted':
const deletedSubscription = event.data.object as Stripe.Subscription;
await handleCustomerSubscriptionDeleted(deletedSubscription, prismaUserDelegate);
break;
default:

console.error('Unhandled event type: ', event.type);
}
response.json({ received: true }); // Stripe expects a 200 response to acknowledge receipt of the webhook
};
export const stripeWebhook: PaymentsWebhook = async (request, response, context) => {
const secret = requireNodeEnvVar('STRIPE_WEBHOOK_SECRET');
const sig = request.headers['stripe-signature'];
if (!sig) {
throw new HttpError(400, 'Stripe Webhook Signature Not Provided');
}
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(request.body, sig, secret);
} catch (err) {
throw new HttpError(400, 'Error Constructing Stripe Webhook Event');
}
const prismaUserDelegate = context.entities.User;
switch (event.type) {
case 'checkout.session.completed':
const session = event.data.object as Stripe.Checkout.Session;
await handleCheckoutSessionCompleted(session, prismaUserDelegate);
break;
case 'invoice.paid':
const invoice = event.data.object as Stripe.Invoice;
await handleInvoicePaid(invoice, prismaUserDelegate);
break;
case 'customer.subscription.updated':
const updatedSubscription = event.data.object as Stripe.Subscription;
await handleCustomerSubscriptionUpdated(updatedSubscription, prismaUserDelegate);
break;
case 'customer.subscription.deleted':
const deletedSubscription = event.data.object as Stripe.Subscription;
await handleCustomerSubscriptionDeleted(deletedSubscription, prismaUserDelegate);
break;
default:

console.error('Unhandled event type: ', event.type);
}
response.json({ received: true }); // Stripe expects a 200 response to acknowledge receipt of the webhook
};
these are my server logs:
2024-10-29T10:33:24.221 app[d890426c46e328] ams [info] POST /operations/update-current-user 200 31.517 ms - 483
2024-10-29T10:33:24.255 app[d890426c46e328] ams [info] GET /auth/me 200 10.987 ms - 639
2024-10-29T10:33:28.583 app[d890426c46e328] ams [info] POST /operations/get-customer-portal-url 200 13.748 ms - 64
2024-10-29T10:33:36.622 app[d890426c46e328] ams [info] using existing customer
2024-10-29T10:33:37.235 app[d890426c46e328] ams [info] POST /operations/generate-checkout-session 200 867.359 ms - 466
2024-10-29T10:33:37.448 app[d890426c46e328] ams [info] POST /operations/get-customer-portal-url 200 120.566 ms - 64
2024-10-29T10:34:32.218 app[d890426c46e328] ams [info] GET /auth/me 304 11.598 ms - -
2024-10-29T10:36:39.417 app[d890426c46e328] ams [info] GET /auth/me 304 67.804 ms - -
2024-10-29T10:39:15.099 app[d890426c46e328] ams [info] GET /auth/me 304 13.146 ms - -
2024-10-29T10:39:15.214 app[d890426c46e328] ams [info] POST /operations/update-current-user 200 33.432 ms - 483
2024-10-29T10:33:24.221 app[d890426c46e328] ams [info] POST /operations/update-current-user 200 31.517 ms - 483
2024-10-29T10:33:24.255 app[d890426c46e328] ams [info] GET /auth/me 200 10.987 ms - 639
2024-10-29T10:33:28.583 app[d890426c46e328] ams [info] POST /operations/get-customer-portal-url 200 13.748 ms - 64
2024-10-29T10:33:36.622 app[d890426c46e328] ams [info] using existing customer
2024-10-29T10:33:37.235 app[d890426c46e328] ams [info] POST /operations/generate-checkout-session 200 867.359 ms - 466
2024-10-29T10:33:37.448 app[d890426c46e328] ams [info] POST /operations/get-customer-portal-url 200 120.566 ms - 64
2024-10-29T10:34:32.218 app[d890426c46e328] ams [info] GET /auth/me 304 11.598 ms - -
2024-10-29T10:36:39.417 app[d890426c46e328] ams [info] GET /auth/me 304 67.804 ms - -
2024-10-29T10:39:15.099 app[d890426c46e328] ams [info] GET /auth/me 304 13.146 ms - -
2024-10-29T10:39:15.214 app[d890426c46e328] ams [info] POST /operations/update-current-user 200 33.432 ms - 483
Vinny (@Wasp)
Vinny (@Wasp)•2mo ago
you need to go into the handleCustomerSubscriptionUpdated function
Sven
SvenOP•2mo ago
export async function handleCustomerSubscriptionUpdated(
subscription: Stripe.Subscription,
prismaUserDelegate: PrismaClient["user"]
) {
const userStripeId = validateUserStripeIdOrThrow(subscription.customer);
let subscriptionStatus: SubscriptionStatus | undefined;

const priceId = extractPriceId(subscription.items);
const subscriptionPlan = getPlanIdByPriceId(priceId);

// There are other subscription statuses, such as `trialing` that we are not handling and simply ignore
// If you'd like to handle more statuses, you can add more cases above. Make sure to update the `SubscriptionStatus` type in `payment/plans.ts` as well
if (subscription.status === 'active') {
subscriptionStatus = subscription.cancel_at_period_end ? 'cancel_at_period_end' : 'active';
} else if (subscription.status === 'past_due') {
subscriptionStatus = 'past_due';
}
if (subscriptionStatus) {
const user = await updateUserStripePaymentDetails({ userStripeId, subscriptionPlan, subscriptionStatus }, prismaUserDelegate);
if (subscription.cancel_at_period_end) {
if (user.email) {
await emailSender.send({
to: user.email,
subject: 'We hate to see you go :(',
text: 'We hate to see you go. Here is a sweet offer...',
html: 'We hate to see you go. Here is a sweet offer...',
});
}
}
return user;
}
}
export async function handleCustomerSubscriptionUpdated(
subscription: Stripe.Subscription,
prismaUserDelegate: PrismaClient["user"]
) {
const userStripeId = validateUserStripeIdOrThrow(subscription.customer);
let subscriptionStatus: SubscriptionStatus | undefined;

const priceId = extractPriceId(subscription.items);
const subscriptionPlan = getPlanIdByPriceId(priceId);

// There are other subscription statuses, such as `trialing` that we are not handling and simply ignore
// If you'd like to handle more statuses, you can add more cases above. Make sure to update the `SubscriptionStatus` type in `payment/plans.ts` as well
if (subscription.status === 'active') {
subscriptionStatus = subscription.cancel_at_period_end ? 'cancel_at_period_end' : 'active';
} else if (subscription.status === 'past_due') {
subscriptionStatus = 'past_due';
}
if (subscriptionStatus) {
const user = await updateUserStripePaymentDetails({ userStripeId, subscriptionPlan, subscriptionStatus }, prismaUserDelegate);
if (subscription.cancel_at_period_end) {
if (user.email) {
await emailSender.send({
to: user.email,
subject: 'We hate to see you go :(',
text: 'We hate to see you go. Here is a sweet offer...',
html: 'We hate to see you go. Here is a sweet offer...',
});
}
}
return user;
}
}
i changed names of the env vars but I also changed them in plans.ts (idk if it matters, also changed last plan to subscirption instead of credits)
PAYMENTS_START_SUBSCRIPTION_PLAN_ID=
PAYMENTS_BOOST_SUBSCRIPTION_PLAN_ID=
PAYMENTS_MAX_10_PLAN_ID=
PAYMENTS_START_SUBSCRIPTION_PLAN_ID=
PAYMENTS_BOOST_SUBSCRIPTION_PLAN_ID=
PAYMENTS_MAX_10_PLAN_ID=
export const paymentPlans: Record<PaymentPlanId, PaymentPlan> = {
[PaymentPlanId.Start]: {
getPaymentProcessorPlanId: () => requireNodeEnvVar('PAYMENTS_START_SUBSCRIPTION_PLAN_ID'),
effect: { kind: 'subscription' },
},
[PaymentPlanId.Boost]: {
getPaymentProcessorPlanId: () => requireNodeEnvVar('PAYMENTS_BOOST_SUBSCRIPTION_PLAN_ID'),
effect: { kind: 'subscription' },
},
[PaymentPlanId.Max]: {
getPaymentProcessorPlanId: () => requireNodeEnvVar('PAYMENTS_MAX_SUBSCRIPTION_PLAN_ID'),
effect: { kind: 'subscription' },
},
};
export const paymentPlans: Record<PaymentPlanId, PaymentPlan> = {
[PaymentPlanId.Start]: {
getPaymentProcessorPlanId: () => requireNodeEnvVar('PAYMENTS_START_SUBSCRIPTION_PLAN_ID'),
effect: { kind: 'subscription' },
},
[PaymentPlanId.Boost]: {
getPaymentProcessorPlanId: () => requireNodeEnvVar('PAYMENTS_BOOST_SUBSCRIPTION_PLAN_ID'),
effect: { kind: 'subscription' },
},
[PaymentPlanId.Max]: {
getPaymentProcessorPlanId: () => requireNodeEnvVar('PAYMENTS_MAX_SUBSCRIPTION_PLAN_ID'),
effect: { kind: 'subscription' },
},
};
yea it looks normal i guess
Vinny (@Wasp)
Vinny (@Wasp)•2mo ago
It seems you're missing your PARMENTS_MAX_10_PLAN_ID, right? If the environment variables are incorrect then the checkout probably wouldn't work eitehr, but It seems to be an issue with your production environment set up. If it works in local development, then it has to be an issue with your production evironment and not the code. I'd double check and reset all your env variables and go from there. Maybe it's good to also add some console.log() statements to the areas that should be working but aren't and check your production server. That's as much help as I can offer for now. Good luck!
Sven
SvenOP•2mo ago
Okay thanks, Hope it will work Oke thing couldnt it be version mismatch Any version recommandation? To install and use in stripe
Vinny (@Wasp)
Vinny (@Wasp)•2mo ago
the version is probably fine, but if there is any issue, you'd be able to determine that in local development (just make sure the stripe test environment is using the same version as production -- which it should be)
Vinny (@Wasp)
Vinny (@Wasp)•2mo ago
here;s some more info on stripe versions: https://docs.opensaas.sh/guides/deploying/#stripe-api-versions
OpenSaaS.sh
Deploying
Open SaaS is a free, open-source, full-stack SaaS starter kit for React + NodeJS.
Vinny (@Wasp)
Vinny (@Wasp)•2mo ago
But like I said, if that version you're using in production is the same as your Stripe test environment, and you have no issues there, then the problem is with your production environment configuration
Sven
SvenOP•2mo ago
if i wanted to change env vars, would i need to reset them or just use set ? Or do you recommend removing all? and re setting all wait @Vinny (@Wasp) sory to tag you, but i see account.updatednot being used here eventhough i set it as event in my webhook.. what areas should work? where does the user get the subscriptionStatus and subscriptioPlan? Also it doesnt matter what name i give my subscriptions right? in wasp it was hobby etc.. i named them start, max, pro and changed what i think all hobby to start everywhere no errors occur altleast SOLVED!!!! I WAS SOOO STUPID, SET THE WEBHOOK URL IN STRIPE TO MY CLIENT URL..... I WAS SUPPOSED TO SET IT TO THE SERVER URL (THE ENDPOINT WHERE MY SERVER IS DEPLOYED) due to lack of sleeppp..... ✅
Vinny (@Wasp)
Vinny (@Wasp)•2mo ago
Nice! Glad you solved it. 🙂
Sven
SvenOP•2mo ago
Thanks for the help!
Want results from more Discord servers?
Add your server