Checkout.php 30 KB

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