Create a basic online payment flow with Flutterwave and Vue.js

JavaScript frameworks and libraries have enabled developers to create optimal solutions when building web apps. Single and progressive web applications have improved features such as loading time, SEO, and accessibility. Lightweight frameworks such as Vue - which is incrementally adoptable and manages state seamlessly, are go-to options for anyone who wants to create a web app without the burden of structuring your application the "right” way.

At the end of this guide, we should:

  • have built a car rental application depicting a basic payment process with Vue and Flutterwave
  • Learned how to integrate Flutterwave inline with VueJs

Getting started

When we are done building this application, our payment flow should be something like this:

  • A user sees a car they want to rent from the list of cars available and clicks the Book Now button
  • The user gets redirected to a payment modal where they make payment (preferably via credit card)
  • When payment is made, Flutterwave sends the user a receipt and also send the details of the payment to you (the merchant)

To build this application, we'll use the following technologies:

  • Vue CLI: Vue's command line tool used for scaffolding Vue projects.
  • Vuetify: A material component design framework for Vue.
  • Axios: A lightweight, promise based HTTP client used for making API calls.
  • MarketCheck: An API that provides access to a database of new, used, and certified vehicles.

Let's begin by installing Vue CLI. Navigate to your terminal and run the following commands:

npm install -g @vue/cli-service-global
# or
yarn global add @vue/cli-service-global
    
#create a new Vue project
vue create car-sales-app
    
#navigate into the newly created app
cd car-sales-app

For styling our markup, we'll install Vuetify. In your terminal, navigate to your project's folder and run the following command to install Vuetify:

vue add vuetify
#choose default when prompted

#start a development server on localhost:8080
npm run serve

On your browser, navigate to localhost:8080 to view the app:

Next, we'll install Axios. Navigate to your project's folder and input the following command:

npm install axios

After installing Axios, we need to obtain an API key from MarketCheck so we can start fetching certified vehicles. To do this, head on to MarketCheck and create an account:

At this point, src/main.js file should look like this:

import Vue from "vue";
import App from "./App.vue";
import vuetify from "./plugins/vuetify";

Vue.config.productionTip = false;

new Vue({
vuetify,
render: h => h(App)
}).$mount("#app");

Fetching data from our API

By default, our project has a HelloWorld component. Let's delete that component and modify our App component to make requests to MarketCheck's API using Axios. Using Vue's mounted() lifecycle method, Axios makes a GET request to the API:

// src/App.vue
<script>
  export default {
  name: "app",
    data() {
    return {
      carResponse: [],
    }    
},
  mounted() {
    axios
      .get('https://marketcheck-prod.apigee.net/v1/search?&year=2016&make=toyota&api_key=INSERT-YOUR-API-KEY-HERE&Content-Type=application/json')
      .then(response => {
      this.carResponse = response.data.listings;
    })
      .catch(error => {
      console.log(error);
    });
  }
}
</script>

carResponse is a data property that is responsible for handling the data received from our API and displaying it on our browser. Let's use UI components from Vuetify and Vue directives to structure our App component:

// src/App.vue
<template>
  <div id="app">
    <header>
      <h2>
      RENT A CAR. CALL 080-RENT-A-CAR
      </h2>
    </header>
<v-container grid-list-xl>
  <v-layout wrap>
    <v-flex xs4 v-for="car in carResponse" :key="car[0]" mb-2>
      <v-card>
      	<v-img :src="car.media.photo_links[0]" aspect-ratio="2"></v-img>
					<v-card-title primary-title>
            <div>
              <h3>{{ car.build.make }} {{ car.build.model }}</h3>
              <div>Year: {{ car.build.year }}</div>
              <div>Type: {{ car.build.vehicle_type }}</div>
              <div>Mileage: {{ car.miles }} miles</div>
              <div>NGN {{ car.price }} / Day</div>
            </div>
          </v-card-title>
          	<v-card-actions class="justify-center">
  	       </v-card-actions>
					</v-card>
				</v-flex>
			</v-layout>
		</v-container>
	</div>
</template>

<script>
  import axios from "axios";
export default {
  name: "app",
  data() {
    return {
      carResponse: [],
    }    
  },
  mounted() {
    axios
      .get('https://marketcheck-prod.apigee.net/v1/search?&year=2016&make=toyota&api_key=INSERT-YOUR-API-KEY-HERE&Content-Type=application/json')
      .then(response => {
      this.carResponse = response.data.listings;
    })
      .catch(error => {
      console.log(error);
    });
  }
};
</script>

Here's a view of the current state of the app in our browser:

Implementing payments with Flutterwave

For now, we are unable to receive payments for any of the displayed vehicles. Let's change that by implementing Flutterwave's payment gateway in our app. First, we need to sign up with Flutterwave and create a merchant account which enables us to receive payment for goods and services:

Once we are through with the sign-up process, we should see a dashboard similar to this:
On the dashboard navigate to Settings and then the API tab to retrieve the API keys.

As this is a tutorial, turn on Sandbox on the dashboard and make payment using a test card supplied by Flutterwave to avoid disclosing sensitive information either by displaying our real API keys or credit card numbers. Below is a screenshot of your test API keys:

In the src/components folder, let's create a new component and name it FlutterwaveModal.
The template in our FlutterwaveModal component won't hold much, just a button that activates the payment modal when clicked:

// src/components/FlutterwaveModal.vue

<template>
  <div class="flw">
    <button class="button" @click="makePayment">Book Now</button>
	</div>
</template>
    
<script>

  export default {
    name: 'FlutterwaveModal',
      ...
  }

  </script>

Using Vue's create() hook in our newly created component, we'll create an instance of Flutterwave's inline script and append it to the DOM:

// src/components/FlutterwaveModal.vue
<script>
      export default {
        name: 'FlutterwaveModal',
        created() {
            const script = document.createElement("script");
            script.src = !this.isProduction
              ? "https://ravemodal-dev.herokuapp.com/v3.js"
              : "https://checkout.flutterwave.com/v3.js";
            document.getElementsByTagName("head")[0].appendChild(script);
          },
    }

Using Vue's method property, we'll embed a payment modal in our component via Flutterwave's FlutterwaveCheckout() function:

// src/components/FlutterwaveModal.vue
<script>
      export default {
       ...
         methods: {
            makePayment() {
              window.FlutterwaveCheckout({
                public_key: this.flwKey,
                tx_ref: this.reference,
                amount: this.amount,
                currency: this.currency,
                payment_options: this.payment_method,
                customer: {
                  name: this.name,
                  email: this.email,
                },
                callback: response => this.callback(response),
                customizations: {
                  title: this.custom_title,
                  description: "Payment for items in cart",
                  logo: this.custom_logo,
                },
              });
            }
          }
    }
</script>

Our next step will be to specify what each value in FlutterwaveCheckout should be. We'll do this using Vue prop types:

// src/components/FlutterwaveModal.vue 
    <script>
      export default {
       ...
        props: {
            isProduction: {
              type: Boolean,
              required: false,
              default: false //set to true if you are going live
            },
            email: {
              type: String,
              required: true
            },
            amount: {
              type: Number,
              required: true
            },
            flwKey: {
              type: String,
              required: true
            },
            callback: {
              type: Function,
              required: true,
              default: () => {
                console.log('Payment made, verify payment');
              }
            },
            close: {
              type: Function,
              required: true,
              default: () => {}
            },
            currency: {
              type: String,
              default: "NGN"
            },
            country: {
              type: String,
              default: "NG"
            },
            custom_title: {
              type: String,
              default: ""
            },
            custom_logo: {
              type: String,
              default: ""
            },
            reference: {
              type: String,
              default: ""
            },
            payment_method: {
              type: String,
              default: "card,mobilemoney,ussd"
            }
          }
    }
</script>

Finally, we'll import FlutterwaveModal into our App component and specify all the values of makePayment() :

// src/App.vue
<script>
  import Flutterwave from "./components/FlutterwaveModal.vue";
	export default {
    name: "app",
    components: {
      Flutterwave
    },
  data() {
    return {
      carResponse: [],
      isProduction: false,
      flwKey: flwKey,
      amount: "",
      currency: "NGN",
      country: "NG",
      customer: {
        name: "Ugwu Raphael",
        email: "[email protected]"
      },
      customizations: {
        title: "Car Shop",
        description: "Payment for car service"
      },
      paymentMethod: ""
    };
  }
}
</script>

To include the payment button on our app, we update the v-card-actions in our template:

// src/App.vue
<template>
  ...
  <v-card-actions class="justify-center">
    <flutterwave
      :isProduction="isProduction"
      :name="customer.name"
      :email="customer.email"
      :amount="car.price"
      :reference="reference"
      :flw-key="flwKey"
      :callback="callback"
      :close="close"
      :currency="currency"
      :country="country"
      :custom_title="customizations.title"
      :custom_logo="customizations.logo"
      :payment_method="paymentMethod"
	/>
            </v-card-actions>
</template>

To generate an example reference on the fly, we would be making use of Vue's computed() method:

// src/App.vue
<script>
...
computed: {
  reference() {
    let text = "";
    let possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    for (let i = 0; i < 10; i++)
    text += possible.charAt(Math.floor(Math.random() * possible.length));
    return text;
  }
},
...
</script>

Finally, let's protect our API keys by storing them in a .env file. In the root folder of your project, create a .env file and store both MarketCheck and Flutterwave APIs:

// .env
VUE_APP_CAR_API_KEY='YOUR MARKETCHECK API HERE'
VUE_APP_FLUTTERWAVE_TEST_KEY='YOUR FLUTTERWAVE API KEY HERE'

When you are done, save the .env file and refer to the stored values in src/App.vue like this:

// src/App.vue
<script>
  ...
    const carKey = process.env.VUE_APP_CAR_API_KEY;
    const flwKey = process.env.VUE_APP_FLUTTERWAVE_TEST_KEY;
  ...
</script>

Restart the development server on your terminal, navigate to your browser and try to make a payment for one of the vehicles:

Upon successful payment, Flutterwave will send an email to the customer with a receipt and also send the payment details to your email (if you enabled it on your dashboard):

Summary

Optimizing performance when building web apps is only going to get more important. Javascript developers have a lot of frameworks and tools to choose from and Vue is an awesome option. As for implementing payment options seamlessly, Flutterwave gets the job done. To check out the source code of this application, head on to GitHub.