Skip to main content

Cookie Session Cleared After Payment

After a customer makes a payment, their cookie session gets cleared. This results in them getting sent back to the home page after a successful redirect since I use cookies to verify what step a customer is at to prevent skips. I cannot reproduce this issue. My colleagues cannot reproduce this issue. However, nearly 100% of payments that go through when I'm not observing result in a cleared cookie session.

Additional context:

  1. I use Node.js.
  2. I host my service on AWS Lambda.
  3. I use Chargebee + Stripe to handle payments.

I've observed this issue with the following browser/device combinations (user agent info):

  • Chrome 95.0.4638 / Mac OS X 10.15.7
  • Chrome 96.0.4664 / Windows 10.0.0
  • Chrome Mobile 96.0.4664 / Android 0.0.0
  • Mobile Safari 15.1.0 / iOS 15.1.1
  • Safari 14.1.2 / Mac OS X 10.15.6
  • Chrome 96.0.4664 / Mac OS X 10.14.6

I've tested this on user agents: Chrome 96.0.4664 / Mac OS X 10.15.7, and Safari 15.1.0 / Mac OS X 10.15.7. I did not verify the user agents of my colleagues, but I know it's been tested on mobile and Windows as well, with no issues.

I've also observed this issue with payment methods originating from multiple countries (including the U.S.) and on both Debit and Credit Cards.

Here's relevant code snippet 1 (app.js, I instantiate a cookie session where I store info about the user). I tried adding sameSite: 'none' after reading other posts that this might solve the issue (see: browser clears session cookies), but the issue persisted.

app.use(cookieSession({
 name: 'session', // name of the cookie
 keys: {defined some keys}, // used to generate signed cookie value.
 cookie: {
    sameSite: 'none', // possibly required for re-directing after payment gateway is used?
    secure: true, // set to true for production. Cookie will only be used on HTTPS domains.
    httpOnly: true, // prevents users from accessing cookies from client side javascript
    signed: true // appends cookie with a .sig suffix (cookie is signed)
}

}));

That being said, I don't see SameSite=None in the browser when clicking on the cookie. See image below:

SameSite not set?

Here's relevant code snippet 2 (use.pug, page that opens a Chargebee modal for payments). This is where I instantiate the payment modal and define what happens on successful payment. Notice I redirect the user to a /basicinfo route on success:

script.
 function sendDataToServer(data) {
  let appEnv = 'prod';

  if (appEnv === 'prod'){
    window.location = `https://{site_url}/basicinfo?hostedPage=${data}`;
  }
 }
 document.addEventListener("DOMContentLoaded", function() {
  // Open chargebee instance
  let cbInstance = Chargebee.getInstance();

  // Open cart info
  let cart = cbInstance.getCart();
  let customer = {}; // create customer object
  customer["cf_u_id"] = '!{ID}'; // assign customer information
  cart.setCustomer(customer); // set customer information

  cbInstance.setCheckoutCallbacks(function(cart) {
    return {
      loaded: function() {
      },
      close: function() {
      },
      success: function(hostedPageId) {
        sendDataToServer(hostedPageId);
        // Hosted page id will be unique token for the checkout that happened
        // You can pass this hosted page id to your backend 
        // and then call our retrieve hosted page api to get subscription details
        // https://apidocs.chargebee.com/docs/api/hosted_pages#retrieve_a_hosted_page
      },
      step: function(value) {
      }
    }
  });
 });

Here's relevant code snippet 3 (app.js, where GET basicinfo route is defined). Essentially, what I do here is check that the payment went through correctly, update the cookie as a result, and then render the basicinfo page assuming success:

app.get(app.locals.ep.basicinfo, csrfProtection, (req, res) => {
 console.log(`New cookie session? ${req.session.isNew}`);
 console.log(`Get BasicInfo Form Step: ${req.session.formStep}`);
 console.log(`Get BasicInfo Session Cookie: ${JSON.stringify(req.session)}`, '\n');
 console.log(`Full ID: ${req.session.ID}`, '\n');

 async function track6(){
    if(req.session.refAns.doc !== {criteria}){ // everyone with bug meets criteria
        // check to see that payment succeeded
        let urlData = url.parse(req.url,true).query; // parse the returned URL
        let hostedPage = urlData.hostedPage; // get the hosted page ID created by chargebee
        console.log(`Hosted Page: ${hostedPage}`);

        await new Promise((resolve, reject) => {
            chargebee.hosted_page.retrieve(`${hostedPage}`).request(function(error,result) {
                if(error){
                    console.log(`Hosted page retrieval error: ${error}`);
                    req.session.formStep = formStep.REFERRAL; // make sure cookie is referral
                    resolve();
                } else {
                    let createdAt = result.hosted_page.created_at;
                    console.log(`Hosted page created at UTC time(s): ${createdAt}`);

                    let invoiceStatus = result.hosted_page.content.invoice.status;
                    console.log(`Invoice status: ${invoiceStatus}`, '\n');

                    let presentTime = Date.now()/1000; // UTC time in seconds
                    console.log(`Present UTC time(s): ${presentTime}`);

                    let timeSinceCreate = presentTime - createdAt; // in seconds
                    console.log(`Days since hosted page was created: ${timeSinceCreate/86400}`);

                    if(invoiceStatus === 'paid' && timeSinceCreate <= 259200){ // paid and no more than 3 days
                        req.session.formStep = formStep.ACKNOWUSE; // update the cookie
                        req.session.payment = 'success';
                        resolve();
                    } else {
                        req.session.formStep = formStep.REFERRAL; // make sure cookie is referral
                        resolve();
                    }
                }
            });
        });
    }

    if(req.session.formStep !== formStep.ACKNOWUSE && req.session.formStep !== formStep.BASICINFO && req.session.formStep !== formStep.EVERIFIED){ // if patient hasn't visited the use page and hasn't been everified
        res.redirect(app.locals.sp.use); // redirect to the use page
    } else {
        let wfPage = serverAuth.webflow.style;

        if(req.session.botStatus === false) {
            await new Promise((resolve, reject) => {
                mixpanel.track('Reached BasicInfo', {
                    distinct_id: req.session.ID
                }, function (error) {
                    console.log(`Track Error: ${error}`, '\n');
                    resolve();
                });
            });
        }

        res.render('basicinfo', {
            wfPage: wfPage,
            payment: req.session.payment,
            csrfToken: req.csrfToken() // pass the csrfToken to the view
        });
    }
}
track6();

});

What I expect, and what I observe in testing, is that the user's cookie will be preserved, I'll check to see that payment succeeded by retrieving payment data from Chargebee, I'll update the cookie accordingly, and then I'll render the basicinfo page.

Instead, what I observe is that req.session.isNew = true (should be false, is false when I test), req.session.formStep is undefined (should be 'referral', is 'referral' when I test), and req.session.refAns is undefined (should exist, exists when I test). As a result, the site crashes at line: 'if(req.session.refAns.doc !== {criteria}){', since req.session.refAns is undefined. req.session.refAns should not be undefined, since it is defined in a prior site step and stored in the user's cookie. In all cases where this bug has occurred, when I read the users cookie data, up to and including the use route where the payment is made, req.session.refAns is defined.

So my question is this: why is the user's cookie getting deleted after they successfully pay? And why can't I recreate this issue? I know the cookie size is not exceeding 4000 bytes. What I typically see in my own testing is a cookie size of about 400 bytes when the bug happens.

Strangely, some people have found a workaround all on their own by attempting to pay multiple times. Sometimes on the 2nd or 3rd attempt it works and the cookie doesn't get cleared.

Via Active questions tagged javascript - Stack Overflow https://ift.tt/2FdjaAW

Comments

Popular posts from this blog

Where and how is this Laravel kernel constructor called? [closed]

Where and how is this Laravel kernel constructor called? public fucntion __construct(Application $app, $Router $roouter) { } I have read the documentation and some online tutorial but I can find any clear explanation. I am learning Laravel and I am wondering where does this kernel constructor receives its arguments from. "POSTMOTERM" CLARIFICATION: Here is more clarity.I have checked the boostrap/app.php and it is only used for boostrapping the interfaces into the container class. What is not clear to me is where and how the Kernel class is instatiated and the arguments passed to the object calling the constructor.Something similar to; obj = new kernel(arg1,arg2) or, is the framework using some magic functions somewhere? Special gratitude to those who burn their eyeballs and brain cells on this trivia before it goes into a full blown menopause alias "MARKED AS DUPLICATE". To some of the itchy-finger keyboard warriors, a.k.a The mods,because I believe in th...

Why is my reports service not connecting?

I am trying to pull some data from a Postgres database using Node.js and node-postures but I can't figure out why my service isn't connecting. my routes/index.js file: const express = require('express'); const router = express.Router(); const ordersCountController = require('../controllers/ordersCountController'); const ordersController = require('../controllers/ordersController'); const weeklyReportsController = require('../controllers/weeklyReportsController'); router.get('/orders_count', ordersCountController); router.get('/orders', ordersController); router.get('/weekly_reports', weeklyReportsController); module.exports = router; My controllers/weeklyReportsController.js file: const weeklyReportsService = require('../services/weeklyReportsService'); const weeklyReportsController = async (req, res) => { try { const data = await weeklyReportsService; res.json({data}) console...

How to show number of registered users in Laravel based on usertype?

i'm trying to display data from the database in the admin dashboard i used this: <?php use Illuminate\Support\Facades\DB; $users = DB::table('users')->count(); echo $users; ?> and i have successfully get the correct data from the database but what if i want to display a specific data for example in this user table there is "usertype" that specify if the user is normal user or admin i want to user the same code above but to display a specific usertype i tried this: <?php use Illuminate\Support\Facades\DB; $users = DB::table('users')->count()->WHERE usertype =admin; echo $users; ?> but it didn't work, what am i doing wrong? source https://stackoverflow.com/questions/68199726/how-to-show-number-of-registered-users-in-laravel-based-on-usertype