StoreOrderComputedServices.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2016~2020 https://www.crmeb.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
  8. // +----------------------------------------------------------------------
  9. // | Author: CRMEB Team <admin@crmeb.com>
  10. // +----------------------------------------------------------------------
  11. namespace app\services\order;
  12. use app\services\BaseServices;
  13. use app\dao\order\StoreOrderDao;
  14. use app\services\pay\PayServices;
  15. use app\services\product\product\StoreCategoryServices;
  16. use app\services\user\MemberCardServices;
  17. use app\services\user\UserServices;
  18. use think\exception\ValidateException;
  19. use app\services\user\UserAddressServices;
  20. use app\services\coupon\StoreCouponUserServices;
  21. use app\services\shipping\ShippingTemplatesFreeServices;
  22. use app\services\shipping\ShippingTemplatesRegionServices;
  23. use app\services\shipping\ShippingTemplatesServices;
  24. /**
  25. * 订单计算金额
  26. * Class StoreOrderComputedServices
  27. * @package app\services\order
  28. */
  29. class StoreOrderComputedServices extends BaseServices
  30. {
  31. /**
  32. * 支付类型
  33. * @var string[]
  34. */
  35. public $payType = ['weixin' => '微信支付', 'yue' => '余额支付', 'offline' => '线下支付', 'pc' => 'pc'];
  36. /**
  37. * 额外参数
  38. * @var array
  39. */
  40. protected $paramData = [];
  41. /**
  42. * StoreOrderComputedServices constructor.
  43. * @param StoreOrderDao $dao
  44. */
  45. public function __construct(StoreOrderDao $dao)
  46. {
  47. $this->dao = $dao;
  48. }
  49. /**
  50. * 设置额外参数
  51. * @param array $paramData
  52. * @return $this
  53. */
  54. public function setParamData(array $paramData)
  55. {
  56. $this->paramData = $paramData;
  57. return $this;
  58. }
  59. /**
  60. * 计算订单金额
  61. * @param int $uid
  62. * @param string $key
  63. * @param array $cartGroup
  64. * @param int $addressId
  65. * @param string $payType
  66. * @param bool $useIntegral
  67. * @param int $couponId
  68. * @param bool $is_create
  69. * @param int $shipping_type
  70. * @return array
  71. */
  72. public function computedOrder(int $uid, array $userInfo = [], array $cartGroup, int $addressId, string $payType, bool $useIntegral = false, int $couponId = 0, bool $isCreate = false, int $shippingType = 1)
  73. {
  74. $offlinePayStatus = (int)sys_config('offline_pay_status') ?? (int)2;
  75. $systemPayType = PayServices::PAY_TYPE;
  76. if ($offlinePayStatus == 2) unset($systemPayType['offline']);
  77. if (strtolower($payType) != 'pc') {
  78. if (!array_key_exists($payType, $systemPayType)) {
  79. throw new ValidateException('选择支付方式有误');
  80. }
  81. }
  82. if (!$userInfo) {
  83. /** @var UserServices $userServices */
  84. $userServices = app()->make(UserServices::class);
  85. $userInfo = $userServices->getUserInfo($uid);
  86. if (!$userInfo) {
  87. throw new ValidateException('用户不存在!');
  88. }
  89. }
  90. $cartInfo = $cartGroup['cartInfo'];
  91. $priceGroup = $cartGroup['priceGroup'];
  92. $other = $cartGroup['other'];
  93. $payPrice = (float)$priceGroup['totalPrice'];
  94. $addr = $cartGroup['addr'] ?? [];
  95. $postage = $priceGroup;
  96. if (!$addr || $addr['id'] != $addressId) {
  97. /** @var UserAddressServices $addressServices */
  98. $addressServices = app()->make(UserAddressServices::class);
  99. $addr = $addressServices->getAddress($addressId) ?? [];
  100. if ($addr) {
  101. $addr = $addr->toArray();
  102. }
  103. //改变地址重新计算邮费
  104. $postage = [];
  105. }
  106. $combinationId = $this->paramData['combinationId'] ?? 0;
  107. $seckillId = $this->paramData['seckill_id'] ?? 0;
  108. $bargainId = $this->paramData['bargainId'] ?? 0;
  109. $isActivity = $combinationId || $seckillId || $bargainId;
  110. if (!$isActivity) {
  111. //使用优惠劵
  112. [$payPrice, $couponPrice] = $this->useCouponId($couponId, $uid, $cartInfo, $payPrice, $isCreate);
  113. //使用积分
  114. [$payPrice, $deductionPrice, $usedIntegral, $SurplusIntegral] = $this->useIntegral($useIntegral, $userInfo, $payPrice, $other);
  115. }
  116. //计算邮费
  117. [$payPrice, $payPostage, $storePostageDiscount, $storeFreePostage, $isStoreFreePostage] = $this->computedPayPostage($shippingType, $payType, $cartInfo, $addr, $payPrice, $postage, $other, $userInfo);
  118. $result = [
  119. 'total_price' => $priceGroup['totalPrice'],
  120. 'pay_price' => $payPrice > 0 ? $payPrice : 0,
  121. 'pay_postage' => $payPostage,
  122. 'coupon_price' => $couponPrice ?? 0,
  123. 'deduction_price' => $deductionPrice ?? 0,
  124. 'usedIntegral' => $usedIntegral ?? 0,
  125. 'SurplusIntegral' => $SurplusIntegral ?? 0,
  126. 'storePostageDiscount' => $storePostageDiscount ?? 0,
  127. 'isStoreFreePostage' => $isStoreFreePostage ?? false,
  128. 'storeFreePostage' => $storeFreePostage ?? 0
  129. ];
  130. $this->paramData = [];
  131. return $result;
  132. }
  133. /**
  134. * 使用优惠卷
  135. * @param int $couponId
  136. * @param int $uid
  137. * @param $cartInfo
  138. * @param $payPrice
  139. * @param bool $is_create
  140. */
  141. public function useCouponId(int $couponId, int $uid, $cartInfo, $payPrice, bool $isCreate)
  142. {
  143. //使用优惠劵
  144. $res1 = true;
  145. if ($couponId) {
  146. /** @var StoreCouponUserServices $couponServices */
  147. $couponServices = app()->make(StoreCouponUserServices::class);
  148. $couponInfo = $couponServices->getOne([['id', '=', $couponId], ['uid', '=', $uid], ['is_fail', '=', 0], ['status', '=', 0], ['start_time', '<', time()], ['end_time', '>', time()]], '*', ['issue']);
  149. if (!$couponInfo) {
  150. throw new ValidateException('选择的优惠劵无效!');
  151. }
  152. $type = $couponInfo['applicable_type'] ?? 0;
  153. $flag = false;
  154. $price = 0;
  155. $count = 0;
  156. switch ($type) {
  157. case 0:
  158. case 3:
  159. foreach ($cartInfo as $cart) {
  160. $price = bcadd($price, bcmul((string)$cart['truePrice'], (string)$cart['cart_num'], 2), 2);
  161. $count++;
  162. }
  163. break;
  164. case 1://品类券
  165. /** @var StoreCategoryServices $storeCategoryServices */
  166. $storeCategoryServices = app()->make(StoreCategoryServices::class);
  167. $cateGorys = $storeCategoryServices->getAllById((int)$couponInfo['category_id']);
  168. if ($cateGorys) {
  169. $cateIds = array_column($cateGorys, 'id');
  170. foreach ($cartInfo as $cart) {
  171. if (isset($cart['productInfo']['cate_id']) && array_intersect(explode(',', $cart['productInfo']['cate_id']), $cateIds)) {
  172. $price = bcadd($price, bcmul((string)$cart['truePrice'], (string)$cart['cart_num'], 2), 2);
  173. $count++;
  174. }
  175. }
  176. }
  177. break;
  178. case 2:
  179. foreach ($cartInfo as $cart) {
  180. if (isset($cart['product_id']) && in_array($cart['product_id'], explode(',', $couponInfo['product_id']))) {
  181. $price = bcadd($price, bcmul((string)$cart['truePrice'], (string)$cart['cart_num'], 2), 2);
  182. $count++;
  183. }
  184. }
  185. break;
  186. }
  187. if ($count && $couponInfo['use_min_price'] <= $price) {
  188. $flag = true;
  189. }
  190. if (!$flag) {
  191. throw new ValidateException('不满足优惠劵的使用条件!');
  192. }
  193. $payPrice = (float)bcsub((string)$payPrice, (string)$couponInfo['coupon_price'], 2);
  194. if ($isCreate) {
  195. $res1 = $couponServices->useCoupon($couponId);
  196. }
  197. $couponPrice = $couponInfo['coupon_price'];
  198. } else {
  199. $couponPrice = 0;
  200. }
  201. if (!$res1) {
  202. throw new ValidateException('使用优惠劵失败!');
  203. }
  204. return [$payPrice, $couponPrice];
  205. }
  206. /**
  207. * 使用积分
  208. * @param $useIntegral
  209. * @param $userInfo
  210. * @param $payPrice
  211. * @param $other
  212. * @return array
  213. */
  214. public function useIntegral(bool $useIntegral, $userInfo, string $payPrice, array $other)
  215. {
  216. $SurplusIntegral = 0;
  217. if ($useIntegral && $userInfo['integral'] > 0) {
  218. //积分抵扣上限
  219. $integralMaxNum = sys_config('integral_max_num', 200);
  220. if ($integralMaxNum > 0 && $userInfo['integral'] > $integralMaxNum) {
  221. $integral = $integralMaxNum;
  222. } else {
  223. $integral = $userInfo['integral'];
  224. }
  225. $deductionPrice = (float)bcmul((string)$integral, (string)$other['integralRatio'], 2);
  226. if ($deductionPrice < $payPrice) {
  227. $payPrice = bcsub((string)$payPrice, (string)$deductionPrice, 2);
  228. $usedIntegral = $integral;
  229. } else {
  230. $deductionPrice = $payPrice;
  231. $usedIntegral = (int)ceil(bcdiv((string)$payPrice, (string)$other['integralRatio'], 2));
  232. $payPrice = 0;
  233. }
  234. $deductionPrice = $deductionPrice > 0 ? $deductionPrice : 0;
  235. $usedIntegral = $usedIntegral > 0 ? $usedIntegral : 0;
  236. $SurplusIntegral = (int)bcsub((string)$userInfo['integral'], $usedIntegral, 0);
  237. } else {
  238. $deductionPrice = 0;
  239. $usedIntegral = 0;
  240. }
  241. if ($payPrice <= 0) $payPrice = 0;
  242. return [$payPrice, $deductionPrice, $usedIntegral, $SurplusIntegral];
  243. }
  244. /**
  245. * 计算邮费
  246. * @param int $shipping_type
  247. * @param string $payType
  248. * @param array $cartInfo
  249. * @param array $addr
  250. * @param string $payPrice
  251. * @param array $other
  252. * @return array
  253. */
  254. public function computedPayPostage(int $shipping_type, string $payType, array $cartInfo, array $addr, string $payPrice, array $postage = [], array $other, $userInfo = [])
  255. {
  256. $storePostageDiscount = 0;
  257. $storeFreePostage = $postage['storeFreePostage'] ?? 0;
  258. $isStoreFreePostage = false;
  259. if (!$storeFreePostage) {
  260. $storeFreePostage = floatval(sys_config('store_free_postage')) ?: 0;//满额包邮金额
  261. }
  262. if (!$addr && !isset($addr['id']) || !$cartInfo) {
  263. $payPostage = 0;
  264. } else {
  265. //$shipping_type = 1 快递发货 $shipping_type = 2 门店自提
  266. if ($shipping_type == 2) {
  267. $store_self_mention = sys_config('store_self_mention') ?? 0;
  268. if (!$store_self_mention) $shipping_type = 1;
  269. }
  270. //门店自提 || (线下支付 && 线下支付包邮) 没有邮费支付
  271. if ($shipping_type === 2 || ($payType == 'offline' && ((isset($other['offlinePostage']) && $other['offlinePostage']) || sys_config('offline_postage')) == 1)) {
  272. $payPostage = 0;
  273. } else {
  274. if (!$postage || !isset($postage['storePostage']) || !isset($postage['storePostageDiscount'])) {
  275. $postage = $this->getOrderPriceGroup($storeFreePostage, $cartInfo, $addr, $userInfo);
  276. }
  277. $payPostage = $postage['storePostage'];
  278. $storePostageDiscount = $postage['storePostageDiscount'];
  279. $isStoreFreePostage = $postage['isStoreFreePostage'] ?? false;
  280. $payPrice = (float)bcadd((string)$payPrice, (string)$payPostage, 2);
  281. }
  282. }
  283. return [$payPrice, $payPostage, $storePostageDiscount, $storeFreePostage, $isStoreFreePostage];
  284. }
  285. /**
  286. * 运费计算,总金额计算
  287. * @param $cartInfo
  288. * @param $addr
  289. * @param array $userInfo
  290. * @return array
  291. */
  292. public function getOrderPriceGroup($storeFreePostage, $cartInfo, $addr, $userInfo = [])
  293. {
  294. $sumPrice = $totalPrice = $costPrice = $vipPrice = 0;
  295. $storePostage = 0;
  296. $storePostageDiscount = 0;
  297. $isStoreFreePostage = false;//是否满额包邮
  298. $sumPrice = $this->getOrderSumPrice($cartInfo, 'sum_price');//获取订单原总金额
  299. $totalPrice = $this->getOrderSumPrice($cartInfo, 'truePrice');//获取订单svip、用户等级优惠之后总金额
  300. $costPrice = $this->getOrderSumPrice($cartInfo, 'costPrice');//获取订单成本价
  301. $vipPrice = $this->getOrderSumPrice($cartInfo, 'vip_truePrice');//获取订单会员优惠金额
  302. if (isset($cartInfo[0]['productInfo']['is_virtual']) && $cartInfo[0]['productInfo']['is_virtual'] == 1) {
  303. $storePostage = 0;
  304. } elseif ($storeFreePostage && $cartInfo && $addr) {
  305. if ($sumPrice >= $storeFreePostage) {//如果总价大于等于满额包邮 邮费等于0
  306. $isStoreFreePostage = true;
  307. $storePostage = 0;
  308. } else {
  309. //按照运费模板计算每个运费模板下商品的件数/重量/体积以及总金额 按照首重倒序排列
  310. $cityId = $addr['city_id'] ?? 0;
  311. $tempIds[] = 1;
  312. foreach ($cartInfo as $key_c => $item_c) {
  313. $tempIds[] = $item_c['productInfo']['temp_id'];
  314. }
  315. $tempIds = array_unique($tempIds);
  316. /** @var ShippingTemplatesServices $shippServices */
  317. $shippServices = app()->make(ShippingTemplatesServices::class);
  318. $temp = $shippServices->getShippingColumn(['id' => $tempIds], 'type,appoint', 'id');
  319. /** @var ShippingTemplatesRegionServices $regionServices */
  320. $regionServices = app()->make(ShippingTemplatesRegionServices::class);
  321. $regions = $regionServices->getTempRegionList($tempIds, [$cityId, 0], 'temp_id,first,first_price,continue,continue_price', 'temp_id');
  322. $temp_num = [];
  323. foreach ($cartInfo as $cart) {
  324. $tempId = $cart['productInfo']['temp_id'] ?? 1;
  325. $type = isset($temp[$tempId]['type']) ? $temp[$tempId]['type'] : $temp[1]['type'];
  326. if ($type == 1) {
  327. $num = $cart['cart_num'];
  328. } elseif ($type == 2) {
  329. $num = $cart['cart_num'] * $cart['productInfo']['attrInfo']['weight'];
  330. } else {
  331. $num = $cart['cart_num'] * $cart['productInfo']['attrInfo']['volume'];
  332. }
  333. $region = isset($regions[$tempId]) ? $regions[$tempId] : $regions[1];
  334. if (!isset($temp_num[$tempId])) {
  335. $temp_num[$tempId] = [
  336. 'number' => $num,
  337. 'type' => $type,
  338. 'price' => bcmul($cart['cart_num'], $cart['truePrice'], 2),
  339. 'first' => $region['first'],
  340. 'first_price' => $region['first_price'],
  341. 'continue' => $region['continue'],
  342. 'continue_price' => $region['continue_price'],
  343. 'temp_id' => $tempId
  344. ];
  345. } else {
  346. $temp_num[$tempId]['number'] += $num;
  347. $temp_num[$tempId]['price'] += bcmul($cart['cart_num'], $cart['truePrice'], 2);
  348. }
  349. }
  350. /** @var ShippingTemplatesFreeServices $freeServices */
  351. $freeServices = app()->make(ShippingTemplatesFreeServices::class);
  352. $freeList = $freeServices->isFreeList($tempIds, $addr['city_id'], 0, 'temp_id,number,price', 'temp_id');
  353. if ($freeList) {
  354. foreach ($temp_num as $k => $v) {
  355. if (isset($temp[$v['temp_id']]['appoint']) && $temp[$v['temp_id']]['appoint'] && isset($freeList[$v['temp_id']])) {
  356. $free = $freeList[$v['temp_id']];
  357. $condition = $v['type'] == 1 ? $free['number'] <= $v['number'] : $free['number'] >= $v['number'];
  358. if ($free['price'] <= $v['price'] && $condition) {
  359. unset($temp_num[$k]);
  360. }
  361. }
  362. }
  363. }
  364. //首件运费最大值
  365. $maxFirstPrice = $temp_num ? max(array_column($temp_num, 'first_price')) : 0;
  366. //初始运费为0
  367. $storePostage_arr = [];
  368. //循环运费数组
  369. foreach ($temp_num as $fk => $fv) {
  370. //找到首件运费等于最大值
  371. if ($fv['first_price'] == $maxFirstPrice) {
  372. //每次循环设置初始值
  373. $tempArr = $temp_num;
  374. $Postage = 0;
  375. //计算首件运费
  376. if ($fv['number'] <= $fv['first']) {
  377. $Postage = bcadd($Postage, $fv['first_price'], 2);
  378. } else {
  379. if ($fv['continue'] <= 0) {
  380. $Postage = $Postage;
  381. } else {
  382. $Postage = bcadd(bcadd($Postage, $fv['first_price'], 2), bcmul(ceil(bcdiv(bcsub($fv['number'], $fv['first'], 2), $fv['continue'] ?? 0, 2)), $fv['continue_price'], 4), 2);
  383. }
  384. }
  385. //删除计算过的首件数据
  386. unset($tempArr[$fk]);
  387. //循环计算剩余运费
  388. foreach ($tempArr as $cv) {
  389. if ($cv['continue'] <= 0) {
  390. $Postage = $Postage;
  391. } else {
  392. $Postage = bcadd($Postage, bcmul(ceil(bcdiv($cv['number'], $cv['continue'] ?? 0, 2)), $cv['continue_price'], 2), 2);
  393. }
  394. }
  395. $storePostage_arr[] = $Postage;
  396. }
  397. }
  398. if (count($storePostage_arr)) {
  399. //获取运费计算中的最大值
  400. $storePostage = max($storePostage_arr);
  401. }
  402. }
  403. }
  404. //会员邮费享受折扣
  405. if ($storePostage) {
  406. if (!$userInfo) {
  407. /** @var UserServices $userService */
  408. $userService = app()->make(UserServices::class);
  409. $userInfo = $userService->getUserInfo($addr['uid']);
  410. }
  411. if ($userInfo && isset($userInfo['is_money_level']) && $userInfo['is_money_level'] > 0) {
  412. //看是否开启会员折扣奖励
  413. /** @var MemberCardServices $memberCardService */
  414. $memberCardService = app()->make(MemberCardServices::class);
  415. $express_rule_number = $memberCardService->isOpenMemberCard('express');
  416. if ($express_rule_number) {
  417. if ($express_rule_number <= 0) {
  418. $storePostageDiscount = $storePostage;
  419. $storePostage = 0;
  420. } else if ($express_rule_number < 100) {
  421. $storePostageDiscount = $storePostage;
  422. $storePostage = bcmul($storePostage, bcdiv($express_rule_number, 100, 4), 2);
  423. $storePostageDiscount = bcsub($storePostageDiscount, $storePostage, 2);
  424. }
  425. }
  426. }
  427. }
  428. return compact('storePostage', 'storeFreePostage', 'isStoreFreePostage', 'sumPrice', 'totalPrice', 'costPrice', 'vipPrice', 'storePostageDiscount');
  429. }
  430. /**
  431. * 获取某个字段总金额
  432. * @param $cartInfo
  433. * @param string $key
  434. * @param bool $is_unit
  435. * @return int|string
  436. */
  437. public function getOrderSumPrice($cartInfo, $key = 'truePrice', $is_unit = true)
  438. {
  439. $SumPrice = 0;
  440. foreach ($cartInfo as $cart) {
  441. if (isset($cart['cart_info'])) $cart = $cart['cart_info'];
  442. if ($is_unit) {
  443. $SumPrice = bcadd($SumPrice, bcmul($cart['cart_num'], $cart[$key], 2), 2);
  444. } else {
  445. $SumPrice = bcadd($SumPrice, $cart[$key], 2);
  446. }
  447. }
  448. return $SumPrice;
  449. }
  450. }