StoreOrderComputedServices.php 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2016~2023 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\dao\product\product\StoreProductDao;
  13. use app\services\agent\AgentLevelServices;
  14. use app\services\BaseServices;
  15. use app\dao\order\StoreOrderDao;
  16. use app\services\pay\PayServices;
  17. use app\services\product\product\StoreCategoryServices;
  18. use app\services\product\product\StoreProductServices;
  19. use app\services\user\member\MemberCardServices;
  20. use app\services\user\UserBillServices;
  21. use app\services\user\UserServices;
  22. use crmeb\exceptions\ApiException;
  23. use app\services\user\UserAddressServices;
  24. use app\services\activity\coupon\StoreCouponUserServices;
  25. use app\services\shipping\ShippingTemplatesFreeServices;
  26. use app\services\shipping\ShippingTemplatesRegionServices;
  27. use app\services\shipping\ShippingTemplatesServices;
  28. /**
  29. * 订单计算金额
  30. * Class StoreOrderComputedServices
  31. * @package app\services\order
  32. */
  33. class StoreOrderComputedServices extends BaseServices
  34. {
  35. /**
  36. * 支付类型
  37. * @var string[]
  38. */
  39. public $payType = ['weixin' => '微信支付', 'yue' => '余额支付', 'offline' => '线下支付', 'pc' => 'pc'];
  40. /**
  41. * 额外参数
  42. * @var array
  43. */
  44. protected $paramData = [];
  45. /**
  46. * StoreOrderComputedServices constructor.
  47. * @param StoreOrderDao $dao
  48. */
  49. public function __construct(StoreOrderDao $dao)
  50. {
  51. $this->dao = $dao;
  52. }
  53. /**
  54. * 设置额外参数
  55. * @param array $paramData
  56. * @return $this
  57. */
  58. public function setParamData(array $paramData)
  59. {
  60. $this->paramData = $paramData;
  61. return $this;
  62. }
  63. /**
  64. * 计算订单金额
  65. * @param int $uid
  66. * @param string $key
  67. * @param array $cartGroup
  68. * @param int $addressId
  69. * @param string $payType
  70. * @param bool $useIntegral
  71. * @param int $couponId
  72. * @param bool $is_create
  73. * @param int $shipping_type
  74. * @return array
  75. */
  76. 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,int $repeat_discount = 0)
  77. {
  78. try {
  79. $offlinePayStatus = (int)sys_config('offline_pay_status') ?? (int)2;
  80. $systemPayType = PayServices::PAY_TYPE;
  81. if ($offlinePayStatus == 2) unset($systemPayType['offline']);
  82. if (!$userInfo) {
  83. /** @var UserServices $userServices */
  84. $userServices = app()->make(UserServices::class);
  85. $userInfo = $userServices->getUserInfo($uid);
  86. if (!$userInfo) {
  87. throw new ApiException(410032);
  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. @file_put_contents("quanju.txt", $isActivity."-这是什么东西啊\r\n", 8);
  111. if (!$isActivity) {
  112. // 当使用了复购折扣时,检查购物车中是否存在复购商品(is_repeat=1)
  113. // 若存在,则取消会员折扣(复购折扣与会员折扣互斥),重新按原价计算总价
  114. @file_put_contents("quanju.txt", $repeat_discount."-复购商品折扣\r\n", 8);
  115. // if ($repeat_discount > 0) {
  116. $productIds = array_unique(array_column($cartInfo, 'product_id'));
  117. /** @var StoreProductServices $productServices */
  118. $productServices = app()->make(StoreProductServices::class);
  119. // @file_put_contents("quanju.txt", json_encode($productIds)."-商品id\r\n", 8);
  120. //
  121. //
  122. $hasRepeat=false;
  123. // try {
  124. $Repeat = $productServices->repeatCount($productIds);
  125. @file_put_contents("quanju.txt", $Repeat."-复购商品数量\r\n", 8);
  126. if ($Repeat > 0){
  127. $hasRepeat = true;
  128. }
  129. @file_put_contents("quanju.txt", $hasRepeat."-是否是复购商品\r\n", 8);
  130. // if ($hasRepeat) {
  131. // // 将所有有会员折扣的商品还原为原价,重新汇总总价
  132. // $newTotalPrice = '0';
  133. // foreach ($cartInfo as &$cartItem) {
  134. // @file_put_contents("quanju.txt", json_encode(['productId'=>$cartItem['product_id'],'price_type'=>$cartItem['price_type']??'undefined','truePrice'=>$cartItem['truePrice']??'undefined','sum_price'=>$cartItem['sum_price']??'undefined'])."-取消会员折扣前\r\n", 8);
  135. // if (isset($cartItem['price_type']) && in_array($cartItem['price_type'], ['level', 'member'])) {
  136. // $cartItem['truePrice'] = $cartItem['sum_price'];
  137. // $cartItem['vip_truePrice'] = 0;
  138. // $cartItem['price_type'] = 'normal';
  139. // @file_put_contents("quanju.txt", json_encode(['productId'=>$cartItem['product_id']])."-已取消会员折扣\r\n", 8);
  140. // }
  141. // $newTotalPrice = bcadd($newTotalPrice, bcmul((string)$cartItem['truePrice'], (string)$cartItem['cart_num'], 4), 2);
  142. // }
  143. // unset($cartItem);
  144. // $payPrice = (float)$newTotalPrice;
  145. // $priceGroup['totalPrice'] = $newTotalPrice;
  146. // @file_put_contents("quanju.txt", json_encode(['newTotalPrice'=>$newTotalPrice,'payPrice'=>$payPrice])."-取消会员折扣后\r\n", 8);
  147. // }
  148. // } catch (\Exception $e) {
  149. // @file_put_contents("quanju.txt", json_encode($e->getMessage())."-报错内容\r\n", 8);
  150. // throw $e;
  151. // }
  152. // }
  153. //使用优惠劵
  154. [$payPrice, $couponPrice] = $this->useCouponId($couponId, $uid, $cartInfo, $payPrice, $isCreate);
  155. //使用积分
  156. [$payPrice, $deductionPrice, $usedIntegral, $SurplusIntegral] = $this->useIntegral($useIntegral, $userInfo, $payPrice, $other);
  157. //使用复购折扣
  158. [$payPrice, $deductionPrice, $usedRepeatDiscount, $SurplusRepeatDiscount] = $this->useRepeatDiscount($repeat_discount, $userInfo, $payPrice, $hasRepeat ?? false, $deductionPrice ?? 0);
  159. }
  160. //计算邮费
  161. [$payPrice, $payPostage, $storePostageDiscount, $storeFreePostage, $isStoreFreePostage] = $this->computedPayPostage($shippingType, $payType, $cartInfo, $addr, $payPrice, $postage, $other, $userInfo);
  162. $result = [
  163. 'total_price' => $priceGroup['totalPrice'],
  164. 'pay_price' => $payPrice > 0 ? $payPrice : 0,
  165. 'pay_postage' => $payPostage,
  166. 'coupon_price' => $couponPrice ?? 0,
  167. 'deduction_price' => $deductionPrice ?? 0,
  168. 'usedIntegral' => $usedIntegral ?? 0,
  169. 'SurplusIntegral' => $SurplusIntegral ?? 0,
  170. 'usedRepeatDiscount' => $usedRepeatDiscount ?? 0,
  171. 'SurplusRepeatDiscount' => $SurplusRepeatDiscount ?? 0,
  172. 'storePostageDiscount' => $storePostageDiscount ?? 0,
  173. 'isStoreFreePostage' => $isStoreFreePostage ?? false,
  174. 'storeFreePostage' => $storeFreePostage ?? 0
  175. ];
  176. $this->paramData = [];
  177. return $result;
  178. } catch (\Exception $e) {
  179. @file_put_contents("quanju.txt", date('Y-m-d H:i:s') . " - computedOrder报错: " . $e->getMessage() . " - 文件: " . $e->getFile() . " - 行号: " . $e->getLine() . "\r\n", FILE_APPEND);
  180. @file_put_contents("quanju.txt", date('Y-m-d H:i:s') . " - Trace: " . $e->getTraceAsString() . "\r\n", FILE_APPEND);
  181. throw $e;
  182. }
  183. }
  184. /**
  185. * 使用优惠卷
  186. * @param int $couponId
  187. * @param int $uid
  188. * @param $cartInfo
  189. * @param $payPrice
  190. * @param bool $is_create
  191. */
  192. public function useCouponId(int $couponId, int $uid, $cartInfo, $payPrice, bool $isCreate)
  193. {
  194. //使用优惠劵
  195. $res1 = true;
  196. if ($couponId) {
  197. /** @var StoreCouponUserServices $couponServices */
  198. $couponServices = app()->make(StoreCouponUserServices::class);
  199. $couponInfo = $couponServices->getOne([['id', '=', $couponId], ['uid', '=', $uid], ['is_fail', '=', 0], ['status', '=', 0], ['start_time', '<', time()], ['end_time', '>', time()]], '*', ['issue']);
  200. if (!$couponInfo) {
  201. throw new ApiException(410242);
  202. }
  203. $type = $couponInfo['applicable_type'] ?? 0;
  204. $flag = false;
  205. $price = 0;
  206. $count = 0;
  207. switch ($type) {
  208. case 0:
  209. case 4:
  210. foreach ($cartInfo as $cart) {
  211. $price = bcadd($price, bcmul((string)$cart['truePrice'], (string)$cart['cart_num'], 2), 2);
  212. $count++;
  213. }
  214. break;
  215. case 3:
  216. foreach ($cartInfo as $cart) {
  217. $price = bcadd($price, bcmul((string)$cart['truePrice'], (string)$cart['cart_num'], 2), 2);
  218. $count++;
  219. }
  220. break;
  221. case 1://品类券
  222. /** @var StoreCategoryServices $storeCategoryServices */
  223. $storeCategoryServices = app()->make(StoreCategoryServices::class);
  224. $coupon_category = explode(',', (string)$couponInfo['category_id']);
  225. $category_ids = $storeCategoryServices->getAllById($coupon_category);
  226. if ($category_ids) {
  227. $cateIds = array_column($category_ids, 'id');
  228. foreach ($cartInfo as $cart) {
  229. if (isset($cart['productInfo']['cate_id']) && array_intersect(explode(',', $cart['productInfo']['cate_id']), $cateIds)) {
  230. $price = bcadd($price, bcmul((string)$cart['truePrice'], (string)$cart['cart_num'], 2), 2);
  231. $count++;
  232. }
  233. }
  234. }
  235. break;
  236. case 2:
  237. foreach ($cartInfo as $cart) {
  238. if (isset($cart['product_id']) && in_array($cart['product_id'], explode(',', $couponInfo['product_id']))) {
  239. $price = bcadd($price, bcmul((string)$cart['truePrice'], (string)$cart['cart_num'], 2), 2);
  240. $count++;
  241. }
  242. }
  243. break;
  244. }
  245. if ($count && $couponInfo['use_min_price'] <= $price) {
  246. $flag = true;
  247. }
  248. if (!$flag) {
  249. throw new ApiException(410243);
  250. }
  251. if ($isCreate) {
  252. $res1 = $couponServices->useCoupon($couponId);
  253. }
  254. $couponPrice = $couponInfo['coupon_price'] > $price ? $price : $couponInfo['coupon_price'];
  255. $payPrice = (float)bcsub((string)$payPrice, (string)$couponPrice, 2);
  256. } else {
  257. $couponPrice = 0;
  258. }
  259. if (!$res1) {
  260. throw new ApiException(410244);
  261. }
  262. return [$payPrice, $couponPrice];
  263. }
  264. /**
  265. * 使用积分
  266. * @param $useIntegral
  267. * @param $userInfo
  268. * @param $payPrice
  269. * @param $other
  270. * @return array
  271. */
  272. public function useIntegral(bool $useIntegral, $userInfo, string $payPrice, array $other)
  273. {
  274. /** @var UserBillServices $userBillServices */
  275. $userBillServices = app()->make(UserBillServices::class);
  276. // 可用积分
  277. $usable = bcsub((string)$userInfo['integral'], (string)$userBillServices->getBillSum(['uid' => $userInfo['uid'], 'is_frozen' => 1]), 0);
  278. $SurplusIntegral = $usable;
  279. if ($useIntegral && $userInfo['integral'] > 0 && $other['integralRatio'] > 0) {
  280. //积分抵扣上限
  281. $integralMaxNum = sys_config('integral_max_num', 200);
  282. if ($integralMaxNum > 0 && $usable > $integralMaxNum) {
  283. $integral = $integralMaxNum;
  284. } else {
  285. $integral = $usable;
  286. }
  287. $deductionPrice = (float)bcmul((string)$integral, (string)$other['integralRatio'], 2);
  288. if ($deductionPrice < $payPrice) {
  289. $payPrice = bcsub((string)$payPrice, (string)$deductionPrice, 2);
  290. $usedIntegral = $integral;
  291. } else {
  292. $deductionPrice = $payPrice;
  293. $usedIntegral = (int)ceil(bcdiv((string)$payPrice, (string)$other['integralRatio'], 2));
  294. $payPrice = 0;
  295. }
  296. $deductionPrice = $deductionPrice > 0 ? $deductionPrice : 0;
  297. $usedIntegral = $usedIntegral > 0 ? $usedIntegral : 0;
  298. $SurplusIntegral = (int)bcsub((string)$usable, $usedIntegral, 0);
  299. } else {
  300. $deductionPrice = 0;
  301. $usedIntegral = 0;
  302. }
  303. if ($payPrice <= 0) $payPrice = 0;
  304. return [$payPrice, $deductionPrice, $usedIntegral, $SurplusIntegral];
  305. }
  306. /**
  307. * 使用复购折扣
  308. * @param int $repeatDiscount
  309. * @param array $userInfo
  310. * @param string $payPrice
  311. * @param bool $hasRepeat
  312. * @param float $deductionPrice
  313. * @return array
  314. */
  315. public function useRepeatDiscount(int $repeatDiscount, $userInfo, string $payPrice, bool $hasRepeat = false, $deductionPrice = 0)
  316. {
  317. @file_put_contents("quanju.txt", $repeatDiscount."使用复购折扣\r\n", 8);
  318. @file_put_contents("quanju.txt", $hasRepeat."使用复购折扣了吗\r\n", 8);
  319. // 先判断永久复购折扣(分销等级level_discount=1)
  320. if ($hasRepeat && $userInfo && isset($userInfo['agent_level']) && $userInfo['agent_level'] > 0) {
  321. $agentLevel = app()->make(AgentLevelServices::class);
  322. $level_discount = $agentLevel->value(['id' => $userInfo['agent_level'], 'is_del' => 0], 'level_discount');
  323. if ($level_discount == 1) {
  324. // 计算有多少个50元
  325. $units = intval(bcdiv((string)$payPrice, '50', 0));
  326. @file_put_contents("quanju.txt", json_encode(['永久复购折扣','payPrice'=>$payPrice,'units'=>$units])."-永久复购折扣计算\r\n", 8);
  327. if ($units > 0) {
  328. // 每满50元减免20元(永久折扣不消耗次数)
  329. $discountAmount = bcmul((string)$units, '20', 2);
  330. $payPrice = bcsub((string)$payPrice, $discountAmount, 2);
  331. // 确保价格不低于0.01
  332. $payPrice = $payPrice < 0.01 ? '0.01' : $payPrice;
  333. // 将复购折扣金额累加到deductionPrice
  334. $deductionPrice = bcadd((string)$deductionPrice, $discountAmount, 2);
  335. @file_put_contents("quanju.txt", json_encode(['units'=>$units,'discountAmount'=>$discountAmount,'payPrice'=>$payPrice,'deductionPrice'=>$deductionPrice])."-永久复购折扣计算结果\r\n", 8);
  336. // 永久复购折扣不消耗次数
  337. return [$payPrice, $deductionPrice, 0, isset($userInfo['repeat_discount_num']) ? (int)$userInfo['repeat_discount_num'] : 0];
  338. }
  339. }
  340. }
  341. // 如果没有永久复购折扣,则使用复购折扣次数
  342. if ($repeatDiscount > 0 && $hasRepeat) {
  343. // @file_put_contents("quanju.txt", "使用复购折扣\r\n", 8);
  344. // 计算有多少个50元
  345. $units = intval(bcdiv((string)$payPrice, '50', 0));
  346. @file_put_contents("quanju.txt", json_encode(['payPrice'=>$payPrice,'units'=>$units])."-复购折扣计算\r\n", 8);
  347. if ($units > 0) {
  348. // 获取用户的复购折扣次数
  349. $availableDiscounts = isset($userInfo['repeat_discount_num']) ? (int)$userInfo['repeat_discount_num'] : 0;
  350. // 使用可用的折扣次数(有多少扣多少)
  351. $useUnits = min($units, $availableDiscounts);
  352. if ($useUnits > 0) {
  353. // 每满50元减免20元
  354. $discountAmount = bcmul((string)$useUnits, '20', 2);
  355. $payPrice = bcsub((string)$payPrice, $discountAmount, 2);
  356. // 确保价格不低于0.01
  357. $payPrice = $payPrice < 0.01 ? '0.01' : $payPrice;
  358. // 将复购折扣金额累加到deductionPrice
  359. $deductionPrice = bcadd((string)$deductionPrice, $discountAmount, 2);
  360. $usedRepeatDiscount = $useUnits;
  361. $SurplusRepeatDiscount = $availableDiscounts - $useUnits;
  362. @file_put_contents("quanju.txt", json_encode(['units'=>$units,'availableDiscounts'=>$availableDiscounts,'useUnits'=>$useUnits,'discountAmount'=>$discountAmount,'payPrice'=>$payPrice,'deductionPrice'=>$deductionPrice,'usedRepeatDiscount'=>$usedRepeatDiscount,'SurplusRepeatDiscount'=>$SurplusRepeatDiscount])."-复购折扣计算结果\r\n", 8);
  363. return [$payPrice, $deductionPrice, $usedRepeatDiscount, $SurplusRepeatDiscount];
  364. }
  365. }
  366. }
  367. // 不使用复购折扣或不符合条件
  368. return [$payPrice, $deductionPrice, 0, 0];
  369. }
  370. /**
  371. * 计算邮费
  372. * @param int $shipping_type
  373. * @param string $payType
  374. * @param array $cartInfo
  375. * @param array $addr
  376. * @param string $payPrice
  377. * @param array $other
  378. * @return array
  379. */
  380. public function computedPayPostage(int $shipping_type, string $payType, array $cartInfo, array $addr, string $payPrice, array $postage = [], array $other, $userInfo = [])
  381. {
  382. $storePostageDiscount = 0;
  383. $storeFreePostage = $postage['storeFreePostage'] ?? 0;
  384. $isStoreFreePostage = false;
  385. if (!$storeFreePostage) {
  386. $storeFreePostage = floatval(sys_config('store_free_postage')) ?: 0;//满额包邮金额
  387. }
  388. if (!$addr && !isset($addr['id']) || !$cartInfo) {
  389. $payPostage = 0;
  390. } else {
  391. //$shipping_type = 1 快递发货 $shipping_type = 2 门店自提
  392. if ($shipping_type == 2) {
  393. $store_self_mention = sys_config('store_self_mention') ?? 0;
  394. if (!$store_self_mention) $shipping_type = 1;
  395. }
  396. //门店自提 || (线下支付 && 线下支付包邮) 没有邮费支付
  397. if ($shipping_type === 2 || ($payType == 'offline' && ((isset($other['offlinePostage']) && $other['offlinePostage']) || sys_config('offline_postage')) == 1)) {
  398. $payPostage = 0;
  399. } else {
  400. if (!$postage || !isset($postage['storePostage']) || !isset($postage['storePostageDiscount'])) {
  401. $postage = $this->getOrderPriceGroup($storeFreePostage, $cartInfo, $addr, $userInfo);
  402. }
  403. $payPostage = $postage['storePostage'];
  404. $storePostageDiscount = $postage['storePostageDiscount'];
  405. $isStoreFreePostage = $postage['isStoreFreePostage'] ?? false;
  406. $payPrice = (float)bcadd((string)$payPrice, (string)$payPostage, 2);
  407. }
  408. }
  409. return [$payPrice, $payPostage, $storePostageDiscount, $storeFreePostage, $isStoreFreePostage];
  410. }
  411. /**
  412. * 运费计算,总金额计算
  413. * @param $cartInfo
  414. * @param $addr
  415. * @param array $userInfo
  416. * @return array
  417. */
  418. public function getOrderPriceGroup($storeFreePostage, $cartInfo, $addr, $userInfo = [])
  419. {
  420. $sumPrice = $totalPrice = $costPrice = $vipPrice = 0;
  421. $storePostage = 0;
  422. $storePostageDiscount = 0;
  423. $isStoreFreePostage = false;//是否满额包邮
  424. $sumPrice = $this->getOrderSumPrice($cartInfo, 'sum_price');//获取订单原总金额
  425. $totalPrice = $this->getOrderSumPrice($cartInfo, 'truePrice');//获取订单svip、用户等级优惠之后总金额
  426. $costPrice = $this->getOrderSumPrice($cartInfo, 'costPrice');//获取订单成本价
  427. $vipPrice = $this->getOrderSumPrice($cartInfo, 'vip_truePrice');//获取订单等级和付费会员总优惠金额
  428. $levelPrice = $this->getOrderSumPrice($cartInfo, 'level');//获取会员等级优惠
  429. $memberPrice = $this->getOrderSumPrice($cartInfo, 'member');//获取付费会员优惠
  430. // 判断商品包邮和固定运费
  431. foreach ($cartInfo as $key => &$item) {
  432. $item['postage_price'] = 0;
  433. if ($item['productInfo']['freight'] == 1) {
  434. $item['postage_price'] = 0;
  435. } elseif ($item['productInfo']['freight'] == 2) {
  436. $item['postage_price'] = bcmul((string)$item['productInfo']['postage'], (string)$item['cart_num'], 2);
  437. $item['origin_postage_price'] = bcmul((string)$item['productInfo']['postage'], (string)$item['cart_num'], 2);
  438. $storePostage = bcadd((string)$storePostage, (string)$item['postage_price'], 2);
  439. }
  440. }
  441. $postageArr = [];
  442. if (isset($cartInfo[0]['productInfo']['is_virtual']) && $cartInfo[0]['productInfo']['is_virtual'] == 1) {
  443. $storePostage = 0;
  444. } elseif ($storeFreePostage && $cartInfo && $addr) {
  445. if ($sumPrice >= $storeFreePostage) {//如果总价大于等于满额包邮 邮费等于0
  446. $isStoreFreePostage = true;
  447. $storePostage = 0;
  448. } else {
  449. //按照运费模板计算每个运费模板下商品的件数/重量/体积以及总金额 按照首重倒序排列
  450. $cityId = $addr['city_id'] ?? 0;
  451. $tempIds[] = 1;
  452. foreach ($cartInfo as $key_c => $item_c) {
  453. if (isset($item_c['productInfo']['freight']) && $item_c['productInfo']['freight'] == 3) {
  454. $tempIds[] = $item_c['productInfo']['temp_id'];
  455. }
  456. }
  457. $tempIds = array_unique($tempIds);
  458. /** @var ShippingTemplatesServices $shippServices */
  459. $shippServices = app()->make(ShippingTemplatesServices::class);
  460. $temp = $shippServices->getShippingColumn(['id' => $tempIds], 'type,appoint', 'id');
  461. /** @var ShippingTemplatesRegionServices $regionServices */
  462. $regionServices = app()->make(ShippingTemplatesRegionServices::class);
  463. $regions = $regionServices->getTempRegionList($tempIds, [$cityId, 0], 'temp_id,first,first_price,continue,continue_price', 'temp_id');
  464. $temp_num = [];
  465. foreach ($cartInfo as $cart) {
  466. if (isset($cart['productInfo']['freight']) && in_array($cart['productInfo']['freight'], [1, 2])) {
  467. continue;
  468. }
  469. $tempId = $cart['productInfo']['temp_id'] ?? 1;
  470. $type = $temp[$tempId]['type'] ?? $temp[1]['type'];
  471. if ($type == 1) {
  472. $num = $cart['cart_num'];
  473. } elseif ($type == 2) {
  474. $num = $cart['cart_num'] * $cart['productInfo']['attrInfo']['weight'];
  475. } else {
  476. $num = $cart['cart_num'] * $cart['productInfo']['attrInfo']['volume'];
  477. }
  478. $region = $regions[$tempId] ?? ($regions[1] ?? []);
  479. if (!$region) continue;
  480. if (!isset($temp_num[$tempId])) {
  481. $temp_num[$tempId] = [
  482. 'number' => $num,
  483. 'type' => $type,
  484. 'price' => bcmul($cart['cart_num'], $cart['truePrice'], 2),
  485. 'first' => $region['first'],
  486. 'first_price' => $region['first_price'],
  487. 'continue' => $region['continue'],
  488. 'continue_price' => $region['continue_price'],
  489. 'temp_id' => $tempId
  490. ];
  491. } else {
  492. $temp_num[$tempId]['number'] += $num;
  493. $temp_num[$tempId]['price'] += bcmul($cart['cart_num'], $cart['truePrice'], 2);
  494. }
  495. }
  496. /** @var ShippingTemplatesFreeServices $freeServices */
  497. $freeServices = app()->make(ShippingTemplatesFreeServices::class);
  498. $freeList = $freeServices->isFreeList($tempIds, $addr['city_id'], 0, 'temp_id,number,price', 'temp_id');
  499. if ($freeList) {
  500. foreach ($temp_num as $k => $v) {
  501. if (isset($temp[$v['temp_id']]['appoint']) && $temp[$v['temp_id']]['appoint'] && isset($freeList[$v['temp_id']])) {
  502. $free = $freeList[$v['temp_id']];
  503. $condition = $free['number'] <= $v['number'];
  504. if ($free['price'] <= $v['price'] && $condition) {
  505. unset($temp_num[$k]);
  506. }
  507. }
  508. }
  509. }
  510. //首件运费最大值
  511. $maxFirstPrice = $temp_num ? max(array_column($temp_num, 'first_price')) : 0;
  512. //初始运费为0
  513. $storePostage_arr = [];
  514. $i = 0;
  515. //循环运费数组
  516. foreach ($temp_num as $fk => $fv) {
  517. //找到首件运费等于最大值
  518. if ($fv['first_price'] == $maxFirstPrice) {
  519. //每次循环设置初始值
  520. $tempArr = $temp_num;
  521. $Postage = 0;
  522. //计算首件运费
  523. if ($fv['number'] <= $fv['first']) {
  524. $Postage = bcadd($Postage, $fv['first_price'], 2);
  525. } else {
  526. if ($fv['continue'] <= 0) {
  527. $Postage = $Postage;
  528. } else {
  529. $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);
  530. }
  531. }
  532. $postageArr[$i]['data'][$fk] = $Postage;
  533. //删除计算过的首件数据
  534. unset($tempArr[$fk]);
  535. //循环计算剩余运费
  536. foreach ($tempArr as $ck => $cv) {
  537. if ($cv['continue'] <= 0) {
  538. $Postage = $Postage;
  539. } else {
  540. $one_postage = bcmul(ceil(bcdiv($cv['number'], $cv['continue'] ?? 0, 2)), $cv['continue_price'], 2);
  541. $Postage = bcadd($Postage, $one_postage, 2);
  542. $postageArr[$i]['data'][$ck] = $one_postage;
  543. }
  544. }
  545. $postageArr[$i]['sum'] = $Postage;
  546. $storePostage_arr[] = $Postage;
  547. }
  548. }
  549. if (count($storePostage_arr)) {
  550. $maxStorePostage = max($storePostage_arr);
  551. //获取运费计算中的最大值
  552. $storePostage = bcadd((string)$storePostage, (string)$maxStorePostage, 2);
  553. }
  554. }
  555. }
  556. //会员邮费享受折扣
  557. if ($storePostage) {
  558. $express_rule_number = 100;
  559. if (!$userInfo) {
  560. /** @var UserServices $userService */
  561. $userService = app()->make(UserServices::class);
  562. $userInfo = $userService->getUserInfo($addr['uid']);
  563. }
  564. if ($userInfo && isset($userInfo['is_money_level']) && $userInfo['is_money_level'] > 0) {
  565. //看是否开启会员折扣奖励
  566. /** @var MemberCardServices $memberCardService */
  567. $memberCardService = app()->make(MemberCardServices::class);
  568. $express_rule_number = $memberCardService->isOpenMemberCard('express');
  569. $express_rule_number = $express_rule_number <= 0 ? 0 : $express_rule_number;
  570. }
  571. $discountRate = bcdiv($express_rule_number, 100, 4);
  572. $truePostageArr = [];
  573. foreach ($postageArr as $postitem) {
  574. if ($postitem['sum'] == ($maxStorePostage ?? 0)) {
  575. $truePostageArr = $postitem['data'];
  576. break;
  577. }
  578. }
  579. $cartAlready = [];
  580. foreach ($cartInfo as &$item) {
  581. if (isset($item['productInfo']['freight']) && in_array($item['productInfo']['freight'], [1, 2])) {
  582. if ($item['productInfo']['freight'] == 2) {
  583. $item['postage_price'] = sprintf("%.2f", bcmul($item['postage_price'], $discountRate, 6));
  584. }
  585. continue;
  586. }
  587. $tempId = $item['productInfo']['temp_id'] ?? 0;
  588. $tempPostage = $truePostageArr[$tempId] ?? 0;
  589. $tempNumber = $temp_num[$tempId]['number'] ?? 0;
  590. if (!$tempId || !$tempPostage) continue;
  591. $type = $temp_num[$tempId]['type'];
  592. if ($type == 1) {
  593. $num = $item['cart_num'];
  594. } elseif ($type == 2) {
  595. $num = $item['cart_num'] * $item['productInfo']['attrInfo']['weight'];
  596. } else {
  597. $num = $item['cart_num'] * $item['productInfo']['attrInfo']['volume'];
  598. }
  599. if ((($cartAlready[$tempId]['number'] ?? 0) + $num) >= $tempNumber) {
  600. $price = isset($cartAlready[$tempId]['price']) ? bcsub((string)$tempPostage, (string)$cartAlready[$tempId]['price'], 6) : $tempPostage;
  601. } else {
  602. $price = bcmul((string)$tempPostage, bcdiv((string)$num, (string)$tempNumber, 6), 6);
  603. }
  604. $cartAlready[$tempId]['number'] = bcadd((string)($cartAlready[$tempId]['number'] ?? 0), (string)$num, 4);
  605. $cartAlready[$tempId]['price'] = bcadd((string)($cartAlready[$tempId]['price'] ?? 0.00), (string)$price, 4);
  606. if ($express_rule_number && $express_rule_number < 100) {
  607. $price = bcmul($price, $discountRate, 4);
  608. }
  609. $item['postage_price'] = sprintf("%.2f", $price);
  610. }
  611. if ($express_rule_number && $express_rule_number < 100) {
  612. $storePostageDiscount = $storePostage;
  613. $storePostage = bcmul($storePostage, bcdiv($express_rule_number, 100, 4), 2);
  614. $storePostageDiscount = bcsub($storePostageDiscount, $storePostage, 2);
  615. } else {
  616. $storePostageDiscount = 0;
  617. $storePostage = $storePostage;
  618. }
  619. }
  620. return compact('storePostage', 'storeFreePostage', 'isStoreFreePostage', 'sumPrice', 'totalPrice', 'costPrice', 'vipPrice', 'storePostageDiscount', 'cartInfo', 'levelPrice', 'memberPrice');
  621. }
  622. /**
  623. * 获取某个字段总金额
  624. * @param $cartInfo
  625. * @param string $key
  626. * @param bool $is_unit
  627. * @return int|string
  628. */
  629. public function getOrderSumPrice($cartInfo, $key = 'truePrice', $is_unit = true)
  630. {
  631. $SumPrice = 0;
  632. foreach ($cartInfo as $cart) {
  633. if (isset($cart['cart_info'])) $cart = $cart['cart_info'];
  634. if ($is_unit) {
  635. if ($key == 'level' || $key == 'member') {
  636. if ($cart['price_type'] == $key) {
  637. $SumPrice = bcadd($SumPrice, bcmul($cart['cart_num'], $cart['vip_truePrice'], 2), 2);
  638. }
  639. } else {
  640. $SumPrice = bcadd($SumPrice, bcmul($cart['cart_num'], $cart[$key], 2), 2);
  641. }
  642. } else {
  643. $SumPrice = bcadd($SumPrice, $cart[$key], 2);
  644. }
  645. }
  646. return $SumPrice;
  647. }
  648. }