Checkout.php 31 KB

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