Checkout.php 32 KB

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