Checkout.php 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907
  1. <?php
  2. namespace app\api\service\order;
  3. use app\api\model\Order as OrderModel;
  4. use app\api\model\User as UserModel;
  5. use app\api\model\Goods as GoodsModel;
  6. use app\api\model\Setting as SettingModel;
  7. use app\api\model\store\Shop as ShopModel;
  8. use app\api\model\UserCoupon as UserCouponModel;
  9. use app\api\model\dealer\Order as DealerOrderModel;
  10. use app\api\service\User as UserService;
  11. use app\api\service\Payment as PaymentService;
  12. use app\api\service\coupon\GoodsDeduct as GoodsDeductService;
  13. use app\api\service\points\GoodsDeduct as PointsDeductService;
  14. use app\api\service\order\source\checkout\Factory as CheckoutFactory;
  15. use app\common\library\helper;
  16. use app\common\enum\OrderType as OrderTypeEnum;
  17. use app\common\enum\DeliveryType as DeliveryTypeEnum;
  18. use app\common\enum\order\PayType as PayTypeEnum;
  19. use app\common\service\goods\source\Factory as StockFactory;
  20. use app\common\enum\order\OrderSource as OrderSourceEnum;
  21. use app\common\service\delivery\Express as ExpressService;
  22. use app\common\exception\BaseException;
  23. /**
  24. * 订单结算台服务类
  25. * Class Checkout
  26. * @package app\api\service\order
  27. */
  28. class Checkout
  29. {
  30. /* $model OrderModel 订单模型 */
  31. public $model;
  32. // 当前小程序id
  33. private $wxapp_id;
  34. /* @var UserModel $user 当前用户信息 */
  35. private $user;
  36. // 订单结算商品列表
  37. private $goodsList = [];
  38. // 错误信息
  39. protected $error;
  40. /**
  41. * 订单结算api参数
  42. * @var array
  43. */
  44. private $param = [
  45. 'delivery' => null, // 配送方式
  46. 'shop_id' => 0, // 自提门店id
  47. 'linkman' => '', // 自提联系人
  48. 'phone' => '', // 自提联系电话
  49. 'coupon_id' => 0, // 优惠券id
  50. 'is_use_points' => 0, // 是否使用积分抵扣
  51. 'remark' => '', // 买家留言
  52. 'pay_type' => PayTypeEnum::WECHAT, // 支付方式
  53. ];
  54. /**
  55. * 订单结算的规则
  56. * @var array
  57. */
  58. private $checkoutRule = [
  59. 'is_user_grade' => true, // 会员等级折扣
  60. 'is_coupon' => true, // 优惠券抵扣
  61. 'is_use_points' => true, // 是否使用积分抵扣
  62. 'is_dealer' => true, // 是否开启分销
  63. ];
  64. /**
  65. * 订单来源
  66. * @var array
  67. */
  68. private $orderSource = [
  69. 'source' => OrderSourceEnum::MASTER,
  70. 'source_id' => 0,
  71. ];
  72. /**
  73. * 订单结算数据
  74. * @var array
  75. */
  76. private $orderData = [];
  77. /**
  78. * 构造函数
  79. * Checkout constructor.
  80. */
  81. public function __construct()
  82. {
  83. $this->model = new OrderModel;
  84. $this->wxapp_id = OrderModel::$wxapp_id;
  85. }
  86. /**
  87. * 设置结算台请求的参数
  88. * @param $param
  89. * @return array
  90. */
  91. public function setParam($param)
  92. {
  93. $this->param = array_merge($this->param, $param);
  94. return $this->getParam();
  95. }
  96. /**
  97. * 获取结算台请求的参数
  98. * @return array
  99. */
  100. public function getParam()
  101. {
  102. return $this->param;
  103. }
  104. /**
  105. * 订单结算的规则
  106. * @param $data
  107. * @return $this
  108. */
  109. public function setCheckoutRule($data)
  110. {
  111. $this->checkoutRule = array_merge($this->checkoutRule, $data);
  112. return $this;
  113. }
  114. /**
  115. * 设置订单来源(普通订单、砍价订单、秒杀订单)
  116. * @param $data
  117. * @return $this
  118. */
  119. public function setOrderSource($data)
  120. {
  121. $this->orderSource = array_merge($this->orderSource, $data);
  122. return $this;
  123. }
  124. /**
  125. * 订单确认-结算台
  126. * @param $user
  127. * @param $goodsList
  128. * @return array
  129. * @throws BaseException
  130. * @throws \think\Exception
  131. * @throws \think\db\exception\DataNotFoundException
  132. * @throws \think\db\exception\ModelNotFoundException
  133. * @throws \think\exception\DbException
  134. */
  135. public function onCheckout($user, $goodsList)
  136. {
  137. $this->user = $user;
  138. $this->goodsList = $goodsList;
  139. // 订单确认-立即购买
  140. return $this->checkout();
  141. }
  142. /**
  143. * 订单结算台
  144. * @return array
  145. * @throws BaseException
  146. * @throws \think\Exception
  147. * @throws \think\db\exception\DataNotFoundException
  148. * @throws \think\db\exception\ModelNotFoundException
  149. * @throws \think\exception\DbException
  150. */
  151. private function checkout()
  152. {
  153. // 整理订单数据
  154. $this->orderData = $this->getOrderData();
  155. // 验证商品状态, 是否允许购买
  156. $this->validateGoodsList();
  157. // 订单商品总数量
  158. $orderTotalNum = helper::getArrayColumnSum($this->goodsList, 'total_num');
  159. // 设置订单商品会员折扣价
  160. $this->setOrderGoodsGradeMoney();
  161. // 设置订单商品总金额(不含优惠折扣)
  162. $this->setOrderTotalPrice();
  163. // 当前用户可用的优惠券列表
  164. $couponList = $this->getUserCouponList($this->orderData['order_total_price']);
  165. // 计算优惠券抵扣
  166. $this->setOrderCouponMoney($couponList, $this->param['coupon_id']);
  167. // 计算可用积分抵扣
  168. $this->setOrderPoints();
  169. // 计算订单商品的实际付款金额
  170. $this->setOrderGoodsPayPrice();
  171. // 设置默认配送方式
  172. !$this->param['delivery'] && $this->param['delivery'] = current(SettingModel::getItem('store')['delivery_type']);
  173. // 处理配送方式
  174. if ($this->param['delivery'] == DeliveryTypeEnum::EXPRESS) {
  175. $this->setOrderExpress();
  176. } elseif ($this->param['delivery'] == DeliveryTypeEnum::EXTRACT) {
  177. $this->param['shop_id'] > 0 && $this->orderData['extract_shop'] = ShopModel::detail($this->param['shop_id']);
  178. }
  179. // 计算订单最终金额
  180. $this->setOrderPayPrice();
  181. // 计算订单积分赠送数量
  182. $this->setOrderPointsBonus();
  183. // 返回订单数据
  184. return array_merge([
  185. 'goods_list' => array_values($this->goodsList), // 商品信息
  186. 'order_total_num' => $orderTotalNum, // 商品总数量
  187. 'coupon_list' => array_values($couponList), // 优惠券列表
  188. 'has_error' => $this->hasError(),
  189. 'error_msg' => $this->getError(),
  190. ], $this->orderData);
  191. }
  192. /**
  193. * 计算订单可用积分抵扣
  194. * @return bool
  195. */
  196. private function setOrderPoints()
  197. {
  198. // 设置默认的商品积分抵扣信息
  199. $this->setDefaultGoodsPoints();
  200. // 积分设置
  201. $setting = SettingModel::getItem('points');
  202. // 条件:后台开启下单使用积分抵扣
  203. if (!$setting['is_shopping_discount'] || !$this->checkoutRule['is_use_points']) {
  204. return false;
  205. }
  206. // 条件:订单金额满足[?]元
  207. if (helper::bccomp($setting['discount']['full_order_price'], $this->orderData['order_total_price']) === 1) {
  208. return false;
  209. }
  210. // 计算订单商品最多可抵扣的积分数量
  211. $this->setOrderGoodsMaxPointsNum();
  212. // 订单最多可抵扣的积分总数量
  213. $maxPointsNumCount = helper::getArrayColumnSum($this->goodsList, 'max_points_num');
  214. // 实际可抵扣的积分数量
  215. $actualPointsNum = min($maxPointsNumCount, $this->user['points']);
  216. if ($actualPointsNum < 1) {
  217. return false;
  218. }
  219. // 计算订单商品实际抵扣的积分数量和金额
  220. $GoodsDeduct = new PointsDeductService($this->goodsList);
  221. $GoodsDeduct->setGoodsPoints($maxPointsNumCount, $actualPointsNum);
  222. // 积分抵扣总金额
  223. $orderPointsMoney = helper::getArrayColumnSum($this->goodsList, 'points_money');
  224. $this->orderData['points_money'] = helper::number2($orderPointsMoney);
  225. // 积分抵扣总数量
  226. $this->orderData['points_num'] = $actualPointsNum;
  227. // 允许积分抵扣
  228. $this->orderData['is_allow_points'] = true;
  229. return true;
  230. }
  231. /**
  232. * 计算订单商品最多可抵扣的积分数量
  233. * @return bool
  234. */
  235. private function setOrderGoodsMaxPointsNum()
  236. {
  237. // 积分设置
  238. $setting = SettingModel::getItem('points');
  239. foreach ($this->goodsList as &$goods) {
  240. // 商品不允许积分抵扣
  241. if (!$goods['is_points_discount']) continue;
  242. // 积分抵扣比例
  243. $deductionRatio = helper::bcdiv($setting['discount']['max_money_ratio'], 100);
  244. // 最多可抵扣的金额
  245. // !!!: 此处应该是优惠券打折后的价格
  246. // bug: $totalPayPrice = $goods['total_price'];
  247. $totalPayPrice = helper::bcsub($goods['total_price'], $goods['coupon_money']);
  248. $maxPointsMoney = helper::bcmul($totalPayPrice, $deductionRatio);
  249. // 最多可抵扣的积分数量
  250. $goods['max_points_num'] = helper::bcdiv($maxPointsMoney, $setting['discount']['discount_ratio'], 0);
  251. }
  252. return true;
  253. }
  254. /**
  255. * 设置默认的商品积分抵扣信息
  256. * @return bool
  257. */
  258. private function setDefaultGoodsPoints()
  259. {
  260. foreach ($this->goodsList as &$goods) {
  261. // 最多可抵扣的积分数量
  262. $goods['max_points_num'] = 0;
  263. // 实际抵扣的积分数量
  264. $goods['points_num'] = 0;
  265. // 实际抵扣的金额
  266. $goods['points_money'] = 0.00;
  267. }
  268. return true;
  269. }
  270. /**
  271. * 整理订单数据(结算台初始化)
  272. * @return array
  273. */
  274. private function getOrderData()
  275. {
  276. // 系统支持的配送方式 (后台设置)
  277. $deliveryType = SettingModel::getItem('store')['delivery_type'];
  278. return [
  279. // 配送类型
  280. 'delivery' => $this->param['delivery'] > 0 ? $this->param['delivery'] : $deliveryType[0],
  281. // 默认地址
  282. 'address' => $this->user['address_default'],
  283. // 是否存在收货地址
  284. 'exist_address' => $this->user['address_id'] > 0,
  285. // 配送费用
  286. 'express_price' => 0.00,
  287. // 当前用户收货城市是否存在配送规则中
  288. 'intra_region' => true,
  289. // 自提门店信息
  290. 'extract_shop' => [],
  291. // 是否允许使用积分抵扣
  292. 'is_allow_points' => false,
  293. // 是否使用积分抵扣
  294. 'is_use_points' => $this->param['is_use_points'],
  295. // 积分抵扣金额
  296. 'points_money' => 0.00,
  297. // 赠送的积分数量
  298. 'points_bonus' => 0,
  299. // 支付方式
  300. 'pay_type' => $this->param['pay_type'],
  301. // 系统设置
  302. 'setting' => $this->getSetting(),
  303. // 记忆的自提联系方式
  304. 'last_extract' => UserService::getLastExtract($this->user['user_id']),
  305. ];
  306. }
  307. /**
  308. * 获取订单页面中使用到的系统设置
  309. * @return array
  310. */
  311. private function getSetting()
  312. {
  313. // 系统支持的配送方式 (后台设置)
  314. $deliveryType = SettingModel::getItem('store')['delivery_type'];
  315. // 积分设置
  316. $pointsSetting = SettingModel::getItem('points');
  317. // 订阅消息
  318. $orderSubMsgList = [];
  319. foreach (SettingModel::getItem('submsg')['order'] as $item) {
  320. !empty($item['template_id']) && $orderSubMsgList[] = $item['template_id'];
  321. }
  322. return [
  323. 'delivery' => $deliveryType, // 支持的配送方式
  324. 'points_name' => $pointsSetting['points_name'], // 积分名称
  325. 'points_describe' => $pointsSetting['describe'], // 积分说明
  326. 'order_submsg' => $orderSubMsgList, // 订阅消息
  327. ];
  328. }
  329. /**
  330. * 当前用户可用的优惠券列表
  331. * @param $orderTotalPrice
  332. * @return mixed
  333. * @throws \think\db\exception\DataNotFoundException
  334. * @throws \think\db\exception\ModelNotFoundException
  335. * @throws \think\exception\DbException
  336. */
  337. private function getUserCouponList($orderTotalPrice)
  338. {
  339. // 是否开启优惠券折扣
  340. if (!$this->checkoutRule['is_coupon']) {
  341. return [];
  342. }
  343. // 整理当前订单所有商品ID集
  344. $orderGoodsIds = helper::getArrayColumn($this->goodsList, 'goods_id');
  345. // 当前用户可用的优惠券列表
  346. $couponList = UserCouponModel::getUserCouponList($this->user['user_id'], $orderTotalPrice);
  347. // 判断当前优惠券是否满足订单使用条件 ( 优惠券适用范围 )
  348. $couponList = UserCouponModel::couponListApplyRange($couponList, $orderGoodsIds);
  349. return $couponList;
  350. }
  351. /**
  352. * 验证订单商品的状态
  353. * @return bool
  354. */
  355. private function validateGoodsList()
  356. {
  357. $Checkout = CheckoutFactory::getFactory(
  358. $this->user,
  359. $this->goodsList,
  360. $this->orderSource['source']
  361. );
  362. $status = $Checkout->validateGoodsList();
  363. $status == false && $this->setError($Checkout->getError());
  364. return $status;
  365. }
  366. /**
  367. * 设置订单的商品总金额(不含优惠折扣)
  368. */
  369. private function setOrderTotalPrice()
  370. {
  371. // 订单商品的总金额(不含优惠券折扣)
  372. $this->orderData['order_total_price'] = helper::number2(helper::getArrayColumnSum($this->goodsList, 'total_price'));
  373. }
  374. /**
  375. * 设置订单的实际支付金额(含配送费)
  376. */
  377. private function setOrderPayPrice()
  378. {
  379. // 订单金额(含优惠折扣)
  380. $this->orderData['order_price'] = helper::number2(helper::getArrayColumnSum($this->goodsList, 'total_pay_price'));
  381. // 订单实付款金额(订单金额 + 运费)
  382. $this->orderData['order_pay_price'] = helper::number2(helper::bcadd($this->orderData['order_price'], $this->orderData['express_price']));
  383. }
  384. /**
  385. * 计算订单积分赠送数量
  386. * @return bool
  387. */
  388. private function setOrderPointsBonus()
  389. {
  390. // 初始化商品积分赠送数量
  391. foreach ($this->goodsList as &$goods) {
  392. $goods['points_bonus'] = 0;
  393. }
  394. // 积分设置
  395. $setting = SettingModel::getItem('points');
  396. // 条件:后台开启开启购物送积分
  397. if (!$setting['is_shopping_gift']) {
  398. return false;
  399. }
  400. // 设置商品积分赠送数量
  401. foreach ($this->goodsList as &$goods) {
  402. // 积分赠送比例
  403. $ratio = $setting['gift_ratio'] / 100;
  404. // 计算抵扣积分数量
  405. $goods['points_bonus'] = !$goods['is_points_gift'] ? 0 : helper::bcmul($goods['total_pay_price'], $ratio, 0);
  406. }
  407. // 订单积分赠送数量
  408. $this->orderData['points_bonus'] = helper::getArrayColumnSum($this->goodsList, 'points_bonus');
  409. return true;
  410. }
  411. /**
  412. * 计算订单商品的实际付款金额
  413. * @return bool
  414. */
  415. private function setOrderGoodsPayPrice()
  416. {
  417. // 商品总价 - 优惠抵扣
  418. foreach ($this->goodsList as &$goods) {
  419. // 减去优惠券抵扣金额
  420. $value = helper::bcsub($goods['total_price'], $goods['coupon_money']);
  421. // 减去积分抵扣金额
  422. if ($this->orderData['is_allow_points'] && $this->orderData['is_use_points']) {
  423. $value = helper::bcsub($value, $goods['points_money']);
  424. }
  425. $goods['total_pay_price'] = helper::number2($value);
  426. }
  427. return true;
  428. }
  429. /**
  430. * 设置订单商品会员折扣价
  431. * @return bool
  432. */
  433. private function setOrderGoodsGradeMoney()
  434. {
  435. // 设置默认数据
  436. helper::setDataAttribute($this->goodsList, [
  437. // 标记参与会员折扣
  438. 'is_user_grade' => false,
  439. // 会员等级抵扣的金额
  440. 'grade_ratio' => 0,
  441. // 会员折扣的商品单价
  442. 'grade_goods_price' => 0.00,
  443. // 会员折扣的总额差
  444. 'grade_total_money' => 0.00,
  445. ], true);
  446. // 是否开启会员等级折扣
  447. if (!$this->checkoutRule['is_user_grade']) {
  448. return false;
  449. }
  450. // 会员等级状态
  451. if (!(
  452. $this->user['grade_id'] > 0 && !empty($this->user['grade'])
  453. && !$this->user['grade']['is_delete'] && $this->user['grade']['status']
  454. )) {
  455. return false;
  456. }
  457. // 计算抵扣金额
  458. foreach ($this->goodsList as &$goods) {
  459. // 判断商品是否参与会员折扣
  460. if (!$goods['is_enable_grade']) {
  461. continue;
  462. }
  463. // 商品单独设置了会员折扣
  464. if ($goods['is_alone_grade'] && isset($goods['alone_grade_equity'][$this->user['grade_id']])) {
  465. // 折扣比例
  466. $discountRatio = helper::bcdiv($goods['alone_grade_equity'][$this->user['grade_id']], 10);
  467. } else {
  468. // 折扣比例
  469. $discountRatio = helper::bcdiv($this->user['grade']['equity']['discount'], 10);
  470. }
  471. if ($discountRatio > 0) {
  472. // 会员折扣后的商品总金额
  473. $gradeTotalPrice = max(0.01, helper::bcmul($goods['total_price'], $discountRatio));
  474. helper::setDataAttribute($goods, [
  475. 'is_user_grade' => true,
  476. 'grade_ratio' => $discountRatio,
  477. 'grade_goods_price' => helper::number2(helper::bcmul($goods['goods_price'], $discountRatio), true),
  478. 'grade_total_money' => helper::number2(helper::bcsub($goods['total_price'], $gradeTotalPrice)),
  479. 'total_price' => $gradeTotalPrice,
  480. ], false);
  481. }
  482. }
  483. return true;
  484. }
  485. /**
  486. * 设置订单优惠券抵扣信息
  487. * @param array $couponList 当前用户可用的优惠券列表
  488. * @param int $couponId 当前选择的优惠券id
  489. * @return bool
  490. * @throws BaseException
  491. */
  492. private function setOrderCouponMoney($couponList, $couponId)
  493. {
  494. // 设置默认数据:订单信息
  495. helper::setDataAttribute($this->orderData, [
  496. 'coupon_id' => 0, // 用户优惠券id
  497. 'coupon_money' => 0, // 优惠券抵扣金额
  498. ], false);
  499. // 设置默认数据:订单商品列表
  500. helper::setDataAttribute($this->goodsList, [
  501. 'coupon_money' => 0, // 优惠券抵扣金额
  502. ], true);
  503. // 验证选择的优惠券ID是否合法
  504. if (!$this->verifyOrderCouponId($couponId, $couponList)) {
  505. return false;
  506. }
  507. // 获取优惠券信息
  508. $couponInfo = $this->getCouponInfo($couponId, $couponList);
  509. // 计算订单商品优惠券抵扣金额
  510. $goodsListTemp = helper::getArrayColumns($this->goodsList, ['total_price']);
  511. $CouponMoney = new GoodsDeductService;
  512. $completed = $CouponMoney->setGoodsCouponMoney($goodsListTemp, $couponInfo['reduced_price']);
  513. // 分配订单商品优惠券抵扣金额
  514. foreach ($this->goodsList as $key => &$goods) {
  515. $goods['coupon_money'] = helper::bcdiv($completed[$key]['coupon_money'], 100);
  516. }
  517. // 记录订单优惠券信息
  518. $this->orderData['coupon_id'] = $couponId;
  519. $this->orderData['coupon_money'] = helper::bcdiv($CouponMoney->getActualReducedMoney(), 100);
  520. return true;
  521. }
  522. /**
  523. * 验证用户选择的优惠券ID是否合法
  524. * @param $couponId
  525. * @param $couponList
  526. * @return bool
  527. * @throws BaseException
  528. */
  529. private function verifyOrderCouponId($couponId, $couponList)
  530. {
  531. // 是否开启优惠券折扣
  532. if (!$this->checkoutRule['is_coupon']) {
  533. return false;
  534. }
  535. // 如果没有可用的优惠券,直接返回
  536. if ($couponId <= 0 || empty($couponList)) {
  537. return false;
  538. }
  539. // 判断优惠券是否存在
  540. $couponInfo = $this->getCouponInfo($couponId, $couponList);
  541. if (!$couponInfo) {
  542. throw new BaseException(['msg' => '未找到优惠券信息']);
  543. }
  544. // 判断优惠券适用范围是否合法
  545. if (!$couponInfo['is_apply']) {
  546. throw new BaseException(['msg' => $couponInfo['not_apply_info']]);
  547. }
  548. return true;
  549. }
  550. private function getCouponInfo($couponId, $couponList)
  551. {
  552. return helper::getArrayItemByColumn($couponList, 'user_coupon_id', $couponId);
  553. }
  554. /**
  555. * 订单配送-快递配送
  556. * @return bool
  557. * @throws \think\db\exception\DataNotFoundException
  558. * @throws \think\db\exception\ModelNotFoundException
  559. * @throws \think\exception\DbException
  560. */
  561. private function setOrderExpress()
  562. {
  563. // 设置默认数据:配送费用
  564. helper::setDataAttribute($this->goodsList, [
  565. 'express_price' => 0,
  566. ], true);
  567. // 当前用户收货城市id
  568. $cityId = $this->user['address_default'] ? $this->user['address_default']['city_id'] : null;
  569. // 初始化配送服务类
  570. $ExpressService = new ExpressService($cityId, $this->goodsList, OrderTypeEnum::MASTER);
  571. // 验证商品是否在配送范围
  572. $isIntraRegion = $ExpressService->isIntraRegion();
  573. if ($cityId > 0 && $isIntraRegion == false) {
  574. $notInRuleGoodsName = $ExpressService->getNotInRuleGoodsName();
  575. $this->setError("很抱歉,您的收货地址不在商品 [{$notInRuleGoodsName}] 的配送范围内");
  576. }
  577. // 订单总运费金额
  578. $this->orderData['intra_region'] = $isIntraRegion;
  579. $this->orderData['express_price'] = $ExpressService->getDeliveryFee();
  580. return true;
  581. }
  582. /**
  583. * 创建新订单
  584. * @param array $order 订单信息
  585. * @return bool
  586. * @throws \Exception
  587. */
  588. public function createOrder($order)
  589. {
  590. // 表单验证
  591. if (!$this->validateOrderForm($order, $this->param['linkman'], $this->param['phone'])) {
  592. return false;
  593. }
  594. // 创建新的订单
  595. $status = $this->model->transaction(function () use ($order) {
  596. // 创建订单事件
  597. return $this->createOrderEvent($order);
  598. });
  599. // 余额支付标记订单已支付
  600. if ($status && $order['pay_type'] == PayTypeEnum::BALANCE) {
  601. return $this->model->onPaymentByBalance($this->model['order_no']);
  602. }
  603. return $status;
  604. }
  605. /**
  606. * 创建订单事件
  607. * @param $order
  608. * @return bool
  609. * @throws BaseException
  610. * @throws \think\exception\DbException
  611. * @throws \Exception
  612. */
  613. private function createOrderEvent($order)
  614. {
  615. // 新增订单记录
  616. $status = $this->add($order, $this->param['remark']);
  617. if ($order['delivery'] == DeliveryTypeEnum::EXPRESS) {
  618. // 记录收货地址
  619. $this->saveOrderAddress($order['address']);
  620. } elseif ($order['delivery'] == DeliveryTypeEnum::EXTRACT) {
  621. // 记录自提信息
  622. $this->saveOrderExtract($this->param['linkman'], $this->param['phone']);
  623. }
  624. // 保存订单商品信息
  625. $this->saveOrderGoods($order);
  626. // 更新商品库存 (针对下单减库存的商品)
  627. $this->updateGoodsStockNum($order);
  628. // 设置优惠券使用状态
  629. $order['coupon_id'] > 0 && UserCouponModel::setIsUse($order['coupon_id']);
  630. // 积分抵扣情况下扣除用户积分
  631. if ($order['is_allow_points'] && $order['is_use_points'] && $order['points_num'] > 0) {
  632. $describe = "用户消费:{$this->model['order_no']}";
  633. $this->user->setIncPoints(-$order['points_num'], $describe);
  634. }
  635. // 获取订单详情
  636. $detail = OrderModel::getUserOrderDetail($this->model['order_id'], $this->user['user_id']);
  637. // 记录分销商订单
  638. $this->checkoutRule['is_dealer'] && DealerOrderModel::createOrder($detail);
  639. return $status;
  640. }
  641. /**
  642. * 构建支付请求的参数
  643. * @return array
  644. * @throws BaseException
  645. * @throws \think\exception\DbException
  646. */
  647. public function onOrderPayment()
  648. {
  649. return PaymentService::orderPayment($this->user, $this->model, $this->param['pay_type']);
  650. }
  651. /**
  652. * 表单验证 (订单提交)
  653. * @param array $order 订单信息
  654. * @param string $linkman 联系人
  655. * @param string $phone 联系电话
  656. * @return bool
  657. */
  658. private function validateOrderForm(&$order, $linkman, $phone)
  659. {
  660. if ($order['delivery'] == DeliveryTypeEnum::EXPRESS) {
  661. if (empty($order['address'])) {
  662. $this->error = '您还没有选择配送地址';
  663. return false;
  664. }
  665. }
  666. if ($order['delivery'] == DeliveryTypeEnum::EXTRACT) {
  667. if (empty($order['extract_shop'])) {
  668. $this->error = '您还没有选择自提门店';
  669. return false;
  670. }
  671. if (empty($linkman) || empty($phone)) {
  672. $this->error = '您还没有填写联系人和电话';
  673. return false;
  674. }
  675. }
  676. // 余额支付时判断用户余额是否足够
  677. if ($order['pay_type'] == PayTypeEnum::BALANCE) {
  678. if ($this->user['balance'] < $order['order_pay_price']) {
  679. $this->error = '您的余额不足,无法使用余额支付';
  680. return false;
  681. }
  682. }
  683. return true;
  684. }
  685. /**
  686. * 当前订单是否存在和使用积分抵扣
  687. * @param $order
  688. * @return bool
  689. */
  690. private function isExistPointsDeduction($order)
  691. {
  692. return $order['is_allow_points'] && $order['is_use_points'];
  693. }
  694. /**
  695. * 新增订单记录
  696. * @param $order
  697. * @param string $remark
  698. * @return false|int
  699. */
  700. private function add($order, $remark = '')
  701. {
  702. // 当前订单是否存在和使用积分抵扣
  703. $isExistPointsDeduction = $this->isExistPointsDeduction($order);
  704. // 订单数据
  705. $data = [
  706. 'user_id' => $this->user['user_id'],
  707. 'order_no' => $this->model->orderNo(),
  708. 'total_price' => $order['order_total_price'],
  709. 'order_price' => $order['order_price'],
  710. 'coupon_id' => $order['coupon_id'],
  711. 'coupon_money' => $order['coupon_money'],
  712. 'points_money' => $isExistPointsDeduction ? $order['points_money'] : 0.00,
  713. 'points_num' => $isExistPointsDeduction ? $order['points_num'] : 0,
  714. 'pay_price' => $order['order_pay_price'],
  715. 'delivery_type' => $order['delivery'],
  716. 'pay_type' => $order['pay_type'],
  717. 'buyer_remark' => trim($remark),
  718. 'order_source' => $this->orderSource['source'],
  719. 'order_source_id' => $this->orderSource['source_id'],
  720. 'points_bonus' => $order['points_bonus'],
  721. 'wxapp_id' => $this->wxapp_id,
  722. ];
  723. if ($order['delivery'] == DeliveryTypeEnum::EXPRESS) {
  724. $data['express_price'] = $order['express_price'];
  725. } elseif ($order['delivery'] == DeliveryTypeEnum::EXTRACT) {
  726. $data['extract_shop_id'] = $order['extract_shop']['shop_id'];
  727. }
  728. // 保存订单记录
  729. return $this->model->save($data);
  730. }
  731. /**
  732. * 保存订单商品信息
  733. * @param $order
  734. * @return int
  735. */
  736. private function saveOrderGoods($order)
  737. {
  738. // 当前订单是否存在和使用积分抵扣
  739. $isExistPointsDeduction = $this->isExistPointsDeduction($order);
  740. // 订单商品列表
  741. $goodsList = [];
  742. foreach ($order['goods_list'] as $goods) {
  743. /* @var GoodsModel $goods */
  744. $item = [
  745. 'user_id' => $this->user['user_id'],
  746. 'wxapp_id' => $this->wxapp_id,
  747. 'goods_id' => $goods['goods_id'],
  748. 'goods_name' => $goods['goods_name'],
  749. 'image_id' => $goods['image'][0]['image_id'],
  750. 'deduct_stock_type' => $goods['deduct_stock_type'],
  751. 'spec_type' => $goods['spec_type'],
  752. 'spec_sku_id' => $goods['goods_sku']['spec_sku_id'],
  753. 'goods_sku_id' => $goods['goods_sku']['goods_sku_id'],
  754. 'goods_attr' => $goods['goods_sku']['goods_attr'],
  755. 'content' => $goods['content'],
  756. 'goods_no' => $goods['goods_sku']['goods_no'],
  757. 'goods_price' => $goods['goods_sku']['goods_price'],
  758. 'line_price' => $goods['goods_sku']['line_price'],
  759. 'goods_weight' => $goods['goods_sku']['goods_weight'],
  760. 'is_user_grade' => (int)$goods['is_user_grade'],
  761. 'grade_ratio' => $goods['grade_ratio'],
  762. 'grade_goods_price' => $goods['grade_goods_price'],
  763. 'grade_total_money' => $goods['grade_total_money'],
  764. 'coupon_money' => $goods['coupon_money'],
  765. 'points_money' => $isExistPointsDeduction ? $goods['points_money'] : 0.00,
  766. 'points_num' => $isExistPointsDeduction ? $goods['points_num'] : 0,
  767. 'points_bonus' => $goods['points_bonus'],
  768. 'total_num' => $goods['total_num'],
  769. 'total_price' => $goods['total_price'],
  770. 'total_pay_price' => $goods['total_pay_price'],
  771. 'is_ind_dealer' => $goods['is_ind_dealer'],
  772. 'dealer_money_type' => $goods['dealer_money_type'],
  773. 'first_money' => $goods['first_money'],
  774. 'second_money' => $goods['second_money'],
  775. 'third_money' => $goods['third_money'],
  776. ];
  777. // 记录订单商品来源id
  778. $item['goods_source_id'] = isset($goods['goods_source_id']) ? $goods['goods_source_id'] : 0;
  779. $goodsList[] = $item;
  780. }
  781. return $this->model->goods()->saveAll($goodsList);
  782. }
  783. /**
  784. * 更新商品库存 (针对下单减库存的商品)
  785. * @param $order
  786. * @return mixed
  787. */
  788. private function updateGoodsStockNum($order)
  789. {
  790. return StockFactory::getFactory($this->model['order_source'])->updateGoodsStock($order['goods_list']);
  791. }
  792. /**
  793. * 记录收货地址
  794. * @param $address
  795. * @return false|\think\Model
  796. */
  797. private function saveOrderAddress($address)
  798. {
  799. if ($address['region_id'] == 0 && !empty($address['district'])) {
  800. $address['detail'] = $address['district'] . ' ' . $address['detail'];
  801. }
  802. return $this->model->address()->save([
  803. 'user_id' => $this->user['user_id'],
  804. 'wxapp_id' => $this->wxapp_id,
  805. 'name' => $address['name'],
  806. 'phone' => $address['phone'],
  807. 'province_id' => $address['province_id'],
  808. 'city_id' => $address['city_id'],
  809. 'region_id' => $address['region_id'],
  810. 'detail' => $address['detail'],
  811. ]);
  812. }
  813. /**
  814. * 保存上门自提联系人
  815. * @param $linkman
  816. * @param $phone
  817. * @return false|\think\Model
  818. */
  819. private function saveOrderExtract($linkman, $phone)
  820. {
  821. // 记忆上门自提联系人(缓存),用于下次自动填写
  822. UserService::setLastExtract($this->model['user_id'], trim($linkman), trim($phone));
  823. // 保存上门自提联系人(数据库)
  824. return $this->model->extract()->save([
  825. 'linkman' => trim($linkman),
  826. 'phone' => trim($phone),
  827. 'user_id' => $this->model['user_id'],
  828. 'wxapp_id' => $this->wxapp_id,
  829. ]);
  830. }
  831. /**
  832. * 设置错误信息
  833. * @param $error
  834. */
  835. protected function setError($error)
  836. {
  837. empty($this->error) && $this->error = $error;
  838. }
  839. /**
  840. * 获取错误信息
  841. * @return mixed
  842. */
  843. public function getError()
  844. {
  845. return $this->error ?: '';
  846. }
  847. /**
  848. * 是否存在错误
  849. * @return bool
  850. */
  851. public function hasError()
  852. {
  853. return !empty($this->error);
  854. }
  855. }