## 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)
- 開始
- 公益
- 更好的使用看云
- 推薦書單
- 優秀資源整理
- 技術文章寫作規范
- SublimeText - 編碼利器
- PSR-0/PSR-4命名標準
- php的多進程實驗分析
- 高級PHP
- 進程
- 信號
- 事件
- IO模型
- 同步、異步
- socket
- Swoole
- PHP擴展
- Composer
- easyswoole
- php多線程
- 守護程序
- 文件鎖
- s-socket
- aphp
- 隊列&并發
- 隊列
- 講個故事
- 如何最大效率的問題
- 訪問式的web服務(一)
- 訪問式的web服務(二)
- 請求
- 瀏覽器訪問阻塞問題
- Swoole
- 你必須理解的計算機核心概念 - 碼農翻身
- CPU阿甘 - 碼農翻身
- 異步通知,那我要怎么通知你啊?
- 實時操作系統
- 深入實時 Linux
- Redis 實現隊列
- redis與隊列
- 定時-時鐘-阻塞
- 計算機的生命
- 多進程/多線程
- 進程通信
- 拜占庭將軍問題深入探討
- JAVA CAS原理深度分析
- 隊列的思考
- 走進并發的世界
- 鎖
- 事務筆記
- 并發問題帶來的后果
- 為什么說樂觀鎖是安全的
- 內存鎖與內存事務 - 劉小兵2014
- 加鎖還是不加鎖,這是一個問題 - 碼農翻身
- 編程世界的那把鎖 - 碼農翻身
- 如何保證萬無一失
- 傳統事務與柔性事務
- 大白話搞懂什么是同步/異步/阻塞/非阻塞
- redis實現鎖
- 淺談mysql事務
- PHP異常
- php錯誤
- 文件加載
- 路由與偽靜態
- URL模式之分析
- 字符串處理
- 正則表達式
- 數組合并與+
- 文件上傳
- 常用驗證與過濾
- 記錄
- 趣圖
- foreach需要注意的問題
- Discuz!筆記
- 程序設計思維
- 抽象與具體
- 配置
- 關于如何學習的思考
- 編程思維
- 談編程
- 如何安全的修改對象
- 臨時
- 臨時筆記
- 透過問題看本質
- 程序后門
- 邊界檢查
- session
- 安全
- 王垠
- 第三方數據接口
- 驗證碼問題
- 還是少不了虛擬機
- 程序員如何談戀愛
- 程序員為什么要一直改BUG,為什么不能一次性把代碼寫好?
- 碎碎念
- 算法
- 實用代碼
- 相對私密與絕對私密
- 學習目標
- 隨記
- 編程小知識
- foo
- 落盤
- URL編碼的思考
- 字符編碼
- Elasticsearch
- TCP-IP協議
- 碎碎念2
- Grafana
- EFK、ELK
- RPC
- 依賴注入
- 科目一
- 開發筆記
- 經緯度格式轉換
- php時區問題
- 解決本地開發時調用遠程AIP跨域問題
- 后期靜態綁定
- 談tp的跳轉提示頁面
- 無限分類問題
- 生成微縮圖
- MVC名詞
- MVC架構
- 也許模塊不是唯一的答案
- 哈希算法
- 開發后臺
- 軟件設計架構
- mysql表字段設計
- 上傳表如何設計
- 二開心得
- awesomes-tables
- 安全的代碼部署
- 微信開發筆記
- 賬戶授權相關
- 小程序獲取是否關注其公眾號
- 支付相關
- 提交訂單
- 微信支付筆記
- 支付接口筆記
- 支付中心開發
- 下單與支付
- 支付流程設計
- 訂單與支付設計
- 敏感操作驗證
- 排序設計
- 代碼的運行環境
- 搜索關鍵字的顯示處理
- 接口異步更新ip信息
- 圖片處理
- 項目搭建
- 閱讀文檔的新方式
- mysql_insert_id并發問題思考
- 行鎖注意事項
- 細節注意
- 如何處理用戶的輸入
- 不可見的字符
- 抽獎
- 時間處理
- 應用開發實戰
- python 學習記錄
- Scrapy 教程
- Playwright 教程
- stealth.min.js
- Selenium 教程
- requests 教程
- pyautogui 教程
- Flask 教程
- PyInstaller 教程
- 蜘蛛
- python 文檔相似度驗證
- thinkphp5.0數據庫與模型的研究
- workerman進程管理
- workerman網絡分析
- java學習記錄
- docker
- 筆記
- kubernetes
- Kubernetes
- PaddlePaddle
- composer
- oneinstack
- 人工智能 AI
- 京東
- pc_detailpage_wareBusiness
- doc
- 電商網站設計
- iwebshop
- 商品規格分析
- 商品屬性分析
- tpshop
- 商品規格分析
- 商品屬性分析
- 電商表設計
- 設計記錄
- 優惠券
- 生成唯一訂單號
- 購物車技術
- 分類與類型
- 微信登錄與綁定
- 京東到家庫存系統架構設計
- crmeb
- 命名規范
- Nginx https配置
- 關于人工智能
- 從人的思考方式到二叉樹
- 架構
- 今日有感
- 文章保存
- 安全背后: 瀏覽器是如何校驗證書的
- 避不開的分布式事務
- devops自動化運維、部署、測試的最后一公里 —— ApiFox 云時代的接口管理工具
- 找到自己今生要做的事
- 自動化生活
- 開源與漿果
- Apifox: API 接口自動化測試指南