🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
## crmeb [TOC=3,8] ---- ### 订单 与 支付 #### 订单表 ```sql CREATE TABLE IF NOT EXISTS `eb_store_order` ( `paid` tinyint(1) NOT NULL DEFAULT '0' COMMENT '支付状态 0: 未支付 1:已支付', `pay_time` int(11) UNSIGNED NOT NULL DEFAULT '0' COMMENT '支付时间', `pay_type` varchar(32) NOT NULL DEFAULT '' COMMENT '支付方式', `add_time` int(11) UNSIGNED NOT NULL DEFAULT '0' COMMENT '创建时间', `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '订单状态(-1 : 申请退款 -2 : 退货成功 0:待发货;1:待收货;2:已收货;3:待评价;-1:已退款)', `refund_status` tinyint(1) UNSIGNED NOT NULL DEFAULT '0' COMMENT '0 未退款 1 申请中 2 已退款', `refund_type` tinyint(1) NOT NULL DEFAULT '0' COMMENT '退款申请类型', `refund_express` varchar(255) NOT NULL DEFAULT '' COMMENT '退货快递单号', `refund_express_name` varchar(255) NOT NULL DEFAULT '' COMMENT '退货快递名称', `refund_reason_wap_img` varchar(2000) NOT NULL DEFAULT '' COMMENT '退款图片', `refund_reason_wap_explain` varchar(255) NOT NULL DEFAULT '' COMMENT '退款用户说明', `refund_reason_time` int(11) UNSIGNED NOT NULL DEFAULT '0' COMMENT '退款时间', `refund_reason_wap` varchar(255) NOT NULL DEFAULT '' COMMENT '前台退款原因', `refund_reason` varchar(255) NOT NULL DEFAULT '' COMMENT '不退款的理由', `refund_price` decimal(8,2) UNSIGNED NOT NULL DEFAULT '0.00' COMMENT '退款金额', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='订单表'; ``` #### 分析 支付回执 AliPayService.php ```php public static function handleNotify() { return self::instance()->notify(function ($notify) { if (isset($notify->out_trade_no)) { if (isset($notify->attach) && $notify->attach) { if (($count = strpos($notify->out_trade_no, '_')) !== false) { $notify->trade_no = $notify->out_trade_no; $notify->out_trade_no = substr($notify->out_trade_no, $count + 1); } return (new Hook(PayNotifyServices::class, 'aliyun'))->listen($notify->attach, $notify->out_trade_no, $notify->trade_no); } return false; } }); } public function notify(callable $notifyFn) { app()->request->filter(['trim']); //商户订单号 $postOrder['out_trade_no'] = $paramInfo['out_trade_no'] ?? ''; //支付宝交易号 $postOrder['trade_no'] = $paramInfo['trade_no'] ?? ''; //交易状态 $postOrder['trade_status'] = $paramInfo['trade_status'] ?? ''; //备注 $postOrder['attach'] = isset($paramInfo['passback_params']) ? urldecode($paramInfo['passback_params']) : ''; if (in_array($paramInfo['trade_status'], ['TRADE_SUCCESS', 'TRADE_FINISHED']) && $this->verifyNotify($paramInfo)) { try { if ($notifyFn((object)$postOrder)) { return 'success'; } } catch (\Exception $e) { Log::error('支付宝异步会回调成功,执行函数错误。错误单号:' . $postOrder['out_trade_no']); } } return 'fail'; } ``` 业务回执处理 PayNotifyServices.php ```php public function aliyunProduct(string $order_id = null, string $trade_no = null) { if (!$order_id || !$trade_no) { return false; } try { /** @var StoreOrderSuccessServices $services */ $services = app()->make(StoreOrderSuccessServices::class); $orderInfo = $services->getOne(['order_id' => $order_id]); if (!$orderInfo) return true; if ($orderInfo->paid) return true; return $services->paySuccess($orderInfo->toArray(), PayServices::ALIAPY_PAY, ['trade_no' => $trade_no]); } catch (\Throwable $e) { return false; } } ``` StoreOrderSuccessServices.php ```php public function paySuccess(array $orderInfo, string $paytype = PayServices::WEIXIN_PAY, array $other = []) { $updata = ['paid' => 1, 'pay_type' => $paytype, 'pay_time' => time()]; if ($other && isset($other['trade_no'])) { $updata['trade_no'] = $other['trade_no']; } $res1 = $this->dao->update($orderInfo['id'], $updata); $resPink = true; if ($orderInfo['combination_id'] && $res1 && !$orderInfo['refund_status']) { /** @var StorePinkServices $pinkServices */ $pinkServices = app()->make(StorePinkServices::class); /** @var StoreOrderServices $orderServices */ $orderServices = app()->make(StoreOrderServices::class); $resPink = $pinkServices->createPink($orderServices->tidyOrder($orderInfo, true));//创建拼团 } //缓存抽奖次数 除过线下支付 if (isset($orderInfo['pay_type']) && $orderInfo['pay_type'] != 'offline') { /** @var LuckLotteryServices $luckLotteryServices */ $luckLotteryServices = app()->make(LuckLotteryServices::class); $luckLotteryServices->setCacheLotteryNum((int)$orderInfo['uid'], 'order'); } //订单支付成功后置事件 event('order.orderPaySuccess', [$orderInfo]); //用户推送消息事件 event('notice.notice', [$orderInfo, 'order_pay_success']); //支付成功给客服发送消息 event('notice.notice', [$orderInfo, 'admin_pay_success_code']); $res = $res1 && $resPink; return false !== $res; } ``` #### 总结 1. 只在 PayNotifyServices 成功 才对 三方 响应 'success' 2. PayNotifyServices 成功 a. 订单不存在 true b. 订单已支付 true c. StoreOrderSuccessServices->paySuccess() true a. 更新订单支付状态 成功 b. 订单支付成功后置事件 结果不关心 1. 只在 PayNotifyServices 成功 才对 三方 响应 'success' 2. PayNotifyServices 成功 a. 订单不存在 true b. 订单已支付 true c. wechatUserRecharge->rechargeSuccess() true a. 更新订单 true b. 更新用户余额 b. 订单支付成功后置事件 结果不关心 不过任何 异常 代码错误,都会使 PayNotifyServices false ,所以如果 代码问题,业务履行失败 都不会 对三方响应 'success' ---- ### 架构 Dao 基于模型对数据获取、操作进行封装,Services 使用 Dao 进行数据操作。调用 Services 上不存在的方法 会调用 到 其 Dao 上,所以 Services 可看作 是 “继承” Dao 的。 优点:将 SQL 拼装 从 业务层拆分到 Dao 上了,Services 层的业务成分就更明显了。 缺点:由于优点,于是 业务也被分散到 Services 和 Dao 上了,有点不清晰,只能说 Dao 的 业务成分 没有 Services 明显,但并不是完全没有。 结论: 1. 所以 Services + Dao = 我们 的 Logic 2. Services > Dao > Model ;我们 Logic > Model PS: ~~我们的 Logic、Service 层确实没什么区别,区分的理由实在太过牵强,没有区分的意义。~~(有待商榷,Logic 注重特定的业务场景 如 User,Service 纯技术方案,无特定业务场景特征,如 JWT) ---- ```php abstract class BaseServices { public function __call($name, $arguments) { // TODO: Implement __call() method. return call_user_func_array([$this->dao, $name], $arguments); } } class StoreOrderServices extends BaseServices { public function setOrderTypePayOffline(string $orderId) { return $this->dao->update($orderId, ['pay_type' => 'offline'], 'order_id'); } public function removeOrder(string $uni, int $uid) { $order = $this->getUserOrderDetail($uni, $uid); if (!$order) { throw new ValidateException('订单不存在!'); } ... } } ``` ```php abstract class BaseDao { abstract protected function setModel(): string; protected function getModel() { return app()->make($this->setModel()); } protected function getPk() { return $this->getModel()->getPk(); } public function update($id, array $data, ?string $key = null) { if (is_array($id)) { $where = $id; } else { $where = [is_null($key) ? $this->getPk() : $key => $id]; } return $this->getModel()::update($data, $where); } } class StoreOrderDao extends BaseDao { protected function setModel(): string { return StoreOrder::class; } public function getUserOrderDetail(string $key, int $uid, $with = []) { return $this->getOne(['order_id|unique' => $key, 'uid' => $uid, 'is_del' => 0], '*', $with); } public function batchUpdateOrder(array $ids, array $data, ?string $key = null) { return $this->getModel()::whereIn(is_null($key) ? $this->getPk() : $key, $ids)->update($data); } } ``` ---- [分层 DAO层,Service层,Controller层、View层 - 简书](https://www.jianshu.com/p/df659d7fbbf7) > DAO完成连接数据库修改删除添加等的实现细节,例如sql语句是怎么写的,怎么把对象放入数据库的。service层是面向功能的,一个个功能模块比如说银行登记并完成一次存款,UI要把请求给service层,然后service曾将这一个case分解成许多步骤调用底层的实现完成这次存款,dao就是下面那层。 [浅谈MyBatis-Plus学习之ActiveRecord - hjjay - 博客园](https://www.cnblogs.com/jayhou/p/9824232.html) > Active Record(简称AR),是一种领域模型模式,特点是一个模型类对应关系型数据库中的一个表,而模型类的一个实例对应表中的一行记录。 [极简架构模式-数据访问对象模式 | Laravel China 社区](https://learnku.com/articles/63753) [dao层写法有什么好处? | Laravel | Laravel China 社区](https://learnku.com/laravel/t/60443)