19

I was wondering if there was some kind of "convention" or otherwise a best practice for storing Stripe payment data in the database. There is a real lack of information online and since I am doing it on my own I wanted to get some feedback from others.

The basic setup of my site is that there is a store with several products. There is also an option to subscribe, to receive these products monthly.

So I set up two post routes. One for buying products, and one for subscribing. These use the Stripe API. For orders, I use stripe.charge, and for subscriptions, I create a customer from stripe.customer and subscribe them to a plan.

For stripe.charges, I save the object returned into the database. This contains all of the charge data. There is a webhook sent as charge.succeeded, if the charge succeeded. I do not store this data. Maybe I should just store the charge id here, instead of the entire charge object. I store it as below:

ORDER ROUTE

module.exports = async (req, res) => {
try {
    const charge = await stripe.charges.create({
        amount: req.session.cart.totalPrice,
        currency: 'usd',
        source: req.body.id
    });

    const order = await new Order({
        user: req.user,
        cart: req.session.cart,
        stripeCharge: charge
    }).save();

    req.session.cart = new Cart({});    
    res.sendStatus(200);

} catch (e) {
    console.log(e);
}
};

With subscriptions, it’s a little more complex, because the created customer object does not contain any charge id. So I save the customer id to that user model. Stripe fires 6 webhooks if the subscription is processed fine. I store customer.subscription.created, customer.created, charge.succeeded, and invoice.created. I store the customer object like this:

SUBSCRIPTION ROUTE

module.exports = async (req, res) => {
try {
    if(!req.user.hasSubscription) {
        const customer = await stripe.customers.create({
            email: req.body.email,
            source: req.body.id,
            plan: 'pro'
        });

        await new Subscription({
            user: req.user,
            customerId: customer.id
        }).save();

        req.user.hasSubscription = true;
        await req.user.save();
        res.send(req.user);
    }

} catch (e) {
    console.log(e);
}
};

As you can see, I have a boolean set up on my Mongoose User model, hasSubscription. If this is true, no customer is created, and so no subscription is set up. If it is false, I save the customer id from the customer object created. Then in the webhook route, I save the 4 events above to the correct user, matching by customer id. I could get away with saving less data here I think, possibly making a record of any subscriptions, and any cancellations. I save the webhook events as below:

WEBHOOKS ROUTE

module.exports = async (req, res) => {
try {
    const data = {};        

    if (req.body.type === 'customer.subscription.created') {
        await Subscription.findOneAndUpdate({ customerId: 
        req.body.data.object.customer }, {
            $set: {
                'stripe_data.customer_subscription_created': 
                 req.body.data.object
            }
        }, {
            new: true
        });
        res.sendStatus(200);            
    };

 …//OTHER STRIPE EVENTS SIMILAR TO ABOVE…

 }

I am storing more data than I likely need just to be on the safe side, and I read somewhere that suggested storing adequate data was good for legal compliance. But since Stripe stores everything in the dashboard, I can see your point that a simple piece of identifying information like the charge id would be enough.

I am still in test mode, but I think Stripe only sends back the last 4 digits of the card, and the expiry year like so:

exp_year: 2042 last4: '4242'

With regards to saving confidential information to the database: I do not see any more sensitive information being exposed. The card itself is referenced by an id that Stripe creates.

HPJM
  • 517
  • 8
  • 17
  • Can you clarify what you mean by "payment data"? Do you mean like-- product/stock management or order history or card details? I'm a bit lost on this. --- I think you're probably best to rely on webhook events to get this information and just update a local datastore asynchronously, regardless of your answer though. – korben Mar 06 '18 at 18:32
  • 5
    In general, you can store any data *returned* by the API's. The important thing is that you're not letting user's payment information (card number, bank numbers, etc.) touch your database and only working with their tokens / ids. As for what you store for your own use, there's not really a "best practice", other than don't store data you don't really need. And even if you do at some point "need" it, do you need it stored or is fetching it OK? In most cases, you can get away with just storing the charge id or equivalent. – Jake T. Mar 07 '18 at 00:47
  • Apologies for the lack of clarity - I have edited my OP above. – HPJM Mar 07 '18 at 12:17

1 Answers1

9

In Stripes security docs, it mentions Out-of-scope card data that can be safely stored. Jake T.'s comment is really the answer -- everything returned from the API is okay for you to store.

Card type, expiration date, and last four digits are not subject to PCI compliance.

wallyjaw
  • 191
  • 1
  • 5