MerchantOrderTrait.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2016~2024 https://www.crmeb.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
  8. // +----------------------------------------------------------------------
  9. // | Author: CRMEB Team <admin@crmeb.com>
  10. // +----------------------------------------------------------------------
  11. namespace app\common\repositories\store\order;
  12. use think\Model;
  13. use think\exception\ValidateException;
  14. trait MerchantOrderTrait
  15. {
  16. /**
  17. * 计算整单改价后的价格
  18. *
  19. * @param array $data
  20. * @return float
  21. */
  22. private function getNewPayPrice(array $data) : array
  23. {
  24. $updatePriceFlag = 0;
  25. // 判断是否改价了,如果没有改价,则返回默认值
  26. if(!isset($data['change_fee_type']) || $data['change_fee_type'] === '') {
  27. return [0, 1, $updatePriceFlag];
  28. }
  29. $newPayPrice = $data['new_pay_price'] ?? 0;
  30. if($data['change_fee_type'] == 1){ // 立减
  31. $newPayPrice = bcsub($data['old_pay_price'], $data['reduce_price'], 2);
  32. }
  33. if($data['change_fee_type'] == 2){ // 折扣
  34. $newPayPrice = bcmul($data['old_pay_price'], $data['discount_rate']/100, 2);
  35. }
  36. $changeFeeRate = bcdiv($newPayPrice, $data['old_pay_price'], 2);
  37. $updatePriceFlag = 1;
  38. return [(float)$newPayPrice, (float)$changeFeeRate, $updatePriceFlag];
  39. }
  40. /**
  41. * 运费模版
  42. *
  43. * @param array $template
  44. * @return array
  45. */
  46. private function template(array $template): array
  47. {
  48. $cartProductShippingFeeTemplate = [];
  49. foreach ($template as $temp) {
  50. $cartProductShippingFeeTemplate[$temp['shipping_template_id']] = $temp;
  51. }
  52. return $cartProductShippingFeeTemplate;
  53. }
  54. /**
  55. * 计算积分
  56. *
  57. * @param array $result
  58. * @param $cartList
  59. * @param integer $merId
  60. * @param integer $useIntegral
  61. * @param integer $userIntegral
  62. * @return array
  63. */
  64. private function calculateIntegral(array $result, $cartList, int $merId, int $useIntegral, int $userIntegral): array
  65. {
  66. $usedNum = 0;
  67. $config = $this->getMerchantConfig($merId);
  68. $sysIntegralConfig = systemConfig(['integral_money', 'integral_status', 'integral_order_rate']);
  69. $integralFlag = ($sysIntegralConfig['integral_status'] == 1 && (isset($config['config']['mer_integral_status']) && $config['config']['mer_integral_status'] == 1) && $userIntegral > 0) ? 1 : 0;
  70. if ($integralFlag && $useIntegral) {
  71. $result['totalPaymentPrice'] = 0;
  72. $result['discountDetail']['integralDiscountAmount'] = 0;
  73. foreach ($cartList as $value) {
  74. // 积分抵扣比例,优先取商品设置的积分抵扣比例,若无则取商户默认设置的积分抵扣比例
  75. $integralRate = ($value['product']['integral_rate'] > 0) ? $value['product']['integral_rate'] : $config['config']['mer_integral_rate'];
  76. $integralRate = min(bcdiv($integralRate, 100, 4), 1);
  77. // 根据比例计算商品积分抵扣金额
  78. $productIntegralPrice = min((float)bcmul($value['paymentPrice'], $integralRate, 2), $value['paymentPrice']);
  79. if ($productIntegralPrice > 0) {
  80. // 计算商品积分数,若商品积分数大于用户积分数,则取用户积分数,否则取商品积分数
  81. $productIntegral = ceil(bcdiv($productIntegralPrice, $sysIntegralConfig['integral_money'], 2));
  82. if ($productIntegral > $userIntegral) {
  83. $use = $userIntegral;
  84. $useIntegralPrice = bcmul($userIntegral, $sysIntegralConfig['integral_money'], 0);
  85. $userIntegral = 0;
  86. } else {
  87. $use = $productIntegral;
  88. $useIntegralPrice = $productIntegralPrice;
  89. $userIntegral = (float)bcsub($userIntegral, $productIntegral, 0);
  90. }
  91. // 各商品积分使用详情
  92. $value['integral'] = [
  93. 'use' => $use,
  94. 'price' => $useIntegralPrice,
  95. 'userIntegral' => $userIntegral
  96. ];
  97. // 更新商品实付金额
  98. $value['paymentPrice'] = max((float)bcsub($value['paymentPrice'], $useIntegralPrice, 2), 0);
  99. // 更新总支付金额和积分抵扣金额
  100. $result['totalPaymentPrice'] += $value['paymentPrice'];
  101. $result['discountDetail']['integralDiscountAmount'] += $value['integral']['price'];
  102. $usedNum += $use;
  103. }
  104. }
  105. }
  106. $result['commission_rate'] = $config['commission_rate'];
  107. $result['commission_switch'] = $config['commission_switch'];
  108. $result['integral']['flag'] = $integralFlag;
  109. $result['integral']['usedNum'] = $usedNum;
  110. $result['integral']['userIntegral'] = $userIntegral;
  111. $result['integral']['deductionAmount'] = (float)bcmul($userIntegral, $sysIntegralConfig['integral_money'], 2);
  112. return $result;
  113. }
  114. /**
  115. * 获取商户配置信息
  116. *
  117. * @param integer $merId
  118. * @return array
  119. */
  120. private function getMerchantConfig(int $merId): array
  121. {
  122. $configFiled = [
  123. 'mer_integral_status', // 商户积分开关
  124. 'mer_integral_rate', // 商户积分比例
  125. 'mer_store_stock', // 库存警戒
  126. 'mer_take_status', // 自提开关
  127. 'mer_take_name', // 自提门店名称
  128. 'mer_take_phone', // 自提电话
  129. 'mer_take_address', // 自提地址
  130. 'mer_take_location', // 自提店铺经纬度
  131. 'mer_take_day', // 自提营业日期
  132. 'svip_coupon_merge', // svip合并优惠券
  133. 'mer_take_time', // 自提时间段
  134. ];
  135. $merchantInfoData = $this->getMerchantRepository()->getSearch([])->where('mer_id', $merId)
  136. ->with([
  137. 'config' => function ($query) use ($configFiled) {
  138. $query->whereIn('config_key', $configFiled);
  139. }
  140. ])->field('mer_id,category_id,mer_name,mer_state,mer_avatar,is_trader,type_id,delivery_way,commission_rate,commission_switch')
  141. ->find()
  142. ->append(['openReceipt'])
  143. ->toArray();
  144. foreach ($merchantInfoData['config'] as $key => $config) {
  145. $merchantInfoData['config'][$config['config_key']] = $config['value'];
  146. unset($merchantInfoData['config'][$key]);
  147. }
  148. return $merchantInfoData;
  149. }
  150. /**
  151. * 计算优惠券优惠金额,并更新实付总金额和优惠详情中的优惠券优惠金额字段
  152. *
  153. * @param array $result
  154. * @param $cartList
  155. * @param $userCoupons
  156. * @param integer $totalPaymentPrice
  157. * @return array
  158. */
  159. private function calculateCoupon(array $result, $cartList, $userCoupons, int $merId, array $useCouponData, $user): array
  160. {
  161. if (empty($userCoupons) || empty($user)) {
  162. return $result;
  163. }
  164. // 会员和优惠券是否叠加
  165. $svipCouponMerge = merchantConfig($merId, 'svip_coupon_merge');
  166. $usePlatformCoupons = (isset($useCouponData[0]) && !empty($useCouponData[0])) ? [end($useCouponData[0])] : [];
  167. $useMerchantCoupons = (isset($useCouponData[1]) && !empty($useCouponData[1])) ? [end($useCouponData[1])] : [];
  168. $useProductCoupons = (isset($useCouponData[2]) && !empty($useCouponData[2])) ? $useCouponData[2] : [];
  169. $useCoupon = array_unique(array_merge($usePlatformCoupons, $useMerchantCoupons, $useProductCoupons));
  170. // 校验优惠券信息,校验优惠券是否可用
  171. $result['couponList'] = $this->getStoreCouponUserRepository()->validCoupon($userCoupons->toArray(), $useCoupon, $cartList, $result['totalPaymentPrice'], $merId);
  172. $usedCoupons = [];
  173. $couponRemainingPrice = [];
  174. $oldTotalPrice = $result['totalPaymentPrice'];
  175. $result['totalPaymentPrice'] = 0;
  176. $result['discountDetail']['couponsDiscountAmount'] = 0;
  177. foreach ($cartList as $key => $cart) {
  178. $defaultMerCoupon = 0;
  179. $defaultProCoupon = 0;
  180. $defaultPlaCoupon = 0;
  181. $paymentPrice = $cart['paymentPrice'];
  182. // 判断优惠券和会员是否可叠加使用
  183. $isNotOverlay = ($svipCouponMerge == 0 && $cart['useSvip']);
  184. foreach ($result['couponList'] as &$coupon) {
  185. if($cart['paymentPrice'] > 0 && !$coupon['disabled']) {
  186. $coupon['checked'] = false;
  187. if($useCoupon) {
  188. if ($isNotOverlay) {
  189. $result['totalPaymentPrice'] = $oldTotalPrice;
  190. throw new ValidateException('优惠券和会员不可叠加使用');
  191. continue;
  192. }
  193. if (!in_array($coupon['coupon_user_id'], $useCoupon)) {
  194. continue;
  195. }
  196. $coupon['checked'] = true;
  197. }
  198. // 平台券
  199. if(in_array($coupon['coupon']['type'], [10, 11, 12]) && !$defaultPlaCoupon && !isset($useCouponData[0])) {
  200. $coupon['checked'] = true;
  201. $defaultPlaCoupon = $coupon['coupon_user_id'];
  202. }
  203. // 店铺券
  204. if($coupon['coupon']['type'] == 0 && !$defaultMerCoupon && !isset($useCouponData[1])) {
  205. $coupon['checked'] = true;
  206. $defaultMerCoupon = $coupon['coupon_user_id'];
  207. }
  208. // 商品券
  209. if($coupon['coupon']['type'] == 1 && !$defaultProCoupon && !isset($useCouponData[2])) {
  210. $coupon['checked'] = true;
  211. $defaultProCoupon = $coupon['coupon_user_id'];
  212. }
  213. // 计算选择的优惠券金额
  214. if($coupon['checked']) {
  215. // 商品券
  216. if (!$coupon['product'] && $coupon['coupon']['type'] == 1) {
  217. // 如果该优惠券已经被使用,则跳过当前循环
  218. $usedProductCoupons = isset($usedCoupons['product']) ? array_column($usedCoupons['product'], 'id') : [];
  219. if (in_array($coupon['coupon_user_id'], $usedProductCoupons)) {
  220. continue;
  221. }
  222. // 商品券,只对指定商品进行优惠
  223. $coupon = $coupon->toArray();
  224. $couponProductIds = array_column($coupon['product'], 'product_id');
  225. if (in_array($cart['product_id'], $couponProductIds)) {
  226. $fee = max((float)bcsub($cart['paymentPrice'], $coupon['coupon_price'], 2), 0);
  227. $productCouponPrice = ($fee == 0) ? $cart['paymentPrice'] : $coupon['coupon_price'];
  228. $usedCoupons = $this->recodeUseCoupon($usedCoupons, $coupon['coupon_user_id'], $productCouponPrice, $coupon['coupon']['type'], $cart);
  229. $cart['paymentPrice'] = $fee;
  230. }
  231. continue;
  232. }
  233. // 平台券和店铺券,对所有商品进行优惠,按比例分配优惠金额
  234. $rate = bcdiv($coupon['coupon_price'], $oldTotalPrice, 4);
  235. if ($rate > 1) {
  236. $usedCoupons = $this->recodeUseCoupon($usedCoupons, $coupon['coupon_user_id'], $cart['paymentPrice'], $coupon['coupon']['type'], $cart);
  237. $cart['paymentPrice'] = 0;
  238. continue;
  239. }
  240. // 是否为最后一个商品,则将剩余的优惠券优惠金额分配给最后一个商品
  241. if (count($cartList) >1 && (count($cartList) == $key + 1) && isset($couponRemainingPrice[$coupon['coupon_id']])) {
  242. $cart['paymentPrice'] = (float)bcsub($cart['paymentPrice'], $couponRemainingPrice[$coupon['coupon_id']], 2);
  243. $usedCoupons = $this->recodeUseCoupon($usedCoupons, $coupon['coupon_user_id'], $couponRemainingPrice[$coupon['coupon_id']], $coupon['coupon']['type'], $cart);
  244. continue;
  245. }
  246. $couponPrice = (count($cartList) == 1) ? $coupon['coupon_price'] : bcmul($cart['paymentPrice'], $rate, 2);
  247. $cart['paymentPrice'] = (float)bcsub($cart['paymentPrice'], $couponPrice, 2);
  248. // 计算剩余优惠金额,用于下一个商品的优惠券分配
  249. $couponRemainingPrice[$coupon['coupon_id']] = (float)bcsub($couponRemainingPrice[$coupon['coupon_id']] ?? $coupon['coupon_price'], $couponPrice, 2);
  250. $usedCoupons = $this->recodeUseCoupon($usedCoupons, $coupon['coupon_user_id'], $couponPrice, $coupon['coupon']['type'], $cart);
  251. }
  252. }
  253. }
  254. $cart['couponsDiscount'] = (float)bcsub($paymentPrice, $cart['paymentPrice'], 2);
  255. $result['totalPaymentPrice'] += $cart['paymentPrice']; // 实付总金额
  256. $result['discountDetail']['couponsDiscountAmount'] += $cart['couponsDiscount']; // 优惠券优惠金额
  257. $result['usedCoupons'] = $usedCoupons;
  258. }
  259. if(!$usedCoupons) {
  260. // 如果使用了优惠券则再次校验优惠券信息
  261. $result['couponList'] = $this->getStoreCouponUserRepository()->validCoupon($result['couponList'], $useCoupon, $cartList, $result['totalPaymentPrice'], $merId);
  262. }
  263. return $result;
  264. }
  265. /**
  266. * 记录使用的优惠券信息,并区分店铺券、商品券和平台券
  267. *
  268. * @param array $usedCoupons
  269. * @param integer $couponId
  270. * @param float $couponPrice
  271. * @param integer $type
  272. * @param $cart
  273. * @return void
  274. */
  275. private function recodeUseCoupon(array $usedCoupons, int $couponId, float $couponPrice, int $type, $cart)
  276. {
  277. switch ($type) {
  278. case 0: // 店铺券
  279. $key = 'merchant';
  280. break;
  281. case 1: // 商品券
  282. $key = 'product';
  283. break;
  284. default: // 平台券
  285. $key = 'platform';
  286. break;
  287. }
  288. $usedCoupons[$key][] = [
  289. 'id' => $couponId,
  290. 'cartId' => $cart['cart_id'],
  291. 'price' => $couponPrice
  292. ];
  293. $cart["{$key}CouponDiscountAmount"] += $couponPrice;
  294. return $usedCoupons;
  295. }
  296. /**
  297. * 根据运费模板计算运费金额
  298. * $regionRule 运费规则数组
  299. * $aggregate 商品规格属性总量,type为0时,表示总数量;type为2时,表示总重量,type为3时,表示总体积。
  300. *
  301. * @param array $cartProductShippingFeeTemplate 运费模板数组
  302. * @return string
  303. */
  304. private function calculateShippingCost(array $cartProductShippingFeeTemplate): string
  305. {
  306. $fee = 0;
  307. foreach ($cartProductShippingFeeTemplate as $value) {
  308. if (empty($value['aggregate'])) {
  309. continue;
  310. }
  311. $value = $value->toArray();
  312. $region = $value['region'] ?? null;
  313. if (!$region) {
  314. continue;
  315. }
  316. $regionRule = array_shift($region);
  317. $aggregate = $value['aggregate'];
  318. $fee += $aggregate > 0 ? bcadd($regionRule['first_price'], bcmul(bcdiv(bcsub($aggregate, $regionRule['first'], 2), $regionRule['continue'], 2), $regionRule['continue_price'], 2), 2) : $regionRule['first_price'];
  319. }
  320. return $fee;
  321. }
  322. /**
  323. * 验证用户信息是否有效
  324. *
  325. * @param integer $uid
  326. * @return Model|null
  327. */
  328. private function validUser(int $uid): ?Model
  329. {
  330. if (!$uid) {
  331. return null;
  332. }
  333. $user = $this->getUserRepository()->userInfo($uid);
  334. if (empty($user)) {
  335. throw new ValidateException('用户不存在');
  336. }
  337. return $user;
  338. }
  339. /**
  340. * 验证地址信息是否有效
  341. *
  342. * @param [type] $addressId
  343. * @param [type] $uid
  344. * @return Model|null
  345. */
  346. private function validAddress($addressId, int $uid): ?Model
  347. {
  348. if (empty($addressId)) {
  349. return null;
  350. }
  351. $where['address_id'] = $addressId;
  352. if ($uid) {
  353. $where['uid'] = $uid;
  354. }
  355. $address = $this->getUserAddressRepository()->getWhere($where);
  356. if (empty($address)) {
  357. throw new ValidateException('地址不存在');
  358. }
  359. return $address;
  360. }
  361. /**
  362. * 验证购物车信息是否有效
  363. * 若存在失效商品则抛出异常
  364. * 若超出限购数也抛出异常
  365. * 若存在非正常商品也抛出
  366. *
  367. * @param $user
  368. * @param integer $merId
  369. * @param array $cartIds
  370. * @param $address
  371. * @return array
  372. */
  373. private function validCartList($user, int $merId, array $cartIds, $address): ?array
  374. {
  375. $res = $this->getStoreCartRepository()->getMerchantList($user, $merId, $cartIds, $address);
  376. if (count($res['fail'])) {
  377. throw new ValidateException('存在已失效商品,请检查');
  378. }
  379. return $res['list'];
  380. }
  381. /**
  382. * 获取优惠券信息
  383. *
  384. * @param integer $uid
  385. * @param integer $merId
  386. * @param array $useCoupon
  387. * @return void
  388. */
  389. private function fetchUserCoupons(int $uid, int $merId)
  390. {
  391. return $this->getStoreCouponUserRepository()->validUserCoupon($uid, $merId);
  392. }
  393. }