Trouble setting up a subscription with Laravel 5.8 / Cashier / Stripe
Asked Answered
I

6

5

I followed this tutorial step by step: https://appdividend.com/2018/12/05/laravel-stripe-payment-gateway-integration-tutorial-with-example/

However, when I go to test it out, I get the following error:

Stripe \ Error \ InvalidRequest No such payment_method:

A couple of notes:

  • I made sure that Stripe is in test mode, that my stripe API keys are set properly, and used the recommended testing card: 4242 4242 4242 4242 | 04/22 | 222 | 12345

  • I perused through the comments of the article, and see that other people have a "similar" issue - but not specifically an error regarding the payment method.

  • Since Laravel 5.8 was released, and Cashier 10 was released - I am seeing bits and pieces about "paymentIntents" - so I'm not sure if that is what is causing the problem.

Does anybody have any ideas on what I can do to fix this error?

Thanks!

enter image description here

Edit: (Adding code, per request)

Here is the various bits of code that I used:

Routes (web.php)

Route::group(['middleware' => 'auth'], function() {
  Route::get('/home', 'HomeController@index')->name('home');
  Route::get('/plans', 'PlanController@index')->name('plans.index');
  Route::get('/plan/{plan}', 'PlanController@show')->name('plans.show');
  Route::post('/subscription', 'SubscriptionController@create')- 
>name('subscription.create');
});

Plan Model (plan.php)

<?php
    namespace App;
    use Illuminate\Database\Eloquent\Model;

    class Plan extends Model {
       protected $fillable = [
         'name',
         'slug',
         'stripe_plan',
         'cost',
         'description'
       ];

       public function getRouteKeyName() {
          return 'slug';
       }
    }

Plan Controller (PlanController.php)

<?php
    namespace App\Http\Controllers;
    use Illuminate\Http\Request;
    use App\Plan;

    class PlanController extends Controller {
        public function index() {
            $plans = Plan::all();
            return view('plans.index', compact('plans'));
        }

        public function show(Plan $plan, Request $request) {
            return view('plans.show', compact('plan'));
        }
    }

Subscription Controller (SubscriptionController.php)

<?php
    namespace App\Http\Controllers;
    use Illuminate\Http\Request;
    use App\Plan;

    class SubscriptionController extends Controller {
        public function create(Request $request, Plan $plan) {
            $plan = Plan::findOrFail($request->get('plan'));

            $request->user()
                ->newSubscription('main', $plan->stripe_plan)
                ->create($request->stripeToken);

            return redirect()->route('home')->with('success', 'Your plan subscribed successfully');
    }
}

Show View (show.blade.php)

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-12">
                <div class="">
                    <p>You will be charged ${{ number_format($plan->cost, 2) }} for {{ $plan->name }} Plan</p>
                </div>
                <div class="card">
                    <form action="{{ route('subscription.create') }}" method="post" id="payment-form">
                      @csrf
                      <div class="form-group">
                        <div class="card-header">
                            <label for="card-element">
                                Enter your credit card information
                            </label>
                        </div>

                        <div class="card-body">
                            <label for="card-element">Credit or debit card</label>

                        <div id="card-element">
                          <!-- A Stripe Element will be inserted here. -->
                        </div>

                        <!-- Used to display form errors. -->
                        <div id="card-errors" role="alert"></div>
                            <input type="hidden" name="plan" value="{{ $plan->id }}" />
                        </div>
                  </div>

                  <div class="card-footer">
                    <button class="btn btn-dark" type="submit">Submit Payment</button>
                  </div>
                </form>
            </div>
        </div>
    </div>
</div>
@endsection

@section('scripts')
    <script src="https://js.stripe.com/v3/"></script>
    <script>
        // Create a Stripe client.
        var stripe = Stripe('{{ env("STRIPE_KEY") }}');

        // Create an instance of Elements.
        var elements = stripe.elements();

        // Custom styling can be passed to options when creating an Element.
        // (Note that this demo uses a wider set of styles than the guide below.)
        var style = {
          base: {
            color: '#32325d',
            lineHeight: '18px',
            fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
            fontSmoothing: 'antialiased',
            fontSize: '16px',
            '::placeholder': {
              color: '#aab7c4'
            }
          },
          invalid: {
            color: '#fa755a',
            iconColor: '#fa755a'
          }
        };

        // Create an instance of the card Element.
        var card = elements.create('card', {style: style});

        // Add an instance of the card Element into the `card-element` <div>.
        card.mount('#card-element');

        // Handle real-time validation errors from the card Element.
        card.addEventListener('change', function(event) {
          var displayError = document.getElementById('card-errors');
          if (event.error) {
            displayError.textContent = event.error.message;
          } else {
            displayError.textContent = '';
          }
        });

        // Handle form submission.
        var form = document.getElementById('payment-form');
        form.addEventListener('submit', function(event) {
          event.preventDefault();

          stripe.createToken(card).then(function(result) {
            if (result.error) {
              // Inform the user if there was an error.
              var errorElement = document.getElementById('card-errors');
              errorElement.textContent = result.error.message;
            } else {
              // Send the token to your server.
              stripeTokenHandler(result.token);
            }
          });
        });

        // Submit the form with the token ID.
        function stripeTokenHandler(token) {
          // Insert token ID into the form so it gets submitted to the server
          var form = document.getElementById('payment-form');
          var hiddenInput = document.createElement('input');
          hiddenInput.setAttribute('type', 'hidden');
          hiddenInput.setAttribute('name', 'stripeToken');
          hiddenInput.setAttribute('value', token.id);
          form.appendChild(hiddenInput);

          // Submit the form
              form.submit();
        }
    </script>
@endsection
Inspissate answered 14/8, 2019 at 20:52 Comment(3)
Can you post your code?Ratchford
@KevinDaniel you bet! I added it to original post.Inspissate
@JohnHubler Did you get it solved. I face exactly same issue.Uella
O
7

Using that tutorial you need to use Laravel Cashier version prior to version 10 that stopped using Stripe Tokens.

For new projects I suggest that you use Laravel Cashier 10 and Stripe Elements as you would otherwise end up having to do some serious refactoring in the near future when the old API gets depreciated.

As Laravel Cashier 10 has just been released there are not much info other than the original docs. I just got a project up and running using it and am happy to answer any questions if you decide to go that route.

The new process is basically:

  1. Create a setupIntent
  2. Collect payment information and CC using Stripe Elements
  3. Send it together with the setupIntent to Stripe and receive the payment_method using the stripe.handleCardSetup().
  4. Use the payment_method instead of the depreciated token when setting up a new subscription.
  5. Have your Stripe Webhook handle the payment/subscription updates.
Overstrung answered 21/8, 2019 at 8:49 Comment(0)
R
13

Solved for Laravel 5.8 and Cashier 10.2

PlanController:

public function show(\App\Plan $plan, Request $request)
{
    $paymentMethods = $request->user()->paymentMethods();

    $intent = $request->user()->createSetupIntent();
    return view('plans.show', compact('plan', 'intent'));
}

View:

<button
   id="card-button"
   class="btn btn-dark"
   type="submit"
   data-secret="{{ $intent->client_secret }}"
 > Pay </button>

...

<script src="https://js.stripe.com/v3/"></script>
<script>
    // Custom styling can be passed to options when creating an Element.
    // (Note that this demo uses a wider set of styles than the guide below.)
    var style = {
        base: {
            color: '#32325d',
            lineHeight: '18px',
            fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
            fontSmoothing: 'antialiased',
            fontSize: '16px',
            '::placeholder': {
                color: '#aab7c4'
            }
        },
        invalid: {
            color: '#fa755a',
            iconColor: '#fa755a'
        }
    };

    const stripe = Stripe('{{ env("STRIPE_KEY") }}', { locale: 'es' }); // Create a Stripe client.
    const elements = stripe.elements(); // Create an instance of Elements.
    const cardElement = elements.create('card', { style: style }); // Create an instance of the card Element.
    const cardButton = document.getElementById('card-button');
    const clientSecret = cardButton.dataset.secret;

    cardElement.mount('#card-element'); // Add an instance of the card Element into the `card-element` <div>.

    // Handle real-time validation errors from the card Element.
    cardElement.addEventListener('change', function(event) {
        var displayError = document.getElementById('card-errors');
        if (event.error) {
            displayError.textContent = event.error.message;
        } else {
            displayError.textContent = '';
        }
    });

    // Handle form submission.
    var form = document.getElementById('payment-form');

    form.addEventListener('submit', function(event) {
        event.preventDefault();

        stripe
            .handleCardSetup(clientSecret, cardElement, {
                payment_method_data: {
                    //billing_details: { name: cardHolderName.value }
                }
            })
            .then(function(result) {
                console.log(result);
                if (result.error) {
                    // Inform the user if there was an error.
                    var errorElement = document.getElementById('card-errors');
                    errorElement.textContent = result.error.message;
                } else {
                    console.log(result);
                    // Send the token to your server.
                    stripeTokenHandler(result.setupIntent.payment_method);
                }
            });
    });

    // Submit the form with the token ID.
    function stripeTokenHandler(paymentMethod) {
        // Insert the token ID into the form so it gets submitted to the server
        var form = document.getElementById('payment-form');
        var hiddenInput = document.createElement('input');
        hiddenInput.setAttribute('type', 'hidden');
        hiddenInput.setAttribute('name', 'paymentMethod');
        hiddenInput.setAttribute('value', paymentMethod);
        form.appendChild(hiddenInput);

        // Submit the form
        form.submit();
    }
</script>

SubscriptionController

public function create(Request $request, \App\Plan $plan)
{
    $plan = \App\Plan::findOrFail($request->get('plan'));
    $user = $request->user();
    $paymentMethod = $request->paymentMethod;

    $user->createOrGetStripeCustomer();
    $user->updateDefaultPaymentMethod($paymentMethod);
    $user
        ->newSubscription('main', $plan->stripe_plan)
        ->trialDays(7)
        ->create($paymentMethod, [
            'email' => $user->email,
        ]);

    return redirect()->route('home')->with('status', 'Your plan subscribed successfully');
}
Ricciardi answered 5/9, 2019 at 20:44 Comment(2)
This worked for me, but only after the customer was created. When I register a new user and try to visit one of the plans, I get the error Laravel \ Cashier \ Exceptions \ InvalidStripeCustomer User is not a Stripe customer. See the createAsStripeCustomer method.Appomattox
I also got this createAsStripeCustomer error. I fixed this by altering the create function in the registerController.php file to protected function create(array $data) { $user = User::create([ 'name' => $data['name'], 'email' => $data['email'], 'password' => Hash::make($data['password']), ]); $user->createAsStripeCustomer(); return $user; }Scurlock
O
7

Using that tutorial you need to use Laravel Cashier version prior to version 10 that stopped using Stripe Tokens.

For new projects I suggest that you use Laravel Cashier 10 and Stripe Elements as you would otherwise end up having to do some serious refactoring in the near future when the old API gets depreciated.

As Laravel Cashier 10 has just been released there are not much info other than the original docs. I just got a project up and running using it and am happy to answer any questions if you decide to go that route.

The new process is basically:

  1. Create a setupIntent
  2. Collect payment information and CC using Stripe Elements
  3. Send it together with the setupIntent to Stripe and receive the payment_method using the stripe.handleCardSetup().
  4. Use the payment_method instead of the depreciated token when setting up a new subscription.
  5. Have your Stripe Webhook handle the payment/subscription updates.
Overstrung answered 21/8, 2019 at 8:49 Comment(0)
J
3

Downgrade your Cashier version to 9.x.

On Cashier 10.x's create() method accepts a paymentMethod as the first parameter.

On Cashier 9.x's create() method accepts a stripeToken as the first parameter.

OR

Upgrade your frontend JS to work with the Payment Intents API. But this will be a problem if you're planning to work with the new Stripe Checkout (as shown here - https://github.com/laravel/cashier/issues/637)

My suggestion is to downgrade the Cashier version until it fully supports.

Jingoism answered 18/8, 2019 at 17:21 Comment(1)
Hi. Ihave pass paymentMethod.but still its not working. it says invalid payment method. Can you please help me?Taurus
R
1

Just in case, any one wants to know how i fixed this error for this particular tutorial :

1) I downgraded cashier version

composer remove laravel/cashier

and then

composer require "laravel/cashier":"~9.0"

2) Then I started getting another error:

no plan exists with the name (Basic/Professional)

To fix this i created a new recurring product instead of one time product in stripe and updated plans table with this new plan entry

3) Then I again got another error:

no plan id exits

To fix this i updated my plans tables strip_plan column entry with the plan id i got from step 2

It's working for this particular tutorial, not sure about other versions

Raoul answered 22/8, 2019 at 16:12 Comment(0)
R
0

I think your problem may be the create method. Try this:

    namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Plan;

class SubscriptionController extends Controller {
    public function create(Request $request, Plan $plan) {
        $plan = Plan::findOrFail($request->get('plan'));

        \Auth::user() //make sure your user is signed in and use the authenticated user
            ->newSubscription('main', $request->plan) //You just send the name of the subscription in Stripe, not the object
            ->create($request->stripeToken);

        return redirect()->route('home')->with('success', 'Your plan subscribed successfully');
}

I think your problem is because you were using an invalid user and / or because you're sending a plan object instead of the payment plan's name. For example, if you have a product named Main in Stripe with pricing plans called "Plan 1" and "Plan 2", to subscribe your authenticated user, you'd do this:

\Auth::user
    ->newSubscription('Main', 'Plan 1')
    ->create($request->stripeToken);

And your Stripe Product should look something like this:

enter image description here

Ratchford answered 16/8, 2019 at 11:59 Comment(1)
Thanks so much for taking the time to lend some suggestions to this problem that I can't seem to get to the bottom off. I took your suggestion. I modified my plans within stripe, so that I have the "Product", which I named the same as my project "Stripesub", and then created two plans under that product, called Basic and Professional. I then updated my create subscription function to accept both parameters: the product name, and the plan name. Alas - in doing all of this, I am still getting the same error regarding the payment_method. Any further ideas?Inspissate
H
0

Maybe im late but you dont always need to setup the payment intent. i was able to do the following

$user = new User();
$user->fill($payload);

$user->createAsStripeCustomer([
  'name' => $user->fullname,
]);

$user->updateDefaultPaymentMethod($data->stripeToken);

$user->newSubscription(env('STRIPE_SUBSCRIPTION_NAME'), env('STRIPE_PLAN_ID'))
    ->create(null, [
      'name' => $this->fullname,
      'email' => $this->email,
    ]) // may not be required as we already do this above;

stripeToken is the token returned when using stripe.createPaymentMethod. One thing of note is that i no longer have to specify a payment method when creating a subscription. Also in my case i had to collect the credit card during user registration. I only start the subscription when the user verifies their email.

The steps are

  1. Create User
  2. Create Stripe User
  3. Create payment method from payment_token returned from stripe elements for user
  4. Start subscription

I really dislike the stripe docs. Too many breaking changes and i feel its incomplete as they are more than one way to do things that arent being documented

.

Hartmann answered 27/7, 2020 at 17:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.