Checkout.php 33 KB

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