Zabezpečení dat kreditních karet v aplikacích elektronického obchodování pomocí Rave

Od nástupu elektronického obchodování se kreditní karty staly rozšířeným způsobem plateb za zboží a služby. Po jejich masivním přijetí se staly zranitelnými vůči únikům dat a hackům. V důsledku toho musí online tržiště chránit platební údaje svých uživatelů. Jedním ze způsobů, jak je to možné, je tokenizace – kdy je číslo kreditní karty zákazníka nahrazeno řadou náhodně generovaných čísel nazývaných „token“. Tento token pak může být předán do různých bezdrátových sítí potřebných ke zpracování platby, aniž by byly odhaleny skutečné údaje o kartě, protože jsou uloženy v zabezpečeném trezoru tokenů. V tomto článku si ukážeme následující:

  • Vytvořte falešnou aplikaci pro výběr daní pomocí prostého JavaScriptu a Rave – online platební brány.
  • Použijte kreditní kartu k zaplacení naší daně a nechte aplikaci zobrazit token použitý k nahrazení údajů o naší kartě.
  • Ověřte, že token skutečně představuje podrobnosti o naší kreditní kartě tím, že jej použijete k provedení další platby v aplikaci.

Pojďme kopat!

Začínáme

Začneme instalací knihoven, které naše aplikace bude potřebovat. Předtím se předpokládá, že na vašem počítači již máte Node a npm. Zde jsou knihovny, které nainstalujeme:

live-server:HTTP vývojový server s možností živého načítání. Nainstalujte to globálně.
body-parser:middleware pro analýzu těla uzlu, který analyzuje těla příchozích požadavků v middlewaru před vašimi handlery a zpřístupňuje je v req.body vlastnictví.
dotenv:Modul s nulovou závislostí, který načítá proměnné prostředí z .env soubor do process.env .
express:Minimální a flexibilní rámec webových aplikací Node, který poskytuje robustní sadu funkcí pro vývoj webových a mobilních aplikací.
morgan:Middleware pro protokolování požadavků HTTP pro Node.
ravepay:Raveova knihovna pro Node.
heroku-logger:Logger pro aplikace Heroku.

Nejprve vytvoříme složku pro daňovou aplikaci, nainstalujeme do ní tyto knihovny spuštěním následujících příkazů na našem terminálu:


   mkdir tax-app
   cd tax-app
   npm install #library name#

Poté si vytvoříme účet Rave, abychom mohli získat naše veřejné a tajné klíče API. Zde si můžete zaregistrovat účet Rave.
Když dokončíme vytváření celého projektu, jeho složka by měla vypadat takto:

Definování uživatelského rozhraní naší aplikace

Prvním krokem je vytvoření HTML stránky pro naši aplikaci. Ve složce aplikace vytvořte podsložku a nazvěte ji frontend . Zde vytvoříte index.html soubor:


// frontend/index.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">
    <title>Tax Application</title>
    <link rel="stylesheet" href="./style.css">
    <link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous">
</head>
<body>
    <div class="parent">
    <div id="wrapper" class="wrapper">
        <section class="card-api">
            <h4>Tax Payment App</h4>
            <h5>Pay via credit/debit card here:</h5>
            <form id="card-form" method="post">
                <div class="cardno">
                        <label for="cardno">Card Number: </label>
                        <input type="text" name="cardno" id="cardno">
                </div>

                <div class="cvv">
                    <label for="cvv">CVV: </label>
                    <input type="text" name="cvv" id="cvv">
                </div>

                <div class="expiry">
                    <label for="expiry">Expiry Date: </label>
                    <input type="text" name="expdate" id="expdate">
                </div>
                <div class="pin">
                    <label for="pin">PIN: </label>
                    <input type="text" name="pin" id="pin">
                </div>
                <button class="pay" id="card-pay" type="submit">Pay Here</button>

            </form>
        </section>
        <section class="tokencharge">
            <h4>Pay with Token here:</h4>
            <form id="token-form" method="post">
                <div class="token">
                    <label for="token">Card Token: </label>
                    <input type="text" name="token" id="token">
                </div>

                <button class="pay" id="token-pay" type="submit">Pay Here</button>
            </form>
        </section>
    </div>
    </div>

    <script src="./js/index.js"></script>
</body>
</html>

Poté nakonfigurujeme styl pro náš soubor HTML:



// frontend/styles.css

input {
    border-radius: 5px;
    width: 50%;
}
.wrapper {
    display: block;
    justify-content:center; 
    align-self: stretch;
    padding: 20px;
    background-color: #75a3a3;
    border-radius: 5px;
    font-family: 'Montserrat', sans-serif;
    font-size: 20px;
    width: 30%;
}
.error {
    margin-top: 15px;
    background: #a5391c;
    color: #fafafa;
    padding: 15px;
    border-radius: 6px;
    margin-left: 10px;
}
.success {
    margin-top: 15px;
    background: #138052;
    color: #fafafa;
    padding: 15px;
    width: auto;
    border-radius: 6px;
    max-width: 100%;
    margin-left: 10px;
}
button.pay {
    padding: 10px;
    border: 1px solid #1d255b;
    background: none;
    cursor: pointer;
    margin-top: 20px;
    border-radius: 5px;
    width: 100%;
    font-family: 'Montserrat', sans-serif;
    font-size: 15px;
}
button.pay:hover{
    background: #1d255b;
    color: #fafafa;
    cursor: pointer;
}
.cardno {
    display: flex;
    justify-content:space-between; 
    margin: 10px
}
.cvv {
    display: flex;
    justify-content:space-between; 
    margin: 10px
}
.expiry {
    display: flex;
    justify-content:space-between; 
    margin: 10px
}
.pin {
    display: flex;
    justify-content:space-between; 
    margin: 10px
}
 .token {
    display: flex;
    justify-content:space-between; 
    margin: 10px
 }
 section {
    justify-content:center;
     margin: 50px;
 }

Až budete hotovi, uložte oba soubory a přejděte na frontend složku ve vašem terminálu pro opětovné spuštění aplikace:


    cd frontend && live-server --port=3000


Ve vašem prohlížeči byste měli mít něco podobného:

Vytváření platebních cest a funkcí

V této části vytvoříme trasy pro naši aplikaci. Uděláme to tak, že nejprve vytvoříme instanci Express Router() a pomocí jeho post směrovací API k vytvoření initiatecharge a completecharge koncové body, které budou účtovat jakékoli poplatky za kreditní karty. Vytvoříme také chargetokenizedcard koncový bod, který tokenizuje všechny informace o kreditní kartě a vrátí token, který lze použít pro následné transakce. Je důležité poznamenat, že toto vše se děje prostřednictvím knihovny Node JS společnosti Rave, která slouží jako základní infrastruktura:



const router = require('express').Router();
var Ravepay = require("ravepay"); // require rave nodejs sdk
const logger = require('heroku-logger');
var rave = new Ravepay(process.env.RAVE_PUBLIC_KEY, process.env.RAVE_SECRET_KEY, false); // get public and secret keys from environment variables stored in the .env file.
const util = require('util');

Pojďme definovat cardPaymentObject který zpracovává údaje o kreditní kartě jakéhokoli daňového poplatníka:


// rave/index.js

var cardPaymentObject = {
    "currency": "NGN",
    "country": "NG",
    "amount": "10",
    "suggested_auth": "pin",
    "email": "[email protected]",
    "phonenumber": "08147658720",
    "firstname": "Raphael",
    "lastname": "Ugwu",
    "IP": "355426087298442",
    "txRef": "MC-" + Date.now(),// your unique merchant reference
    "meta": [{metaname: "flightID", metavalue: "123949494DC"}],
    "redirect_url": "https://your-redirect-url.com/redirect",
    "device_fingerprint": "69e6b7f0b72037aa8428b70fbe03986c",
}

Poté definujeme naše směrovací API:


    // rave/index.js

    router.get('/', (req, res) => {
        console.log("Here's the rave route");
        res.json({message: "Here's the rave route"});
    });

    router.post('/initiatecharge', (req, res) => {
        var { cardno, expdate, cvv, pin } = req.body;
        // update payload
        cardPaymentObject.cardno = cardno;
        cardPaymentObject.cvv = cvv;
        cardPaymentObject.pin = pin;
        cardPaymentObject.expirymonth = expdate.split('/')[0];
        cardPaymentObject.expiryyear = expdate.split('/')[1];
        logger.info(JSON.stringify(cardPaymentObject));
        rave.Card.charge(cardPaymentObject)
            .then((response) => {
                logger.info(JSON.stringify(response));
                res.json(response)
            })
            .catch((error) => {
                logger.error(error)
                res.json(error)
            })
    });
    router.post('/chargetokenizedcard', (req, res) =>  {
        var { token } = req.body;
        cardPaymentObject.token = token;
        logger.info(cardPaymentObject);
        rave.TokenCharge.card(cardPaymentObject)
            .then((response) => {
                // console.log(response)
                res.json(response)
            }).catch(error => {
                // console.log(error)
                res.json(error)
            })
    });
    router.post('/completecharge', (req,res) => {
        var { transaction_reference, transactionreference, otp } = req.body;
        // perform card charge validation
        rave.Card.validate({
            transaction_reference,
            otp
        }).then((response) => {
            console.log(response)
            res.json(response)
        }).catch(error => {
            console.log(error)
            res.json(error)
        })

    })

    module.exports = router;

Vytvoření serveru Node

Naším dalším krokem je vytvoření serveru Node, který bude reagovat na požadavky, které zadáme na frontendu naší aplikace. V kořenové složce aplikace vytvořte soubor s názvem app.js a do něj vložte ukázku kódu níže:


const app = require('express')();
const fs = require('fs')
const bodyParser = require('body-parser');
const morgan = require('morgan');
var port = process.env.PORT || 80 // 2. Using process.env.PORT
// app.use(cors(corsOptions));
app.use(function (req, res, next) {
    // 'https://hidden-earth-62758.herokuapp.com'
    // Website you wish to allow to connect
    res.setHeader('Access-Control-Allow-Origin', '*');
    // Request methods you wish to allow
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
    // Request headers you wish to allow
    res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,Content-Type,Authorization,Accept');
    // Set to true if you need the website to include cookies in the requests sent
    // to the API (e.g. in case you use sessions)
    res.setHeader('Access-Control-Allow-Credentials', true);
    // Pass to next layer of middleware
    next();
});
const rave = require('./rave');
app.use(bodyParser.urlencoded({extended:false, limit: '10mb'}));
app.use(bodyParser.json());
app.use(morgan('dev'));
app.get('/', (req, res) => {
    res.send({message: 'Split Payment Sample'});
})
app.use('/rave', rave);
app.set('port', port);
app.listen(port, '', () => {
     console.info('App listening on port %s', port);
})

Ve výše uvedené ukázce kódu zajišťujeme, že náš server zpracovává jakýkoli příchozí požadavek prostřednictvím middlewaru Express. To zahrnuje protokolování všech požadavků HTTP pomocí morgan , analyzující tělo příchozích požadavků pomocí bodyParser a vyžadující platební cesty a funkce, které jsme definovali dříve.

Zpracování platební logiky naší aplikace

Pojďme vytvořit některé funkce pro lepší interakci s našimi aplikacemi na frontendu. V frontend vytvořte soubor a pojmenujte jej index.js . Nejprve definujeme všechny proměnné, které potřebujeme pro manipulaci s daty:


    var cardPay = document.getElementById('card-pay');
    var tokenPay = document.getElementById('token-pay');
    var cardForm = document.getElementById("card-form");
    var tokenForm = document.getElementById("token-form");
    var wrapper = document.getElementById("wrapper");
    var server_url = 'http://localhost:80/'; // the nodejs server url

Poté definujeme funkci, kterou potřebujeme pro zpracování jakéhokoli požadavku, který vytvoříme na koncových bodech, které jsme vytvořili dříve:


    function makeRequest(endpoint, data, external=false) {
        var url = external ? endpoint : server_url + endpoint;
        var options = {
            method: "POST", 
            mode: "cors",
            cache: "no-cache",
            credentials: "same-origin",
            headers: {
                "Content-Type": "application/json; charset=utf-8",
            },
            redirect: "follow", 
            referrer: "no-referrer", 
            body: JSON.stringify(data)
        }
        return new Promise(function _(resolve, reject) {
            fetch(url, options).then(function _(response) {
                console.log(response)
                return response.json()
            }).then(function _ (data) {
                console.log(data)
                if(data.body == undefined) resolve(data)
                resolve(data.body)
            }).catch(function _ (error) {
                reject(error)
            }).catch(function _ (error) {
                reject(error)
            })
        });
    }

Abychom dali našim uživatelům vědět, pokud by mohlo dojít k chybě, vytvoříme dvě funkce – jednu pro zobrazení zprávy o úspěchu a druhou pro zobrazení chybové zprávy v případě, že požadavek POST neprojde:


 //frontent/index.js

function showSuccessMessage(message, element) {
    var div = document.createElement("div");
    div.setAttribute('class','success');
    div.setAttribute('id','message');
    div.innerHTML = '<i class="fas fa-check-circle"></i>  ' +message
    div.appendAfter(element)
}

function showErrorMessage(message, element) {
    var div = document.createElement("div");
    div.setAttribute('class','error')
    div.setAttribute('id','message')
    div.innerHTML = '<i class="fas fa-times-circle"></i>  ' +message
    div.appendAfter(element)
}

Dále propojme tlačítko s makeRequest() funkce a initiatecharge koncový bod:


cardPay.addEventListener('click', function(e) {
 e.preventDefault();

 // call api
 var formData = extractFormValues(cardForm);
 makeRequest('rave/initiatecharge', formData)
  .then(response => {
   if (response.status == "error") {
    showErrorMessage(response.message, cardPay);
   }
   if (response.data.chargeResponseCode == 02) { // a chargeResponseCode of 02 depicts that the transaction needs OTP validation to continue
    otp = prompt(response.data.chargeResponseMessage);
    transaction_reference = response.data.flwRef;
    makeRequest('rave/completecharge', {
      otp,
      transaction_reference
     })
     .then(function _(response) {
      if (response.data.data.responsecode == 00) {
       // the card token is accessed here: response.data.tx.chargeToken.embed_token
       showSuccessMessage(response.data.data.responsemessage + "<br/>Here is your token, you may use this for subsequent payments<br/>" + response.data.tx.chargeToken.embed_token, cardPay);
      } else if (response.data.data.responsecode == 'RR') { // the charge failed for the reason contained in // response.data.data.responsemessage
       showErrorMessage(response.data.data.responsemessage, cardPay)
      } else { // the charge failed for the reason contained in // response.message
       showErrorMessage(response.message, cardPay)
      }
     }).catch(function _(error) {
      showErrorMessage(error, cardPay)
     })
   }
  }).catch(function _(error) {
   showErrorMessage(error, cardPay)
  })
});

Zadávání proměnných prostředí

Ve všech trasách, které jsme vytvořili, jsme zmínili naše veřejné a tajné klíče, ale ještě je musíme definovat. Udělejme to tak, že je specifikujeme jako proměnné prostředí v .env soubor, který vytvoříme v kořenové složce naší aplikace:


    //  .env

    RAVE_PUBLIC_KEY= *YOUR RAVE PUBLIC KEY HERE*
    RAVE_SECRET_KEY=*YOUR RAVE SECRET KEY HERE*

Ověření platby

Dokončili jsme vytváření celé aplikace. Uložte veškerou svou práci a na svém terminálu spusťte server Node:


    nodemon app.js

Zatímco server běží, otevřete další kartu v terminálu a spusťte vývojový server pro aplikaci:


    cd client && live-server --port=3000

Tím se aplikace otevře na adrese http://127.0.0.1:3000 ve vašem prohlížeči. V tomto okamžiku byste měli aplikaci vidět. Zkuste provést platbu pomocí jedné z testovacích karet uvedených zde:

Abychom potvrdili, že platba proběhla úspěšně, zašleme nám potvrzení:

Když jsme poprvé použili k platbě kartu, dostali jsme token. Proveďte platbu pomocí tokenu:

A také za to dostaneme účtenku:

Závěr

Tokenizace karty je skvělý způsob, jak chránit údaje o kartách uživatelů vaší aplikace před zachycením a zneužitím ke škodlivým účelům. V tomto článku jsme vytvořili aplikaci, která ukazuje, jak Rave tokenizuje kartu a používá tento token pro budoucí transakci. Zdrojový kód této aplikace si můžete prohlédnout na GitHubu.