:braintree
This article was peer reviewed by Viraj Khatavkar. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!
本文由Viraj Khatavkar进行了同行评审。 感谢所有SitePoint的同行评审人员使SitePoint内容达到最佳状态!
Previously, we saw how to configure a Laravel app to handle Braintree subscriptions.
之前 ,我们看到了如何配置Laravel应用来处理Braintree订阅。
![Braintree Logo](https://dab1nmslvvntp.cloudfront.net/wp-content/uploads/2017/01/14833826181409502454Braintree-Logo.jpg)
This time, we’ll talk about how to:
这次,我们将讨论如何:
- Prevent users from signing up to the same plan twice 防止用户两次签署相同的计划
- Add basic flash messaging to our application 向我们的应用程序添加基本的Flash消息传递
- Add the ability to swap plans 添加交换计划的功能
- Create middleware to protect some routes based on the subscription status 创建中间件以根据订阅状态保护某些路由
- Restrict users with basic subscriptions from accessing premium content 限制具有基本订阅的用户访问高级内容
- Cancel and resume subscriptions 取消并恢复订阅
- Add Braintree notifications to the application’s events via webhooks 通过webhooks将Braintree通知添加到应用程序的事件
双重订阅 (Double Subscriptions)
As it stands, if we visit the plans index
page, we can still see the Choose Plan
button for the plan we are currently subscribed to, and this shouldn’t be the case. In the plans index
view, let’s add an if conditional
to hide the button based on the user’s subscription status:
就目前而言,如果我们访问计划index
页面,我们仍然会看到当前订阅的计划的Choose Plan
按钮,事实并非如此。 在plans index
视图中,让我们添加一个if conditional
来根据用户的订阅状态隐藏按钮:
[...]
@if (!Auth::user()->subscribedToPlan($plan->braintree_plan, 'main'))
<a href="{{ url('/plan', $plan->slug) }}" class="btn btn-default pull-right">Choose Plan</a>
@endif
[...]
But that’s not to say users can’t access the plan by typing in the URL pointing to the same plan in the address bar. To counter this, let’s update the code in the show
action of the PlansController
to this:
但这并不是说用户无法通过在地址栏中输入指向同一计划的URL来访问该计划。 为了解决这个问题,让我们将PlansController
的show
动作中的代码更新为:
[...]
public function show(Request $request, Plan $plan)
{
if ($request->user()->subscribedToPlan($plan->braintree_plan, 'main')) {
return redirect('home')->with('error', 'Unauthorised operation');
}
return view('plans.show')->with(['plan' => $plan]);
}
[...]
Here, we are getting the user from the request object; remember all our routes fall under the auth middleware and thus it’s possible to get the authenticated user. Once we get the user, we check if they are already subscribed to the plan. If that’s the case, we redirect them to the homepage and display a notification. We will implement basic flash messaging later.
在这里,我们从请求对象获取用户; 请记住,我们所有的路由都属于auth中间件,因此可以获取经过身份验证的用户。 获取用户后,我们将检查他们是否已订阅该计划。 如果是这种情况,我们会将其重定向到首页并显示一条通知。 稍后我们将实现基本的Flash消息传递。
One last precaution is preventing users from submitting the payment form with a different plan ID
value. It’s possible to inspect the DOM elements and change the value for the hidden input. In our SubscriptionsController
, let’s update the store
method to this:
最后一项预防措施是阻止用户提交具有不同plan ID
值的付款表单。 可以检查DOM元素并更改隐藏输入的值。 在我们的SubscriptionsController
,让我们将store
方法更新为:
[...]
public function store(Request $request)
{
$plan = Plan::findOrFail($request->plan);
if ($request->user()->subscribedToPlan($plan->braintree_plan, 'main')) {
return redirect('home')->with('error', 'Unauthorised operation');
}
$request->user()->newSubscription('main', $plan->braintree_plan)->create($request->payment_method_nonce);
// redirect to home after a successful subscription
return redirect('home')->with('success', 'Subscribed to '.$plan->braintree_plan.' successfully');
}
[...]
Flash消息 (Flash Messaging)
Let’s now implement some basic flash messaging to display notifications in the app in response to certain operations. In the resources/views/layouts/app.blade.php
file, let’s insert this block right above our content:
现在,让我们实现一些基本的Flash消息传递,以响应某些操作在应用程序中显示通知。 在resources/views/layouts/app.blade.php
文件中,让我们在内容上方插入此代码块:
[...]
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
@include ('layouts.partials._notifications')
</div>
</div>
</div>
@yield('content')
[...]
Then, we create the notifications partial:
然后,我们创建部分通知:
resources/views/layouts/partials/_notifications.blade.php
资源/视图/布局/部分/_notifications.blade.php
@if (session('success'))
<div class="alert alert-success">
{{ session('success') }}
</div>
@endif
@if (session('error'))
<div class="alert alert-danger">
{{ session('error') }}
</div>
@endif
In the partial, we have used the session
helper to help us come up with different notification colors based on the session status i.e. success
or error
.
在部分内容中,我们使用了session
帮助程序来根据会话状态(即success
或error
提供不同的通知颜色。
交换计划 (Swapping Plans)
After a user has subscribed to our application, they may occasionally want to change to a new subscription plan.
用户订阅了我们的应用程序后,他们有时可能希望更改为新的订阅计划。
To accomplish this, we’ll first have to check if the user is subscribed to any plan inside the store
method of the SubscriptionsController
. If not, we subscribe them to a new plan. To swap a user to a new subscription, we pass the plan’s identifier to the swap
method.
为此,我们首先必须检查用户是否已订阅SubscriptionsController
的store
方法内的任何计划。 如果没有,我们将为他们订阅新计划。 要将用户交换到新订阅,我们将计划的标识符传递给swap
方法。
Let’s open our SubscriptionsController
and update the store
method:
让我们打开我们的SubscriptionsController
并更新store
方法:
public function store(Request $request)
{
[...]
if (!$request->user()->subscribed('main')) {
$request->user()->newSubscription('main', $plan->braintree_plan)->create($request->payment_method_nonce);
} else {
$request->user()->subscription('main')->swap($plan->braintree_plan);
}
return redirect('home')->with('success', 'Subscribed to '.$plan->braintree_plan.' successfully');
}
Let’s see if things are working as intended by choosing a different plan, filling in fake card details (same details used when subscribing to a new plan in the previous post) then submitting the form. If we look inside the subscriptions
table, we should notice the plan for the authenticated user has changed. This change should also be reflected in Braintree under transactions. Next, we want to protect routes based on subscription status.
让我们通过选择一个不同的计划,填写虚假的卡详细信息(在上一篇文章中订购新计划时使用的相同详细信息) ,看看事情是否按预期进行,然后提交表单。 如果我们查看subscriptions
表,则应注意已认证用户的计划已更改。 此更改也应反映在Braintree的交易下。 接下来,我们要基于订阅状态保护路由。
保护路线 (Protecting Routes)
For this application, we want to introduce lessons. Only subscribed users should have access to lessons. We’ll generate a LessonsController
:
对于此应用程序,我们要介绍课程。 只有订阅的用户才能访问课程。 我们将生成一个LessonsController
:
php artisan make:controller LessonsController
… then head over to the controller and create an index
action. We won’t be creating views for this, just displaying some text:
…然后转到控制器并创建index
动作。 我们不会为此创建视图,仅显示一些文本:
[...]
public function index()
{
return 'Normal Lessons';
}
[...]
Then, we create the route pointing to this index action inside the route group with the auth middleware
:
然后,使用auth middleware
在路由组内部创建指向此index操作的路由:
Route::group(['middleware' => 'auth'], function () {
Route::get('/lessons', 'LessonsController@index');
});
Let’s also update our navbar to include a link to these ‘lessons’:
让我们还更新导航栏,以包含指向这些“课程”的链接:
resources/views/layouts/app.blade.php
资源/视图/布局/app.blade.php
[...]
<ul class="nav navbar-nav navbar-left">
<li><a href="{{ url('/plans') }}">Plans</a></li>
<li><a href="{{ url('/lessons') }}">Lessons</a></li>
</ul>
[...]
If we now click on Lessons
from the navbar, we’ll see the text “Normal Lessons” regardless of the subscription status. This shouldn’t be the case. Only subscribed users should be able to access lessons. To enable this behavior, we’ll have to create some middleware:
如果我们现在点击Lessons
从导航栏,我们将看到文本“正常的教训”不管订阅状态。 事实并非如此。 只有订阅的用户才能访问课程。 为了实现这种行为,我们必须创建一些中间件:
php artisan make:middleware Subscribed
Let’s open the file in app/Http/Middleware/Subscribed.php
and update the handle
method to this:
让我们在app/Http/Middleware/Subscribed.php
打开文件,并将handle
方法更新为:
[...]
public function handle($request, Closure $next)
{
if (!$request->user()->subscribed('main')) {
if ($request->ajax() || $request->wantsJson()) {
return response('Unauthorized.', 401);
}
return redirect('/plans');
}
return $next($request);
}
[...]
In the code block above, we are checking for users that are not subscribed to any plan. If that’s the case, whether using ajax or not, they will get the Unauthorized
response or else be redirected to the plans’ index
page. If a user is already subscribed to a plan, we’ll just proceed with the next request.
在上面的代码块中,我们正在检查未预订任何计划的用户。 如果是这种情况,无论是否使用ajax,他们都将收到Unauthorized
响应,否则将被重定向到计划的index
页面。 如果用户已经订阅了计划,我们将继续处理下一个请求。
With this defined, let’s head over to app/Http/Kernel.php
and register our middleware to the $routeMiddleware
so we can call the middleware within our routes:
有了这个定义,让我们转到app/Http/Kernel.php
并将我们的中间件注册到$routeMiddleware
以便我们可以在路由中调用中间件:
app/Http/Kernel.php
app / Http / Kernel.php
protected $routeMiddleware = [
[...]
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'subscribed' => \App\Http\Middleware\Subscribed::class,
];
We can now create another route group inside the group with the auth
middleware, but this time around, we pass in subscribed
as the middleware. It’s inside this new route group where we will define the route to access lessons:
现在,我们可以使用auth
中间件在组内创建另一个路由组,但是这次,我们传入已subscribed
的中间件。 在这个新的路线组中,我们将定义访问课程的路线:
Route::group(['middleware' => 'auth'], function () {
[...]
Route::group(['middleware' => 'subscribed'], function () {
Route::get('/lessons', 'LessonsController@index');
});
});
If we try signing up a new user, then clicking on Lessons
from the navbar, we will be redirected to /plans
. If we subscribe to a plan with the new account, we now have access to lessons.
如果我们尝试注册新用户,然后从导航栏中单击“ Lessons
”,我们将被重定向到/plans
。 如果我们使用新帐户订阅计划,则现在可以访问课程。
保护基本用户的高级内容 (Protecting Premium Content from Basic Users)
For our app, we want to provide basic and premium content. Users with basic plans will have access to normal lessons but only users with premium subscriptions will have access to premium lessons. For demonstration purposes, let’s create a premium
method inside LessonsController
:
对于我们的应用程序,我们希望提供基本内容和高级内容。 具有基本计划的用户将有权访问正常课程,但只有具有高级订阅的用户才有权访问高级课程。 出于演示目的,让我们在LessonsController
创建一个premium
方法:
[...]
public function premium()
{
return 'Premium Lessons';
}
[...]
Let’s also update the navbar to include a link pointing to Premium Lessons
:
让我们还更新导航栏,使其包含指向“ Premium Lessons
的链接:
[...]
<ul class="nav navbar-nav navbar-left">
<li><a href="{{ url('/plans') }}">Plans</a></li>
<li><a href="{{ url('/lessons') }}">Lessons</a></li>
<li><a href="{{ url('/prolessons') }}">Premium Lessons</a></li>
</ul>
[...]
We could have placed the route to access the premium lessons inside the route group with the subscribed
middleware but that will let anyone access premium content regardless of whether they are subscribed or not. We will have to create another middleware to prevent basic users from accessing premium content:
我们可以使用subscribed
中间件将路由放置在路由组内访问高级课程,但这将使任何人都可以访问高级内容,而不管他们是否已订阅。 我们将不得不创建另一个中间件,以防止基本用户访问高级内容:
php artisan make:middleware PremiumSubscription
The code going into the PremiumSubscription
middleware won’t be so different from the one we had in the Subscribed
middleware. The only difference is that we’ll have to be plan specific when checking the user’s subscription status. If the user is not subscribed to the premium plan, they’ll be redirected to the plans’ index
page:
PremiumSubscription
中间件中的代码与Subscribed
中间件中的代码没有太大不同。 唯一的区别是,在检查用户的订阅状态时,我们必须特定于计划。 如果用户未订阅高级计划,他们将被重定向到计划的index
页面:
app/Http/Middleware/Premium.php
app / Http / Middleware / Premium.php
[...]
public function handle($request, Closure $next)
{
if (!$request->user()->subscribed('premium', 'main')) {
if ($request->ajax() || $request->wantsJson()) {
return response('Unauthorized.', 401);
}
return redirect('/plans');
}
return $next($request);
}
[...]
Let’s register this new middleware in the Kernel before creating a new route group for premium users:
在为高级用户创建新的路由组之前,让我们在内核中注册此新的中间件:
app/Http/Kernel.php
app / Http / Kernel.php
protected $routeMiddleware = [
[...]
'subscribed' => \App\Http\Middleware\Subscribed::class,
'premium-subscribed' => \App\Http\Middleware\PremiumSubscription::class,
];
Then, we create the new route group just below the group with the subscribed
middleware:
然后,在带有已subscribed
中间件的组下面创建新的路由组:
Route::group(['middleware' => 'auth'], function () {
[...]
Route::group(['middleware' => 'premium-subscribed'], function () {
Route::get('/prolessons', 'LessonsController@premium');
});
});
And that’s it. Anyone with a basic subscription cannot access premium content, but premium users can access both premium and basic content. Next, we’ll look at how to cancel and resume subscriptions.
就是这样。 具有基本订阅的任何人都不能访问高级内容,但是高级用户可以访问高级和基本内容。 接下来,我们将研究如何取消和恢复订阅。
取消订阅 (Cancelling Subscriptions)
To enable cancelling and resuming subscriptions, we’ll have to create a new page. Let’s open our SubscriptionsController
and add an index
action. It is inside the index
action where we will return the view to manage subscriptions:
要启用取消和恢复订阅,我们必须创建一个新页面。 让我们打开我们的SubscriptionsController
并添加一个index
操作。 它位于index
操作中,我们将返回该视图以管理预订:
[...]
class SubscriptionController extends Controller
{
public function index()
{
return view('subscriptions.index');
}
[...]
}
Let’s first create a subscriptions folder inside views before creating the index
view. Then, we paste the snippet below inside the view:
首先,在创建index
视图之前,先在视图内部创建一个subscriptions文件夹。 然后,将以下代码段粘贴到视图内:
resources/views/subscriptions/index.blade.html
资源/视图/订阅/index.blade.html
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Manage Subscriptions</div>
<div class="panel-body">
...
</div>
</div>
</div>
</div>
</div>
@endsection
We should then define a route pointing to this view. This route should be inside the route group with the subscribed
middleware:
然后,我们应该定义指向该视图的路线。 该路由应位于带有已subscribed
中间件的路由组中:
Route::group(['middleware' => 'subscribed'], function () {
Route::get('/lessons', 'LessonsController@index');
Route::get('/subscriptions', 'SubscriptionController@index');
});
Let’s update the dropdown in the navbar to include a link pointing to the index
view we just created (place the link above the logout link). This link should only be visible to subscribed users:
让我们更新导航栏中的下拉列表,以包括指向我们刚创建的index
视图的链接(将链接放置在注销链接上方) 。 此链接仅对订阅的用户可见:
resources/views/layouts/app.blade.html
资源/视图/布局/app.blade.html
<ul class="dropdown-menu" role="menu">
[...]
<li>
@if (Auth::user()->subscribed('main'))
<a href="{{ url('/subscriptions') }}">
Manage subscriptions
</a>
@endif
</li>
[...]
</ul>
The next step is creating a link to enable users to cancel their subscriptions. For this, we’ll need a form. The form is important since we need to use CSRF protection to make sure the user who is cancelling the subscription is indeed the correct user. Let’s create the form inside the div
with the class panel-body
:
下一步是创建一个链接,使用户可以取消其订阅。 为此,我们需要一个表格。 表单很重要,因为我们需要使用CSRF保护来确保要取消订阅的用户确实是正确的用户。 让我们使用panel-body
类在div
创建表单:
resources/views/subscriptions/index.blade.html
资源/视图/订阅/index.blade.html
[...]
<div class="panel-body">
@if (Auth::user()->subscription('main')->cancelled())
<!-- Will create the form to resume a subscription later -->
@else
<p>You are currently subscribed to {{ Auth::user()->subscription('main')->braintree_plan }} plan</p>
<form action="{{ url('subscription/cancel') }}" method="post">
<button type="submit" class="btn btn-default">Cancel subscription</button>
{{ csrf_field() }}
</form>
@endif
</div>
[...]
Notice how we have a conditional to check if the user has a cancelled subscription, and if that’s the case, we’ll leave them with the option to resume it. If the user still has an active subscription, that’s when the form to cancel will show up.
请注意,我们如何有条件地检查用户是否已取消订阅,如果是这种情况,我们将让他们选择恢复订阅。 如果用户仍具有有效的订阅,则将显示要取消的表单。
When a subscription is cancelled, Cashier will automatically set the ends_at
column in our database. This column is used to know when the subscribed
method should begin returning false
. For example, if a customer cancels a subscription on March 1st, but the subscription was not scheduled to end until March 5th
, the subscribed
method will continue to return true
until March 5th.
取消订阅后,收银员将自动在我们的数据库中设置ends_at
列。 该列用于了解subscribed
方法何时应开始返回false
。 例如,如果客户在3月1日取消了订阅,但该订阅原定于March 5th
日结束,则subscribed
方法将继续返回true
直到3月5日。
With the form to cancel subscriptions in place, we can now define the various controller actions and routes to handle the cancellation process.
有了取消订阅的表单,我们现在可以定义各种控制器操作和路由来处理取消过程。
In the SubscriptionsController
, let’s add a cancel
method:
在SubscriptionsController
,让我们添加一个cancel
方法:
[...]
class SubscriptionController extends Controller
{
public function cancel(Request $request)
{
$request->user()->subscription('main')->cancel();
return redirect()->back()->with('success', 'You have successfully cancelled your subscription');
}
[...]
}
Here, we are grabbing the user from the request object, getting their subscription, then calling Cashier’s cancel
method on this subscription. After that, we redirect the user back to the same page but with a notification that the cancellation was successful.
在这里,我们从请求对象中获取用户,获取他们的订阅,然后在此订阅上调用Cashier的cancel
方法。 此后,我们将用户重定向到同一页面,但会通知您取消已成功。
We also need a route to handle the posting action of the form:
我们还需要一条路线来处理表单的发布操作:
Route::group(['middleware' => 'subscribed'], function () {
[...]
Route::post('/subscription/cancel', 'SubscriptionController@cancel');
});
恢复订阅 (Resuming Subscriptions)
The user must still be on their “grace period” in order to resume a subscription. If the user cancels a subscription and then resumes that subscription before the subscription has fully expired, they will not be billed immediately. Instead, their subscription will simply be re-activated, and they will be billed on the original billing cycle.
用户必须仍然处于其“宽限期”才能恢复订阅。 如果用户取消订阅,然后在订阅完全到期之前恢复该订阅,则不会立即向他们收费。 取而代之的是,将仅重新激活其订阅,并在原始计费周期内对其进行计费。
The procedure to resume a subscription is similar to what we did when cancelling a subscription. The only difference is we call the resume
method on a subscription instead of the cancel
method. We will also notify the user when the grace period is supposed to end on top of the form.
恢复订阅的过程与取消订阅时的操作类似。 唯一的区别是我们在订阅上调用了resume
方法,而不是cancel
方法。 当宽限期应在表格顶部结束时,我们还将通知用户。
Let’s update our resources/views/subscriptions/index.blade.html
view to include the form which lets users resume subscriptions:
让我们更新我们的resources/views/subscriptions/index.blade.html
视图,以包括允许用户恢复订阅的表单:
<div class="panel-body">
@if (Auth::user()->subscription('main')->cancelled())
<p>Your subscription ends on {{ Auth::user()->subscription('main')->ends_at->format('dS M Y') }}</p>
<form action="{{ url('subscription/resume') }}" method="post">
<button type="submit" class="btn btn-default">Resume subscription</button>
{{ csrf_field() }}
</form>
@else
[...]
Then, we create the controller action and routes to handle the form’s posting action:
然后,我们创建控制器动作并路由以处理表单的发布动作:
app/Http/controllers/SubscriptionsController.php
app / Http / controllers / SubscriptionsController.php
[...]
class SubscriptionController extends Controller
{
[...]
public function resume(Request $request)
{
$request->user()->subscription('main')->resume();
return redirect()->back()->with('success', 'You have successfully resumed your subscription');
}
[...]
}
Let’s update our routes to accommodate the form’s posting action:
让我们更新路线以适应表单的发布操作:
Route::group(['middleware' => 'subscribed'], function () {
[...]
Route::post('/subscription/cancel', 'SubscriptionController@cancel');
Route::post('/subscription/resume', 'SubscriptionController@resume');
});
As you can see, Cashier makes it extremely easy to manage subscriptions. Next, we’ll look at how to set up webhooks for our application.
如您所见,Cashier使订阅管理变得非常容易。 接下来,我们将研究如何为我们的应用程序设置webhooks。
网络挂钩 (Webhooks)
Both Stripe and Braintree can notify our application of a variety of events via webhooks. A webhook is a part of your application that can be called when an action happens on a third party service or some other server. In simpler terms, how does our application know if someone’s card was declined when we try to charge them for membership, or their card expires, or something else goes wrong? When that happens on Braintree, we want our application to know about it so we can cancel the subscription.
Stripe和Braintree都可以通过webhooks通知我们的应用程序各种事件。 Webhook是应用程序的一部分,当在第三方服务或某些其他服务器上发生操作时可以调用它。 简而言之,当我们尝试向某人收取会员卡费用,其卡过期或发生其他问题时,我们的应用程序如何知道该卡是否被拒绝? 当Braintree发生这种情况时,我们希望我们的应用程序知道它,以便我们取消订阅。
To handle Braintree webhooks, we define a route that points to Cashier’s webhook controller. This controller will handle all incoming webhook requests and dispatch them to the proper controller method:
为了处理Braintree Webhook,我们定义了指向Cashier的Webhook控制器的路由。 该控制器将处理所有传入的Webhook请求,并将它们分派给适当的控制器方法:
Route::post(
'braintree/webhooks',
'\Laravel\Cashier\Http\Controllers\WebhookController@handleWebhook'
);
Note that the route is not inside any route group as we don’t need to be authenticated for this.
请注意,该路由不在任何路由组内,因为我们不需要为此进行身份验证。
By default, the WebhookController
will automatically handle cancelling subscriptions that have too many failed charges (as defined by your Braintree settings); however, you can extend this controller to handle any webhook event you like. You can read more on how to extend the WebhookController
here.
默认情况下, WebhookController
将自动处理收费过多(由您的Braintree设置定义)的取消订阅。 但是,您可以扩展此控制器以处理您喜欢的任何webhook事件。 您可以在此处阅读有关如何扩展WebhookController
信息 。
Once we have registered this route, let’s configure the webhook URL in the Braintree control panel settings. There are a variety of events we can choose to be notified about, but for this tutorial, we will just stick to cancelled:
一旦注册了此路由,就可以在Braintree控制面板设置中配置webhook URL。 我们可以选择通知各种事件,但是对于本教程,我们将坚持取消:
![alt](https://i-blog.csdnimg.cn/blog_migrate/f5e0ff84af64fd9dfc48dc3bdcf819ea.gif)
Webhooks和CSRF保护 (Webhooks and CSRF Protection)
Since Braintree webhooks need to bypass Laravel’s CSRF protection, we have to list the URI as an exception in our VerifyCsrfToken middleware or list the route outside of the web middleware group:
由于Braintree Webhooks需要绕过Laravel的CSRF保护,因此我们必须在VerifyCsrfToken中间件中将URI列为例外或在Web中间件组之外列出路由:
app/Http/Middleware/VerifyCsrfToken.php
app / Http / Middleware / VerifyCsrfToken.php
protected $except = [
'braintree/*',
];
测试Webhooks (Testing Webhooks)
So far, we’ve been developing our application locally, and its not possible to access the application from another computer.
到目前为止,我们一直在本地开发应用程序,无法从另一台计算机访问该应用程序。
To test if the webhooks are working as intended, we’ll have to use something like Ngrok to serve the application.
为了测试网络钩子是否按预期工作,我们必须使用Ngrok之类的东西来服务该应用程序。
Ngrok is a handy tool and service that allows us to tunnel requests from the wide open Internet to our local machine when it’s behind a NAT or firewall. Serving the app via Ngrok will also provide us with a URL which lets anyone access our app from another machine. Let’s update Braintree’s webhook URL to use this new URL:
Ngrok是一种方便的工具和服务,使我们可以在NAT或防火墙后面将请求从广阔的Internet隧道传输到我们的本地计算机。 通过Ngrok服务该应用程序还将为我们提供一个URL,任何人都可以从另一台计算机访问我们的应用程序。 让我们更新Braintree的webhook URL以使用以下新URL:
![alt](https://i-blog.csdnimg.cn/blog_migrate/eac93dbbc00c43050429c60eb3632019.gif)
We can test things out by manually cancelling a subscription for a user from Braintree’s control panel. Once we cancel the subscription, Braintree will send this data to the webhook we just created, then Cashier will handle the rest.
我们可以通过从Braintree的控制面板中手动取消用户的订阅来测试情况。 取消订阅后,Braintree会将这些数据发送到我们刚刚创建的Webhook,然后Cashier将处理其余的数据。
If we now look at the data in the subscriptions table, we notice the ends_at
column for the user whose subscription we just cancelled has been updated.
现在,如果我们查看订阅表中的数据, ends_at
注意到刚取消订阅的用户的ends_at
列。
结论 (Conclusion)
We’ve come a long way to get to this point. Our app now supports subscription features found in most websites. We might not have exhausted all the methods offered by Cashier, but as you might have noticed, Cashier makes it extremely easy to manage Braintree subscriptions thereby saving developers a lot of time.
到目前为止,我们已经走了很长一段路。 我们的应用程序现在支持大多数网站中提供的订阅功能。 我们可能没有穷尽Cashier提供的所有方法,但是您可能已经注意到,Cashier使得管理Braintree订阅非常容易,从而为开发人员节省了大量时间。
Cashier is not limited to Braintree as it also supports Stripe. If you think we’ve failed to cover some of the more important features, or have your own implementations to demonstrate, please let us know in the comments!
收银员不仅限于Braintree,还支持Stripe 。 如果您认为我们未能涵盖某些更重要的功能,或者您无法演示自己的实现,请在评论中告知我们!
翻译自: https://www.sitepoint.com/laravel-and-braintree-middleware-and-other-advanced-concepts/
:braintree