DEV Community

Cover image for How To Integrate Direct Card Payment on Your Website Using Flutterwave

How To Integrate Direct Card Payment on Your Website Using Flutterwave

As the demand for online payment options increases, card payments have quickly proven to spike the interest of most customers. Their appeal lies in their speed, accessibility, and security.

Unlike some payment methods, which require multiple steps for bank approval or third-party verification (like Automated Clearing House (ACH) transfers or wire transfers), card payments offer a more direct process. Once the issuing bank authorizes a card payment, it is then processed almost immediately, without the need for additional approval, reducing the delay in completing the transaction.

Flutterwave facilitates various payment methods, including card charges, bank transfers, and mobile money. Flutterwave’s card payment option provides a reliable and secure platform for handling card payments, fully compliant with Payment Card Industry Data Security Standard (PCI DSS). This makes collecting payments seamless, ensuring a smooth customer experience.

In this tutorial, you’ll learn how to integrate Flutterwave’s direct card payment into your web application. You’ll also learn about the available card options and understand their differences. Finally, you’ll review the best practices for making online card payments.

Prerequisites

Before delving into integration, you should have the following:

  1. Experience with building server-side apps with Nodejs.
  2. Experience with HTML, CSS, and Javascript.
  3. A good understanding of how payment gateways work.
  4. A Flutterwave account.
  5. The node package manager npm installed on your device. If you don't have one, here’s how to install it.

Let's look at the basic steps to follow when integrating.

Basic Steps to Integrating Card Payment

There are 3 main steps to keep in mind when integrating Flutterwave direct card charges:

  1. Initiating the Charge: This is the initial request, where you will send the card details (Card number, expiry date, CVV). The request will also include other required payloads like:

    • currency - The currency for the transaction (e.g., "NGN")
    • amount - The amount to charge
    • email - Customer's email address
    • full_name - Customer's full name
    • phone_number - Customer's phone number
    • enckey - Your Flutterwave encryption key
    • tx_ref - A unique transaction reference

    An encryption key is needed to make this request. Encrypting sensitive card details enhances the security of the transaction data being sent.

  2. Completing the Charge: Depending on the type of card passed in the previous request, you’ll get a response from Flutterwave regarding the next step. Here are the four possible card scenarios you can expect and the next steps for these cards.

    a. PIN Cards: These are cards that require a PIN for authorization.

    • Next Step: First, collect the card PIN from the customer and send it with the next request. Then, the user will be required to enter an OTP to complete the payment.

    b. 3DS/VBV Cards: These cards use 3D Secure or Verified by Visa authorization.

    • Next Step: Redirect the customer to their bank page to complete the charge.

    c. No Authentication: These card types require completing the charge with the user’s PIN.

    • Next Step: Collect the card PIN from the customer and send it with the next request.

    d. Address Verification Service (AVS): These cards require the user’s billing address for authentication.

    • Next Step: Collect the billing address details from the customer and send it with the next request.
  3. Validate the Charge: This is the final step to completing the charge. It is mainly applicable to the PIN cards. You’ll need to send in a one-time password to validate and complete the card payment.

Now that you understand the basic steps for mocking different cards let’s create a work environment to integrate them.

Setting Up Work Environment

  1. First, go to your preferred IDE and run the command below to create a new directory.

    mkdir direct-charge
    cd direct-charge
    
  2. Next, run the command below to initiate a node project.

    npm init -y 
    
  3. Add a .env and a server.js file to the directory with the command.

    touch server.js .env
    
  4. Go to your Flutterwave dashboard and navigate to the Settings > API key section to get your API keys. Copy and paste them into the variables below in your .env file.

    FLW_PUBLIC_KEY=<YOUR_PUBLIC_KEY_HERE>
    FLW_SECRET_KEY=<YOUR_SECRET_KEY_HERE>
    FLW_ENCRYPTION_KEY=<YOUR_ENCRYPTION_KEY_HERE>
    
  5. Finally, run the command below to install the following packages.

    npm install cors dotenv express Flutterwave-node-v3 nodemon 
    

Let’s set up your node server.

Setting Up Integration Server

Now that you have the essential setup ready and understand how the payment flow works, let’s integrate Flutterwave’s direct card charges using the Flutterwave Node.js SDK.

  1. Navigate to the server.js file and add the code below to initialize your backend server.

    // server.js
    
    const express = require('express');
    const Flutterwave = require('Flutterwave-node-v3');
    const cors = require('cors');
    const bodyParser = require('body-parser');
    const app = express();
    const port = process.env.PORT || 3000;
    const dotenv = require('dotenv');
    dotenv.config();
    
    const flw = new Flutterwave(process.env.FLW_PUBLIC_KEY, process.env.FLW_SECRET_KEY);
    
    app.use(cors());
    app.use(express.json());
    app.use(bodyParser.json());
    app.use(express.urlencoded({ extended: true }));
    app.use(express.static(__dirname));
    
    // Endpoint for rendering static HTML form
    app.get('/', (req, res) => {
    res.sendFile(__dirname + './index.html');
    });
    
    app.listen(port, () => {
    console.log(`Server running at http://localhost:${port}`);
    });
    

    The code above imports and sets up the packages you’ll need to build your integration server, including the Flutterwave-node-v3 package. The first endpoint here will be responsible for rendering the HTML form you’ll create to simulate your payment flow.

  2. Next, set up a /charge-card endpoint to initialize the card payment. The endpoint initializes the card payment by calling the flw.Charge.card method. It handles the various flows that Flutterwave requires to send the next step parameters. Add the code below:

    // server.js
    // Endpoint for rendering static HTML form
    
    // Handle card charge
    app.post('/charge-card', async (req, res) => {
        new_ref = "example" + Date.now();
        const payload = {
            card_number: req.body.card_number,
            cvv: req.body.cvv,
            expiry_month: req.body.expiry_month,
            expiry_year: req.body.expiry_year,
            currency: "NGN",
            amount: "100",
            email: req.body.email,
            fullname: req.body.fullname,
            phone_number: req.body.phone_number,
            enckey: process.env.FLW_ENCRYPTION_KEY,
            tx_ref: new_ref,
        };
        new_payload = payload;
    
        try {
            const response = await flw.Charge.card(payload);
            if (response.meta.authorization.mode === 'pin') {
                res.json({ 
                    status: 'pin_required', 
                });
            } else if (response.meta.authorization.mode === 'redirect') {
                res.json({ 
                    status: 'redirect_required', 
                    url: response.meta.authorization.redirect, 
                    tx_ref: new_ref
                });
            } else if (response.meta.authorization.mode === 'otp') {
                res.json({ 
                    status: 'otp_required', 
                    tx_ref: new_ref,
                    flw_ref: response.data.flw_ref
                });
            } else {
                res.json({ status: 'success', data: response.data });
            }
        } catch (error) {
            console.error(error);
            res.status(500).json({ status: 'error', message: error.message });
        }
    });
    
    // app.listen(port, () => {
    

    If the card details sent in the request are a PIN Card or a 3DS Card, Flutterwave returns a payload telling you what to send to authorize the charge.

  3. Next, create a /complete-charge endpoint that calls Flutterwave flw.Charge.card method, but this time, passing in the authorization required by Flutterwave to authorize the charge.

    // server.js
    // Endpoint to Handle card charge
    
    // Handle additional authorization
    app.post('/complete-charge', async (req, res) => {
        const { authMode, pin } = req.body;
        try {
            let response;
            if (authMode === 'pin') {
                response = await flw.Charge.card({
                    ...new_payload,
                    authorization: {
                        mode: 'pin',
                        pin: pin
                    }
                });            
            }
    
            if (response.status === "success") {
                res.json({ status: 'success', data: response });
            } else {
                res.json({ status: 'failed', message: response.data.processor_response });
            }
        } catch (error) {
            console.error(error);
            res.status(500).json({ status: 'error', message: error.message });
        }
    });
    
  4. Add an endpoint to validate the transaction, which will call the flw.Charge.validate method passing in an OTP to complete the card charge.

    // server.js
    // Endpoint to Handle additional authorization
    
    // Validates the card charge
    app.post('/validate-charge', async (req, res) => {
        try {
            const { otp, flw_ref } = req.body;
            const response = await flw.Charge.validate({
                otp: otp,
                flw_ref: flw_ref
            });
            res.json(response);
        } catch (error) {
            res.status(500).json({ error: error.message });
        }
    });
    
  5. Finally, run the command below to run the server. Use nodemon for this, so your server keeps running while you make changes.

    nodemon server.js
    

    Now that you have your server-side integration set up, let’s create a front-end form for users to enter their card details before making requests to Flutterwave.

Implementing the Payment Form

  1. Create the following files index.html and styles.css, by running the command below:

    touch index.html styles.css
    
  2. Head over to GitHub to copy and add the CSS script to your styles.css file.

  3. Now, let's create a basic HTML form for users to input their card details and the additional authorization parameters (PIN and OTP) required to authorize the card payment. You’ll only display the field for these inputs after the first response is sent, and you will know the type of card you’re charging and the parameters to send next.

    /* index.html */
    
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="stylesheet" href="./styles.css"/>
        <title>Flutterwave Card Payment</title>
      </head>
      <body>
        <div class="container">
          <h1>Flutterwave Card Payment</h1>
          <form id="paymentForm">
            <input type="text" name="card_number" placeholder="Card Number" >
            <input type="text" name="cvv" placeholder="CVV" >
            <input type="text" name="expiry_month" placeholder="Expiry Month" >
            <input type="text" name="expiry_year" placeholder="Expiry Year" >
            <input type="email" name="email" placeholder="Email" >
            <input type="text" name="fullname" placeholder="Full Name" >
            <input type="tel" name="phone_number" placeholder="Phone Number" >
            <button type="submit">Pay Now</button>
          </form>
    
          <div id="authForm" style="display: none;">
            <h2>Additional Authorization Required</h2>
            <form id="additionalAuthForm">
              <div id="pinAuth" style="display: none;">
                <input type="password" name="pin" placeholder="Enter PIN" >
              </div>
              <div id="otpAuth" style="display: none;">
                <input type="text" name="otp" placeholder="Enter OTP" >
              </div>
              <button type="submit">Complete Payment</button>
            </form>
          </div>
    
          <div id="response"></div>
        </div>
        <script></script>
      </body>
    </html>
    

    Now that your server is running, navigate to the port http://localhost:3000/ on your browser, and you should see an image like the one below.

    Form to initiate card payment

    Next, you’ll create the flow to simulate multiple scenarios.

    Note: For this tutorial, you’ll focus on simulating the PIN, 3DS, and No-Authorization card scenarios.
    Head over to Flutterwave’s testing page to get card details to mock the multiple card scenarios you’ll cover.

Handling Payment Flow

Over the next few steps, you’ll simulate the payment flow for the multiple card scenarios (PIN, No Auth, and 3DS).

Step 1: Initialize Variable

Create the global variables to capture new responses from Flutterwave and use them across multiple endpoints.

    <!-- index.html  -->
    </div> 
          <script>
              let currentFlwRef = '';
              let currentAuthMode = '';
              let currentTxRef = '';
          </script>
      </body>

Enter fullscreen mode Exit fullscreen mode
  • currentFlwRef: Stores the current Flutterwave reference for the transaction.

  • currentAuthMode: Tracks the current authentication mode (e.g., 'pin', 'OTP').

  • currentTxRef: Stores the current transaction reference.

Step 2: Handle Form Submission

Attach an event listener to the form with the ID paymentForm to enable asynchronous handling of the form submission. When the form is submitted, you’ll use a handleChargeResponse() function to handle the authorization for the next step.

    <!-- index.html -->
    <script>
    ...
    // Step 1 code goes here
    ...

      document.getElementById('paymentForm').addEventListener('submit', async (e) => {
                  e.preventDefault();
                  const formData = new FormData(e.target);
                  const payload = Object.fromEntries(formData);
                  try {
                      const response = await fetch('/charge-card', {
                          method: 'POST',
                          headers: { 'Content-Type': 'application/json' },
                          body: JSON.stringify(payload)
                      });
                      const result = await response.json();
                      handleChargeResponse(result);
                  } catch (error) {
                      console.error('Error:', error);
                  }
              });
    </script>
Enter fullscreen mode Exit fullscreen mode

Step 3: Handle Payment Response

Now, let’s create a handleChargeResponse function to handle the response you get from Flutterwave depending on the type of card used to initiate the transfer so you’ll know what authorization parameters to send.

/* index.html */

<script>
...
// Step 2 code goes here
...

function handleChargeResponse(result) {
            if (result.status === 'pin_required') {
                currentFlwRef = result.flw_ref;
                currentTxRef = result.tx_ref;
                currentAuthMode = 'pin';
                showAuthForm(currentAuthMode);
            } else if (result.status === 'redirect_required') {
                // Automatically redirect to the OTP page
                window.location.href = result.url;
            } else if (result.status === 'success') {
                document.getElementById('response').innerText = 'Payment successful!';
            } else {
                document.getElementById('response').innerText = 'Payment failed. Please try again.';
            }
        }
</script>
Enter fullscreen mode Exit fullscreen mode

Step 4: Show Authorization Form

After sending the initial request /charge-card and getting a response from Flutterwave, depending on the type of card details passed, you’ll display the additional form to the user, either to pass in their card PIN for PIN cards or redirect them to their bank or an OTP to validate and complete the charge.

/* index.html */
<script>
...
// Step 3 code goes here
...
        function showAuthForm(authMode) {
            document.getElementById('authForm').style.display = 'block';
            document.getElementById('pinAuth').style.display = authMode === 'pin' ? 'block' : 'none';
            document.getElementById('otpAuth').style.display = authMode === 'otp' ? 'block' : 'none';
        }
</script>
Enter fullscreen mode Exit fullscreen mode

Put in your PIN

redirected to bank for OTP

Step 5: Handle Additional Authorization Form Submission

When the additionalAuthForm is submitted, the code prevents the default behaviour of handling the submission. It collects the form data and checks the currentAuthMode to determine whether the request should proceed with PIN or OTP verification.

/* index.html */
<script>
...
// Step 4 code goes here
...
// Handle Additional Authorization Form Submission
document.getElementById('additionalAuthForm').addEventListener('submit', async (e) => {
    e.preventDefault();

    const formData = new FormData(e.target);
    const authData = Object.fromEntries(formData);

    try {
        if (currentAuthMode === 'pin') {
            // Handle PIN submission in the next step
        } else if (currentAuthMode === 'otp') {
            // Handle OTP submission
            const response = await fetch('/validate-charge', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    otp: authData.otp,
                    flw_ref: currentFlwRef
                })
            });
            const result = await response.json();

            if (result.status === 'success') {
                document.getElementById('response').innerText = 'Payment successful!';
                document.getElementById('authForm').style.display = 'none';
            } else {
                document.getElementById('response').innerText = 'OTP validation failed. Please try again.';
            }
        }
    } catch (error) {
        console.error('Error:', error);
        document.getElementById('response').innerText = 'An error occurred. Please try again.';
    }
});
</script>
Enter fullscreen mode Exit fullscreen mode

Step 6: Handle PIN Response

If the currentAuthMode is set to pin, a request is sent to flw.Charge.validate with the entered PIN. The response is checked for success:

/* index.html */
<script>
...
// Step 5 code goes here
...
if (currentAuthMode === 'pin') {
    const response = await fetch('/complete-charge', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            authMode: 'pin',
            pin: authData.pin,
        })
    });
    const result = await response.json();

    currentFlwRef = result.data.data.flw_ref;
    currentTxRef = result.data.data.tx_ref;

    if (result.status === 'success' && result.data.data.status === 'pending') {
        currentAuthMode = 'otp';
        showAuthForm('otp');
    } else if (result.status === 'success' && result.data.data.status === 'successful') {
        document.getElementById('response').innerText = 'Payment successful!';
        document.getElementById('authForm').style.display = 'none';
    } else {
        document.getElementById('response').innerText = 'PIN validation failed. Please try again.';
    }
}
</script>
Enter fullscreen mode Exit fullscreen mode
  • If pending, the currentAuthMode is updated to otp, prompting the user for OTP verification.

Enter your OTP

  • If successful, a success message is displayed like in the image below.

Payment Successful

  • If validation fails, an error message prompts the user to re-enter the PIN.

Error message prompts user to try again

You can also check out the complete demo app on GitHub.

Wrapping Up

Now that you’ve gone through the steps to integrate Flutterwave card payment into your website using the Flutterwave Node SDK, you can begin accepting seamless card payments from your customers.

With this integration, you’ve added a key feature that meets the demands of today’s economy, setting your business up for growth and customer satisfaction.

Check out our Node.js SDK for more payment methods you can integrate. You can also check out our developer documentation on best practices.

Read more on how to integrate a payment gateway into your e-commerce app.

Top comments (0)