Node.js: how to implement payments with Stripe, PayPal and Braintree

Node.js: how to implement payments with Stripe, PayPal and Braintree

In this article we're going to see how to implement payments in Node.js with Stripe, PayPal and Braintree.

In this article we're going to see how to implement payments in Node.js with Stripe, PayPal and Braintree.

Stripe

We install the official Stripe NPM module:

npm install stripe --save

We save our Stripe API credentials in a configuration file:

'use strict';

module.exports = {
    publishableKey: '',
    secretKey: ''
};

Stripe follows an approach already seen in Braintree: payments are processed through interaction between the client-side JavaScript SDK and the server-side NPM module.

The module creates a token that the client must use to finalize the payment. We can create a specific route in ExpressJS for this purpose.

'use strict';

const express = require('express');
const app = express();
const { secretKey } = require('./config');
const stripe = require('stripe')(secretKey);

app.post('/checkout', async (req, res) => {
    const { amount } = req.body;
    try {
        const paymentIntent = await stripe.paymentIntents.create({
            amount: Number(amount) * 100,
            currency: 'eur',
            metadata: {integration_check: 'accept_a_payment'},
        });
        res.json({ clientSecret:  paymentIntent.client_secret });
    } catch(err) {
        res.json(err);
    }
});

The amount sent by the client with the amount parameter must be transformed in number and multiplied by 100 in order to meet the rounding method used by Stripe.

The client-side code is entered like this in the page view:

<head>
<script src="https://js.stripe.com/v3/"></script>
</head>
<body>
<main id="site">

    <form id="payment-form">
        <div id="card-element">
         
        </div>
      
        <div id="card-errors" role="alert"></div>
      
        <div class="form-group mt-5 mb-5">

            <input type="text" id="amount" name="amount" placeholder="Amount" class="form-control">
            
        </div>
        <p>
            <button id="submit" class="btn btn-primary">Pay</button>
        </p>
        <div class="alert mt-5" id="status"></div>    
    </form>

</main>
<script src="/public/js/index.js"></script>
</body>

The client-side JavaScript code is divided into two parts: the first initializes the interface with which the user can enter the credit card number, the expiry date, CVV and zip code.

const stripe = Stripe('publishableKey');
    const elements = stripe.elements();
    const  style = {
        base: {
          color: '#32325d',
        }
    };
    const card = elements.create('card', { style: style });
    card.mount('#card-element');

    card.addEventListener('change', ({error}) => {
        const displayError = document.getElementById('card-errors');
        if (error) {
          displayError.textContent = error.message;
        } else {
          displayError.textContent = '';
        }
    });

The second part makes the payment request. To make it, you must first send an AJAX call in POST to the route that we set previously to get the requested token.

const form = document.getElementById('payment-form');

    form.addEventListener('submit', async e => {
        e.preventDefault();
        let status = document.querySelector('#status');
        let amount = document.querySelector('#amount').value;

        status.style.display = 'none';

        if(!isNaN(Number(amount))) {
            try {
                const data = { amount };
                const resp = await fetch('/checkout', { 
                    method: 'POST', 
                    headers: {
                    'Content-Type': 'application/json'
                    },
                    body: JSON.stringify(data)
                });
                const { clientSecret } = await resp.json();
                stripe.confirmCardPayment(clientSecret, {
                    payment_method: {
                      card: card
                    }
                  }).then(result => {
                    if (result.error) {
                      
                      status.innerText = result.error.message;
                      status.classList.add('alert-danger');
                      status.style.display = 'block';
                    } else {
                      
                      if (result.paymentIntent.status === 'succeeded') {
                        status.innerText = 'Transaction was successfull.';
                        status.classList.add('alert-success');
                        status.style.display = 'block';
                      } else {
                         
                         status.innerText = result.paymentIntent.status;
                        status.classList.add('alert-danger');
                        status.style.display = 'block'; 
                      }
                    }
                  });
            } catch(err) {
                console.log(err);
            }
        }
    });    

Stripe validates the data entered, therefore our task it is limited to the only operation of getting the token from the AJAX route and sending the payment request. Please refer to the Stripe documentation for the various options to customize the payment process.

Source code

GitHub

PayPal

After creating a developer account on the PayPal site, we save our API credentials in a configuration file.

'use strict';

module.exports = {
  mode: 'sandbox',
  clientId: '',
  clientSecret: '',
  returnUrl: 'http://localhost:3000/return',
  cancelUrl: 'http://localhost:3000/cancel'
}; 

We then install the required NPM module:

npm install paypal-rest-sdk --save

The payment flow is as follows:

  1. A payment is created on PayPal with the requested amount. PayPal returns an item containing also the link that the user must use to authorize the payment.
  2. The user authorizes the payment on PayPal and is redirected to the URL specified in the returnUrl variable. Here PayPal returns the transaction data as GET parameters.
  3. Using PayPal's GET parameters, we complete the transaction and inform the user of its outcome.

The route that creates the payment must also save the amount in the current session so that the transaction can then be completed.

'use strict';

const express = require('express');
const router = express.Router();
const paypal = require('paypal-rest-sdk');
const { mode, clientId, clientSecret, returnUrl, cancelUrl } = require('../config');

paypal.configure({
    mode,
    client_id: clientId,
    client_secret: clientSecret
});

router.post('/checkout', (req, res, next) => {
    const { amount } = req.body;
    const item = {
        name: 'Sample product',
        sku: 'sample',
        price: amount,
        currency: 'EUR',
        quantity: 1
    };
    const paymentData = {
        intent: 'sale',
        payer: {
            payment_method: 'paypal'
        },
        redirect_urls: {
            return_url: returnUrl,
            cancel_url: cancelUrl
        },
        transactions: [{
            item_list: {
                items: [ item ]
            },
            amount: {
                currency: 'EUR',
                total: amount
            },
            description: 'Sample payment.'
        }]
    };

    paypal.payment.create(paymentData, (error, payment) => {
        if (error) {
            res.json({ status: false, info: error });
        } else {
            let url = '';
            payment.links.forEach(link => {
                if (link.rel === 'approval_url') {
                    url = link.href;
                }
            });
            req.session.amount = amount;
            res.json({ url });
        }
    });
});

We send the PayPal URL via AJAX to the client-side code so that the latter can perform the redirect.

Now we can handle the end of the transaction when the user is redirected to our application.

router.get('/return', (req, res, next) => {
    if(req.session.amount) {

    const { paymentId, PayerID } = req.query;
    const { amount } = req.session;

    const executePaymentData = {
        payer_id: PayerID,
        transactions: [{
            amount: {
                currency: 'EUR',
                total: amount
            }
        }]
    };

    paypal.payment.execute(paymentId, executePaymentData, (error, payment) => {
        
        delete req.session.amount;

        if (error) {
            res.redirect('/?status=error'); 
        } else {
            res.redirect('/?status=success');
        }
    });
  } else {
      res.redirect('/');
  }
});

As you can see, the procedure is truly linear and very simple to follow and implement.

Source code

GitHub

Braintree

First we create an account in the sandbox of Braintree and we get our API access credentials, i.e. the merchant ID and the public and private keys.

At this point we install the official package.

npm install braintree --save

We insert our credentials in the main configuration file of our application.

'use strict';

module.exports = {
    merchantId: '',
    publicKey: '',
    privateKey: ''
};

Braintree's solution involves the interaction between the client-side JavaScript SDK and the server-side code. So in our reference view we insert the SDK in the head section of the page.

<script src="https://js.braintreegateway.com/web/dropin/1.22.1/js/dropin.min.js"></script>

In order to function, the client-side code requires a token generated by the server. In our reference route we have to generate this token and pass it to the view.

'use strict';

const express = require('express');
const router = express.Router();
const braintree = require('braintree');
const { merchantId, publicKey, privateKey } = require('../config');
const gateway = braintree.connect({
    environment: braintree.Environment.Sandbox,
    merchantId,
    publicKey,
    privateKey
});

router.get('/', (req, res, next) => {
    gateway.clientToken.generate({}, (err, response) => {
        const { clientToken } = response;
        res.render('index', { clientToken } );
    });    
});

//...

module.exports = router;

At this point we can insert the token generated in the view as a JavaScript variable.

<script>
  var clientToken = '<%= clientToken %>';
</script>

The client-side code of the SDK uses this token to require an instance of the main class with which we can perform a payment request and create the payment interface in the view. This request in turn returns an object containing the unique nonce of the transaction. We will use this nonce in an AJAX request to the route that manages the transaction by also passing the total amount to be paid.

"use strict";

$(function() {
    if( clientToken ) {
        var $button = $( "#request" );

        braintree.dropin.create({
            authorization: clientToken,
            container: "#dropin-container"
          }, function ( createErr, instance ) {
            $button.on( "click", function () {
              
              var amount = $( "#amount" ).val();
              var amt = $.trim( amount ).replace( ",", "." );

              if( !isNaN( Number( amt ) ) ) {
                instance.requestPaymentMethod(function ( err, payload ) {
                    $.post( "/checkout", { payment_method_nonce: payload.nonce, amount: amt }, function( res ) {
                        // res.status deve essere true
                    });
                });
              }
            });
        });
    }
});

We just have to process the transaction in the dedicated route.

router.post('/checkout', (req, res, next) => {

    const nonceFromTheClient = req.body.payment_method_nonce;
    const amount = req.body.amount;

    gateway.transaction.sale({
        amount: amount,
        paymentMethodNonce: nonceFromTheClient,
        options: {
          submitForSettlement: true
        }
      }, (err, result) => {
          res.send( { status: result.success  } )
    });
});

As you can see, payments with Braintree are perfectly balanced in the interaction between client and server.

Source code

GitHub

Documentation

  1. Testing
  2. Set Up Your Client
  3. Set Up Your Server