I'm using Stripe to let my customers change their plans, and we send them to an external Stripe-hosted page to complete the checkout. After they checkout, Stripe sends them back to a thank-you page and initiates a webhook.
The webhooks are great, because it doesn't update any database information or changes their plan until after the payment has succeeded and their card has been charged.
I also use webhooks to "reset" their plan limits on a successful card charge. So each month, on each successful charge webhook, the limits update. But if the user is in an active session when they update, I should update that session.
BUT what I'm struggling with is, in a webhook event how do I update an active express session?
There's no req.session object, because the "event" is coming from Stripe's webhoook.
app.post('/webhook', async (req, res) => {
/* <-- Update the database, which is good --> */
database.updateUser()
/* <-- Update their session if it's active --> */
// This won't work, because there is no req.session and no cookie being sent!
req.session.user.plan = parseWebhookData()
// I can't access the store, because I don't know the user's session id
// Plus, all my store is encrypted so I can't do a MongoDb query search for their session.
store.get(sid, callback)
// How do I update their session??
})
The only solution I have so far is to have the database re-update their information on every page they visit. But that slows down my response time by 1s per page!
I solved this myself.
Solution #1: Recommended by Stripe: Update user on login, not with the webhook
Stripe recommends checking a user's subscription on login, and resetting their "plan usage" on login INSTEAD of resetting their plan usage on a webhook.
I thought this was very interesting and after implementing I've found this is a much better solution than trying to deal with Webhooks and session objects!
Solution #2: Get the session ID and update the express-session database!
Express-session gives you the sessionID in "req.sessionID" so you can search your database for the sessionID and update what you need to.
app.get('/getID', (req, res) => {
console.log("sessionID: " + req.sessionID);
res.send(req.sessionID);
});
It also helps to do MongoDB's atomic updates if you turn off the stringify feature in your express-session.
const store = MongoStore.create({
stringify:false
});
Related
I am working on a MERN application. I am trying to implement a stripe subscription in it.
Whenever a user tries to pay using the stripe checkout session the webhooks route is invoked and the database is updated according to the event type.
Here is my webhook controller code:
export const postStripeWebhook = async (req: ExtendedRequest, res: Response) => {
//code to determine the eventype from the stripe signature.
switch (eventType) {
case "checkout.session.completed":
subscription = event.data.object;
await prisma.users.update({
where: {
id: subscription.metadata.userId,
},
data: {
stripeUserId: subscription.customer,
subscriptionStatus: true,
},
});
break;
//other events type
}
res.sendStatus(200);
};
As one can see when the customer pays for the product, stripe sends a checkout.session.complete event. On receiving this event the subscriptionStatus of the customer is updated in the database and the product is provisioned to the user.
But what if an error occurs while updating the database? Now the user will be left with a situation where his account has been deducted with the subscription money but since the database is not updated he has not provisioned the product.
According to me, this situation can be eliminated if the whole process is made atomic i.e if the database does not change send the deducted amount to the user.
But I don't know whether this is the right way to tackle this problem. Also I don't know how to perform atomic operations in node.js.
Please guide me with the problem.
I've multiple stripe account on my site, and each stripe account is associated with a webhook.
My webhook is returning 403 Error "No signatures found matching the expected signature for payload"
i've checked the Cashier middleware and its getting the webhook secret key from the env file.
Since this project attached to multiple stripe account, we can't store the webhook secret in env file. so, we're placing the webhook secret key of each stripe account in a table.
I would like to get the secret key from database instead of this config file.
Is it possible to listen to multiple stripe account's webhook?
Any help will be appreciated.
I am not sure if this is a good approach but you can:
Send meta data in the checkout which gets posted to the web hook
Get the raw json posted by the web hook before you use the web hook key to validate that the post was from Stripe. Stripe.Net contains a parse method and a construct method. Parse does not require the key. Construct uses the key to validate the post was from Stripe.
So with Stripe.net:
string endpointSecret;
// get the json posted
var json = await new
StreamReader(HttpContext.Request.Body).ReadToEndAsync();
// convert the json into a stripe event object
var objStripeEvent = EventUtility.ParseEvent(json);
if (objStripeEvent.Type == Events.CheckoutSessionCompleted)
{
// get the session object and see if it contains the Meta data we passed
// in at checkout
var session = objStripeEvent.Data.Object as Session;
var met = session.Metadata;
if (met.ContainsKey("FranchiseGuid"))
{
// if the meta data contains the franchise guid get the correct
// wh secret from the DB
var FranchiseGuid= new Guid(met["FranchiseGuid"]);
endpointSecret = _repo.GetWebHookSecret(FranchiseGuid);
}
}
// Then you can go on to use the Construct method to validate the post with the correct key for the Stripe account where the web hook is based.
try
{
// check if was from Stripe
var stripeEvent = EventUtility.ConstructEvent(
json,
Request.Headers["Stripe-Signature"],
endpointSecret);
---- etc
I've requested help on this from Stripe support but they have promised to get back to me. I'll test out the above to see if it works. I don't think it's ideal though because if a hacker were able to get a valid franchise guid they could possibly fake posts and spam the endpoint. It would not be easy to guess a guid and these id's are not available in any way publicly. Plus https is used. But it still makes me nervous because the franchise guid would be one of a dozen or more. Not like a booking guid which is generated and sent once for the booking that is marked as paid. The franchise guid would be sent every time a payment was made for that franchise.
I think what I may do is use the booking guid since this is randomly generated for every booking. I can join to the franchise table from the booking and get the web hook secret.
We'll see if Stripe come back with something useful.
I followed the Shopify tutorial to create an embedded app using node.js and react. However, I am facing a problem. If the user "Cancel" the subscription, I need to prevent the user to use the app. I have successfully retrieved the subscription status using a webhook, but I am not able to use it on my index.js to determine if the user has approved or not. The problem is that I cannot use cookies inside a POST request.
ctx.res.statusCode = 200;
//COOKIES NOT WORKING INSIDE POST - how to retrieve the subscription status in my index.js so
//I can redirect the user to the subcription url if he didn't accept the subcription ?
ctx.cookies.set('subscriptionStatus', ctx.state.webhook.payload.app_subscription.status, {
httpOnly: false,
sameSite: 'none',
secure: true
});
console.log('received webhook subcription: ', ctx.state.webhook.payload.app_subscription.status);
});
When you receive a cancel webhook in your App, Shopify is giving you the myshopify.com domain name of the store cancelling use of your App. Since your App is receiving the webhook, and since you are storing the name of the shop in your App database, you can simply delete the entry for the shop from your database. That will erase the access token you stored, and hence the person can now not use your App anymore.
Has nothing to do with POST, cookies or anything else really, it is simply you, taking a string, and removing a line from your DB.
I am creating an flutter app that uses firebase authentication. I have added a change email option and have some questions regarding the sessions.
Firstly, when the user changes their email the refresh token is revoked. The firebase documentation examples make use of the realtime database to keep track of the times when tokens are revoked. These are then checked in the database rules. The example to update the database can be seen below
const metadataRef = admin.database().ref('metadata/' + uid);
metadataRef.set({revokeTime: utcRevocationTimeSecs})
.then(() => {
console.log('Database updated successfully.');
});
https://firebase.google.com/docs/auth/admin/manage-sessions#detect_id_token_revocation_in_the_sdk
I'm not sure where to call this code when the email address is changed via the client sdk. Is there a email updated firebase function trigger that I am missing where this timestamp can be written?
I thought about just calling a firebase function but what stops this from being commented out before an attacker updates the email.
Thanks.
I'm currently using express-stormpath for authentication with stormpath in my node.js application. I'm also using stripe. I'm attempting to figure out how to store sessions correctly. Here's what I plan to store in a user session:
session: {
sp_user: { //stormpath user details }
db_user: { //details from my DB }
stripe_expiraton: '234253843923' // unix timestamp
}
So on login I'm planning to create a session for the user with a 7-day expiration. On every API call express middleware will check to see if the stripe expiration date has arrived. If it has, or if there is no expiration date that means we need to redirect the user to the payment page. Otherwise will assume everything is normal and paid up and carry on.
Should I be storing session information this way? I'm not super experienced with session management.
What I'd recommend you do is store this information in your User Account's custom data store. Each Account in Stormpath lets you store any JSON information you want.
This is going to be a much better solution that storing your data in a session, as it might get wiped by the user at any time if it's only client-side.
Also: for Stripe, it doesn't really make sense to store a timestamp in most cases. The way you typically do billing is:
Collect the user's billing information.
Send it to Stripe using their JS APIs, you'll then get back a token.
Send the token to your Express app.
Use that token to create a Stripe Customer Object. This way you can bill the user in the future.
Store the Stripe Customer ID in your Stormpath Custom Data, this way you know how to bill the user later on!
With the above flow, you can automatically bill the user when you need to, instead of redirecting them to the payment page all the time. This also means you don't need to worry about session expiration, etc., and only need to redirect the user to the payment page if their card is expired or no longer valid.
Here's how to do it inside of a route:
app.post('/blah', stormpath.loginRequired, function(req, res, next) {
req.user.getCustomData(function(err, data) {
if (err) return next(err);
data.stripeCustomerId: 'xxx'
data.save(function(err) {
if (err) return next(err);
});
});
});
The above code will save your Stripe Customer ID inside your Stormpath account.
You can then retrieve this data at any time by saying:
req.user.getCustomData(function(err, data) {
if (err) return next(err);
data.stripeCustomerId; // this will be the value you've previously stored
});
Hope that helps =)