├── .gitignore
├── src
├── client
│ ├── css
│ │ └── main.css
│ └── js
│ │ └── main.js
└── server
│ ├── views
│ ├── index.html
│ ├── error.html
│ ├── layout.html
│ └── product.html
│ ├── routes
│ └── index.js
│ ├── app.js
│ └── bin
│ └── www
├── TODO.md
├── README.md
├── package.json
└── .jshintrc
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .DS_Store
3 | npm-debug.log
4 | .env
5 | dist/
6 |
--------------------------------------------------------------------------------
/src/client/css/main.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding: 50px;
3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
4 | }
5 |
6 | a {
7 | color: #00B7FF;
8 | }
9 |
--------------------------------------------------------------------------------
/src/server/views/index.html:
--------------------------------------------------------------------------------
1 | {% extends 'layout.html' %}
2 |
3 | {% block title %}{% endblock %}
4 |
5 |
6 | {% block content %}
7 |
8 |
9 |
10 |
{{ title }}
11 |
Welcome to {{ title }}
12 |
13 |
14 |
15 | {% endblock %}
16 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | # Todo
2 |
3 | ## Server
4 |
5 | 1. Create a GET route `/products/:name` along with a view
6 | 1. Create a POST route `/charge`
7 |
8 | ## Client
9 |
10 | 1. *main.js* - handle form submission, create stripe token, throw any errors that we get back stripe
11 |
12 |
13 | LOTS MORE!
14 |
--------------------------------------------------------------------------------
/src/server/views/error.html:
--------------------------------------------------------------------------------
1 | {% extends 'layout.html' %}
2 |
3 | {% block title %}{% endblock %}
4 |
5 |
6 | {% block content %}
7 |
8 |
9 |
10 |
{{ message }}
11 |
{{ error.status }}
12 |
{{ error.stack }}
13 |
14 |
15 |
16 | {% endblock %}
17 |
--------------------------------------------------------------------------------
/src/server/views/layout.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ title }}
6 |
7 |
8 |
9 |
10 | {% block content %}
11 | {% endblock %}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Node Stripe Example
2 |
3 | Node + Express + Stripe
4 |
5 | > Looking for a more complex example? [Node Stripe Charge](https://github.com/mjhea0/node-stripe-charge)
6 |
7 | ## Quick Start
8 |
9 | 1. Fork/Clone
10 |
11 | 1. Install dependencies:
12 |
13 | ```sh
14 | $ npm install
15 | ```
16 |
17 | 1. create a *.env* and add the following env variable:
18 |
19 | ```
20 | STRIPE_SECRET_KEY=ADD-YOUR-OWN-KEY
21 | ```
22 |
23 | 1. update *src/client/js/main.js* with your publishable key:
24 |
25 | ```javascript
26 | Stripe.setPublishableKey('UPDATE ME');
27 | ```
28 |
29 | 1. Fire up the app - `npm start`. Then, navigate to [http://localhost:3000/products/1](http://localhost:3000/products/1) in your browser of choice.
30 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "_example",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "start": "node ./src/server/bin/www"
7 | },
8 | "dependencies": {
9 | "body-parser": "~1.13.2",
10 | "cookie-parser": "~1.3.5",
11 | "debug": "~2.2.0",
12 | "dotenv": "^2.0.0",
13 | "express": "~4.13.1",
14 | "morgan": "~1.6.1",
15 | "serve-favicon": "~2.3.0",
16 | "stripe": "^4.4.0",
17 | "swig": "^1.4.2"
18 | },
19 | "devDependencies": {
20 | "browser-sync": "2.9.6",
21 | "gulp": "^3.9.0",
22 | "gulp-clean": "^0.3.1",
23 | "gulp-concat": "^2.6.0",
24 | "gulp-connect": "^2.2.0",
25 | "gulp-jshint": "^1.11.2",
26 | "gulp-minify-css": "^1.2.1",
27 | "gulp-nodemon": "^2.0.4",
28 | "gulp-uglify": "^1.4.2",
29 | "jshint-stylish": "^2.0.1",
30 | "run-sequence": "^1.1.4"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "globals": {
3 | "esnext": true,
4 | "jasmine": false,
5 | "spyOn": false,
6 | "it": false,
7 | "console": false,
8 | "describe": false,
9 | "Stripe": false,
10 | "expect": false,
11 | "beforeEach": false,
12 | "afterEach": false,
13 | "waits": false,
14 | "waitsFor": false,
15 | "runs": false,
16 | "$": false,
17 | "confirm": false
18 | },
19 | "esnext": true,
20 | "node" : true,
21 | "browser" : true,
22 | "boss" : false,
23 | "curly": false,
24 | "debug": false,
25 | "devel": false,
26 | "eqeqeq": true,
27 | "evil": true,
28 | "forin": false,
29 | "immed": true,
30 | "laxbreak": false,
31 | "newcap": true,
32 | "noarg": true,
33 | "noempty": false,
34 | "nonew": false,
35 | "nomen": false,
36 | "onevar": true,
37 | "plusplus": false,
38 | "regexp": false,
39 | "undef": true,
40 | "sub": true,
41 | "strict": false,
42 | "white": true,
43 | "unused": false
44 | }
45 |
--------------------------------------------------------------------------------
/src/client/js/main.js:
--------------------------------------------------------------------------------
1 | Stripe.setPublishableKey("UPDATE ME");
2 |
3 | $(document).on("ready", () => { $("form-errors").hide(); });
4 |
5 | $("#product-form").on("submit", event => {
6 | event.preventDefault();
7 |
8 | $("form-errors").hide();
9 |
10 | Stripe.card.createToken(
11 | {
12 | number: $("#card-number").val(),
13 | cvc: $("#cvv").val(),
14 | exp_month: $("#expiry-month").val(),
15 | exp_year: $("#expiry-year").val()
16 | },
17 | stripeResponseHandler
18 | );
19 |
20 | $("#submit-btn").prop("disabled", true);
21 |
22 | });
23 |
24 | const stripeResponseHandler = (status, response) => {
25 | const $form = $("#product-form");
26 | if (response.error) {
27 | // Show the errors on the form
28 | $("#form-errors").show();
29 | $("#form-errors").html(response.error.message);
30 | $("#submit-btn").prop("disabled", false);
31 | } else {
32 | // response contains id and card, which contains additional card details
33 | const token = response.id;
34 | // Insert the token into the form so it gets submitted to the server
35 | $form.append($('').val(token));
36 | // and submit
37 | console.log($form.get(0));
38 | $form.get(0).submit();
39 | }
40 | };
41 |
--------------------------------------------------------------------------------
/src/server/routes/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
4 |
5 | const products = [
6 | {
7 | uuid: 1,
8 | productName: 'Soylent',
9 | productDescription: 'Meal replacement that tastes like pancake batter.',
10 | productPrice: 10.99
11 | }
12 | ];
13 |
14 | router.get('/', (req, res, next) => {
15 | res.render('index', { title: 'Express' });
16 | });
17 |
18 | router.get('/products/:uuid', (req, res, next) => {
19 | const productID = req.params.uuid;
20 | const product = products.filter((product) => {
21 | return parseInt(productID) === product.uuid;
22 | });
23 | if (product[0]) {
24 | return res.render('product', {productInfo: product[0]});
25 | }
26 | return res.send('Product does not exist.');
27 | });
28 |
29 | router.post('/charge', (req, res,next) => {
30 | const stripeToken = req.body.stripeToken;
31 | const price = req.body.price;
32 | const amount = req.body.price * 100;
33 | const productName = req.body.name;
34 | // ensure amount === actual product amount to avoid fraud
35 | const product = products.filter((product) => {
36 | return productName === product.productName && parseFloat(price) === parseFloat(product.productPrice);
37 | });
38 |
39 | if (product[0]) {
40 | stripe.charges.create({
41 | card: stripeToken,
42 | currency: 'usd',
43 | amount: amount
44 | }, (err, charge) => {
45 | if (err) {
46 | console.log('here');
47 | // console.log(err);
48 | res.send('error');
49 | } else {
50 | res.send('success');
51 | }
52 |
53 | });
54 | } else {
55 | console.log('Product name or price mismatch');
56 | res.send('error');
57 | }
58 | });
59 |
60 | module.exports = router;
61 |
--------------------------------------------------------------------------------
/src/server/app.js:
--------------------------------------------------------------------------------
1 | // *** load env variables *** //
2 | require('dotenv').config();
3 |
4 | // *** main dependencies *** //
5 | const express = require('express');
6 | const path = require('path');
7 | const favicon = require('serve-favicon');
8 | const logger = require('morgan');
9 | const cookieParser = require('cookie-parser');
10 | const bodyParser = require('body-parser');
11 | const { Swig } = require('swig');
12 |
13 | // *** routes *** //
14 | const routes = require('./routes/index.js');
15 |
16 | // *** express instance *** //
17 | const app = express();
18 |
19 | // *** view engine *** //
20 | const swig = new Swig();
21 | app.engine('html', swig.renderFile);
22 | app.set('view engine', 'html');
23 |
24 | // *** static directory *** //
25 | app.set('views', path.join(__dirname, 'views'));
26 |
27 | // *** config middleware *** //
28 | app.use(logger('dev'));
29 | app.use(bodyParser.json());
30 | app.use(bodyParser.urlencoded({ extended: false }));
31 | app.use(cookieParser());
32 | app.use(express.static(path.join(__dirname, '../client')));
33 |
34 | // *** main routes *** //
35 | app.use('/', routes);
36 |
37 | // catch 404 and forward to error handler
38 | app.use((req, res, next) => {
39 | const err = new Error('Not Found');
40 | err.status = 404;
41 | next(err);
42 | });
43 |
44 | // *** error handlers *** //
45 |
46 | // development error handler
47 | // will print stacktrace
48 | if (app.get('env') === 'development') {
49 | app.use((err, req, res, next) => {
50 | res.status(err.status || 500);
51 | res.render('error', {
52 | message: err.message,
53 | error: err
54 | });
55 | });
56 | }
57 |
58 | // production error handler
59 | // no stacktraces leaked to user
60 | app.use((err, req, res, next) => {
61 | res.status(err.status || 500);
62 | res.render('error', {
63 | message: err.message,
64 | error: {}
65 | });
66 | });
67 |
68 | module.exports = app;
69 |
--------------------------------------------------------------------------------
/src/server/bin/www:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * Module dependencies.
5 | */
6 |
7 | const app = require("../app");
8 | const debug = require("debug")("_example:server");
9 | const http = require("http");
10 |
11 | /**
12 | * Normalize a port into a number, string, or false.
13 | */
14 |
15 | const normalizePort = (val) => {
16 | const port = parseInt(val, 10);
17 |
18 | if (isNaN(port)) {
19 | // named pipe
20 | return val;
21 | }
22 |
23 | if (port >= 0) {
24 | // port number
25 | return port;
26 | }
27 |
28 | return false;
29 | }
30 |
31 | /**
32 | * Event listener for HTTP server "error" event.
33 | */
34 |
35 | const onError = error => {
36 | if (error.syscall !== "listen") {
37 | throw error;
38 | }
39 |
40 | const bind = typeof port === "string" ? `Pipe ${port}` : `Port ${port}`;
41 |
42 | // handle specific listen errors with friendly messages
43 | switch (error.code) {
44 | case "EACCES":
45 | console.error(`${bind} requires elevated privileges`);
46 | process.exit(1);
47 | break;
48 | case "EADDRINUSE":
49 | console.error(`${bind} is already in use`);
50 | process.exit(1);
51 | break;
52 | default:
53 | throw error;
54 | }
55 | };
56 |
57 | /**
58 | * Event listener for HTTP server "listening" event.
59 | */
60 |
61 | const onListening = () => {
62 | const addr = server.address();
63 | const bind = typeof addr === "string" ? `pipe ${addr}` : `port ${addr.port}`;
64 | debug(`Listening on ${bind}`);
65 | console.log(`Listening on ${bind}`);
66 | };
67 |
68 | /**
69 | * Get port from environment and store in Express.
70 | */
71 |
72 | const port = normalizePort(process.env.PORT || "3000");
73 | app.set("port", port);
74 |
75 | /**
76 | * Create HTTP server.
77 | */
78 |
79 | const server = http.createServer(app);
80 |
81 | /**
82 | * Listen on provided port, on all network interfaces.
83 | */
84 |
85 | server.listen(port);
86 | server.on("error", onError);
87 | server.on("listening", onListening);
88 |
--------------------------------------------------------------------------------
/src/server/views/product.html:
--------------------------------------------------------------------------------
1 | {% extends 'layout.html' %}
2 |
3 | {% block title %}{% endblock %}
4 |
5 |
6 | {% block content %}
7 |
8 |
9 |
10 |
{{ productInfo.productName }}
11 |
{{ productInfo.productDescription }}
12 |
{{ productInfo.productPrice }}
13 |
14 |
15 |
16 |
17 |
18 |
84 |
85 |
86 |
87 | {% endblock %}
88 |
--------------------------------------------------------------------------------