上一节我们简单讲述了Laravel Cashier的安装配置,这一节我们将使用Laravel Cashier来实现一个常见的功能——付费会员。比如QQ、微博、优酷等应用都有这一功能,并且该功能已然成为许多网站收入的重要来源,可见其地位之重要,而在Laravel中我们可以借助Cashier通过Stripe轻松实现该功能,正如我们前面提到的,Laravel Cashier为我们封装了支付功能,所以我们不需要处理如何支付,也不需要关心任何与支付相关的细节,而只需要关心具体的业务逻辑。
正所谓兵马未动,粮草先行,在正式编写业务逻辑代码之前,我们先要到Stripe个人中心创建订购计划。导航到https://dashboard.stripe.com/test/plans页面创建两个付费会员级别:Silver(银牌会员)和Gold(金牌会员):
注意页面左上角的TEST,我们目前是在Stripe的测试环境进行操作。
创建好订购计划后,我们在routes.php
中为业务逻辑定义好相关路由:
- //用户个人主页
- Route::get('profile','UserController@profile');
- //用户会员级别
- Route::get('service','UserController@service');
- //付费会员页面
- Route::get('subscription','UserController@subscription');
- //处理付费逻辑
- Route::post('subscribe','UserController@subscribe');
- //升级到更高级别
- Route::get('upgrade','UserController@upgrade');
接下来自然而然就是到控制器UserController
中编写业务逻辑代码,由于之前我们已经讲过如何实现登录认证,这里我们使用GitHub进行登录认证。认证完成后跳转到/profile
页面,然后我们的业务逻辑由此开始。
首先在profile页面会直接跳转到查看会员级别页面:
- public function profile(Request $request)
- {
- $user = $request->user();
- return redirect('service');
- }
然后在service页面我们编写控制器代码如下:
- public function service(Request $request){
- $user = $request->user();
- if (!$user->subscribed()) {
- return redirect('subscription');
- }
- if($user->onPlan('silver')){
- $service = ['type'=>1,'name'=>'银牌会员'];
- }else{
- $service = ['type'=>2,'name'=>'金牌会员'];
- }
- return view('user.service',['service'=>$service]);
- }
首先,我们从请求中取出认证用户实例,然后调用该实例上的subscribed
方法判断用户是否是付费会员,如果不是的话跳转到付费页面,否则通过onPlan
方法判断用户是银牌会员还是金牌会员,并渲染对应视图resources/views/user/service.blade.php
:
- 你已经是{{$service['name']}}<br>
- @if ($service['type'] == 1)
- <a href="/upgrade">升级为金牌会员</a>
- @endif
升级的事情我们先按下不表,先来看看购买付费会员的实现逻辑。我们在UserController
中编写subscription
对应的代码如下:
- public function subscription(Request $request)
- {
- $user = $request->user();
- if($user->subscribed()){
- return redirect('service');
- }
- return view('user.subscription');
- }
该页面逻辑很简单,先判断是否已经是付费会员,是的话跳转到会员级别页面,否则才会渲染购买付费页面,接下来我们定义其对应视图文件resources/views/user/subscription.blade.php
如下:
- <form action="/subscribe" method="post" id="subscription-form">
- <span class="payment-errors"></span>
- {!! csrf_field() !!}
- <div>
- 付费计划:
- <select name="plan">
- <option value="silver">银牌会员</option>
- <option value="gold">金牌会员</option>
- </select>
- </div>
- <div>
- 信用卡号:
- <input type="text" name="number" id="number">
- </div>
- <div>
- 过期时间:
- 月份:<input type="text" name="exp_month" id="exp_month">
- 年份:<input type="text" name="exp_year" id="exp_year">
- </div>
- <button type="submit">订购服务</button>
- </form>
- <script type="text/javascript" src="http://libs.baidu.com/jquery/1.9.1/jquery.min.js"></script>
- <script type="text/javascript" src="https://js.stripe.com/v2/"></script>
- <script>
- Stripe.setPublishableKey('{{config("services.stripe.key")}}');
- jQuery(function($) {
- $('#subscription-form').submit(function(event) {
- var form = $(this);
- form.find('button').prop('disabled', true);
- Stripe.card.createToken({
- number: $('#number').val(),
- exp_month: $('#exp_month').val(),
- exp_year: $('#exp_year').val()
- }, stripeResponseHandler);
- return false;
- });
- });
- var stripeResponseHandler = function(status, response) {
- var form = $('#subscription-form');
- if (response.error) {
- form.find('.payment-errors').text(response.error.message);
- form.find('button').prop('disabled', false);
- } else {
- var token = response.id;
- form.append($('<input type="hidden" name="stripeToken" />').val(token));
- form.get(0).submit();
- }
- };
- </script>
该视图文件相对比较复杂,在该视图中我们要选择购买的会员级别,填写信用卡号,以及信用卡对应的过期时间。会员级别对应的值即为我们在Stripe中创建订购计划时对应的计划ID,至于信用卡号和过期时间是用来生成stripeToken
的,这个stripeToken
就是我们在调用create
创建订购计划时要传入的值。Stripe还贴心的为我们提供了一系列可用的信用卡测试账号:
卡号 | 类型 |
---|---|
4242424242424242 |
Visa |
4012888888881881 |
Visa |
4000056655665556 |
Visa (debit) |
5555555555554444 |
MasterCard |
5200828282828210 |
MasterCard (debit) |
5105105105105100 |
MasterCard (prepaid) |
378282246310005 |
American Express |
371449635398431 |
American Express |
6011111111111117 |
Discover |
6011000990139424 |
Discover |
30569309025904 |
Diners Club |
38520000023237 |
Diners Club |
3530111333300000 |
JCB |
3566002020360505 |
JCB |
至于过期时间嘛,只要大于当前时间就好了。可能在别处你还会看到需要输入CVC,这是生成stripeToken
的可选参数,既然是可选,这里我们就不费那个事了。
这个页面在浏览器中看上去是这样的:
在点击“订购服务”之前,我们先来编写subscribe
对应的代码:
- public function subscribe(Request $request)
- {
- $plan = $request->input('plan');
- $creditCardToken = $request->input('stripeToken');
- $user = $request->user();
- $user->subscription($plan)->create($creditCardToken);
- if($user->save()){
- return redirect('service');
- }else{
- return back()->withInput();
- }
- }
可以看到我们调用:
- $user->subscription($plan)->create($creditCardToken);
完成付费操作实现订购。
回到subscription
视图页面点击“订购服务”,成功后页面跳转到之前的service
页面,页面显示如下:
最后我们来处理升级逻辑,编写upgrade
对应业务逻辑代码如下:
- public function upgrade(Request $request){
- $user = $request->user();
- if (!$user->subscribed()) {
- return redirect('subscription');
- }
- if($user->onPlan('gold')){
- exit('您已经是金牌会员了');
- }
- try{
- $user->subscription('gold')->swap();
- }catch(Exception $ex){
- exit('升级失败!');
- }
- return redirect('service');
- }
通过
- $user->subscription('gold')->swap();
即可实现升级操作。
回到service
页面点击“升级到金牌会员”,成功后页面跳转回service
页面并显示如下信息:
- 你已经是金牌会员
好了,至此我们已经完成了付费会员功能的一般逻辑。后续我们将讨论除信用卡外如何支持更多支付方式,比如银联、支付宝、微信支付等。
走完上面的所有流程后,你会发现该实现逻辑和另外一个应用场景非常类似,就是分期付款,我们可以将商品/服务总价格+利息分成若干期,然后算出每月应还款额,然后在Stripe中创建对应分期付款计划。剩下的业务逻辑实现和付费会员并无二致,将会员级别换成商品规格或对应服务级别,剩下的支付&升级参考其实现即可,这里不再赘述。
对于有时候我们需要对分期付款的物品进行一次性付清,比如剩余总额为100美元,可以通过调用如下方法实现:
- $user->charge(100);
该方法有返回值,支付成功返回true
,否则返回false
。
支付完成后,如果想要获取发票信息,Laravel Cashier也对此提供了支持。
首先通过调用用户实例上的invoices
方法获取该用户所有有效发票信息:
- $invoices = $user()->invoices();
然后在视图中将获取到的$invoices
循环显示出来:
- <table>
- @foreach ($invoices as $invoice)
- <tr>
- <td>{{ $invoice->dateString() }}</td>
- <td>{{ $invoice->dollars() }}</td>
- <td><a href="/order/invoice/{{ $invoice->id }}">下载发票</a></td>
- </tr>
- @endforeach
- </table>
我们将这段代码放到上述resources/views/user/service.blade.php
之后:
- 你已经是{{$service['name']}}<br>
- @if ($service['type'] == 1)
- <a href="/upgrade">升级为金牌会员</a>
- @endif
- <div>
- 发票信息:
- <table>
- @foreach ($invoices as $invoice)
- <tr>
- <td>{{ $invoice->dateString() }}</td>
- <td>{{ $invoice->dollars() }}</td>
- <td><a href="/user/invoice/{{ $invoice->id }}">下载发票</a></td>
- </tr>
- @endforeach
- </table>
- </div>
这样在我们付费成功后,页面显示如下:
最后我们在路由中对点击“下载发票”进行处理:
Route::get('user/invoice/{invoice}', function ($invoiceId) {
return Auth::user()->downloadInvoice($invoiceId, [
'vendor' => 'Laravel Academy',
'product' => 'Gold Member',
]);
});
该代码会将对应发票生成PDF文档并下载。我们点击上述的“下载发票按钮”,下载的PDF发票截图如下: