简体   繁体   English

使用 Laravel 5.8 / Cashier / Stripe 设置订阅时遇到问题

[英]Trouble setting up a subscription with Laravel 5.8 / Cashier / Stripe

I followed this tutorial step by step: https://appdividend.com/2018/12/05/laravel-stripe-payment-gateway-integration-tutorial-with-example/我一步一步跟着本教程: 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: Stripe \\ Error \\ InvalidRequest 没有这样的 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 |我确保 Stripe 处于测试模式,我的 Stripe API 密钥设置正确,并使用推荐的测试卡:4242 4242 4242 4242 | 04/22 | 04/22 | 222 |第222话12345 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.自从 Laravel 5.8 和 Cashier 10 发布以来——我看到了关于“paymentIntents”的零碎细节——所以我不确定这是否是导致问题的原因。

Does anybody have any ideas on what I can do to fix this error?有没有人对我可以做些什么来解决这个错误有任何想法?

Thanks!谢谢!

在此处输入图片说明

Edit: (Adding code, per request)编辑:(根据请求添加代码)

Here is the various bits of code that I used:这是我使用的各种代码:

Routes (web.php)路线 (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)计划模型 (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)计划控制器 (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)订阅控制器 (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)显示视图 (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

Solved for Laravel 5.8 and Cashier 10.2解决了 Laravel 5.8 和 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');
}

Using that tutorial you need to use Laravel Cashier version prior to version 10 that stopped using Stripe Tokens.使用该教程,您需要使用 Laravel Cashier 版本 10 之前停止使用 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.对于新项目,我建议您使用 Laravel Cashier 10 和 Stripe Elements,否则当旧 API 贬值时,您将不得不在不久的将来进行一些严重的重构。

As Laravel Cashier 10 has just been released there are not much info other than the original docs.由于 Laravel Cashier 10 刚刚发布,除了原始文档之外没有太多信息。 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创建一个 setupIntent
  2. Collect payment information and CC using Stripe Elements使用 Stripe Elements 收集付款信息和抄送
  3. Send it together with the setupIntent to Stripe and receive the payment_method using the stripe.handleCardSetup().将它与 setupIntent 一起发送到 Stripe 并使用 stripe.handleCardSetup() 接收 payment_method。
  4. Use the payment_method instead of the depreciated token when setting up a new subscription.设置新订阅时,使用 payment_method 而不是折旧的令牌。
  5. Have your Stripe Webhook handle the payment/subscription updates.让您的 Stripe Webhook 处理付款/订阅更新。

Downgrade your Cashier version to 9.x.将您的收银台版本降级到 9.x。

On Cashier 10.x's create() method accepts a paymentMethod as the first parameter.在 Cashier 10.x 的create()方法接受一个paymentMethod作为第一个参数。

On Cashier 9.x's create() method accepts a stripeToken as the first parameter.在 Cashier 9.x 的create()方法接受一个stripeToken作为第一个参数。

OR或者

Upgrade your frontend JS to work with the Payment Intents API.升级您的前端 JS 以使用 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 )但是,如果您打算使用新的 Stripe Checkout(如下所示 - https://github.com/laravel/cashier/issues/637 ),这将是一个问题

My suggestion is to downgrade the Cashier version until it fully supports.我的建议是降级收银台版本,直到它完全支持。

Just in case, any one wants to know how i fixed this error for this particular tutorial :以防万一,有人想知道我是如何为这个特定教程修复这个错误的:

1) I downgraded cashier version 1)我降级收银台版本

composer remove laravel/cashier

and then进而

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

2) Then I started getting another error: 2)然后我开始收到另一个错误:

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: 3)然后我又遇到了另一个错误:

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为了解决这个问题,我用我从第 2 步得到的计划 ID 更新了我的计划表 strip_plan 列条目

It's working for this particular tutorial, not sure about other versions它适用于这个特定的教程,不确定其他版本

I think your problem may be the create method.我认为您的问题可能是 create 方法。 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:例如,如果您有一个名为 Main in Stripe 的产品,其定价计划称为“计划 1”和“计划 2”,要订阅您的经过身份验证的用户,您可以执行以下操作:

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

And your Stripe Product should look something like this:您的 Stripe 产品应如下所示:

在此处输入图片说明

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 . stripeToken是使用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从用户的条带元素返回的 payment_token 创建付款方式
  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太多的破坏性更改,我觉得它不完整,因为它们不仅仅是一种方式来做没有被记录的事情

. .

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何使用 Stripe 使用 Laravel 6 和 Cashier 10 创建订阅? - How to create Subscription with Laravel 6 and Cashier 10 with Stripe? 使用 Laravel 和收银员直接从 Stripe 查看 Stripe 订阅状态 - Using Laravel & cashier to check Stripe subscription status directly from stripe 在Laravel 5.2中,Laravel Cashier条带无法创建订阅。 - In Laravel 5.2, Laravel Cashier stripe can'not create subscription. 使用发票描述更新和开票 Stripe 订阅数量 laravel 收银员 - Updating and Invoicing Stripe Subscription Quantity with Invoice description laravel cashier 如何使用Laravel Cashier向Stripe订阅添加税款(4.2) - How to add tax to Stripe subscription using Laravel Cashier (4.2) Laravel 收银员 (Stripe) - 如何为计量使用创建订阅? - Laravel cashier (Stripe) - How to create subscription for metered usage? Laravel Cashier + Stripe:没有这样的客户 - Laravel Cashier + Stripe: No Such Customer Laravel 收银员 - 终身订阅 - Laravel Cashier - Lifetime Subscription Laravel 收银员订阅 noProrate first charge when setting anchor date - Laravel Cashier Subscription noProrate first charge when setting anchor date 订阅已创建后,如何使用 Laravel Cashier 将优惠券添加到 *Stripe 订阅 * - How to add coupon to *Stripe subscription* with Laravel Cashier after subscription already created
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM