StoreOrderComputedServices.php 18 KB

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