Order.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. <?php
  2. /**
  3. *
  4. * @copyright ©2020 点小铺
  5. * @author hanj
  6. * @link: https://dyuit.com
  7. * Created by VSCode
  8. */
  9. namespace app\api\model\sharing;
  10. use app\api\model\User as UserModel;
  11. use app\api\model\Setting as SettingModel;
  12. use app\api\model\UserCoupon as UserCouponModel;
  13. use app\api\model\sharing\Goods as GoodsModel;
  14. use app\api\model\sharing\GoodsSku as GoodsSkuModel;
  15. use app\common\model\sharing\Order as OrderModel;
  16. use app\api\model\sharing\OrderGoods as OrderGoodsModel;
  17. use app\api\service\User as UserService;
  18. use app\api\service\sharing\order\PaySuccess;
  19. use app\api\service\Payment as PaymentService;
  20. use app\common\enum\OrderType as OrderTypeEnum;
  21. use app\common\enum\order\Status as OrderStatusEnum;
  22. use app\common\enum\DeliveryType as DeliveryTypeEnum;
  23. use app\common\enum\order\PayType as PayTypeEnum;
  24. use app\common\enum\order\PayStatus as PayStatusEnum;
  25. use app\common\service\order\Complete as OrderCompleteService;
  26. use app\common\library\helper;
  27. use app\common\exception\BaseException;
  28. /**
  29. * 拼团订单模型
  30. * Class Order
  31. * @package app\api\model
  32. */
  33. class Order extends OrderModel
  34. {
  35. /**
  36. * 隐藏字段
  37. * @var array
  38. */
  39. protected $hidden = [
  40. 'wxapp_id',
  41. 'update_time'
  42. ];
  43. /**
  44. * 订单结算api参数
  45. * @var array
  46. */
  47. private $checkoutParam = [
  48. 'active_id' => 0, // 参与的拼单id
  49. 'delivery' => null, // 配送方式
  50. 'shop_id' => 0, // 自提门店id
  51. 'linkman' => '', // 自提联系人
  52. 'phone' => '', // 自提联系电话
  53. 'coupon_id' => 0, // 优惠券id
  54. 'remark' => '', // 买家留言
  55. 'pay_type' => PayTypeEnum::WECHAT, // 支付方式
  56. ];
  57. /**
  58. * 待支付订单详情
  59. * @param $orderNo
  60. * @return null|static
  61. * @throws \think\exception\DbException
  62. */
  63. public static function getPayDetail($orderNo)
  64. {
  65. return self::get(['order_no' => $orderNo, 'pay_status' => 10, 'is_delete' => 0], ['goods', 'user']);
  66. }
  67. /**
  68. * 获取订单商品列表
  69. * @param $params
  70. * @return array
  71. */
  72. public function getOrderGoodsListByNow($params)
  73. {
  74. // 商品详情
  75. /* @var GoodsModel $goods */
  76. $goods = GoodsModel::detail($params['goods_id']);
  77. // 商品sku信息
  78. $goods['goods_sku'] = GoodsModel::getGoodsSku($goods, $params['goods_sku_id']);
  79. // 商品列表
  80. $goodsList = [$goods->hidden(['category', 'content', 'image', 'sku'])];
  81. foreach ($goodsList as &$item) {
  82. // 商品单价(根据order_type判断单买还是拼单)
  83. // order_type:下单类型 10 => 单独购买,20 => 拼团
  84. $item['goods_price'] = $params['order_type'] == 10 ? $item['goods_sku']['goods_price']
  85. : $item['goods_sku']['sharing_price'];
  86. // 商品购买数量
  87. $item['total_num'] = $params['goods_num'];
  88. $item['spec_sku_id'] = $item['goods_sku']['spec_sku_id'];
  89. // 商品购买总金额
  90. $item['total_price'] = helper::bcmul($item['goods_price'], $params['goods_num']);
  91. }
  92. return $goodsList;
  93. }
  94. /**
  95. * 订单支付事件
  96. * @param int $payType
  97. * @return bool
  98. * @throws \think\exception\DbException
  99. */
  100. public function onPay($payType = PayTypeEnum::WECHAT)
  101. {
  102. // 判断商品状态、库存
  103. if (!$this->checkGoodsStatusFromOrder($this['goods'])) {
  104. return false;
  105. }
  106. // 余额支付
  107. if ($payType == PayTypeEnum::BALANCE) {
  108. return $this->onPaymentByBalance($this['order_no']);
  109. }
  110. return true;
  111. }
  112. /**
  113. * 构建支付请求的参数
  114. * @param $user
  115. * @param $order
  116. * @param $payType
  117. * @return array
  118. * @throws BaseException
  119. * @throws \think\exception\DbException
  120. */
  121. public function onOrderPayment($user, $order, $payType)
  122. {
  123. if ($payType == PayTypeEnum::WECHAT) {
  124. return $this->onPaymentByWechat($user, $order);
  125. }
  126. return [];
  127. }
  128. /**
  129. * 构建微信支付请求
  130. * @param $user
  131. * @param $order
  132. * @return array
  133. * @throws BaseException
  134. * @throws \think\exception\DbException
  135. */
  136. private function onPaymentByWechat($user, $order)
  137. {
  138. return PaymentService::wechat(
  139. $user,
  140. $order['order_id'],
  141. $order['order_no'],
  142. $order['pay_price'],
  143. OrderTypeEnum::SHARING
  144. );
  145. }
  146. /**
  147. * 余额支付标记订单已支付
  148. * @param $orderNo
  149. * @return bool
  150. * @throws \think\exception\DbException
  151. */
  152. public function onPaymentByBalance($orderNo)
  153. {
  154. // 获取订单详情
  155. $PaySuccess = new PaySuccess($orderNo);
  156. // 发起余额支付
  157. $status = $PaySuccess->onPaySuccess(PayTypeEnum::BALANCE);
  158. if (!$status) {
  159. $this->error = $PaySuccess->getError();
  160. }
  161. return $status;
  162. }
  163. /**
  164. * 验证拼单是否允许加入
  165. * @param $active_id
  166. * @return bool
  167. * @throws BaseException
  168. * @throws \think\exception\DbException
  169. */
  170. public function checkActiveIsAllowJoin($active_id)
  171. {
  172. // 拼单详情
  173. $detail = Active::detail($active_id);
  174. if (!$detail) {
  175. throw new BaseException('很抱歉,拼单不存在');
  176. }
  177. // 验证当前拼单是否允许加入新成员
  178. return $detail->checkAllowJoin();
  179. }
  180. /**
  181. * 保存上门自提联系人
  182. * @param $linkman
  183. * @param $phone
  184. * @return false|\think\Model
  185. */
  186. public function saveOrderExtract($linkman, $phone)
  187. {
  188. // 记忆上门自提联系人(缓存),用于下次自动填写
  189. UserService::setLastExtract($this['user_id'], trim($linkman), trim($phone));
  190. // 保存上门自提联系人(数据库)
  191. return $this->extract()->save([
  192. 'linkman' => trim($linkman),
  193. 'phone' => trim($phone),
  194. 'user_id' => $this['user_id'],
  195. 'wxapp_id' => self::$wxapp_id,
  196. ]);
  197. }
  198. /**
  199. * 用户拼团订单列表
  200. * @param $user_id
  201. * @param string $type
  202. * @return \think\Paginator
  203. * @throws \think\exception\DbException
  204. */
  205. public function getList($user_id, $type = 'all')
  206. {
  207. // 筛选条件
  208. $filter = [];
  209. // 订单数据类型
  210. switch ($type) {
  211. case 'all':
  212. // 全部
  213. break;
  214. case 'payment';
  215. // 待支付
  216. $filter['pay_status'] = PayStatusEnum::PENDING;
  217. break;
  218. case 'sharing';
  219. // 拼团中
  220. $filter['active.status'] = 10;
  221. break;
  222. case 'delivery';
  223. // 待发货
  224. $this->where('IF ( (`order`.`order_type` = 20), (`active`.`status` = 20), TRUE)');
  225. $filter['pay_status'] = 20;
  226. $filter['delivery_status'] = 10;
  227. break;
  228. case 'received';
  229. // 待收货
  230. $filter['pay_status'] = 20;
  231. $filter['delivery_status'] = 20;
  232. $filter['receipt_status'] = 10;
  233. break;
  234. case 'comment';
  235. $filter['order_status'] = 30;
  236. $filter['is_comment'] = 0;
  237. break;
  238. }
  239. return $this->with(['goods.image', 'active'])
  240. ->alias('order')
  241. ->field('order.*, active.status as active_status')
  242. ->join('sharing_active active', 'order.active_id = active.active_id', 'LEFT')
  243. ->where('user_id', '=', $user_id)
  244. ->where($filter)
  245. ->where('order.is_delete', '=', 0)
  246. ->order(['create_time' => 'desc'])
  247. ->paginate(15, false, [
  248. 'query' => \request()->request()
  249. ]);
  250. }
  251. /**
  252. * 取消订单
  253. * @param UserModel $user
  254. * @return bool|false|int
  255. */
  256. public function cancel($user)
  257. {
  258. // 订单是否已支付
  259. $isPay = $this['pay_status']['value'] == PayStatusEnum::SUCCESS;
  260. // 已发货订单不可取消
  261. if ($this['delivery_status']['value'] == 20) {
  262. $this->error = '已发货订单不可取消';
  263. return false;
  264. }
  265. // 已付款的拼团订单不允许取消
  266. if ($isPay && $this['order_type']['value'] == 20) {
  267. $this->error = '已付款的拼团订单不允许取消';
  268. return false;
  269. }
  270. // 订单取消事件
  271. $this->transaction(function () use ($user, $isPay) {
  272. // 未付款的订单
  273. if ($isPay == false) {
  274. // 回退商品库存
  275. (new OrderGoodsModel)->backGoodsStock($this['goods'], $isPay);
  276. // 回退用户优惠券
  277. $this['coupon_id'] > 0 && UserCouponModel::setIsUse($this['coupon_id'], false);
  278. // 回退用户积分
  279. $describe = "订单取消:{$this['order_no']}";
  280. $this['points_num'] > 0 && $user->setIncPoints($this['points_num'], $describe);
  281. }
  282. // 更新订单状态
  283. return $this->save(['order_status' => $isPay ? OrderStatusEnum::APPLY_CANCEL : OrderStatusEnum::CANCELLED]);
  284. });
  285. return true;
  286. }
  287. /**
  288. * 确认收货
  289. * @return bool|mixed
  290. */
  291. public function receipt()
  292. {
  293. // 验证订单是否合法
  294. // 条件1: 订单必须已发货
  295. // 条件2: 订单必须未收货
  296. if ($this['delivery_status']['value'] != 20 || $this['receipt_status']['value'] != 10) {
  297. $this->error = '该订单不合法';
  298. return false;
  299. }
  300. return $this->transaction(function () {
  301. // 更新订单状态
  302. $status = $this->save([
  303. 'receipt_status' => 20,
  304. 'receipt_time' => time(),
  305. 'order_status' => 30
  306. ]);
  307. // 执行订单完成后的操作
  308. $OrderCompleteService = new OrderCompleteService(OrderTypeEnum::SHARING);
  309. $OrderCompleteService->complete([$this], static::$wxapp_id);
  310. return $status;
  311. });
  312. }
  313. /**
  314. * 获取订单总数
  315. * @param $user_id
  316. * @param string $type
  317. * @return int|string
  318. * @throws \think\Exception
  319. */
  320. public function getCount($user_id, $type = 'all')
  321. {
  322. // 筛选条件
  323. $filter = [];
  324. // 订单数据类型
  325. switch ($type) {
  326. case 'all':
  327. break;
  328. case 'payment';
  329. $filter['pay_status'] = PayStatusEnum::PENDING;
  330. break;
  331. case 'received';
  332. $filter['pay_status'] = PayStatusEnum::SUCCESS;
  333. $filter['delivery_status'] = 20;
  334. $filter['receipt_status'] = 10;
  335. break;
  336. case 'comment';
  337. $filter['order_status'] = 30;
  338. $filter['is_comment'] = 0;
  339. break;
  340. }
  341. return $this->where('user_id', '=', $user_id)
  342. ->where('order_status', '<>', 20)
  343. ->where($filter)
  344. ->where('is_delete', '=', 0)
  345. ->count();
  346. }
  347. /**
  348. * 订单详情
  349. * @param $order_id
  350. * @param $user_id
  351. * @return array|false|\PDOStatement|string|\think\Model|static
  352. * @throws BaseException
  353. */
  354. public static function getUserOrderDetail($order_id, $user_id)
  355. {
  356. $order = (new static)->with(['goods' => ['image', 'refund'], 'address', 'express', 'extract_shop'])
  357. ->alias('order')
  358. ->field('order.*, active.status as active_status')
  359. ->join('sharing_active active', 'order.active_id = active.active_id', 'LEFT')
  360. ->where([
  361. 'order_id' => $order_id,
  362. 'user_id' => $user_id,
  363. ])->find();
  364. if (!$order) {
  365. throw new BaseException(['msg' => '订单不存在']);
  366. }
  367. return $order;
  368. }
  369. /**
  370. * 判断商品库存不足 (未付款订单)
  371. * @param $goodsList
  372. * @return bool
  373. * @throws \think\exception\DbException
  374. */
  375. private function checkGoodsStatusFromOrder($goodsList)
  376. {
  377. foreach ($goodsList as $goods) {
  378. // 判断商品是否下架
  379. if (
  380. empty($goods['goods'])
  381. || $goods['goods']['goods_status']['value'] != 10
  382. ) {
  383. $this->error = "很抱歉,商品 [{$goods['goods_name']}] 已下架";
  384. return false;
  385. }
  386. // 获取商品的sku信息
  387. $goodsSku = GoodsSkuModel::detail($goods['goods_id'], $goods['spec_sku_id']);
  388. // sku已不存在
  389. if (empty($goodsSku)) {
  390. $this->error = "很抱歉,商品 [{$goods['goods_name']}] sku已不存在,请重新下单";
  391. return false;
  392. }
  393. // 付款减库存
  394. if ($goods['deduct_stock_type'] == 20 && $goods['total_num'] > $goodsSku['stock_num']) {
  395. $this->error = "很抱歉,商品 [{$goods['goods_name']}] 库存不足";
  396. return false;
  397. }
  398. }
  399. return true;
  400. }
  401. /**
  402. * 当前订单是否允许申请售后
  403. * @return bool
  404. */
  405. public function isAllowRefund()
  406. {
  407. // 必须是已发货的订单
  408. if ($this['delivery_status']['value'] != 20) {
  409. return false;
  410. }
  411. // 允许申请售后期限(天)
  412. $refundDays = SettingModel::getItem('trade')['order']['refund_days'];
  413. // 不允许售后
  414. if ($refundDays == 0) {
  415. return false;
  416. }
  417. // 当前时间超出允许申请售后期限
  418. if (
  419. $this['receipt_status'] == 20
  420. && time() > ($this['receipt_time'] + ((int)$refundDays * 86400))
  421. ) {
  422. return false;
  423. }
  424. return true;
  425. }
  426. /**
  427. * 判断当前订单是否允许核销
  428. * @param static $order
  429. * @return bool
  430. */
  431. public function checkExtractOrder(&$order)
  432. {
  433. if (
  434. $order['pay_status']['value'] == PayStatusEnum::SUCCESS
  435. && $order['delivery_type']['value'] == DeliveryTypeEnum::EXTRACT
  436. && $order['delivery_status']['value'] == 10
  437. // 拼团订单验证拼单状态
  438. && ($order['order_type']['value'] == 20 ? $order['active']['status']['value'] == 20 : true)
  439. ) {
  440. return true;
  441. }
  442. $this->setError('该订单不能被核销');
  443. return false;
  444. }
  445. /**
  446. * 设置错误信息
  447. * @param $error
  448. */
  449. private function setError($error)
  450. {
  451. empty($this->error) && $this->error = $error;
  452. }
  453. /**
  454. * 是否存在错误
  455. * @return bool
  456. */
  457. public function hasError()
  458. {
  459. return !empty($this->error);
  460. }
  461. }