Laravel Cashier, Stripe, Vue
Managing Stripe Payment Methods in VueJS SPA and Laravel API
Part 6 of 7 in Using Laravel Cashier with VueJS SPA and Laravel Passport API
Dan Pastori
Managing Stripe Payment Methods in VueJS SPA and Laravel API Part 6 of 7 in Using Laravel Cashier with VueJS SPA and Laravel Passport API

We’ve gotten up to the meat and potatoes of our course, managing payment methods! All of this has to be in place before we actually subscribe a user to a plan. Since we are working with recurring payments in a subscription, we have to have payment methods on file and a way to manage them correctly.

In the last tutorial, we loaded a payment intent (Creating Stripe Setup Intents With Laravel API and VueJS SPA). In this tutorial, we will be using that intent client_secret to pass to Stripe and save a payment method to charge.

We will also be adding 3 API routes. The first route will be to save the payment method, the second route will be to retrieve payment methods (through an API this requires a little modification), and the 3rd route will be to remove payment methods. Let’s get started!

1. Saving a Stripe Payment Method (with Stripe)

This is by far the most important part of the tutorial. Everything depends on having a payment method saved.

First, let’s start in our SubscriptionManagement.vue component. We will need to add a name field to save along with the payment method. This will be the name of the credit card holder and will be used to pass along with the card information to Stripe to store as a payment. If you need to pass and store any other billing information, add that now similar to how we have the name variable. More information can be found here: https://stripe.com/docs/stripe-js#elements.

Let’s also add a status called addPaymentStatus so we can display results to the user and an addPaymentStatusError so we can properly display any error messages if they arise.

Let’s add the name, addPaymentStatus, addPaymentStatusError variables to the data() like this:


data(){
    return {
        ...

        name: '',
                addPaymentStatus: 0,
                addPaymentStatusError: ''
    }
},

Next, add an input above the Card field in your template for the name:


<template>
    <div>
        <h3>Manage Your Subscription</h3>

        <label>Card Holder Name</label>
        <input id="card-holder-name" type="text" v-model="name" class="form-control mb-2">

        <label>Card</label>
        <div id="card-element">

        </div>
    </div>
</template>

Now we have the card holder name in our component. While we are in our component’s template, let’s add a button to allow the user to save the payment method. Once everything has been entered correctly, this is what the user will press to save the payment method with Stripe:


<template>
    <div>
        ...

        <button class="btn btn-primary mt-3" id="add-card-button" v-on:click="submitPaymentMethod()">
            Save Payment Method
        </button>
    </div>
</template>

Okay, so now we have our template laid out, it’s time to shell out some functionality and actually save our payment method!

In the methods object in our component, let’s add the method submitPaymentMethod() that you saw in the v-on:click handler in the template. When the user clicks to Save Payment Method, this is what will send the data to Stripe’s API to save the payment method for the user and return the payment identifier key that we can use later for charges.

The method is fairly large and should look like this (don’t worry, we will step through all of it!):


submitPaymentMethod(){
    this.addPaymentStatus = 1;

    this.stripe.confirmCardSetup(
        this.intentToken.client_secret, {
            payment_method: {
                card: this.card,
                billing_details: {
                    name: this.name
                }
            }
        }
    ).then(function(result) {
        if (result.error) {
            this.addPaymentStatus = 3;
            this.addPaymentStatusError = result.error.message;
        } else {
            this.savePaymentMethod( result.setupIntent.payment_method );
            this.addPaymentStatus = 2;
            this.card.clear();
            this.name = '';
        }
    }.bind(this));
},

That’s quite the method so let’s break it down! First, we set our addPaymentStatus to 1 which means we are adding a payment method, but haven’t heard a response back. I like keeping it simple so we can show in the UI or listen and respond accordingly.

Next, we call the confirmCardSetup() method on our local stripe object that we set up in Using Stripe Elements in a VueJS Component . For more information, check out the Stripe Docs here: https://stripe.com/docs/stripe-js/reference#stripe-confirm-card-setup This method accepts 2 parameters:

  1. Intent Token Client Secret → This is the client_secret key from the intent token we received from the API in Creating Stripe Setup Intents With Laravel API and VueJS SPA.
  2. Data → This is the guts of the request. The first key of the object is payment_method which contains the information regarding the card entered and billing details. The card key is the Card Element Object from Stripe Elements. This will contain ALL of the credit card data to be sent to Stripe’s API. The billing_details contains the name of the card holder.

After we send our payment to Stripe, we need to handle the result. Luckily the confirmCardSetup() returns a promise with all of the information that we need!

In the callback function we are passed a result object. This object will contain an error, if there is one, otherwise it will contain a setupIntent key with a payment_method key. This key is what we are looking to save with the user! It’s an identifier for the user and their card on file so we can bill them at set periods.

In our method, if the callback function has a key of error, we set the addPaymentStatus to be 3 which means there was an error in the process and addPaymentStatusError to be the result.error.message which is the message returned from Stripe’s API. We can then display that in the UI if you want.

What we are MOST concerned about is what happens on a successful payment method stored in Stripe. The first thing we do is call this.savePaymentMethod( result.setupIntent.payment_method );. Wonderful right? We don’t have this implemented yet. Next Step. This method accepts the result.setupIntent.payment_method which is the unique identifier for the user’s credit card within Stripe. We will be saving this with the user to use later. Next we set the addPaymentStatus equal to 2 which means everything is successful. You can then display success to the user in your component when addPaymentStatus == 2. Finally, we call this.card.clear() and this.name = ''. this.card.clear() calls the method to remove any of the user’s entered credit card input from the Stripe Elements card element and this.name = '' resets the card holder name to empty.

Whew! That was an intense method, but we now have the payment being saved within Stripe! Now, let’s persist that payment method to our own database.

2. Saving the Stripe Payment Method for Later Use

Now that we have the payment method’s unique identifier we can save it locally to our own database. Since we are using an API to communicate with our backend, we will have to set up another route. Let’s get that in place before we write our savePaymentMethod( method ) function discussed at the end of Step 1.

First, open up your routes/api.php file and add the following route:


Route::post('/user/payments', 'API\[email protected]');

MAKE SURE to do it under the auth:api middleware group or however you prevent unauthorized access. This URL will accept a POST request to /api/v1/user/payments and will save the payment with the user.

Now, let’s add the following method to our API\UserController:


/**
 * Adds a payment method to the current user. 
 * 
 * @param Request $request The request data from the user.
*/
public function postPaymentMethods( Request $request ){
    $user = $request->user();
    $paymentMethodID = $request->get('payment_method');

    if( $user->stripe_id == null ){
        $user->createAsStripeCustomer();
    }

    $user->addPaymentMethod( $paymentMethodID );
    $user->updateDefaultPaymentMethod( $paymentMethodID );
    
    return response()->json( null, 204 );        
}

In this method, we are going to make heavy use of the Billable traits added to the user from Laravel Cashier. These are extremely helpful and take SO MUCH pain away from doing this straight through the Stripe API.

First, we grab the $user and $paymentMethodID from the $request. The $request->user() is attached through Laravel Passport and the $request->get('payment_method') is what we send to the API route from our VueJS component when we implement the savePaymentMethod() (next step).

Now, the first thing we do is check to see if the user’s stripe_id is set. This is the customer id for the user within Stripe. If this is not set, we need to call the createAsStripeCustomer()method on the user to add them as a Stripe user (https://laravel.com/docs/6.x/billing#creating-customers). This helps before creating the subscription for the user so we have an identifier for all future transactions.

Next, we call 2 more methods from the Billable trait on the user. First it’s the addPaymentMethod() which takes the unique $paymentMethodID as the parameter to save with the user. This will save the payment method with the user within Stripe. Finally, we call the updateDefaultPaymentMethod() and pass the $paymentMethodID for the payment ID. I choose to set the newest payment method as the default right away, but you don’t have to do this. If you want to have an option to save as default, you could implement that as well.

Finally, we return a 204 null response which means everything was successfully saved! One thing you will notice though is the actual payment_method_id is NOT persisted to the database. This is GOOD! We just created a customer within Stripe for the user and saved that ID locally. The payment method is bound to that customer within Stripe. We don’t need to store that and loading any saved payment methods we will use a trait on the user to make an API request call to Stripe. Always best practice to have as much secure information stored with Stripe as possible!

3. Sending the Payment Method to the API

So now that we have our payment method identifier and our API set up to save it with the user, it’s time to glue the front and back end together! Luckily this is fairly straight forward.

First, let’s add the following method to our methods object in the SubscriptionManagement.vue component:


/*
    Saves the payment method for the user and
    re-loads the payment methods.
*/
savePaymentMethod( method ){
    axios.post('/api/v1/user/payments', {
        payment_method: method
    }).then( function(){
        this.loadPaymentMethods();
    }.bind(this));
},

Now, we’ve pieced together the puzzle! After we get the payment_method from the setupIntent in Step 1, we call the savePaymentMethod() method and pass the unique ID of the payment method. This method calls the API route that we set up in Step 2, saving the method with the user. Finally, we call another method (which, once again, we will define in the next step) that loads the payment methods for the user.

Other than that, we now have a payment method saved with the user and connected through Stripe! Since this tutorial covers the management of payment methods (adding, loading, deleting) we have a few more methods to add! Luckily, they aren’t all super complicated.

4. Loading a User’s Payment Methods with Laravel Cashier through the API

Now that we have the payment methods being saved, it’d be nice to load these methods and display them to the user so they can select what they want to use when selecting a a subscription to purchase. Once again, since we are in an API Driven Development lens, we need to make an API route to handle this. We will then complete the flow by implementing the loadPaymentMethods() method foreshadowed in the last step.

First, let’s open up our routes/api.php file and add the following route ONCE AGAIN protected behind the auth:api middleware:


Route::get('/user/payment-methods', 'API\[email protected]');

This API route will resolve to /api/v1/user/payment-methods. Next, let’s open up our UserController.php and add the following method:


/**
 * Returns the payment methods the user has saved
 * 
 * @param Request $request The request data from the user.
 */
public function getPaymentMethods( Request $request ){
    $user = $request->user();

    $methods = array();

    if( $user->hasPaymentMethod() ){
        foreach( $user->paymentMethods() as $method ){
            array_push( $methods, [
                'id' => $method->id,
                'brand' => $method->card->brand,
                'last_four' => $method->card->last4,
                'exp_month' => $method->card->exp_month,
                'exp_year' => $method->card->exp_year,
            ] );
        }
    }

    return response()->json( $methods );
}

What this method does is grabs the payment methods stored with the user through Stripe. First, we check through the hasPaymentMethod() if the user even has any payment methods. If they do, we load all of the methods by calling $user->paymentMethods() and adding them to the local $methods array. We then return the $methods array as JSON to the component so we can display the possibilities to the user to select when purchasing a subscription.

Now that we have our API built, let’s open up our SubscriptionManagement.vuecomponent and add the following method:


/*
    Loads all of the payment methods for the
    user.
*/
loadPaymentMethods(){
    axios.get('/api/v1/user/payment-methods')
        .then( function( response ){
            this.paymentMethods = response.data;
        }.bind(this));
},

This method will call our new route, loading the payment methods for the user. The only thing we need to add is the paymentMethods array to the data() locally in the component like so:


data(){
    return {
        ...

        paymentMethods: [],
    }
},

Finally, we should call this method in the mounted() lifecycle hook:


mounted(){
    ...

    this.loadPaymentMethods();
},

This way we have all of the payment methods displayed for the user when they are signing up for the app. When we get to the next tutorial of actually signing up for the subscription, we will display these payment methods as options for the user to select.

5. Deleting a Stored Payment Method

While we won’t add the UI in this tutorial (the next tutorial we will), let’s set up our methods to delete a stored payment method.

First, let’s start with our routes/api.php file and add the following route:


Route::post('/user/remove-payment', 'API\[email protected]');

This would resolve to a POST request to /api/v1/user/remove-payment. In a proper RESTful API this would be a DELETE request. However, we don’t want to pass the payment ID through the URL, we want it in the body on an encrypted https request, we will use POST.

Now, let’s add the following method to the UserController.php:


/**
 * Removes a payment method for the current user.
 * 
 * @param Request $request The request data from the user.
 */
public function removePaymentMethod( Request $request ){
    $user = $request->user();
    $paymentMethodID = $request->get('id');

    $paymentMethods = $user->paymentMethods();

    foreach( $paymentMethods as $method ){
        if( $method->id == $paymentMethodID ){
            $method->delete();
            break;
        }
    }
    
    return response()->json( null, 204 );
}

What this does is accepts a parameter named id. It will then iterate over all of the payment methods the user has which are instances of the Laravel\Cashier\PaymentMethod class. Once it finds the ID that matches the one we are looking for, we run the delete() method which removes the payment method from the user. For more information view: https://laravel.com/docs/6.x/billing#deleting-payment-methods. Note, that if the user has an active subscription, prevent them from deleting their default payment method as noted in the docs!

Finally, let’s re-open the SubscriptionManagement.vue component and add the following method to the methods object:


removePaymentMethod( paymentID ){
    axios.post('/api/v1/user/remove-payment', {
        id: paymentID
    }).then( function( response ){
        this.loadPaymentMethods();
    }.bind(this));
}

This makes a simple request to our newly defined remove payment method route and accepts the ID of the payment method we are removing as it’s parameter. Upon completion, it re-loads the active payment methods that the user has.

Conclusion

Well, that was a long one! Now we can add payment methods, save them to the user, load the existing payment methods and delete them. Up next, we will build on this base and actually subscribe the user to a plan for the app! If you have any questions, like to contribute thoughts or ideas, please leave a comment in the comment section below or reach out to me via Twitter @danpastori ! On to subscriptions!

About the Author
Builder, creator, and maker. Dan Pastori has over 10 years experience as a full stack developer. When you aren't finding Dan exploring new techniques in programming, catch him at the beach or hiking in the National Parks. Follow me on Twitter
More posts in this course
Using Laravel Cashier with VueJS SPA and Laravel Passport API See The Entire Course