Skip to content

Build fintech application with Spring Boot, Vavr and Vue.js (Part 7 – Subscriptions – Vue part)

Hello! We already did a great job and implemented many essential components for our SaaS application. In the previous part we started an implementation of subscriptions (recurring payments) with Stripe platform. This time we will do client-side half of subscription management with Vue.js and Stripe.js library.

Table of contents

What we will do in this part

In this part we will continue our work on subscriptions, that we started in part 6. We already implemented Spring Boot part and now we can proceed to Vue.js half of the job. We will check how to work with Stripe.js library on frontend side and how to create recurring payments in Vue apps.

Step 1. Get publishable key

In the previous part, we obtained Stripe keys. As you remember there are two types of credentials:

  • Secret key – we use it on backend
  • Publishable key – we need it on frontend
Image 1. Get Stripe keys

Step 2. Install Stripe

Unlike other dependencies, that we installed using package manager, Stripe is a different case. It is recommended to reference it directly from Stripe repository, which means we need to include it in index.html directly:

Include the Stripe.js script on each page of your site—it should always be loaded directly from https://js.stripe.com, rather than included in a bundle or hosted yourself.

Stripe.js documentation

Open public/index.html and add following line there inside <head> tag:

<script src="https://js.stripe.com/v3/"></script>

Step 3. Define a subscription page layout

Create a new view called SubscriptionView.vue. It will responsible for all things on subscriptions, including:

  1. Display current subscription data
  2. Cancel subscription
  3. Choose a new plan and start a new subscription

That means we will have several logical blocks: current subscription info, list of plans and payment form. Take a look on the code snippet below that contains template for SubscriptionView:

<template>
  <div class="app-content">
    <div class="app-block">
      <!-- Current subscription info -->
      <h2 class="title is-2">Subscription</h2>
      <p>Current status: 
          <span class="tag is-success" v-if="subscription.status">Valid</span>
          <span class="tag is-light" v-if="!subscription.status">No subscription</span>
      </p>
      <p v-if="subscription.status">Current plan: {{getTier}}</p>
      <div>
        <b-button type="is-danger" v-if="subscription.status" v-on:click="cancelSubscription">Cancel subscription</b-button>
      </div>
    </div>

    <div class="app-block">
      <!-- Plans -->
      <h3 class="title is-3">Change your plan</h3>
      <div class="columns">
        <div class="column is-one-half" v-for="plan in plans" :key="plan.providerId">
          <div class="box has-text-centered">
            <h4 class="title is-4 has-text-primary">{{plan.name}}</h4>
            <ul>
              <li>Lorem ipsum dolor sit amet</li>
              <li>Consectetur adipiscing elit</li>
              <li>Sed do eiusmod tempor</li>
            </ul>
            <p class="is-size-4">{{plan.price}} EUR</p>
            <b-button expanded v-on:click="selectPlan(plan.providerId)">Start</b-button>
          </div>
        </div>
      </div>
    </div>

    <div class="app-block">
      <!-- Stripe payment form -->
      <h2 class="title" v-if="showCardForm">Enter your card data</h2>
      <div id="stripe-card"></div>
      <b-button expanded v-if="showCardForm" v-on:click="doPayment">Pay</b-button>
    </div>

  </div>
</template>

We also need to add some data here to check that everything is rendered properly: plans array with plan options and subscription object (that actually corresponds to the backend’s SubscriptionResponse):

data() {
    return {
        plans: [{
              name: 'Basic',
              providerId:'prod_GWltl9m1YY2d5O',
              price: '9.99'
            },{
              name: 'Premium',
              providerId: 'prod_ZKltl1z1MM3e64',
              price: '15.99'
            }],
        subscription: {
            status: true,
            tier: 'prod_GWltl9m1YY2d5O',
            subscriptionId: '123456'
        },
        showCardForm: false
    }
}

Another thing we also need here is computed property getTier. Its role is quite simple – we need to convert subscription.tier value (that is Stripe plan ID) to human-readable form:

computed: {
    getTier() {
        let plan = this.plans.filter(p => {return p.providerId === this.subscription.tier})[0]
        return plan.name
    }
}

Don’t worry about methods, we don’t need them for now. Run your Vue app and navigate to localhost:8080/#/subscriptions to assert that we now have a page for subscriptions:

Image 2. Subscription view layout

From now we can start code actual methods implementations

Step 4. Retrieve subscription data from API

The first thing here is to replace hardcoded subscription data with an actual data, retrieved from API. This would be done by getSubscription() method that will use AxiosClient to send GET request to the Spring API and then save results as subscription object:

getSubscription() {
    let url = 'subscriptions/' + getUserId()
    AxiosClient.get(`${url}`).then(res => {
        this.subscription = res.data
    }).catch(err => {
        console.log(err)
    })
}

Don’t forget to reference this method in created lifecycle hook, as we need this subscription data as soon as user acccesses the subscriptions’ page:

created() {
    this.getSubscription()
}

Step 5. Cancel a subscription

When we built REST controller for handle subscriptions, we agreed that user can cancel a subscription simply by sending HTTP DELETE request with the subscription’s ID. Take a look on the code snippet below, which provides an implementation for cancelSubscription method:

cancelSubscription(){
    let url = '/subscriptions/' + this.subscription.subscriptionId
    AxiosClient.delete(`${url}`).then(res=>{
        this.$buefy.toast.open('Subscription cancelled!')
        this.subscription.status = false
    }).catch(err=>{
        this.$buefy.toast.open('Cannot cancel subscription!')
        console.log(err)
    })
}

Also, remember that if subscription was successfully cancelled, we need to indicate it to user by setting subscription.status to false.

Step 6. Select a plan and show payment form

There we will finally move to Stripe-specific stuff. First, let define three new data() values:

data() {
    return {
        // ..
        stripe: null,
        cardForm: null,
        subscriptionRequest: {}
    }
}

The first one – stripe will reference an actual Stripe object, that we need to use in several methods. The second value – cardForm references a Stripe’s card object. We have a DOM element that is called stripe-card, but this is a container, where Stripe will inject (or mount) its own payment form. Finally, we declared Subscription request, which we will send to backend later.

Inside created hook we need to define Stripe instance:

created() {
    // ..
    this.stripe = Stripe('{YOUR_PUBLISHABLE_KEY}')
}

Then, define selectPlan implementation, that accepts providerId of selected plan and mounts a payment card form:

selectPlan(id){
    this.showCardForm = true
    // hardcoded value for now
    this.subscriptionRequest.userId = 'spring-user'
    this.subscriptionRequest.tier = id
    let elements = this.stripe.elements()
    this.cardForm = elements.create('card')
    this.cardForm.mount('#stripe-card')
}

When user clicks on Start button of the plan, Vue will mount new payment card form to collect card’s data, as well set showCardForm value to true in order to display all surrounding elements – title and payment button.

NB! don’t apply v-if="showCardForm" directive to the card form’s wrapper, but only to button and title, otherwise you will get an error, because Stripe can’t access an element, that Vue does not provide yet.

Step 7. Handle payment!

And the most important step! As long as user provides card data, we need to handle a payment. You remember, that in order to create a subscription, we need to send to the backend an object that contains Stripe token value. Let define a test implementation of doPayment method to verify, that we can get this token from Stripe first:

doPayment(){
    this.stripe.createToken(this.cardForm).then(res => {
        this.subscriptionRequest.token = res.token.id
        console.log(this.subscriptionRequest)
}

For now we just need to assert that Stripe does it job and can get a token. NB don’t try to test Stripe with real card numbers. Instead, select mock card numbers, from Stripe documentation!

Run this code, enter fake card number and check the console output:

Image 3. Get a Stripe token

Yes! It works! The only missing thing is to actually send this token to the backend:

doPayment(){
    this.stripe.createToken(this.cardForm).then(res => {
        this.subscriptionRequest.token = res.token.id
            AxiosClient.post(`subscriptions`, this.subscriptionRequest).then(res=>{
                this.$buefy.toast.open('Payment successful!')
                this.subscription = res.data
            }).catch(err=>{
                this.$buefy.toast.open('Cannot create a payment!')
            })
        })
}

That’s all for this part! We successfully implemented subscription management in our Vue application. In the next parts we will finish our project by securing it with JWT tokens. Meanwhile, don’t forget to subscribe for updates and follow me in socials to be notified about updates.

Copy link
Powered by Social Snap