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

Trouble setting up a subscription with Laravel 5.8 / Cashier / Stripe

我一步步跟着这个教程: https://appdividend.com/2018/12/05/laravel-stripe-payment-gateway-integration-tutorial-with-example/

然而,当我去测试它时,我得到以下错误:

条纹\错误\无效请求 没有这样的payment_method:

几个注意事项:

有人知道我可以做什么来修复这个错误吗?

谢谢!

编辑:(根据请求添加代码)

这是我使用的各种代码:

路线(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.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';
       }
    }

计划控制器(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'));
        }
    }

订阅控制器(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.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

我想你的问题可能是创建方法。试试这个:

    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');
}

我认为您的问题是因为您使用的用户无效和/或因为您发送的是计划对象而不是付款计划的名称。例如,如果您在 Stripe 中有一个名为 Main 的产品,其定价计划名为 "Plan 1" 和 "Plan 2",要订阅您的经过身份验证的用户,您可以这样做:

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

你的 Stripe 产品应该看起来像这样:

将您的 Cashier 版本降级到 9.x。

收银台 10.x 的 create() 方法接受 paymentMethod 作为第一个参数。

收银台 9.x 的 create() 方法接受 stripeToken 作为第一个参数。

升级您的前端 JS 以使用付款意图 API。但是,如果您打算使用新的 Stripe Checkout(如此处所示 - https://github.com/laravel/cashier/issues/637

,这将是一个问题

我的建议是降级Cashier版本,直到完全支持。

使用该教程,您需要使用 Laravel 版本 10 之前的 Cashier 版本,该版本停止使用 Stripe Tokens。

对于新项目,我建议您使用 Laravel Cashier 10 和 Stripe Elements,否则当旧的 API 贬值时,您最终不得不在不久的将来进行一些认真的重构。

由于 Laravel Cashier 10 刚刚发布,除了原始文档外,没有太多信息。我刚刚启动了一个项目并 运行 使用它,如果您决定走这条路,我很乐意回答任何问题。

新流程基本上是:

  1. 创建一个 setupIntent
  2. 使用 Stripe Elements 收集付款信息和 CC
  3. 将其与 setupIntent 一起发送到 Stripe 并使用 stripe.handleCardSetup() 接收 payment_method。
  4. 设置新订阅时使用 payment_method 而不是折旧令牌。
  5. 让您的 Stripe Webhook 处理 payment/subscription 更新。

以防万一,有人想知道我是如何修复这个特定教程的错误的:

1) 我降级了收银版

composer remove laravel/cashier

然后

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

2) 然后我开始收到另一个错误:

no plan exists with the name (Basic/Professional)

为了解决这个问题,我在条带中创建了一个新的经常性产品而不是一次性产品,并使用这个新计划条目更新了计划table

3) 然后我又遇到了另一个错误:

no plan id exits

为了解决这个问题,我更新了我的计划 tables strip_plan 列条目,其中包含我从第 2 步

获得的计划 ID

它适用于这个特定的教程,不确定其他版本

已解决 Laravel 5.8 和 Cashier 10.2

计划控制器:

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

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

查看:

<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>

订阅控制器

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');
}

也许我来晚了,但您并不总是需要设置付款意向。我能够做到以下几点

$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是使用stripe.createPaymentMethod时返回的token。需要注意的一件事是,我不再需要在创建订阅时指定付款方式。同样在我的情况下,我必须在用户注册期间收集信用卡。我只在用户验证他们的电子邮件时才开始订阅。

步骤是

  1. 创建用户
  2. 创建条纹用户
  3. 根据从用户
  4. 的条带元素返回的 payment_token 创建支付方式
  5. 开始订阅

我真的不喜欢条纹文档。太多的破坏性变化,我觉得它不完整,因为它们不止是一种方式来做那些没有被记录下来的事情

.