问题所在
- 全部支付走统一的二维码生成接口,导致需要通过 type 区分接收不同的字段,随着支付方式越来越多,参数判断越来越多,难以维护
- 代码解构混乱,一个
$data
变量贯通整个方法,导致最后不知道$data
变量里面什么数据,开发、排错越来越复杂 - 异常处理,业务代码处处抛出
\Exception
和捕获\Exception
,导致如果程序遇到了系统异常也不能及时的通知错误
改造前的一段伪代码
- 所有业务逻辑错误也抛出
\Exception
异常,捕获\Exception
后返回下单失败
导致如果程序遇到真正错误时,无法及时排查错误 - 单看
checkVerifyType()
方法名会认为只是检查支付type
是否正确, 但却不是,这个方法把所有该干不该干的事都干完了 - 传参用
0
,1
也不能明确知道是代表什么东西 qrcode
接口参数也很复杂,例:type
= 1时,必须要code
参数;type
= 2 时,必须要price
参数;type
= 3 时 ....$data
里面各种数据,有:请求数据,订单临时数据,订单预览数据,根据购买商品的不同又放入不同的数据,结果$data
就是个大杂烩,修改起来实在一言难尽
// 所有购买入口获取二维码的入口
public function qrcode(Request $request)
{
try {
// ...
$key = $this->checkVerifyType(0, 1);
// ...
return $key;
} catch (\Exception $e) {
return '下单失败';
}
}
*/Service/PayService.php
public function checkVerifyType($payType1 = 0, $payType2 = 0)
{
$data = request()->all();
if (!ctype_digit(strval($data['type']))) {
throw new \Exception('type err');
}
// ..... 还有一堆的参数验证
switch ($data['type']) {
case 'vip':
// ... 验证
$data['vip_info'] = Vip::where('code', $data['code'])->first();
break;
case 'recharge':
// ... 验证
$data['money'] = $data['money'];
break;
// case...
}
// 优惠券判断
if ($data['coupon_id']) {
$money = Coupon::where('id', $data['coupon_id'])->value("money");
$data['reduce'] = $money;
// ....
}
// 订单预览信息
$data['show_title'] = "购买一个会员";
$data['show_money'] = 100;
$key = "abcdefg";
Redis::set($key, $data);
return $key;
}
着手改造
前期准备
原来的返回格式:
public function json($code, $msg, $data)
{
return ['status' => $code, 'message' => $msg, 'data' => $data];
}
// 调用
json(200, "Ok", []);
虽然没什么大问题,但调用起来不太方便,也不直观,每次还需要传入一些不必要的参数,这里增加一些常用的返回方法
在 BaseContrller
中增加几个返回数据的方法,方便调用
const SUCCESS_CODE = 200;
const SUCCESS_FAIL = 100;
protected function success($msg = 'ok', $data = [], $code = self::SUCCESS_CODE)
{
return ['status' => $code, 'message' => $msg, 'data' => $data];
}
protected function data($data = [], $msg = 'ok', $code = self::SUCCESS_CODE)
{
return ['status' => $code, 'message' => $msg, 'data' => $data];
}
protected function fail($msg = 'ok', $data = [], $code = self::SUCCESS_FAIL)
{
return ['status' => $code, 'message' => $msg, 'data' => $data];
}
按模块区分不同的下单链接
- 开通会员:
/buy/vip
- 充值:
/buy/recharge
- 购买商品:
/buy/goods
创建临时订单策略
-
创建一个订单的
抽象策略
,定义算法的接口,所有策略必须实现临时订单的接口,app/Http/Services/PayOrder/PayOrderStrategy.php
abstract class PayOrderStrategy { abstract function createTemporaryOrder($request); }
-
创建一个
Context
类app/Http/Services/PayOrder/PayOrderStrategy.php
class PayOrderContext { private $strategy; public function __construct(PayOrderStrategy $payOrderStrategy) { return $this->strategy = $payOrderStrategy; } public function createOrder(Request $request) { return $this->strategy->createTemporaryOrder($request); } }
-
基础的策略框架已经搭建好,现在就需要具体的策略了
$request
是开通 vip 接口中传入的$request
app/Http/Services/PayOrder/Strategy/VipStrategy.php
// 开通 vip class VipStrategy extends PayOrderStrategy { // 组装临时订单的数据,然后存入 redis // 这里是 vip 策略,所以只专注 vip 需要的数据就好 function createTemporaryOrder(Request $request) { $packageCode = $request['code']; $package = app(PayOrderService::class)->getVipByCode($packageCode); // 临时订单数据 $tmpOrder = [ 'package_cope' => $package->toArray(), 'type' => PayOrderService::TYPE_VIP, 'uid' => 1, 'ip' => $request->ip(), // .... ]; return app(PayOrderService::class)->saveTemporaryOrder($tmpOrder); } }
-
创建一个订单服务类,写一些创建订单的公共方法
app/Http/Services/PayOrderService.php
use Ramsey\Uuid\Uuid; class PayOrderService { const TYPE_VIP = 1; // 购买 vip const TYPE_RECHARGE = 2; // 充值 const TYPE_GOODS = 3; // 购买商品 // 通过 code 查询 vip 套餐信息 public function getVipByCode(string $code) { // 这里应是从数据库获取数据返回 return collect(['id' => 1, 'code' => 'vip1', 'price' => 100, 'vip_day' => 30]); } // 保存临时订单 public function saveTemporaryOrder(array $tmpOrder) { $key = Uuid::uuid4()->toString(); Cache::set($key, $tmpOrder, 3); return $key; } }
目前的目录解构
app/Http/Services/
├── PayOrder │ ├── PayOrderContext.php │ ├── PayOrderStrategy.php │ └── Strategy │ └── VipStrategy.php └── PayOrderService.php
实现开通vip接口
所有接口的数据都是通过 laravel
表单请求验证
路由:routes/web.php
Route::get('/buy/vip', "PayController@vip")->name('vip');
app/Http/Controllers/PayController.php
public function vip(Request $request)
{
$strategy = new VipStrategy();
$tmpOrderKey = (new PayOrderContext($strategy))->createOrder($request);
return $this->data(['key' => $tmpOrderKey]);
}
curl http://127.0.0.1:8000/buy/vip?code=vip1 | json
{
"status": 200,
"message": "ok",
"data": {
"key": "35349845-0e76-4973-b240-67e7b3cdda42"
}
}
临时订单已生成,现在需要需要开发手机扫码后的预览接口
预览订单
正常来说预览订单是每个支付都需要有的功能,所以增加一个抽象方法
-
在
app/Http/Services/PayOrder/PayOrderStrategy.php
新增一个preview
的抽象方法abstract class PayOrderStrategy { // 创建临时订单 abstract function createTemporaryOrder(Request $request); // 预览订单 protected function preview(array $tmpOrder) { throw new UnsupportedOperationException("不支持的方法"); } }
你可能会好奇,这里预览订单为什么要抛出一个异常呢?因为有些第三方支付没有手机支付,只能 pc 端跳转,所以就不会涉及预览这一说
如果定义成
abstract
下面继承的方法有必须实现,这个非必须的就直接定义成protected
并抛出一个异常,开发的时候如果错误的调用了这个方法就会知道,当前支付方式不支持订单的预览 -
开通 vip 策略实现
preview
方法,参数是临时订单的信息app/Http/Services/PayOrder/Strategy/VipStrategy.php
function createTemporaryOrder(Request $request){ /*...*/ } function preview(array $tmpOrder) { $preview = [ 'title' => '开通会员', 'price' => $tmpOrder['price'], 'vip_day' => $tmpOrder['vip_day'] ]; return $preview; }
-
预览订单接口
这个接口返回一个页面,手机扫码收可以预览并且有下单按钮
路由:routes/web.php
Route::get('/buy/preview', "PayController@preview")->name('preview');
app/Http/Controllers/PayController.php
// 预览订单接口 public function preview(Request $request) { // 请求下单接口后返回的临时订单 key $tmpOrderKey = $request->get('key'); // 获取临时订单 $tmpOrder = app(PayOrderService::class)->getTemporaryOrder($tmpOrderKey); if (!$tmpOrder) { throw new TemporaryOrderException("订单已过期"); } $strategy = new VipStrategy(); $preview = (new PayOrderContext($strategy))->preview($tmpOrder); return view('preview', $preview); }
-
生成二维码
前端请求 vip 接口之后使用返回的临时订单 key,作为
query
参数请求预览订单
接口http://127.0.0.1:8000/buy/preview?key=35349845-0e76-4973-b240-67e7b3cdda42
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!