Stripe: Must provide source or customer

vnguyen picture vnguyen · Feb 28, 2017 · Viewed 17.3k times · Source

I'm trying to integrate Stripe for my shopping cart project. I can't get my checkout form to submit. I keep getting this error message: "Must provide source or customer." Either I didn't set up my Stripe account correctly or I'm missing some parameters in my javascript. I've spent hours on this problem and still can't figure it out.

This is from Stripe's log:
Parsed Request POST Body

{
  "amount": "21000",
  "currency": "usd",
  "description": "Test Charge"
}

Response Body

{
  "error": {
    "type": "invalid_request_error",
    "message": "Must provide source or customer."
  }
} 

This is my app.js

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var expressHbs = require('express-handlebars');
var mongoose = require('mongoose');
var session = require('express-session');
var passport = require('passport');
var flash = require('connect-flash');
var validator = require('express-validator');
var MongoStore = require('connect-mongo')(session);

var routes = require('./routes/index');
var userRoutes = require('./routes/user');

var app = express();

mongoose.connect('localhost:27017/shopping');
require('./config/passport');

// view engine setup
app.engine('.hbs', expressHbs({defaultLayout: 'layout', extname: '.hbs'}));
app.set('view engine', '.hbs');

// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(validator());
app.use(cookieParser());
app.use(session({
    secret: 'mysupersecret', 
    resave: false, 
    saveUninitialized: false,
    store: new MongoStore({ mongooseConnection: mongoose.connection }),
    cookie: { maxAge: 180 * 60 * 1000 }
}));
app.use(flash());
app.use(passport.initialize());
app.use(passport.session());
app.use(express.static(path.join(__dirname, 'public')));

app.use(function(req, res, next){
    res.locals.login = req.isAuthenticated();
    res.locals.session = req.session;
    next();
});

app.use('/user', userRoutes);
app.use('/', routes);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

This is my index.js:

var express = require('express');
var router = express.Router();
var Cart = require('../models/cart');

var Product = require('../models/product');

/* GET home page. */
router.get('/', function(req, res, next) {
    var successMsg = req.flash('success')[0];
    Product.find(function(err, docs) {
        var productChunks = [];
        var chunkSize = 3;
        for (var i = 0; i < docs.length; i += chunkSize) {
            productChunks.push(docs.slice(i, i + chunkSize));
        }
        res.render('shop/index', { title: 'Shopping Cart', products: productChunks, successMsg: successMsg, noMessages: !successMsg});
    });
});

router.get('/add-to-cart/:id', function(req, res, next) {
    var productId = req.params.id;
    var cart = new Cart(req.session.cart ? req.session.cart : {});

    Product.findById(productId, function(err, product){
        if (err) {
            return res.redirect('/');
        }
        cart.add(product, product.id);
        req.session.cart = cart;
        console.log(req.session.cart);
        res.redirect('/');
    });
});

router.get('/shopping-cart', function(req, res, next) {
    if (!req.session.cart) {
        return res.render('shop/shopping-cart', {products: null});
    }
    var cart = new Cart(req.session.cart);
    res.render('shop/shopping-cart', {products: cart.generateArray(), totalPrice: cart.totalPrice});
});

router.get('/checkout', function(req, res, next) {
    if (!req.session.cart) {
        return res.redirect('/shopping-cart');
    }
    var cart = new Cart(req.session.cart);
    var errMsg = req.flash('error')[0];
    res.render('shop/checkout', {total: cart.totalPrice, errMsg: errMsg, noError: !errMsg});
});

router.post('/checkout', function(req, res, next) {
    if (!req.session.cart) {
        return res.redirect('/shopping-cart');
    }
    var cart = new Cart(req.session.cart);
    var stripe = require("stripe")(
      "**hidden**"
    );

    stripe.charges.create({
      amount: cart.totalPrice * 100,
      currency: "usd",
      source: req.body.stripeToken,
      description: "Test Charge"
    }, function(err, charge) {
        if (err) {
            req.flash('error', err.message);
            return res.redirect('/checkout');
        }
        req.flash('success', 'Successfully bought product!');
        req.cart = null;
        res.redirect('/');
    });
});

module.exports = router;

This is my checkout.js:

Stripe.setPublishableKey('**hidden**');

var $form = $('checkout-form');

$form.submit(function(event) {
    $('#charge-errors').addClass('hidden');
    $form.find('button').prop('disabled', true);
    Stripe.card.createToken({
      number: $('#card-number').val(),
      cvc: $('#card-cvc').val(),
      exp_month: $('#card-expiry-month').val(),
      exp_year: $('#card-expiry-year').val(),
      name: $('#card-name').val()
    }, stripeResponseHandler);
    return false;
});

function stripeResponseHandler(status, response) {
    if (response.error) { // Problem!

    // Show the errors on the form
    $('#charge-errors').text(response.error.message);
    $('#charge-errors').removeClass('hidden');
    $form.find('button').prop('disabled', false); // Re-enable submission

  } else { // Token was created!

    // Get the token ID:
    var token = response.id;

    // Insert the token into the form so it gets submitted to the server:
    $form.append($('<input type="hidden" name="stripeToken" />').val(token));

    // Submit the form:
    $form.get(0).submit();
  }
};

This is my checkout form (.hbs):

<div class="row">
  <div class="col-sm-6 col-md-6 col-md-offset-3 col-sm-offset-3">
    <h1>Checkout</h1>
    <h4>Your Total: ${{total}}</h4>
    <div id="charge-error" class="alert alert-danger {{# if noError}}hidden{{/if}}">
        {{errMsg}}
    </div>
    <form action="/checkout" method="post" id="checkout-form">
      <div class="row">
          <div class="col-xs-12">
            <div class="form-group">
              <label for="name">Name</label>
              <input type="text" id="name" class="form-control" required>
            </div>
          </div>
          <div class="col-xs-12">
            <div class="form-group">
              <label for="name">Address</label>
              <input type="text" id="address" class="form-control" required>
            </div>
          </div>
          <div class="col-xs-12">
            <div class="form-group">
              <label for="name">Name on Card</label>
              <input type="text" id="card-name" class="form-control" required>
            </div>
          </div>
          <div class="col-xs-12">
            <div class="form-group">
              <label for="name">Credit Card Number</label>
              <input type="text" id="card-number" class="form-control" required>
            </div>
          </div>
          <div class="col-xs-6">
            <div class="form-group">
              <label for="name">Expiration Month</label>
              <input type="text" id="card-expiry-month" class="form-control" required>
            </div>
          </div>
          <div class="col-xs-6">
            <div class="form-group">
              <label for="name">Expiration Year</label>
              <input type="text" id="card-expiry-year" class="form-control" required>
            </div>
          </div>
          <div class="col-xs-12">
              <div class="form-group">
              <label for="name">
              CVC</label>
              <input type="text" id="card-cvc" class="form-control" required>
              </div>
          </div>
      </div>
      <button type="submit" class="btn btn-success">Buy Now</button>
    </form>
  </div>
</div>

<script type="text/javascript" src="https://js.stripe.com/v2/"></script>
<script type="text/javascript" src="javascripts/checkout.js"></script>

Answer

Ywain picture Ywain · Feb 28, 2017

A payment flow with Stripe is divided in two steps:

  1. Client-side, in your frontend code, you collect and tokenize the customer's payment information using Checkout or Elements, then send the resulting token to your backend server.

  2. Server-side, in your backend code, you use the token in an API request, e.g. to create a charge or a customer.

The code you shared is for the first step. However, the error message you mentioned:

Must provide source or customer.

occurs in the second step. This error is returned by Stripe's API when you send a charge creation request without a source or customer parameter.

You need to check your server-side code to figure out what the problem is exactly. You can also check the logs of all requests sent by your integration in your dashboard: https://dashboard.stripe.com/test/logs?method=not_get.