Environment: Express, express-session, Stripe
In the following simplified example when a user requests the home page express-session
assigns the user a session cookie. Refreshing the page retains the same session id as does visiting the success or fail routes. Clicking the upgrade button takes the client to a Stripe shopping cart screen that also keeps the same session id. However once the user is at the Stripe shopping cart if the user makes a successful purchase he is forwarded to the success route and the session id is over-written by Stripe. In the full version this is a problem because the user would be logged in and this causes the user to be automatically logged out after the successful purchase. I'm not sure why this is happening or how to stop it.
app.js
const bodyParser = require('body-parser');
require('dotenv').config();
const express = require('express');
const session = require('express-session');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const app = express();
app.set('view engine', 'ejs');
app.use(express.static('views'));
app.use(
session({
maxAge: 24 * 60 * 60 * 1000,
name: 'randomName',
resave: false,
saveUninitialized: true,
secret: 'randomSecret',
cookie: {
sameSite: true,
secure: false
}
})
);
app.get('/', function(req, res) {
req.session.userValues = true;
console.log(req.session);
res.render('index', { stripePublicKey: process.env.STRIPE_PUBLIC_KEY });
});
app.get('/success', function(req, res) {
console.log(req.session);
res.render('success');
});
app.get('/fail', function(req, res) {
console.log(req.session);
res.render('fail');
});
app.post('/create-checkout-session', bodyParser.raw({ type: 'application/json' }), async function(req, res) {
console.log(req.session);
const session = await stripe.checkout.sessions.create({
submit_type: 'auto',
payment_method_types: ['card'],
line_items: [
{
price_data: {
currency: 'usd',
product_data: {
name: 'name of product',
description: 'description of product'
},
unit_amount: 100
},
quantity: 1,
}
],
locale: 'en',
mode: 'payment',
success_url: 'http://localhost:8080/success',
cancel_url: 'http://localhost:8080/fail'
});
res.json({ id: session.id });
});
app.listen(8080, function() {
console.log('listening on port 8080');
});
index.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Upgrade</title>
<script>var stripePublicKey = '<%- stripePublicKey %>';</script>
<script defer src="https://js.stripe.com/v3/"></script>
<script defer src="checkout.js"></script>
</head>
<body>
<button id="checkout-button">upgrade premium</button>
</body>
</html>
checkout.js
var stripe = Stripe(stripePublicKey);
var checkoutButton = document.getElementById('checkout-button');
checkoutButton.addEventListener('click', checkoutSequence);
function checkoutSequence() {
fetch('/create-checkout-session', {
method: 'POST',
})
.then(function(response) {
return response.json();
})
.then(function(session) {
console.log(session);
return stripe.redirectToCheckout({ sessionId: session.id });
})
.then(function(result) {
if (result.error) {
alert(result.error.message);
}
})
.catch(function(error) {
console.error('Error:', error);
});
}
After 6 hours of testing I found the problem. cookie.sameSite
must be set to lax
instead of true
. Evidently when Stripe hits the success route express-session
determines that this is coming from an outside site and resets the cookie.
app.use(
session({
maxAge: 24 * 60 * 60 * 1000,
name: 'randomName',
resave: false,
saveUninitialized: true,
secret: 'randomSecret',
cookie: {
sameSite: 'lax',
secure: false
}
})
);