Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stripe: Must provide source or customer

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>
like image 294
vnguyen Avatar asked Feb 28 '17 05:02

vnguyen


People also ask

What is source in Stripe payment?

A source represents a customer's payment instrument, and can be used with the Stripe API to create payments. Sources can be charged directly, or attached to customers for later reuse.

How do I change my default source in Stripe?

Here, because a default source might already exist for the Customer object, the newly attached source does not become the default source. However, you can change the default source by updating the Customer object and specifying the source as a value for default_source .

Does Stripe store customer data?

The Customer resource is a core entity within Stripe. Use it to store all of the profile, billing, and tax information required to bill a customer for subscriptions and one-off invoices.

Can I charge a customer on Stripe?

You can charge your customers at any time and for any amount once you have saved the customer's card in Stripe. If you have certain action-based criteria which you want to trigger a charge, you can set that logic up on your back end and directing it to charge the card associated with the customer ID.


4 Answers

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.

like image 145
Ywain Avatar answered Oct 16 '22 16:10

Ywain


So I emailed the Stripe support team for help on this issue a few days ago and finally got the solution.

The error was actually in the front-end side. I did not reference jquery under the checkout form. I was able to fix the whole thing by simply adding <script type="text/javascript" src="/javascripts/jquery-3.1.1.min.js"></script> before calling the checkout.js file. My checkout form works now.

Huge props to Stripe for going above and beyond with their support!

like image 43
vnguyen Avatar answered Oct 16 '22 18:10

vnguyen


Maybe I was also referring to same video lectures as you and I encountered the same problem. I just made changes in the /checkout route(post request) in index.js file. Replace source: req.body.stripeToken with source: "tok_mastercard" and it worked for me, but I'm not sure about the reason behind this.

like image 31
Dipesh Budhiraja Avatar answered Oct 16 '22 18:10

Dipesh Budhiraja


Perhaps the same video lecture, but changing to "tok_mastercard" worked for me

like image 24
user10913556 Avatar answered Oct 16 '22 17:10

user10913556