Creating a Vue.js Serverless Checkout Form: Configure the Checkout Component

Avatar of Sarah Drasner
Sarah Drasner on (Updated on )

This is the fourth post in a four-part series. In Part one, we set up a serverless Stripe function on Azure. Part two covered how we hosted the function on Github. The third part covered Stripe Elements in Vue. This last post shows how to configure the checkout component and make the shopping cart fully functional.

Article Series:

  1. Setup and Testing
  2. Stripe Function and Hosting
  3. Application and Checkout Component
  4. Configure the Checkout Component (This Post)

As a reminder, here’s where we are in our application at this point:

cart and checkout

Configuring the Checkout Component

We have to do a few things to adjust the component in order for it to meet our needs:

  • Make sure the form is only displaying if we haven’t submitted it—we’ll deal with the logic for this in our pay method in a moment
  • Allow the form to take a customer’s email address in case something is wrong with the order.
  • Disable the submit button until the required email address is provided
  • Finally and most importantly, change to our testing key

Here’s our updated checkout component with the changes to the original code highlighted:

<div v-if="!submitted" class="payment">
  <h3>Please enter your payment details:</h3>
  <label for="email">Email</label>
  <input id="email" type="email" v-model="stripeEmail" placeholder="[email protected]"/>
  <label for="card">Credit Card</label>
  <p>Test using this credit card: <span class="cc-number">4242 4242 4242 4242</span>, and enter any 5 digits for the zip code</p>
  <card class='stripe-card'
    id="card"
    :class='{ complete }'
    stripe='pk_test_5ThYi0UvX3xwoNdgxxxTxxrG'
    :options='stripeOptions'
    @change='complete = $event.complete'
  />
  <button class='pay-with-stripe' @click='pay' :disabled='!complete || !stripeEmail'>Pay with credit card</button>
</div>

There are a number of things we need to store and use for this component, so let’s add them to data or bring them in as props. The props that we need from our parent component will be total and success. We’ll need the total amount of the purchase so we can send it to Stripe, and the success will be something we need to coordinate between this component and the parent, because both components need to know if the payment was successful. I write out the datatypes as well as a default for my props.

props: {
  total: {
    type: [Number, String],
    default: '50.00'
  },
  success: {
    type: Boolean,
    default: false
  }
},

Next, the data we need to store will be the stripeEmail we collected from the form, stripeOptions that I left in to show you can configure some options for your form, as well as status and response that we’ll get from communicating with the server and Stripe. We also want to hold whether or not the form was submitted, and whether the form was completed for enabling and disabling the submit button, which can both be booleans.

data() {
  return {
    submitted: false,
    complete: false,
    status: '',
    response: '',
    stripeOptions: {
      // you can configure that cc element. I liked the default, but you can
      // see https://stripe.com/docs/stripe.js#element-options for details
    },
    stripeEmail: ''
  };
},

Now for the magic! We have everything we need—we just need to alter our pay() method, which will do all the heavy lifting for us. I’m going to use Axios for this, which will receive and transform the data:

npm i axios --save

…or using Yarn:

yarn add axios

If you’re not familiar with Axios and what it does, check out this article for some background information.

pay() {
  createToken().then(data => {
    this.submitted = true; // we'll change the flag to let ourselves know it was submitted
    console.log(data.token); // this is a token we would use for the stripeToken below
    axios
      .post(
        'https://sdras-stripe.azurewebsites.net/api/charge?code=zWwbn6LLqMxuyvwbWpTFXdRxFd7a27KCRCEseL7zEqbM9ijAgj1c1w==',
        {
          stripeEmail: this.stripeEmail, // send the email
          stripeToken: data.token.id, // testing token
          stripeAmt: this.total // send the amount
        },
        {
          headers: {
            'Content-Type': 'application/json'
          }
        }
      )
      .then(response => {
        this.status = 'success';
        this.$emit('successSubmit');
        this.$store.commit('clearCartCount');
        // console logs for you :)
        this.response = JSON.stringify(response, null, 2);
        console.log(this.response);
      })
      .catch(error => {
        this.status = 'failure';
        // console logs for you :)
        this.response = 'Error: ' + JSON.stringify(error, null, 2);
        console.log(this.response);
      });
  });
},

The code above does a number of things:

  • It allows us to track whether we’ve submitted the form or not, with this.submitted
  • It uses Axios to post to our function. We got this URL from going to where the function lives in the portal, and clicking “Get Function URL” on the right side of the screen.
    get function url
  • It sends the email, token, and total to the serverless function
  • If it’s successful, it changes the status to success, commits to our Vuex store, uses a mutation to clear our cart, and emits to the parent cart component that the payment was successful. It also logs the response to the console, though this is for educational purposes and should be deleted in production.
  • If it errors, it changes the status to failure, and logs the error response for help with debugging

If the payment fails, which we’ll know from our status, we need to let the user know something went wrong, clear our cart, and allow them to try again. In our template:

<div v-if="status === 'failure'">
  <h3>Oh No!</h3>
  <p>Something went wrong!</p>
  <button @click="clearCheckout">Please try again</button>
</div>

The button executes the following clearCheckout method that clears a number of the fields and allow the customer to try again:

clearCheckout() {
  this.submitted = false;
  this.status = '';
  this.complete = false;
  this.response = '';
}

If the payment succeeds, we will show a loading component, that will play an SVG animation until we hear back from the server. Sometimes this can take a couple of seconds, so it’s important that our animation make sense if it is seen for a short or long amount of time, and can loop as necessary.

<div v-else class="loadcontain">
  <h4>Please hold, we're filling up your cart with goodies</h4>
  <app-loader />
</div>

Here’s what that looks like:

See the Pen shop loader by Sarah Drasner (@sdras) on CodePen.

Now if we revisit the first cart component we looked at in pages/cart.vue, we can fill that page based on the logic we set up before because it’s been completed:

<div v-if="cartTotal > 0">
  <h1>Cart</h1>
  ...
  <app-checkout :total="total" @successSubmit="success = true"></app-checkout>
</div>

<div v-else-if="cartTotal === 0 && success === false" class="empty">
  <h1>Cart</h1>
  <h3>Your cart is empty.</h3>
  <nuxt-link exact to="/"><button>Fill 'er up!</button></nuxt-link>
</div>

<div v-else>
  <app-success @restartCart="success = false"/>
  <h2>Success!</h2>
  <p>Your order has been processed, it will be delivered shortly.</p>
</div>

If we have items in our cart, we show the cart. If the cart is empty and the success is false, we’ll let them know that their cart is empty. Otherwise, if the checkout has just been processed, we’ll let them know that everything has been executed successfully!

We are now here:

success.vue in the application

In the AppSuccess.vue component, we have a small SVG animation designed to make them feel good about the purchase:

See the Pen success by Sarah Drasner (@sdras) on CodePen.

(You may have to hit “Rerun” to replay the animation.)

We also put a small timer in the mounted() lifecycle hook:

window.setTimeout(() => this.$emit('restartCart'), 3000);

This will show the success for three seconds while they read it then kick off the restartCart that was shown in the component above. This allows us to reset the cart in case they would like to continue shopping.

Conclusion

You learned how to make a serverless function, host it on Github, add required dependencies, communicate with Stripe, set up a Shopping Cart in a Vue application, establish a connection with the serverless function and Stripe, and handle the logic for all of the cart states. Whew, way to go!

It’s worth mentioning that the demo app we looked at is a sample application built for specifically for this purpose of this tutorial. There are a number of steps you’d want to go through for a production site, including testing, building the app’s dist folder, using real Stripe keys, only sending the item from the client and adding the price in the serverless function, etc. There are also so many ways to set this up, Serverless functions can be so flexible in tandem with something like Vue. Hopefully this gets you on track and saves you time as you try it out yourself.