使用 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:
几个注意事项:
我确保 Stripe 处于测试模式,我的 stripe API 键设置正确,并使用推荐的测试卡:4242 4242 4242 4242 | 04/22 | 222 | 12345
我仔细阅读了这篇文章的评论,发现其他人有一个 "similar" 问题 - 但不是具体关于付款方式的错误。
自从 Laravel 5.8 发布后,Cashier 10 也发布了 - 我看到了关于 "paymentIntents" 的点点滴滴 - 所以我不确定这是否是造成这种情况的原因问题。
有人知道我可以做什么来修复这个错误吗?
谢谢!
编辑:(根据请求添加代码)
这是我使用的各种代码:
路线(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 刚刚发布,除了原始文档外,没有太多信息。我刚刚启动了一个项目并 运行 使用它,如果您决定走这条路,我很乐意回答任何问题。
新流程基本上是:
- 创建一个 setupIntent
- 使用 Stripe Elements 收集付款信息和 CC
- 将其与 setupIntent 一起发送到 Stripe 并使用 stripe.handleCardSetup() 接收 payment_method。
- 设置新订阅时使用 payment_method 而不是折旧令牌。
- 让您的 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。需要注意的一件事是,我不再需要在创建订阅时指定付款方式。同样在我的情况下,我必须在用户注册期间收集信用卡。我只在用户验证他们的电子邮件时才开始订阅。
步骤是
- 创建用户
- 创建条纹用户
- 根据从用户
的条带元素返回的 payment_token 创建支付方式
- 开始订阅
我真的不喜欢条纹文档。太多的破坏性变化,我觉得它不完整,因为它们不止是一种方式来做那些没有被记录下来的事情
.
我一步步跟着这个教程: https://appdividend.com/2018/12/05/laravel-stripe-payment-gateway-integration-tutorial-with-example/
然而,当我去测试它时,我得到以下错误:
条纹\错误\无效请求 没有这样的payment_method:
几个注意事项:
我确保 Stripe 处于测试模式,我的 stripe API 键设置正确,并使用推荐的测试卡:4242 4242 4242 4242 | 04/22 | 222 | 12345
我仔细阅读了这篇文章的评论,发现其他人有一个 "similar" 问题 - 但不是具体关于付款方式的错误。
自从 Laravel 5.8 发布后,Cashier 10 也发布了 - 我看到了关于 "paymentIntents" 的点点滴滴 - 所以我不确定这是否是造成这种情况的原因问题。
有人知道我可以做什么来修复这个错误吗?
谢谢!
编辑:(根据请求添加代码)
这是我使用的各种代码:
路线(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 刚刚发布,除了原始文档外,没有太多信息。我刚刚启动了一个项目并 运行 使用它,如果您决定走这条路,我很乐意回答任何问题。
新流程基本上是:
- 创建一个 setupIntent
- 使用 Stripe Elements 收集付款信息和 CC
- 将其与 setupIntent 一起发送到 Stripe 并使用 stripe.handleCardSetup() 接收 payment_method。
- 设置新订阅时使用 payment_method 而不是折旧令牌。
- 让您的 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。需要注意的一件事是,我不再需要在创建订阅时指定付款方式。同样在我的情况下,我必须在用户注册期间收集信用卡。我只在用户验证他们的电子邮件时才开始订阅。
步骤是
- 创建用户
- 创建条纹用户
- 根据从用户 的条带元素返回的 payment_token 创建支付方式
- 开始订阅
我真的不喜欢条纹文档。太多的破坏性变化,我觉得它不完整,因为它们不止是一种方式来做那些没有被记录下来的事情
.