OrderService.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. <?php
  2. namespace addons\QiShangTong\common\service;
  3. use addons\QiShangTong\common\enums\QiShangTongEnums;
  4. use addons\QiShangTong\common\expand\sdk\qishangtong\src\model\LogisticsModel;
  5. use addons\QiShangTong\common\expand\sdk\qishangtong\src\model\OrderCancelModel;
  6. use addons\QiShangTong\common\expand\sdk\qishangtong\src\model\OrderConfirmGoodsItemModel;
  7. use addons\QiShangTong\common\expand\sdk\qishangtong\src\model\OrderCreateModel;
  8. use addons\QiShangTong\common\expand\sdk\qishangtong\src\model\OrderMakeDeliveryModel;
  9. use addons\QiShangTong\common\expand\sdk\qishangtong\src\model\OrderPaidModel;
  10. use addons\QiShangTong\common\expand\sdk\qishangtong\src\request\OrderCancelRequest;
  11. use addons\QiShangTong\common\expand\sdk\qishangtong\src\request\OrderCreateRequest;
  12. use addons\QiShangTong\common\expand\sdk\qishangtong\src\request\OrderMakeDeliveryRequest;
  13. use addons\QiShangTong\common\expand\sdk\qishangtong\src\request\OrderPaidRequest;
  14. use addons\QiShangTong\common\helper\RequestHelper;
  15. use addons\QiShangTong\common\models\QiShangTongEvent;
  16. use addons\QiShangTong\common\models\QiShangTongGoods;
  17. use addons\QiShangTong\common\models\QiShangTongGoodsAttr;
  18. use addons\QiShangTong\common\models\QiShangTongOrder;
  19. use addons\QiShangTong\common\models\QiShangTongRegion;
  20. use common\enums\AddonsEnum;
  21. use common\enums\OrderStatusEnum;
  22. use common\enums\RabbitMqEnum;
  23. use common\enums\ShippingTypeEnum;
  24. use common\enums\StatusEnum;
  25. use common\helpers\FormatHelper;
  26. use common\models\common\Express;
  27. use common\models\mall\Mall;
  28. use common\models\order\Order;
  29. use common\models\order\OrderDetail;
  30. use common\models\order\OrderExpress;
  31. use common\models\user\User;
  32. class OrderService
  33. {
  34. public function page(int $mall_id, array $params)
  35. {
  36. $where = [
  37. ['status' => [StatusEnum::ENABLED, StatusEnum::DISABLED]]
  38. ];
  39. if (!empty($params['order_no'])) {
  40. $where[] = ['like', 'order_no', $params['order_no']];
  41. }
  42. $is_master = SettingService::isMasterMall($mall_id);
  43. if (!$is_master) {
  44. $where[] = ['mall_id' => $mall_id];
  45. } elseif (!empty($params['keyword'])) {
  46. $mIds = Mall::find()->where(['or', ['like', 'id', $params['keyword']], ['like', 'name', $params['keyword']]])
  47. ->select('id')->column();
  48. if (empty($mIds)) return [];
  49. $where[] = ['mall_id' => $mIds];
  50. }
  51. if (!empty($params['third_order_no'])) {
  52. $where[] = ['like', 'third_order_no', $params['third_order_no']];
  53. }
  54. if (!empty($params['goods_name'])) {
  55. $where[] = ['like', 'content', $params['goods_name']];
  56. }
  57. if (!empty($params['member'])) {
  58. $query = User::find();
  59. if (!$is_master) $query->where(['mall_id' => $mall_id]);
  60. $uids = $query->andWhere(['or', ['like', 'id', $params['member']], ['like', 'mobile', $params['member']], ['like', 'nickname', $params['member']]])
  61. ->andWhere(['status' => StatusEnum::ENABLED])
  62. ->select('id')->column();
  63. if (empty($uids)) return [];
  64. $where[] = ['user_id' => $uids];
  65. }
  66. $ret = QiShangTongOrder::listPage([
  67. 'where' => $where,
  68. 'order' => 'id desc',
  69. 'limit' => $params['limit'] ?? '',
  70. 'with' => ['order' => function($query) {
  71. $query->with(['detail']);
  72. }, 'mall']
  73. ]);
  74. if (!empty(QiShangTongOrder::getStaticError())) return QiShangTongOrder::getStaticError();
  75. if (!empty($ret['list'])) {
  76. foreach ($ret['list'] as &$item) {
  77. $item['mall']['logo'] = MallService::getLogo($item['mall_id']);
  78. }
  79. }
  80. return $ret;
  81. }
  82. /**
  83. * 创建订单
  84. * @param array $order
  85. * @param int $a_order_id
  86. * @return bool
  87. */
  88. public function create(array $order, int $a_order_id = 0)
  89. {
  90. try {
  91. $goods_ids = array_column($order['detail'], 'goods_id');
  92. $list = QiShangTongGoods::lists([
  93. 'where' => [
  94. ['goods_id' => $goods_ids],
  95. ['mall_id' => $order['mall_id']]
  96. ],
  97. 'index' => 'goods_id',
  98. 'select'=> 'goods_id, third_goods_id'
  99. ]);
  100. $order_detail_ids = [];
  101. if (!empty($list)) {
  102. $goods = [];
  103. foreach ($order['detail'] as $item) {
  104. if (!empty($list[$item['goods_id']])) {
  105. $tGoods = new OrderConfirmGoodsItemModel();
  106. $tGoods->goods_id = $list[$item['goods_id']]['third_goods_id'];
  107. $tGoods->spec_id = QiShangTongGoodsAttr::find()->where(['goods_attr_id' => $item['goods_attr_id']])
  108. ->select('third_spec_id')->scalar(); // 商品规格ID
  109. $tGoods->num = $item['num'];
  110. $order_detail_ids[] = $item['id'];
  111. $goods[] = $tGoods;
  112. }
  113. }
  114. if (!empty($goods)) {
  115. if (!empty($a_order_id)) {
  116. $orderModel = QiShangTongOrder::findOne(['id' => $a_order_id]);
  117. } else {
  118. $orderModel = new QiShangTongOrder();
  119. }
  120. $is_d = true;
  121. $region = QiShangTongRegion::findOne(['local_id' => $order['area']]);
  122. if (empty($region)) {
  123. $is_d = false;
  124. $region = QiShangTongRegion::findOne(['local_id' => $order['city']]);
  125. }
  126. $contentModel = new OrderCreateModel();
  127. $contentModel->phone_mob = $order['mobile'];
  128. $contentModel->region_name = str_replace(' ', '', $order['region_name']);
  129. $contentModel->address = $order['address'];
  130. $contentModel->province = $region->province_id; // ID要相通
  131. $contentModel->city = $region->city_id; // 直辖市可以传0,但是 区县/省市不能传0
  132. $contentModel->area = $is_d ? $region->id : $region->district_id;
  133. $contentModel->consignee = $order['receiver_name'];
  134. $contentModel->order_no = $order['order_no'];
  135. $contentModel->details = $goods;
  136. // 商品信息
  137. $res = RequestHelper::send(new OrderCreateRequest(), $contentModel, $order['mall_id']);
  138. $orderModel->req_success = 100;
  139. if (!is_array($res)) {
  140. // throw new \Exception($res);
  141. $orderModel->req_success = StatusEnum::ENABLED;
  142. }
  143. // 创建订单
  144. $orderModel->content = [
  145. 'create' => $res
  146. ];
  147. $orderModel->post = [
  148. 'create' => $contentModel->toArray()
  149. ];
  150. if (empty($a_order_id)) {
  151. $orderModel->order_no = $order['order_no'];
  152. $orderModel->order_id = $order['id'];
  153. $orderModel->mall_id = $order['mall_id'];
  154. $orderModel->user_id = $order['user_id'];
  155. $orderModel->order_detail_ids = $order_detail_ids;
  156. }
  157. $orderModel->third_order_no = $res['order_parent_sn'] ?? '';
  158. $orderModel->last_req = 'create';
  159. $orderModel->last_resp = var_export($res, true);
  160. if (!$orderModel->save()) throw new \Exception($orderModel->getErrorMessage());
  161. }
  162. }
  163. } catch (\Exception $e) {
  164. var_dump($e->getMessage(), $e->getLine(), $e->getFile());
  165. \Yii::error(FormatHelper::exception($e, __METHOD__));
  166. return false;
  167. }
  168. return true;
  169. }
  170. /**
  171. * 订单更新
  172. * @param QiShangTongEvent $event
  173. * @param $type
  174. * @return void
  175. * @throws \Exception
  176. */
  177. public function eventUpdate(QiShangTongEvent &$event, $type)
  178. {
  179. $aOrder = QiShangTongOrder::findOne(['third_order_no' => $event->content['order_parent_sn']]);
  180. if (empty($aOrder)) throw new \Exception("订单不存在!");
  181. // 做处理
  182. $order = Order::getOne([
  183. ['id' => $aOrder->order_id]
  184. ], true, 'detail');
  185. if (empty($order)) throw new \Exception("订单不存在");
  186. $event->mall_id = $aOrder->mall_id;
  187. $t = \Yii::$app->db->beginTransaction();
  188. try {
  189. switch ($type) {
  190. case 'ready':
  191. // 配货中
  192. break;
  193. case 'delivery':
  194. // 已发货
  195. // 执行发货
  196. // 查快递
  197. /**
  198. * @var $express Express
  199. */
  200. $express = Express::getOne([
  201. ['like', 'name', $event->content['Kuaidiname']]
  202. ]);
  203. if (empty($express)) {
  204. $express = Express::getOne([
  205. ['id' => Express::EXPRESS_OTHER]
  206. ]);
  207. }
  208. $form['mall_id'] = $event->mall_id;
  209. $form['shipping_type'] = ShippingTypeEnum::LOGISTICS;
  210. $form['order_detail_ids'] = $aOrder->order_detail_ids;
  211. $form['express_id'] = $express->id;
  212. $form['express_no'] = $event->content['KuaidiNo'];
  213. $form['express_name'] = $express->name;
  214. $form['memo'] = "{$event->content['Kuaidiname']}, {$event->content['KuaidiNo']}, " .
  215. "{$event->content['third_code']}, {$event->content['Kuaidicode']}, " . date('Y-m-d H:i:s', $event->content['date']);
  216. $form['id'] = $aOrder->order_id;
  217. $form['mch_id'] = 0;
  218. $form['operator_id'] = 0;
  219. $form['operator_name'] = "企商通供应链发货";
  220. $res = OrderExpress::delivery($form); // 发货
  221. if ($res === false) throw new \Exception(OrderExpress::getStaticError());
  222. break;
  223. }
  224. $aOrder->content = array_merge($aOrder->content, [
  225. 'event' => [
  226. $event->content['type'] => $event->content,
  227. ]
  228. ]);
  229. if (!$aOrder->save()) throw new \Exception($aOrder->getErrorMessage());
  230. $t->commit();
  231. } catch (\Exception $e) {
  232. $t->rollBack();
  233. \Yii::error(FormatHelper::exception($e, __METHOD__));
  234. throw new \Exception($e->getMessage());
  235. }
  236. }
  237. /**
  238. * 订单取消
  239. * @param array $order
  240. * @return true
  241. * @throws \Exception
  242. */
  243. public function cancel(array $order)
  244. {
  245. $aOrder = QiShangTongOrder::findOne(['order_id' => $order['id']]);
  246. if (empty($aOrder)) return true;
  247. $contentModel = new OrderCancelModel();
  248. $contentModel->order_no = $aOrder->order_no;
  249. $contentModel->order_parent_sn = $aOrder->third_order_no;
  250. $res = RequestHelper::send(new OrderCancelRequest(), $contentModel, $order['mall_id']);
  251. $aOrder->req_success = 100;
  252. if (!is_array($res)) {
  253. $aOrder->req_success = StatusEnum::ENABLED;
  254. }
  255. $aOrder->post = array_merge($aOrder->post, ['cancel' => $contentModel->toArray()]);
  256. $aOrder->content = array_merge($aOrder->content, ['cancel' => $res]);
  257. $aOrder->last_req = 'cancel';
  258. $aOrder->last_resp = var_export($res, true);
  259. if (!$aOrder->save()) throw new \Exception($aOrder->getErrorMessage());
  260. $aOrder->order_status = OrderStatusEnum::REPEAL;
  261. return true;
  262. }
  263. /**
  264. * 订单支付
  265. * @param array $order
  266. * @return bool
  267. * @throws \Exception
  268. */
  269. public function pay(array $order)
  270. {
  271. $aOrder = QiShangTongOrder::findOne(['order_id' => $order['id']]);
  272. if (empty($aOrder)) return true;
  273. try {
  274. $contentModel = new OrderPaidModel();
  275. $contentModel->order_no = $aOrder->order_no;
  276. $contentModel->order_parent_sn = $aOrder->third_order_no;
  277. $contentModel->pay_time = $order['pay_time'];
  278. // 计算价格
  279. $amount = 0;
  280. foreach ($order['detail'] as $item) {
  281. $price = QiShangTongGoodsAttr::find()->where(['goods_attr_id' => $item['goods_attr_id']])->select('gonghuojia')->scalar();
  282. $amount += (int) bcmul(bcmul($price, 100), $item['num']);
  283. }
  284. $shipping_fee = (int) bcmul(!empty($order['shipping_money']) ? $order['shipping_money'] : 0, 100);
  285. // 如果失败了 这一行需要去掉
  286. $shipping_fee_txt = bcmul($shipping_fee, 0.01, 2);
  287. $amount_txt = round(bcmul(bcmul($amount, MallService::rate($aOrder->mall_id), 2), 0.01, 3), 2);
  288. $balanceLogModel = (new FundService())->create([
  289. 'mall_id' => $aOrder->mall_id,
  290. 'amount' => bcadd($amount_txt, $shipping_fee_txt, 2),
  291. 'remark' => "来自订单号为[{$aOrder->order_no}]的购物消费,其中商品金额为[{$amount_txt}],运费金额为[$shipping_fee_txt]",
  292. 'type' => QiShangTongEnums::BALANCE_LOG_TYPE_CONSUMER,
  293. 'direction' => QiShangTongEnums::BALANCE_DIRECTION_OUT,
  294. 'order_no' => $aOrder->order_no,
  295. 'addons' => AddonsEnum::ADDONS_NAME_QI_SHANG_TONG,
  296. ]);
  297. if (!is_string($balanceLogModel)) {
  298. $contentModel->amount = $amount + $shipping_fee;
  299. $res = RequestHelper::send(new OrderPaidRequest(), $contentModel, $order['mall_id']);
  300. $aOrder->req_success = 100;
  301. if (!is_array($res)) {
  302. // throw new \Exception($res);
  303. $aOrder->req_success = StatusEnum::ENABLED;
  304. }
  305. } else {
  306. // 无法回滚
  307. $res = $balanceLogModel;
  308. $aOrder->req_success = StatusEnum::ENABLED;
  309. }
  310. $aOrder->post = array_merge($aOrder->post, ['pay' => $contentModel->toArray()]);
  311. $aOrder->content = array_merge($aOrder->content, ['pay' => $res ?? []]);
  312. $aOrder->order_status = OrderStatusEnum::PAY;
  313. $aOrder->last_req = 'pay';
  314. $aOrder->last_resp = var_export($res ?? [], true);
  315. if (!$aOrder->save()) throw new \Exception($aOrder->getErrorMessage());
  316. if ($aOrder->req_success != 100 && !empty($balanceLogModel) && !is_string($balanceLogModel)) { // 退回资金
  317. FundService::asyncRefund($balanceLogModel->id);
  318. }
  319. } catch (\Exception $e) {
  320. return $e->getMessage();
  321. }
  322. return true;
  323. }
  324. /**
  325. * 订单收货
  326. * @param array $order
  327. * @return true
  328. * @throws \Exception
  329. */
  330. public function takeDelivery(array $order)
  331. {
  332. $aOrder = QiShangTongOrder::findOne(['order_id' => $order['id']]);
  333. if (empty($aOrder)) return true;
  334. $orderDetails = OrderDetail::lists([
  335. 'where' => [
  336. ['id' => $aOrder->order_detail_ids]
  337. ]
  338. ]);
  339. $post = [];
  340. $resp = [];
  341. foreach ($orderDetails as $orderDetail) {
  342. $contentModel = new OrderMakeDeliveryModel();
  343. $contentModel->order_no = $aOrder->order_no;
  344. $contentModel->order_parent_sn = $aOrder->third_order_no;
  345. $contentModel->order_sn = $orderDetail['id'];
  346. $aGoods = QiShangTongGoods::findOne(['goods_id' => $orderDetail['goods_id']]);
  347. $spec_id = QiShangTongGoodsAttr::find()->where(['goods_attr_id' => $orderDetail['goods_attr_id']])->select('third_spec_id')->scalar();
  348. $contentModel->spec_id = $spec_id;
  349. $contentModel->goods_id = $aGoods->third_goods_id;
  350. $res = RequestHelper::send(new OrderMakeDeliveryRequest(), $contentModel, $aOrder->mall_id);
  351. $aOrder->req_success = 100;
  352. if (!is_array($res)) {
  353. $aOrder->req_success = StatusEnum::ENABLED;
  354. // throw new \Exception($res);
  355. }
  356. $post[] = $contentModel->toArray();
  357. $resp[] = $res;
  358. }
  359. $aOrder->post = array_merge($aOrder->post, ['take_delivery' => $post]);
  360. $aOrder->content = array_merge($aOrder->content, ['take_delivery' => $resp]);
  361. $aOrder->order_status = OrderStatusEnum::SING;
  362. $aOrder->last_req = 'takeDelivery';
  363. $aOrder->last_resp = var_export($resp, true);
  364. if (!$aOrder->save()) throw new \Exception($aOrder->getErrorMessage());
  365. return true;
  366. }
  367. /**
  368. * 此任务不需要使用,因为有异步
  369. * @return void
  370. * @throws \Exception
  371. */
  372. public function logisticsTask()
  373. {
  374. foreach (QiShangTongOrder::find()->select('id, mall_id')->where(['is_logistics' => StatusEnum::DISABLED])
  375. ->andWhere(['order_status' => OrderStatusEnum::PAY])->each() as $item) {
  376. \Yii::$app->services->rabbitMq->push(RabbitMqEnum::EXCHANGE_ORDER, RabbitMqEnum::ORDER_QUEUE,
  377. ['mall_id' => $item['mall_id'], 'id' => $item['id']],
  378. self::class, 'asyncDelivery');
  379. // $this->asyncDelivery(['mall_id' => $item['mall_id'], 'id' => $item['id']]);
  380. }
  381. }
  382. /**
  383. * 同步发货信息
  384. * @param array $params
  385. * @return bool
  386. */
  387. public function asyncDelivery(array $params)
  388. {
  389. try {
  390. $aOrder = QiShangTongOrder::findOne(['id' => $params['id']]);
  391. if (empty($aOrder) || !empty($aOrder->is_logistics)) return true;
  392. $contentModel = new LogisticsModel();
  393. $contentModel->order_no = $aOrder->order_no;
  394. $contentModel->order_parent_sn = $aOrder->third_order_no;
  395. $res = RequestHelper::send(new OrderMakeDeliveryRequest(), $contentModel, $params['mall_id']);
  396. if (!is_array($res)) {
  397. throw new \Exception($res);
  398. }
  399. $t = \Yii::$app->db->beginTransaction();
  400. $aOrder->post = array_merge($aOrder->post, ['logistics' => $contentModel->toArray()]);
  401. $aOrder->content = array_merge($aOrder->content, ['logistics' => $res]);
  402. $aOrder->order_status = OrderStatusEnum::SHIPMENTS;
  403. $aOrder->is_logistics = StatusEnum::ENABLED;
  404. if (!$aOrder->save()) throw new \Exception($aOrder->getErrorMessage());
  405. /**
  406. * @var $express Express
  407. */
  408. $express = Express::getOne([
  409. ['like', 'name', $res['Kuaidiname']]
  410. ]);
  411. if (!empty($express)) {
  412. $express = Express::getOne([
  413. ['id' => Express::EXPRESS_OTHER]
  414. ]);
  415. }
  416. // 执行发货
  417. $express_no = explode(",", $res['KuaidiNo']);
  418. $form['mall_id'] = $params['mall_id'];
  419. $form['mch_id'] = 0;
  420. $form['operator_id'] = 0;
  421. $form['shipping_type'] = ShippingTypeEnum::LOGISTICS;
  422. $form['express_id'] = $express->id;
  423. $form['express_name'] = $express->name;
  424. $form['express_no'] = $express_no[0] ?? '';
  425. $form['memo'] = "{$res['Kuaidiname']}, {$res['KuaidiNo']}, {$res['Kuaidicode']}, {$res['third_code']}, {$res['ship_no_detail']}";
  426. $form['order_detail_ids'] = ShippingTypeEnum::LOGISTICS;
  427. $form['operator_name'] = '企商通供应链发货';
  428. $res = OrderExpress::delivery($form); // 发货
  429. if ($res === false) throw new \Exception(OrderExpress::getStaticError());
  430. $t->commit();
  431. } catch (\Exception $e) {
  432. if (!empty($t)) $t->rollBack();
  433. \Yii::error(FormatHelper::exception($e, __METHOD__));
  434. return false;
  435. }
  436. return true;
  437. }
  438. public function compensate(int $mall_id, $id)
  439. {
  440. $aOrder = QiShangTongOrder::findOne(['id' => $id, 'mall_id' => $mall_id]);
  441. if (empty($aOrder)) return '记录不存在';
  442. if ($aOrder->req_success == 100) return '当前状态无效补偿';
  443. try {
  444. $order = Order::getOne([['id' => $aOrder->order_id]],true, ['detail']);
  445. switch ($aOrder->last_req) {
  446. case 'create':
  447. return $this->create($order);
  448. case 'takeDelivery':
  449. return $this->takeDelivery($order);
  450. case 'pay':
  451. return $this->pay($order);
  452. case 'cancel':
  453. return $this->cancel($order);
  454. }
  455. } catch (\Exception $e) {
  456. return $e->getMessage();
  457. }
  458. return true;
  459. }
  460. /**
  461. * @param int $order_id
  462. * @return bool
  463. */
  464. public static function existOrder(int $order_id)
  465. {
  466. return !empty(QiShangTongOrder::findOne(['order_id' => $order_id]));
  467. }
  468. }