Checkout.php 32 KB

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