Integrate Payment with Flutterwave and Facebook Authentication into a React Application.

Hey there you have gone through the react documentation and some resources over the internet and you are ready to put this knowledge to use. In this tutorial, we are going to integrate Facebook Login and Flutterwave V3 rave API in a react application. So I have this site under construction spyo.com, a campaign platform where brands get support from their fans in a bid to grow. Spyo is built using NextJs - A framework for building react applications, MongoDB a document-based database, the Bulma react UI library kit and Flutterwave’s Rave Card payment API. Flutterwave is an African payment solution provider to make and accept payments from customers anywhere in the world. By the end of this tutorial, you should have built some parts of Spyo, and learned some underlying react concepts. The tutorial will cover authentication with Facebook, and the integration of payment with Flutterwave.

React concepts

In this tutorial we are going to cover the following react components or concepts:

Let us get started

Installing required applications to run the application

The first step to this tutorial will be installing all relevant dependencies. Make sure you have the following installed Node Js for running the base application we shall be building on, Mongo for the application database, git to clone the base app repository. After installing all dependencies required to run the app, the next step is to clone the application.

Cloning base app repo

Open your terminal and change to the directory on your PC you wish to clone the app and run the git clone command.

git clone --recursive https://github.com/tanerochris/spo-tuto-base.git

Install dependencies with yarn

yarn install

Running the app

When you are done cloning, the name of the application folder will be spo-tuto-base. Change to the spo-tuto-base directory and run yarn to start the application. We will start by running the application in developer mode.

    cd spo-tuto-base
    yarn dev

In your browser go to the URL http://localhost:3000, you should see the base interface we shall be building with. To focus only on the essential react concepts to be presented in this write up, the application is set up with some design and functionality already.

To run the application in production mode, you first build the app for optimizations by NextJS framework then run app.

    yarn build
    yarn start

NextJS comes with react installed. To check for the version of react installed, open a new terminal session.

    yarn list --pattern --depth=0 "^react*"

You should see the version of react installed together with other react packages. In my own case the above command produces the following output at the terminal. Notice we are working with version 16.13.1 of react which is currently the latest at the time of this writing.

    $ yarn list --pattern --depth=0 "^react*"
    yarn list v1.22.4
    warning Filtering by arguments is deprecated. Please use the pattern option instead.
    ├─ react-dom@16.13.1
    ├─ react-is@16.13.1
    ├─ react-refresh@0.8.3
    └─ react@16.13.1
    Done in 1.67s.

Structure of the application

We are going to look at the structure of the app, where each item will fit in, some screen shots at the output we wish to obtain.

.
|-- LICENSE // hello
|-- README.md
|-- app.properties
|-- components
|   |-- partials
|   `-- theme
|-- helpers
|-- middlewares
|   |-- Database.js
|   |-- Session.js
|   `-- index.js
|-- models
|   `-- user
|-- node_modules
|-- package.json
|-- pages
|   |-- _app.js
|   |-- api
|   `-- index.js
|-- public
|-- schemas

NextJs is an opinionated framework, it requires the pages and public folders to be present in the directory structure and located at the root level. We will add other directories to hold specific items of our application. Below is a summary detail of what each directory should contain. Note this is not specific to react or NextJS so you can define your own structure.

Let’s start building with react

Building the Signup — Authenticating with Facebook Login

In react application is broken down into components were each component can be seen as an independent re-usable piece of code. In react there are two types of components functional components and class components. In this tutorial we shall use functional components as they allow us to use the concepts of hooks. Functional components are stateless components while class components are stateful as the come with a built-in component state object. Functional components make use of the useState hook to implement state to a component.

Class component

    import React from 'react';

    class Signup extends React.Component {
        state = {}
        render() {
            return <>
                A React Class Component.
            </>
        }
    }
    export default Login;

Functional Component

    import React from 'react';
    
    const Login = (props) => {
        return <> 
            A react functional component.
        </>
    }
    export default Login;

props and state

In react the props object is used to pass information from a parent component to a child component. Attributes of a component become keys in the props object and the passed values are accessed through these attribute keys. With a react class component the prop object is imutable unlike the case of functional components. React class components manage component state by using the state property and setState method to update the state.

Now we are going to create our SignUp component that will represent the Signup page and authenticate with Facebook Login.

Step 1

In the pages folder we create the file signup.js and type in the following code.

import axios from 'axios';
import FacebookLogin from 'react-facebook-login';
import { useRouter } from 'next/router';
import { getSession } from '../helpers/session-helpers';
import AppHeader from '../components/partials/appHeader';

const Signup = (props) => {
    const [errorMessage, setErrorMessage] = useState('');
    const router = useRouter();
    const onSignUpResponse = (response) => {};  
    return (
        <>
            <AppHeader session={props.session}/>
            <main>
                <div className="alert-error">
                    <span>{errorMessage}</span>
                </div>
                <div className="auth-container bg-secondary" >
                    <div>
                        <FacebookLogin  
                            appId="xxxxxxxxxxxxx"
                            autoLoad={false}
                            textButton=" Register with Facebook" 
                            fields="name,email,picture"
                            callback={onSignUpResponse}
                            cssClass="social-button fb-button"
                            icon="fab fa-facebook-square" />
                    </div>
                </div>
            </main>
        </>
    );
  }

function isBrowser() {
    return typeof window !== 'undefined';
}

export async function getServerSideProps( { req, res }) {
    const sessionString = await getSession(req, res);
    const session = JSON.parse(sessionString);
    if (!isBrowser() && session.user) {
      res.writeHead(302, { Location: '/' });
      res.end();
    }
    return { props: { session }}
}
export default Signup;

Step 2

Install the react-facebook-login package for Facebook login button and the axios package to make api calls to save user credentials.

    yarn add react-facebook-login axios

To authenticate with Facebook you need to go to developers.facebook.com and create an application to have an app id. Copy your app Id and make it the value of the appId attribute of the FacebookLogin component above. Here is an online that tutorial that shows how you can generate an appId for your app. When you are done setting up your app and updating the appId property of the FacebookLogin button, reload the Signup page. You should have the result below.

Notice the isBrowser and getServerSideProps functions, isBrowser tests if the component is running from a server or browser window and getServerSideProps is a NextJs function to pass properties to the client component on server-side rendering. We can have our sign up component without the above functions, but in our case we want to track if a user has an active session and redirect the user to the index page. We are using the NextJS router and useState when we cover react hooks later in the tutorial.

Data returned by the getServerSideProps function is passed as an argument to the Signup functional component. This argument represents the properties of the functional component. Props properties can also be passed to other child components as attributes as we can observe with the <AppHeader session={props.session} />.

    <AppHeader session={props.session} />

Step 3

Now let’s connect the signup with the backend to save registered users to the database. The base app backend is already setup for authentication. Replace the onSignUpResponse callback with the following code.

    const onSignUpResponse = (response) => {        
        if (!response.id) {
            // prints error to screen
            setErrorMessage('Unable to sign up to Facebook.');
            return false;
        }
        const postBody = { ...response, providerAccessToken: response.accessToken, provider: 'facebook'};
        return axios({
            method: 'POST',
            url: '/api/user/signup',
            postBody,
            validateStatus: () => true
        })
        .then((res) => {
            if (res.status === 200) {
                // redirect to login page
                router.push('/login');
            } else {
                setErrorMessage(res.data.message);
            }
        })
        .catch((error) => {
            setErrorMessage(error);
        });
    };

For the Facebook button to work properly we need to do one more thing.

Step 4

Facebook’s authentication must be done over https. To be able to that on localhost we will need to download Ngrok to expose our server to the internet, this will equally create an https link we can use as domain on our facebook app settings. When you must have downloaded ngrok, save the executable in your project folder or where you can easily reach it. I saved mine one directory above my project. Make sure the spo-tuto application is running, copy the port number and in your terminal run the ngrok program.

    ../ngrok.exe http 3000

Ngrok will create a proxy to your local server. For the free account the generated link is valid for 7 hours 49 minutes.

Step 5

Copy the https link, go to the dashboard of the Facebook app you created earlier, follow Settings > Basic scr oll down to Website and update the site URL field. Note if you don’t do this Facebook will not recognize the domain accessing its auth service and your login will fail and Login with Facebook will not work.

Step 6

Now we go to the browser, link to the secured link generared by ngrok. Try to Register with Facebook, the user object is returned with information concerning the user such as name, email, id and profile picture, which is saved to the database and redirects us to the login page.

The login page has been implemented, open pages/login.js you will notice there is no significant difference with the signup page. Separating both files was to allow for subsequent updates on other means of authentication and equally separating the login process from the signup process. After login with Facebook you should see the index page.

Integrating Flutterwave

We are now going to integrate Card payments with Flutterwave in our application. Some react concepts we come across are react hooks, how to manipulate the Virtual DOM, suspense and lazy loading.

Hooks

Before integrating Flutterwave lets first talk about Hooks. Hooks were introduced in react version 16.8 , to give developers the ability to manipulate state and use react features in functional components. In this tutorial we look at some common hooks like useState, useEffect and useRef.

Create a file pages/transactions.js and write in the following code.

// pages/transactions.js

import { useState, useEffect, useRef } from 'react';
import path from 'path';
import mongoose from 'mongoose';
import PropertiesReader from 'properties-reader';
import { getSession } from '../helpers/session-helpers';
import AppHeader from '../components/partials/appHeader';

function isBrowser() {
    return typeof window !== 'undefined';
}
const  Transactions = ({session, currency, errorMessage, topUpQuota}) => {
    const [error, setErrorMessage] = useState('');
    const [runningBalance, setRunningBalance] = useState(0.0);
    const [totalBalance, setTotalBalance] = useState(0.0);
    const [transactions, setTransactions] = useState([]);
    const paymentModalRef = useRef(null);
    const openPaymentModal = () => {
        paymentModalRef.current.classList.add('is-active');
    }
    return <>
        <AppHeader session={session} errorMessage={error || errorMessage} />
        <main className="columns">
            <aside className="column is-hidden-mobile"></aside>
            <article className="column is-two-thirds bg-default transaction-container">
                <div className="transaction-header bg-primary">
                    <div className="header">
                        <h2 className="">Transactions</h2> <button className="button is-accent" onClick={openPaymentModal}>Top Up</button>
                    </div>
                    <div className="columns sub-header">
                        <div className="column balances">
                            <span class="total is-text-warning">Balance: {totalBalance} &nbsp;{currency}</span>
                            <span class="running">Running: {runningBalance} &nbsp;{currency}</span>
                        </div>
                        <div className="column filters is-hidden-mobile">
                        </div>
                    </div>
                </div>
                <div className="transaction-content">
                        <table class="table is-bordered">
                            <thead className="bg-primary">
                                <th>#Ref</th>
                                <th>Reason</th>
                                <th>Amt / {currency}</th>
                                <th>Balance / {currency}</th>
                                <th>Date</th>
                            </thead>
                            <tbody>
                                {
                                    transactions ? 
                                        transactions.map( (transaction, index) => 
                                            <tr key={index}>
                                                <td>{transaction.serial}</td>
                                                <td>{transaction.reason}</td>
                                                <td>{transaction.amount}</td>
                                                <td>{transaction.amount - topUpQuota*transaction.amount}</td>
                                                <td>{transaction.createdAt}</td>
                                            </tr>) : <tr>No transaction.</tr>
                                }
                            </tbody>
                        </table>
                </div>
            </article>
            <aside className="column is-hidden-mobile"></aside>
        </main>
    </>
}
export async function getServerSideProps( { req, res }) {
    const sessionString = await getSession(req, res);
    const session = JSON.parse(sessionString);
    const properties = PropertiesReader(path.resolve('app.properties'));
    // amount charged for each transaction to the user, ie. platform charges
    const paymentQuotaTopProp = 'payment.quota.topup';
    const paymentQuotaTop = properties.get(paymentQuotaTopProp);
    if (!isBrowser() && !session.user) {
        res.writeHead(302, { Location: '/login' });
        res.end();
    }
    const currency = session.user.currency || 'XAF';
    return {
        props: { 
            session,
            currency,
            topUpQuota: Number(paymentQuotaTop)
        }
    }
}
export default Transactions;

In the above code we used useState hook to create and intialize state variables transactions, runningBalance, totalBalance and errorMessage. Next will be creating the PaymentModal and PaymentWidget components. Notice we already have an openPaymentModal onClick handler to registered on the Top Up button to make payments to the platform.

The PaymentCardWidget component has been included in the spo-tuto-base repo, it is found in the component/partial/widgets/PaymentCardWidget.js file.

// component/partial/widgets/PaymentCardWidget.js
import React, { useState, useRef } from 'react';
import { useRouter } from 'next/router';
import { usePaymentInputs } from 'react-payment-inputs';
import axios from 'axios';
import PaymentIcon from "react-payment-icons-inline";

import images from 'react-payment-inputs/images';

const PaymentCardWidget = ({currency, quota}) => {
    ...

    // initiate a payment request
    const createPaymentRequest = () => {
        ...
    }
    // submit extra data to start payment
    const initiatePayment = (payload) => {
        ...
    }   
    return <>...</>
}
export default PaymentCardWidget;

Create the modal component from which we are going to call the PaymentCardWidget component above.

// component/partial/modal/paymentModal.js

import React, {useEffect, useRef} from 'react';
import PaymentCardWidget from '../widgets/PaymentCardWidget';
const PaymentModal = React.forwardRef((props, ref) => {
    const close = () => ref.current.classList.remove('is-active'); 
    return (
        <>
            <div class="modal payment-modal" ref={ref}>
                <div class="modal-background"></div>
                <div class="modal-content bg-default">
                    <PaymentCardWidget modal={ref} {...props} />
                </div>
                <button class="modal-close is-large"  aria-label="close" onClick={close} ></button>
            </div>
        </>
     )
})
export default PaymentModal;

We are almost done with the payment UI. Import the PaymentModal component into the Transaction Page.

    ...
    import PaymentModal from '../components/partials/modals/paymentModal';
    ...

Below the <table /> closing tag, add the PaymentModal component.

    <PaymentModal ref={paymentModalRef} {...{currency, paymentUrl, quota: topUpQuota}} />

Note to manipulate HTML DOM, you assign the ref attribute to a native HTML tag, since the modal we are targetting is instead found in the PaymentModal component, we will forward the reference to the div element present on the PaymentModal component. You should notice on the PaymentModal component is wrapped in React.forwardRef call. This also allows us to control the child DOM from the parent component as can be seen in the body of the openPaymentModal function in Transactions Page Component. You also use refs when you want to store a value which does not force rendering of the page when updated.

Step 2: Create an account on Flutterwave

To integrate flutterwave api you will need to create and account on Flutterwave.

  1. Go to Flutterwave and fill the signup form.
  2. Check your email for verification link, you may have to check your spam for the email.
  3. Follow the verification email, you will need to specify how you want to use Flutterwave to accept payment.
  4. Select how you will want to accept payment from Flutterwave. Select the option of your choice, for my case I chose “To Accept Payment as an Individual”.

  5. You will be asked to provide some documents to verify your identity.
  6. Go to settings and configure your application.

Step 3: Integrate Flutterwave

For a transaction to take place with Flutterwave three steps must be completed.

  1. Make a payment request.
  2. Initiate payment.
  3. Complete payment.
  4. Verify transaction. We need to first download the flutterwave-node-v3 package. We can now move to the next stage.
    yarn add flutterwave-node-v3

We are not yet there, lets setup keys that will enable us connect to the Flutterwave platform and make payments. In your Flutterwave dashboard settings > api your will see all keys required to connect and interact with the Flutterwave payment platform. Open the app.properties and add the following properties, replace placeholders with corresponding key values.

    payment.flutterwave.encryptionKey=ENCRIPTION_KEY
    payment.flutterwave.publicKey=PUBLIC_KEY
    payment.flutterwave.secretKey=SECRET_KEY
    payment.flutterwave.redirectUrl=http://localhost:3000/campaign/payment

Make a Card payment request Move to the PaymentCardWidget found in components\partials\widgets\PaymentCardWidget.js, this file was shared earlier. Flutterwave requires different information for different card payments. The function createPaymentRequest sends a payload to the server which calls the Charge api endpoint of Flutterwave, the response body returned by Flutterwave contains a meta property which defines the authentication procedure to use to charge or initiate payment on the card. Depending on the card additional information might be needed to complete payment. pages\api\transaction\cardPayment.js backend controller processes payload from the createPaymentRequest client request.

Flutterwave provides test cards to test your code. Make sure on your Flutterwave dashboard you are switched to test mode.

We will be using the test card Test MasterCard PIN authentication

Card number: 5531 8866 5214 2950
cvv: 564
Expiry: 09/32
Pin: 3310
OTP: 12345

Payment request payload

{
  "name": "John Doe",
  "card_number": "5531886652142950",
  "email": "",
  "cvc": "564",
  "expiry_date": "09 / 32",
  "type": "card",
  "currency": "XAF",
  "amount": 2000,
  "state": "",
  "city": "",
  "address": "",
  "country": "",
  "expiry_month": "09",
  "expiry_year": "32",
  "tx_ref": "100000005",
  "enckey": "xxxxxxxxxxxxxxxxxxxx",
  "redirect_url": "http://localhost:3000/payment"
}

Initiate payment request Once an initiate payment request is made, Flutterwave returns a response body which specifies what authorization type is needed to initiate a payment.

{
  "status": "success",
  "message": "Charge authorization data required",
  "meta": {
    "authorization": {
      "mode": "pin",
      "fields": [
        "pin"
      ]
    }
  }
}

The authorization required by the card above is pin. So we will need to provide the pin, we allow for the same payment widget to be used for the payment process. In the payment widget the existPaymentRequest boolean variable tracks if a payment request has been made, and specifies what handler will be called on the next Pay button click.

    <button className="button is-primary" onClick={existPaymentRequest ? initiatePayment : createPaymentRequest}>Pay</button>

Our selected card needs a pin to authorize payment, in the payment widget there is logic to request additional information from the user.

// components\partials\widgets\PaymentCardWidget.js
    const createPaymentRequest = () => {
        ...
        const postData = {...};
        return axios({...})
        .then((res) => {
             if (res.status === 200) {
                const authMode = res.data.mode;
                switch(authMode) {
                    case 'pin':
                        setAuthType('pin'); // set the authtype required
                        additionalInputRef.current.classList.remove('is-hidden'); // Show additional input needed
                        pinRef.current.classList.remove('is-hidden'); // show pin input
                        addressRef.current.classList.add('is-hidden'); // hide address input
                        setExistPaymentRequest(true);
                        break;
                    case 'avs_noauth':
                        setAuthType('avs_noauth'); // set the authtype required
                        additionalInputRef.current.classList.remove('is-hidden'); // Show additional input needed
                        addressRef.current.classList.remove('is-hidden'); // show address inputs
                        pinRef.current.classList.add('is-hidden'); // hide payment
                        setExistPaymentRequest(true);
                        break;
                    case 'redirect':
                        location.assign(res.data.redirect); // authorization needed redirect to payment url to make payment.                                      
                        break;
                    default:
                }
            } else {
                ...
            }
        })
        .catch((error) => {
            ...
        });
    }

The following code section in components\partials\widgets\PaymentCardWidget.js will show additional information based on the type of authentication.

    <div className="column payment-methods-content is-hidden" ref={additionalInputRef}>
        <div className="field is-hidden has-text-centered" ref={pinRef}>
            <span>You need to provide additional card details to complete this transaction.</span>
            <input className="input" placeholder="PIN" name="pin" type="text" onChange={handleInputChange} value={pin} />
        </div>
        <div className="address is-hidden has-text-centered" ref={addressRef}>
            <span>You need to provide additional card details to complete this transaction.</span>
            <div className="field">
                <input className="input" name="city" placeholder="City" type="text" onChange={handleInputChange} value={city} />
            </div>
            <div className="field">
                <input className="input" name="address" placeholder="Address" type="text" onChange={handleInputChange} value={address} />
            </div>
            <div className="field">
                <input className="input" placeholder="State" type="text" name="state" onChange={handleInputChange} value={state} />
            </div>
            <div className="field">
                <input className="input" placeholder="Zip" type="text" name="zip" onChange={handleInputChange} value={zip} />
            </div>
            <div className="field">
                <input className="input" placeholder="Country" type="text" name="country" onChange={handleInputChange} value={country} />
            </div>
        </div>
    </div>

Payload sent to Flutterwave to initiate payment process.

{
  "name": "Tane",
  "card_number": "5531886652142950",
  "cvc": "564",
  "reason": "Reward",
  "type": "card",
  "currency": "XAF",
  "amount": 2000,
  "authorization": {
    "mode": "pin",
    "pin": "3310"
  },
  "expiry_month": "09",
  "expiry_year": "32",
  "email": "demo@xmail.com",
  "phone_number": "",
  "tx_ref": "100000005",
  "enckey": "xxxxxxxxxxxxxxxxxxxxxxxx",
  "redirect_url": "http://localhost:3000/payment"
}

Response on payment initiation.

{
  "status": "success",
  "message": "Charge initiated",
  "data": {
    "id": 1665371,
    "tx_ref": "100000006",
    "flw_ref": "FLW-MOCK-97ed4f455f5bd4ba5cc4026802f39f29",
    "device_fingerprint": "N/A",
    "amount": 2000,
    "charged_amount": 2000,
    "app_fee": 76,
    "merchant_fee": 0,
    "processor_response": "Please enter the OTP sent to your mobile number 080****** and email te**@rave**.com",
    "auth_model": "PIN",
    "currency": "XAF",
    "ip": "::ffff:10.67.186.14",
    "narration": "CARD Transaction ",
    "status": "pending",
    "auth_url": "N/A",
    "payment_type": "card",
    "plan": null,
    "fraud_status": "ok",
    "charge_type": "normal",
    "created_at": "2020-10-31T04:50:48.000Z",
    "account_id": 118468,
    "customer": {
      "id": 517427,
      "phone_number": null,
      "name": "Anonymous customer",
      "email": "demo@gmail.com",
      "created_at": "2020-10-31T04:50:47.000Z"
    },
    "card": {
      "first_6digits": "553188",
      "last_4digits": "2950",
      "issuer": "MASTERCARD  CREDIT",
      "country": "NG",
      "type": "MASTERCARD",
      "expiry": "09/32"
    }
  },
  "meta": {
    "authorization": {
      "mode": "otp",
      "endpoint": "/v3/validate-charge"
    }
  }
}

Complete payment After filling additional details and initiating payment, an OTP code is sent to the users email or phonenumber, in our case at the backend I used the current session users email. You can as well add an email or phonenumber input to the PaymentWidget. The different authorization modes need to be handled differently.

  1. With pin authorization, you need to create a page in your app that will accept the OTP and confirm payment.

  2. With avs_noauth and no authorization, you will be redirected to a Flutterwave link to enter the otp code and later redirected to the redirection link entered in the request body during the payment initialization step.

To complete payment we create a payment page pages\payment.js, this has already been created for you. This page does two things.

  1. For pin authorization it will accept pin , then call the completeCardPayment function to complete payment. After payment is completed, the user is redirected to the transaction page.
  2. For no authorization modes, the page will gather payment details from the query params, then call the completeCardPayment function to complete payment. After payment is completed, the user is redirected to the transaction page. Note that the link to the payment page is the redirect url passed during the initiate payment stage of the payment workflow.

Payload sent to Flutterwave to complete payment.

{
  "otp": "12345",
  "flw_ref": "FLW-MOCK-6deb1aa461865414a4a274df78397291"
}

Response on completing payment

    {}

Verify transaction In the backend, upon completing payment, a call to verify payment must be made using the Id of the completed transaction. In the payment verification response below data.processor_response is ‘successful’ indicates that payment was successful.

Payload sent to Flutterwave.

{
    "id":1665320
}

Flutterwave response upon verifying payment.

{
  status: 'success',
  message: 'Transaction fetched successfully',
  data: {
    id: 1665320,
    tx_ref: '100000005',
    flw_ref: 'FLW-MOCK-6deb1aa461865414a4a274df78397291',
    device_fingerprint: 'N/A',
    amount: 2000,
    currency: 'XAF',
    charged_amount: 2000,
    app_fee: 76,
    merchant_fee: 0,
    processor_response: 'successful',
    auth_model: 'PIN',
    ip: '::ffff:10.65.248.108',
    narration: 'CARD Transaction ',
    status: 'successful',
    payment_type: 'card',
    created_at: '2020-10-31T04:08:49.000Z',
    account_id: 118468,
    card: {
      first_6digits: '553188',
      last_4digits: '2950',
      issuer: ' CREDIT',
      country: 'NIGERIA NG',
      type: 'MASTERCARD',
      token: 'flw-t1nf-dd9f5ed8aceaeb3d0ad66f68e0f2cf5a-m03k',
      expiry: '09/32'
    },
    meta: null,
    amount_settled: 1924,
    customer: {
      id: 517424,
      name: 'Anonymous customer',
      phone_number: 'N/A',
      email: 'demo@xmail.com',
      created_at: '2020-10-31T04:08:48.000Z'
    }
  }
}

Payment backend

In the backend 2 Controllers files handle client transaction requests. The pages\api\transaction\cardPayment.js controller manages payment request and payment initiation calls. The pages\api\transaction\completeCardPayment.js controller handle client requests concerned with validating and verifying card payment was successful. Both the PaymentCardHandler and CompleteCardPaymentHandler use functions from the helpers\payment.js file.

// helpers\payment.js

// called to make payment request and initiate payment
export const makePayment = async (payload) => {
    payload.enckey = flwEncKey;
    payload.redirect_url = flwRedUrl;
    const payment = await flw.Charge.card(payload);
    return payment;
}
// finalises a payment
export const completeCardPayment = async (payload) => {
    const completedPayment = flw.Charge.validate(payload);
    return completedPayment;
}
// verifies that payment was done
export const verifyCardPayment = async (payload) => {
    const payment = flw.Transaction.verify(payload);
    return payment;
}

Loading and Updating Transactions

Finally we are done with integrating payment. Now we want to display our transactions. Here we are going to use Hooks and Suspense.

Support or Contact

Facebook login React docs Flutterwave V3 Card payment api docs Flutterwave card test Tutorial codebase repo Tutorial repo For questions or bugs create an issue on the tutorial codebase repo or tutorial itself.

Author

Tane J. Tangu, Software Engineer Email tanejuth@gmail.com Twitter @tanerochris LinkedIn https://www.linkedin.com/in/tane-j-tangu-72034ba8/