ServeOrderRepository.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2016~2024 https://www.crmeb.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
  8. // +----------------------------------------------------------------------
  9. // | Author: CRMEB Team <admin@crmeb.com>
  10. // +----------------------------------------------------------------------
  11. namespace app\common\repositories\system\serve;
  12. use AlibabaCloud\SDK\Dysmsapi\V20170525\Models\AddShortUrlResponseBody\data;
  13. use app\common\dao\system\serve\ServeOrderDao;
  14. use app\common\model\system\serve\ServeOrder;
  15. use app\common\repositories\BaseRepository;
  16. use app\common\repositories\store\order\StoreOrderRepository;
  17. use app\common\repositories\store\product\ProductCopyRepository;
  18. use app\common\repositories\system\merchant\MerchantRepository;
  19. use app\common\repositories\user\UserBillRepository;
  20. use app\common\repositories\user\UserRepository;
  21. use crmeb\services\CombinePayService;
  22. use crmeb\services\PayService;
  23. use think\exception\ValidateException;
  24. use think\facade\Cache;
  25. use think\facade\Db;
  26. use think\facade\Log;
  27. class ServeOrderRepository extends BaseRepository
  28. {
  29. protected $dao;
  30. public function __construct(ServeOrderDao $dao)
  31. {
  32. $this->dao = $dao;
  33. }
  34. //复制商品
  35. const TYPE_COPY_PRODUCT = 1;
  36. //电子面单
  37. const TYPE_DUMP = 2;
  38. //保证金 margin
  39. const TYPE_MARGIN = 10;
  40. //补缴
  41. const TYPE_MARGIN_MAKE_UP = 11;
  42. //同城配送delivery
  43. const TYPE_DELIVERY = 20;
  44. const PAY_TYPE_WEIXIN = 1;
  45. const PAY_TYPE_ALIPAY= 2;
  46. const PAY_TYPE_SYS = 3;
  47. /**
  48. * 购买一号通 支付
  49. * @param $merId
  50. * @param $data
  51. * @return array
  52. * @author Qinii
  53. * @day 1/26/22
  54. */
  55. public function meal($merId, $data)
  56. {
  57. $ret = app()->make(ServeMealRepository::class)->get($data['meal_id']);
  58. if(!$ret) throw new ValidateException('数据不存在');
  59. $key = 'Meal_'.$merId.'_'.$data['meal_id'].'_'.date('YmdH',time());
  60. $arr = [
  61. 'meal_id' => $ret['meal_id'],
  62. 'name' => $ret['name'],
  63. 'num' => $ret['num'],
  64. 'price' => $ret['price'],
  65. 'type' => $ret['type'],
  66. ];
  67. $param = [
  68. 'status' => 0,
  69. 'is_del' => 0,
  70. 'mer_id' => $merId,
  71. 'type' => $ret['type'],
  72. 'meal_id'=> $ret['meal_id'],
  73. 'pay_type' => $data['pay_type'],
  74. 'attach' => 'meal',
  75. 'order_info' => json_encode($arr,JSON_UNESCAPED_UNICODE),
  76. 'pay_price' => $ret['price'],
  77. ];
  78. return compact('key', 'param');
  79. }
  80. /**
  81. * 商户保证金 支付
  82. * @param $merId
  83. * @param $data
  84. * @return array
  85. * @author Qinii
  86. * @day 1/26/22
  87. */
  88. public function margin($merId, $data)
  89. {
  90. $ret = app()->make(MerchantRepository::class)->get($merId);
  91. if ($ret['is_margin'] !== 1 && $data['type'] == 10)
  92. throw new ValidateException('此商户无需支付保证金');
  93. if ($data['type'] == self::TYPE_MARGIN_MAKE_UP) {
  94. $margin = bcsub($ret->ot_margin,$ret->margin,2);
  95. } else {
  96. $margin = $ret['margin'];
  97. }
  98. $key = 'Margin_'.$merId.'_'.date('YmdH',time());
  99. $arr = [
  100. 'type_id' => $ret['type_id'],
  101. 'is_margin' => $ret['is_margin'],
  102. 'margin' => $margin,
  103. ];
  104. $param = [
  105. 'status' => 0,
  106. 'is_del' => 0,
  107. 'mer_id' => $merId,
  108. 'type' => self::TYPE_MARGIN,
  109. 'meal_id'=> $ret['type_id'],
  110. 'pay_type' => $data['pay_type'],
  111. 'attach' => 'meal',
  112. 'order_info' => json_encode($arr,JSON_UNESCAPED_UNICODE),
  113. 'pay_price' => $margin
  114. ];
  115. return compact('key', 'param');
  116. }
  117. /**
  118. * 处理配送相关信息的函数
  119. *
  120. * 该函数用于生成与配送相关的键名和参数,以供后续处理订单或配送时使用。
  121. * 它结合了商家ID、当前时间和订单金额等信息,创建了一个唯一且难以伪造的键名,
  122. * 同时也构造了订单的参数数组,包含了订单的状态、支付方式等重要信息。
  123. *
  124. * @param string $merId 商家ID,用于区分不同商家的订单
  125. * @param array $data 包含订单价格和支付方式等信息的数据数组
  126. * @return array 返回包含键名和参数的数组,供其他部分使用
  127. */
  128. public function delivery($merId, $data)
  129. {
  130. // 生成唯一的键名,用于标识和存储订单信息
  131. $key = 'Delivery_'.$merId.'_'.md5(date('YmdH',time()).$data['price']);
  132. // 初始化订单详细信息数组,这里仅包含订单价格
  133. $arr = ['price' => $data['price']];
  134. // 构造订单参数数组,包含了订单的各种状态和信息
  135. $param = [
  136. 'status' => 0, // 订单状态,初始为0表示未处理
  137. 'is_del' => 0, // 订单删除状态,0表示未删除
  138. 'mer_id' => $merId, // 商家ID
  139. 'type' => 20, // 订单类型,这里假设为20表示配送订单
  140. 'meal_id'=> 0, // 餐品ID,初始为0,可能根据实际业务进行修改
  141. 'pay_type' => $data['pay_type'], // 支付方式,从$data中获取
  142. 'attach' => 'meal', // 订单附加信息,这里标识为meal
  143. 'order_info' => json_encode($arr,JSON_UNESCAPED_UNICODE), // 将订单详细信息编码为JSON格式
  144. 'pay_price' => $data['price'], // 订单支付价格,从$data中获取
  145. ];
  146. // 返回包含键名和参数的数组
  147. return compact('key', 'param');
  148. }
  149. /**
  150. * 生成二维码
  151. *
  152. * 该方法用于根据商家ID、二维码类型和具体数据生成相应的二维码配置。
  153. * 它首先尝试从缓存中获取二维码配置,如果缓存不存在,则生成新的二维码配置,
  154. * 并将其与相关数据一起存储在缓存中,以供后续使用。
  155. *
  156. * @param int $merId 商家ID,用于标识商家
  157. * @param string $type 二维码类型,决定使用哪种方式生成二维码
  158. * @param array $data 生成二维码所需的具体数据,包括支付类型等
  159. * @return array 返回包含二维码配置、过期时间和价格的信息
  160. */
  161. public function QrCode(int $merId, string $type, array $data)
  162. {
  163. // 根据二维码类型调用相应的方法,并获取生成二维码所需的键值和参数
  164. $res = $this->{$type}($merId, $data);
  165. $key = $res['key'];
  166. $param = $res['param'];
  167. // 尝试从缓存中获取二维码配置
  168. if(!$result = Cache::store('file')->get($key)){
  169. // 生成新的订单号
  170. $order_sn = app()->make(StoreOrderRepository::class)->getNewOrderId(StoreOrderRepository::TYPE_SN_SERVER_ORDER);
  171. // 使用订单号作为订单描述和支付body
  172. $param['order_sn'] = $order_sn;
  173. $param['body'] = $order_sn;
  174. // 根据支付类型创建支付服务实例,并生成支付二维码
  175. $payType = $data['pay_type'] == 1 ? 'weixinQr' : 'alipayQr';
  176. $service = new PayService($payType,$param);
  177. $code = $service->pay(null);
  178. // 设置二维码过期时间,并生成包含配置、过期时间和价格的数组
  179. $endtime = time() + 1800 ;
  180. $result = [
  181. 'config' => $code['config'],
  182. 'endtime'=> date('Y-m-d H:i:s',$endtime),
  183. 'price' => $param['pay_price']
  184. ];
  185. // 将二维码配置存储在缓存中,设置过期时间为30分钟
  186. Cache::store('file')->set($key,$result,30);
  187. // 将订单号和相关参数存储在缓存中,过期时间为24小时
  188. $param['key'] = $key;
  189. Cache::store('file')->set($order_sn,$param,60 * 24);
  190. }
  191. // 返回二维码配置信息
  192. return $result;
  193. }
  194. /**
  195. * 处理支付成功的逻辑。
  196. * 当收到支付成功的通知时,本函数将验证订单是否存在。如果订单不存在于数据库中,则尝试从缓存中恢复订单数据,
  197. * 并将订单状态更新为已支付,同时清除相关的缓存数据。
  198. *
  199. * @param array $data 包含订单信息的数据数组,其中应包含订单号(order_sn)。
  200. */
  201. public function paySuccess($data)
  202. {
  203. // 根据订单号尝试从数据库中获取订单信息
  204. $get = $this->dao->getWhere(['order_sn' => $data['order_sn']]);
  205. // 如果订单不存在于数据库中
  206. if(!$get){
  207. // 尝试从缓存中获取订单数据
  208. $dat = Cache::store('file')->get($data['order_sn']);
  209. $key = $dat['key'];
  210. // 移除不需要的字段,以准备更新数据库中的订单信息
  211. unset($dat['attach'],$dat['body'],$dat['key']);
  212. // 设置订单状态为已支付,并记录支付时间
  213. $dat['status'] = 1;
  214. $dat['pay_time'] = date('y_m-d H:i:s', time());
  215. // 使用事务处理来确保数据的一致性
  216. Db::transaction(function () use($data, $dat,$key){
  217. // 在数据库中创建(或更新)订单信息
  218. $res = $this->dao->create($dat);
  219. // 处理支付成功后的逻辑,如发送通知等
  220. $this->payAfter($dat,$res);
  221. // 清除订单号和key相关的缓存数据
  222. Cache::store('file')->delete($data['order_sn']);
  223. Cache::store('file')->delete($key);
  224. });
  225. }
  226. }
  227. /**
  228. * 根据支付类型处理支付后业务逻辑
  229. *
  230. * @param array $dat 包含订单信息和支付类型的数组
  231. * @param null $ret 保留参数,未使用
  232. * @return void
  233. */
  234. public function payAfter($dat, $ret = null)
  235. {
  236. // 解析订单信息
  237. $info = json_decode($dat['order_info']);
  238. // 根据支付类型执行不同的业务逻辑
  239. switch ($dat['type']) {
  240. case self::TYPE_COPY_PRODUCT:
  241. // 处理复制产品套餐支付
  242. app()->make(ProductCopyRepository::class)->add([
  243. 'type' => 'pay_copy',
  244. 'num' => $info->num,
  245. 'info' => $dat['order_info'],
  246. 'message' => '购买复制商品套餐',
  247. ], $dat['mer_id']);
  248. break;
  249. case self::TYPE_DUMP:
  250. // 处理电子面单套餐支付
  251. app()->make(ProductCopyRepository::class)->add([
  252. 'type' => 'pay_dump',
  253. 'num' => $info->num,
  254. 'info' => $dat['order_info'],
  255. 'message' => '购买电子面单套餐',
  256. ], $dat['mer_id']);
  257. break;
  258. case self::TYPE_MARGIN:
  259. // 处理保证金支付逻辑
  260. $res = app()->make(MerchantRepository::class)->get($dat['mer_id']);
  261. if ($res['is_margin'] == 1) {
  262. // 如果已经缴纳保证金,则更新保证金金额
  263. $margin = $res->margin;
  264. $ot_margin = $res->margin;
  265. $_margin = $res->margin;
  266. } else {
  267. // 否则,更新原保证金和现保证金金额,并计算差额
  268. $margin = $res->ot_margin;
  269. $ot_margin = $res->ot_margin;
  270. $_margin = bcsub($res->ot_margin, $res->margin, 2);
  271. }
  272. if (bccomp($_margin, $dat['pay_price'], 2) === 0) {
  273. // 如果支付金额与差额相符,更新保证金状态
  274. $res->ot_margin = $ot_margin;
  275. $res->margin = $margin;
  276. $res->margin_remind_time = '';
  277. $res->is_margin = 10;
  278. } else {
  279. // 否则,设置为保证金支付异常状态
  280. // 支付金额与订单金额不一致
  281. $res->is_margin = 20;
  282. }
  283. $res->save();
  284. // 记录保证金缴纳账单
  285. $bill = [
  286. 'title' => '线上缴纳保证金',
  287. 'mer_id' => $dat['mer_id'],
  288. 'number' => $dat['pay_price'],
  289. 'mark' => '缴纳保证金',
  290. 'balance' => $res->margin,
  291. ];
  292. app()->make(UserBillRepository::class)->bill(0, 'mer_margin', 'mer_margin', 1, $bill);
  293. break;
  294. case self::TYPE_MARGIN_MAKE_UP:
  295. // 处理保证金补缴逻辑
  296. $res = app()->make(MerchantRepository::class)->get($dat['mer_id']);
  297. if (bccomp($res['margin'], $dat['pay_price'], 2) === 0) {
  298. // 如果支付金额与保证金补齐金额相符,更新保证金余额
  299. $res->margin = bcadd($res['margin'], $dat['pay_price'], 2);
  300. $res->margin_remind_time = '';
  301. $res->is_margin = 10;
  302. } else {
  303. // 否则,设置为保证金支付异常状态
  304. // 支付金额与订单金额不一致
  305. $res->is_margin = 20;
  306. }
  307. $res->save();
  308. // 记录保证金补缴账单
  309. $bill = [
  310. 'mer_id' => $dat['mer_id'],
  311. 'number' => $dat['pay_price'],
  312. 'mark' => '线上补缴保证金',
  313. 'balance' => $res->margin,
  314. ];
  315. app()->make(UserBillRepository::class)->bill(0, 'mer_margin', 'pay_margin', 1, $bill);
  316. break;
  317. case self::TYPE_DELIVERY:
  318. // 处理同城配送充值
  319. $res = app()->make(MerchantRepository::class)->get($dat['mer_id']);
  320. if ($res) {
  321. // 更新配送余额
  322. $res->delivery_balance = bcadd($res->delivery_balance, $dat['pay_price'], 2);
  323. $res->save();
  324. } else {
  325. // 记录异常信息
  326. Log::info('同城配送充值异常 :' . json_encode($dat));
  327. }
  328. break;
  329. default:
  330. break;
  331. }
  332. return;
  333. }
  334. /**
  335. * 获取服务订单列表
  336. *
  337. * 根据给定的条件和分页参数,从数据库中检索服务订单列表。此方法专注于查询操作,不涉及数据的增删改。
  338. *
  339. * @param array $where 查询条件,允许通过数组传递多个条件。
  340. * @param int $page 当前页码,用于分页查询。
  341. * @param int $limit 每页的记录数,用于分页查询。
  342. * @return array 返回包含订单总数和订单列表的数组。
  343. */
  344. public function getList(array $where, int $page, int $limit)
  345. {
  346. // 默认不查询已删除的订单
  347. $where['is_del'] = 0;
  348. // 构建查询语句,包括关联查询和排序
  349. $query = $this->dao->search($where)
  350. ->with([
  351. 'merchant' => function($query){
  352. // 关联查询商家信息,包括商家类型,并只选取需要的字段
  353. $query->with(['merchantType']);
  354. $query->field('mer_id,mer_name,is_trader,mer_avatar,type_id,mer_phone,mer_address,is_margin,margin,real_name,ot_margin');
  355. }
  356. ])
  357. ->order('ServeOrder.create_time DESC');
  358. // 计算满足条件的订单总数
  359. $count = $query->count();
  360. // 分页查询满足条件的订单列表
  361. $list = $query->page($page, $limit)->select();
  362. // 将订单总数和订单列表打包成数组返回
  363. return compact('count', 'list');
  364. }
  365. }