: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订阅。

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来访问该计划。 为了解决这个问题,让我们将PlansControllershow动作中的代码更新为:

[...]
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帮助程序来根据会话状态(即successerror提供不同的通知颜色。

交换计划 (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.

为此,我们首先必须检查用户是否已订阅SubscriptionsControllerstore方法内的任何计划。 如果没有,我们将为他们订阅新计划。 要将用户交换到新订阅,我们将计划的标识符传递给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。 我们可以选择通知各种事件,但是对于本教程,我们将坚持取消:

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:

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

:braintree_Laravel和Braintree:中间件和其他高级概念相关推荐

  1. 数据库:学好SQL必须知道的10个高级概念

    今天给大家分享学好SQL必须知道的10个高级概念. 1.常见表表达式(CTEs) 如果您想要查询子查询,那就是CTEs施展身手的时候 - CTEs基本上创建了一个临时表. 使用常用表表达式(CTEs) ...

  2. SQL 开发的十个高级概念

    导读 SQL 是一种每位数据开发者必备的开发语言,不同的用户使用 SQL 语言的程度不同,最开始接触到的 SQL 就是 SELECT ,INSERT, UPDATE, DELETE 以及 WHERE ...

  3. Games104现代游戏引擎入门-lecture14游戏引擎的引擎工具高级概念与应用

    Games104现代游戏引擎入门-lecture14游戏引擎的引擎工具高级概念与应用 1 Glance of Game Production 2 World Editor 1 viewport 2 不 ...

  4. braintree_Laravel和Braintree,坐在树上……

    braintree This article was peer reviewed by Younes Rafie and Wern Ancheta. Thanks to all of SitePoin ...

  5. RocketMQ高级概念

    一 RocketMQ核心概念 1.消息模型(Message Model) RocketMQ主要由 Producer.Broker.Consumer 三部分组成,其中Producer 负责⽣产消息,Co ...

  6. 学 SQL 必须了解的10个高级概念

    点击关注公众号,利用碎片时间学习 随着数据量持续增长,对合格数据专业人员的需求也会增长.具体而言,对SQL流利的专业人士的需求日益增长,而不仅仅是在初级层面. 因此,Stratascratch的创始人 ...

  7. 数据挖掘导论读书笔记6关联分析的高级概念

    处理联系属性: 基于离散化的方法 基于统计学的方法 非离散化方法 处理概念分层 定义在一个特定领域的各种实体或者概念的多层组织.概念分层可以用有向无环图DAG来标示. 序列模式 可选计数方案 COBJ ...

  8. android数据绑定_Android数据绑定高级概念

    android数据绑定 In this tutorial we'll look into some other tricky usages of Data Binding in our applica ...

  9. linux中国vi,使用 Vi/Vim 编辑器:高级概念 | Linux 中国

    早些时候我们已经讨论了一些关于 VI/VIM 编辑器的基础知识,但是 VI 和 VIM 都是非常强大的编辑器,还有很多其他的功能可以和编辑器一起使用.在本教程中,我们将学习 VI/VIM 编辑器的一些 ...

最新文章

  1. MAVEN项目标准目录结构 ;
  2. sp烘焙流程_次世代86机甲战神制作全流程
  3. 数据结构与算法——冒泡排序(改进后)
  4. STL源码剖析 Stack栈 queue队列
  5. java怎么编程方程_[编程入门]自定义函数求一元二次方程-题解(Java代码)
  6. Linq学习之路(07) - 使用Linq进行参数化编程step by step
  7. SpringBoot开发案例之拦截器注入Bean
  8. html 图片链接怎么生成器,jQuery接口图片上传自动生成图片链接地址
  9. 前端三剑客---HTML
  10. Q绑查询HTML源码
  11. 轻量级自动化测试框架 UFT 初学者 学习编写
  12. 【微信小程序】z-index失效
  13. 【Kibana】索引生命周期策略错误illegal_argument_exception: index.lifecycle.rollover_alias does not point to index
  14. python全栈工程师薪水_python全栈+爬虫+自动化+AI=python全能工程师-挑战年薪30W+
  15. 上网速度(网速)的计算
  16. 【解决】Splunk Lookup table owner: nobody
  17. 轻松解决win10不能联网问题
  18. RPC好,还是RESTful好?
  19. css+js校验 实现仿网易邮箱注册界面 和 校验 功能
  20. 在VLC控件中添加接口的方法

热门文章

  1. 论文阅读 | Rethinking Coarse-to-Fine Approach in Single Image Deblurring
  2. Redis 无法持久化到硬盘错误:not able to persist on disk
  3. 学习python好就业么
  4. HashMap的底层存储结构和实现原理
  5. OpenGL学习4——图元属性
  6. VCR,DVR与NVR的区别
  7. excel不显示0_excel技巧:Excel中,为什么有时候会只显示公式不显示结果?
  8. python 定义函数参数的类型_1作为函数声明中的参数类型 - python
  9. 信息奥赛一本通1185:单词排序(OJ题目描述有问题)
  10. swt matlab,SWT 彩色图像融合处理 swt matlab程序并有整套评价 和设计图片 271万源代码下载- www.pudn.com...