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
  • Learnt how to integrate Rave 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 but still verifies the authenticity of the transaction. If the transaction is found to be valid, a unique authentication code is shown to the user, else an error message telling the user to retry the payment is displayed.

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

  • 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 input the following command:

    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 input 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 . begin 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 which 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>

    <style>
    #app {
      font-family: "Avenir", Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: justify;
      background-color: hsla(0, 0%, 75%, 0.1);
    }
    header {
      margin-bottom: 60px;
      text-align: center;
    }
    </style>


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, switch to Test mode 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 RaveModal

The template in our RaveModal component won't hold much, just a button that activates the payment modal when clicked:

 // src/components/RaveModal.vue

<template>
  <div class="rave">
    <button class="button" @click="payWithRave">Book Now</button>
  </div>
</template>

<script>

  export default {

    name: 'RaveModal',
    ...
    }

</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/RaveModal.vue
<script>

  export default {

    name: 'RaveModal',
    created() {
        const script = document.createElement("script");
        script.src = !this.isProduction
          ? "https://ravesandboxapi.flutterwave.com/flwv3-pug/getpaidx/api/flwpbf-inline.js"
          : "https://api.ravepay.co/flwv3-pug/getpaidx/api/flwpbf-inline.js";
        document.getElementsByTagName("head")[0].appendChild(script);
      },
}

Using Vue's method property, we'll embed a payment modal in our component via Rave's getPaidSetup function:

    // src/components/RaveModal.vue
<script>

  export default {
   ...

     methods: {
        payWithRave() {
          window.getpaidSetup({
            customer_email: this.email,
            amount: this.amount,
            txref: this.reference,
            PBFPubKey: this.raveKey,
            onclose: () => this.close(),
            callback: response => this.callback(response),
            currency: this.currency,
            country: this.country,
            custom_title: this.custom_title,
            custom_logo: this.custom_logo,
            payment_method: this.payment_method,
          });
        }
      }

}
</script>


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

    // src/components/RaveModal.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
        },
        raveKey: {
          type: String,
          required: true
        },
        callback: {
          type: Function,
          required: true,
          default: response => {}
        },
        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: ""
        }
      }
}
</script>


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

    // src/App.vue

    <script>
    import Rave from "./components/RaveModal.vue";
    export default {
      name: "app",
      components: {
        Rave
      },
     data() {
        return {
          carResponse: [],
          isProduction: false,
          raveKey: raveKey,
          email: "ugwuraphael@gmail.com",
          amount: "",
          currency: "NGN",
          country: "NG",
          custom: {
            title: "Car Shop"
          },
          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">
              <rave
                :isProduction="isProduction"
                :email="email"
                :amount="car.price"
                :reference="reference"
                :rave-key="raveKey"
                :callback="callback"
                :close="close"
                :currency="currency"
                :country="country"
                :custom_title="custom.title"
                :custom_logo="custom.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 Rave APIs:

    // .env

    VUE_APP_CAR_API_KEY='YOUR MARKETCHECK API HERE'
    VUE_APP_RAVE_TEST_KEY='YOUR RAVE 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 raveKey = process.env.VUE_APP_RAVE_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 payment, Rave emails the customer a receipt:

Handling payment authentication

Although we can confirm that a customer made payment by checking our Rave dashboard for details of the transaction, it's still important to perform an authentication check for each transaction to detect issues such as reversed or fraudulent transactions. To achieve this, we'll define the callback property in Rave's getPaidSetup function to check for the authenticity of every transaction and return its transaction ID to the customer:

    <script>
      import Rave from "./components/RaveModal.vue";
      export default {
        name: "app",
        components: {
            Rave
        }
...
        methods: {
            callback: function(response) {
                if (
                    response.data.tx.status == "successful" &&
                    response.data.tx.chargeResponseCode === "00"
                ) {
                    if (response.data.tx.fraud_status === "ok") {
                        alert(
                            Authenticate your payment via our mobile app with this code: ${response.data.tx.txRef}
                        );
                    }
                } else {
                    alert("Your payment could not be processed. Please try again later");
                }
            }
        }
    </script>

Now a customer can pay for an item and get an identifier such as an authentication code as an additional layer of authenticity:

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, Rave gets the job done. To check out the source code of this application, head on to GitHub.

Did this answer your question?