Checkout.php 32 KB

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