Monday 17 September 2018

Stripe api + node express + heroku credit card transaction


left sales form, right response from Stripe account

 set price, click purchase, form open enter test visa #

transaction, card, customer info processed by Stripe account

set #, click history, top few transactions are displayed

app is published to heroku, Stripe account keys are stored privately in config

In Stripe account, all transactions are logged

look into transaction detail

project folder

--config
  --keys.js
  --keys_dev.js
  --keys_prod.js

--public
  --img
    --marketplace.png
    --sale.png

--views
  --layouts
    --main.handlebars
  --index.handlebars

--.gitignore
--app.js
--package.json

----------------------------------------------

package.json

{
  "name": "node-stripe",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "start": "node app.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.18.3",
    "express": "^4.16.3",
    "express-handlebars": "^3.0.0",
    "stripe": "^6.10.0"
  }
}

-----------------------------------

app.js

const express = require('express');
const keys = require('./config/keys')
const stripe = require('stripe')(keys.stripeSecretKey);
const bodyParser = require('body-parser');
const exphbs = require('express-handlebars');
const path = require('path');

const app = express();

//Handlebars Middleware
app.engine('handlebars', exphbs({ defaultLayout: 'main' }));
app.set('view engine', 'handlebars');

//Body parser Middleware
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

//Set static Folder
app.use(express.static(path.join(__dirname, './public')));

//Index Route
app.get('/', (req, res) => {
    res.render('index', { stripePublishableKey: keys.stripePublishableKey }); 
})

//Charge Route
app.post('/charge', (req, res) => {
    console.log(req.body);

    //create/update customer
    stripe.customers.create({
        email: req.body.token.email,
        source: req.body.token.id
    })
        //create charge
        .then(customer => stripe.charges.create(
            {
                amount: req.body.price*100,
                description: 'one step charge',
                currency: 'cad',
                customer: customer.id,
                metadata: { order_id: req.body.orderId },
            }
        ))
        .then(charge => res.send('Transaction successful'))
        .catch(err => res.send(err.message))
});

//transaction history route
app.post('/history', (req, res) => {
    console.log(req.body);

    stripe.balance.listTransactions({ limit: req.body.num }, function (err, transactions) {
        // asynchronously called
        if (!err) {
            console.log(transactions);
            res.send(transactions.data);
        } else {
            console.log('error');
        }
    });
});

const port = process.env.PORT || 5000;

app.listen(port, () => {
    console.log('server started on port ', port);
});

----------------------------------

index.handlebars

<section>
    <div class="row">

        <div class="col-md-6 text-center">
            <img src="/img/sale.png" alt="sale" class="img-fluid" />

            <form>
                Price: $<input type="number" id="price" placeholder="123.45"
                               onchange=priceChange()><br /><br />

                <script src="https://checkout.stripe.com/checkout.js"></script>
                <script src="https://unpkg.com/axios/dist/axios.min.js"></script>

                <button id="customButton" class="btn-primary btn-block">Purchase</button><br />

                Show # of transactions: <input type="number" id="transactionNum" placeholder="3"
                                               onchange=transactionNumChange()><br /><br />
                <button id="historyButton" class="btn-success btn-block">Transaction Histories</button><br />

                <script>

                    var handler = StripeCheckout.configure({
                        key: '{{stripePublishableKey}}',
                        image: '/img/marketplace.png',
                        locale: 'auto',
                        token: function (token) {
                            //when form is submitted, form will wait for token to be generated before close

                            document.getElementById('transactionId').innerHTML = token.id.slice(4);
                            document.getElementById('transactionTime').innerHTML = new Date(token.created * 1000).toUTCString();
                            document.getElementById('cardId').innerHTML = token.card.id.slice(5);
                            document.getElementById('clientIP').innerHTML = token.client_ip;

                            //generate order ID
                            orderId = parseInt(Math.random() * 10000);

                            //send to server
                            axios.post('/charge', { token: token, price: price, orderId: orderId })
                                .then(function (response) {
                                    document.getElementById('serverRes').innerHTML = 'Purchase request sent. ' + response.data;
                                    console.log(response);
                                })
                                .catch(function (error) {
                                    document.getElementById('serverRes').innerHTML = error.statusText;
                                    console.log(error);
                                });
                        }
                    });

                    //purchase button click event
                    document.getElementById('customButton').addEventListener('click', async function (e) {
                        // Open Checkout with further options:
                        await handler.open({
                            name: 'Purchase',
                            description: 'Credit card',
                            currency: 'cad',
                            amount: price * 100,
                        });
                        e.preventDefault();
                    });

                    //history button click event
                    document.getElementById('historyButton').addEventListener('click', function (e) {

                        //send to server
                        axios.post('/history', { num: transactionNum })
                            .then( function (response) {
                                var serverText = document.getElementById('serverRes');
                                serverText.innerHTML = '<br/>';                             

                                for (var i = 0; i < response.data.length; i++) {
                                    var time = new Date(response.data[i].created * 1000).toUTCString();

                                    serverText.innerHTML = serverText.innerHTML
                                        + 'ID: '+ response.data[i].id + '<br/>'
                                        + 'Time: ' + time + '<br/>'
                                        + 'Amount: ' + response.data[i].amount/100 + '<br/>'
                                        + 'Fee: ' + response.data[i].fee / 100 + '<br/>'
                                        + 'Net: ' + response.data[i].net / 100 + '<br/><br/>'
                                }

                             
                                console.log(response);
                            })
                            .catch(function (error) {
                                document.getElementById('serverRes').innerHTML = error.statusText;
                                console.log(error);
                            });

                        e.preventDefault();
                    });

                    // Close Checkout on page navigation:
                    window.addEventListener('popstate', function () {
                        handler.close();
                    });
                </script>
            </form>
        </div>

        <div class="col-md-6">
            use card # 4242 4242 4242 4242 M/Y 12/34 CYC 111 to test<br />

            <span class="text-warning">server response:</span>  <span id="serverRes"></span><br />

            <span class="text-info">transactionId:</span> <span id="transactionId"></span><br />
            <span class="text-info">transactionTime:</span> <span id="transactionTime"></span><br />
            <span class="text-info">cardId:</span> <span id="cardId"></span><br />
            <span class="text-info">clientIP:</span> <span id="clientIP"></span><br />
        </div>
    </div>
</section>
<script>
    var price = 123.45;
    var orderId = 1234;
    var transactionNum = 3;

    function priceChange() {
        price = parseFloat(document.getElementById("price").value).toFixed(2);
    }

    function transactionNumChange() {
        transactionNum = parseInt(document.getElementById("transactionNum").value)
    }

</script>

--------------------------------------

main.handlebars

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width-device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" crossorigin="anonymous">
    <title>stripe transactions</title>
</head>
<body>
    <nav class="navbar navbar-expand-md navbar-dark bg-dark">
        <div class="container">
            <a href="/" class="navbar-brand">
                <h3 class="d-inline align-middle">Stripe</h3>
            </a>
        </div>
    </nav>
    <div class="container">
        {{{body}}}
    </div>
</body>
</html>

----------------------------------------------

keys.js

if (process.env.NODE_ENV === 'production') {
    module.exports = require('./keys_prod');
} else {
    module.exports = require('./keys_dev');
}

------------------------------------

keys_prod.js


module.exports = {
    stripePublishableKey: process.env.STRIPE_PUBLISHABLE_KEY,
    stripeSecretKey: process.env.STRIPE_SECRET_KEY
}
------------------------------------

.gitignore

node_modules
key_dev.js
--------------------------------

reference:
http://chuanshuoge2.blogspot.com/2018/09/stripe-api.html

No comments:

Post a Comment