SplitAccountService.java 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. package com.caimei.modules.order.service;
  2. import com.alibaba.fastjson.JSON;
  3. import com.alibaba.fastjson.JSONObject;
  4. import com.caimei.modules.order.dao.NewOrderDao;
  5. import com.caimei.modules.order.dao.NewShopOrderDao;
  6. import com.caimei.modules.order.entity.*;
  7. import com.caimei.modules.order.utils.Disguiser;
  8. import com.caimei.redis.RedisService;
  9. import com.caimei.utils.MathUtil;
  10. import com.thinkgem.jeesite.common.service.BaseService;
  11. import okhttp3.*;
  12. import org.apache.commons.lang3.StringUtils;
  13. import org.springframework.stereotype.Service;
  14. import org.springframework.transaction.annotation.Transactional;
  15. import javax.annotation.Resource;
  16. import java.io.IOException;
  17. import java.lang.reflect.Field;
  18. import java.math.BigDecimal;
  19. import java.text.SimpleDateFormat;
  20. import java.util.*;
  21. import java.util.concurrent.TimeUnit;
  22. import java.util.concurrent.atomic.AtomicReference;
  23. import java.util.stream.Collectors;
  24. @Service
  25. @Transactional(readOnly = true)
  26. public class SplitAccountService extends BaseService {
  27. public static OkHttpClient client = new OkHttpClient.Builder()
  28. .connectTimeout(3, TimeUnit.SECONDS)
  29. .readTimeout(20, TimeUnit.SECONDS)
  30. .build();
  31. @Resource
  32. private NewOrderDao newOrderDao;
  33. @Resource
  34. private RedisService redisService;
  35. @Resource
  36. private NewShopOrderDao newShopOrderDao;
  37. @Transactional(readOnly = false, rollbackFor = Exception.class)
  38. public void SplitAccount(String shopOrderIds) {
  39. logger.info("【手动分账开始】>>>>>>>>>>手动分账");
  40. Calendar calendar = Calendar.getInstance();
  41. calendar.setTime(new Date());
  42. SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  43. String currentTime = format.format(calendar.getTime());
  44. List<String> ids = new ArrayList<>();
  45. if (shopOrderIds.contains(",")) {
  46. ids = Arrays.stream(shopOrderIds.split(",")).collect(Collectors.toList());
  47. } else {
  48. ids.add(shopOrderIds);
  49. }
  50. ids.forEach(i -> {
  51. // 查询未分账已支付收款
  52. List<OrderReceiptRelationPo> orderRelations = newOrderDao.getUndividedPaidReceipt(currentTime, i);
  53. List<SplitAccountPo> splitBillDetail = new ArrayList<>();
  54. AtomicReference<Double> costPrice = new AtomicReference<Double>(0d);
  55. AtomicReference<Double> cmCostPrice = new AtomicReference<Double>(0d);
  56. AtomicReference<Double> organizeCostPrice = new AtomicReference<Double>(0d);
  57. AtomicReference<Double> total = new AtomicReference<Double>(0d);
  58. // 收款对应的订单信息
  59. ShopOrderVo shopOrder = newOrderDao.getShopOrderListByOrderId(Integer.valueOf(i));
  60. for (OrderReceiptRelationPo orderRelation : orderRelations) {
  61. logger.info("【分账】>>>>>>>>>>子订单id:" + orderRelation.getShopOrderId() + "进入分账");
  62. setSplitAccountDetail(costPrice, organizeCostPrice, cmCostPrice, total, shopOrder, orderRelation);
  63. }
  64. if (MathUtil.compare(total, MathUtil.add(MathUtil.add(costPrice, cmCostPrice), organizeCostPrice)) <= 0) {
  65. logger.info("收款总金额不足分帐------------->total: " + total.get()
  66. + "costPrice: " + costPrice.get() + "----------"
  67. + "cmCostPrice: " + cmCostPrice.get() + "-------------"
  68. + "organizeCostPrice: " + organizeCostPrice.get() + "---------------");
  69. return;
  70. }
  71. /**
  72. * 线上订单,付第三方如果不为0,需要从供应商成本中支付
  73. */
  74. double payOther = newOrderDao.findPayOther(Integer.valueOf(i));
  75. if (MathUtil.compare(payOther, 0) > 0) {
  76. SplitAccountPo splitAccount = new SplitAccountPo();
  77. splitAccount.setOrderId(shopOrder.getOrderId());
  78. splitAccount.setShopOrderId(shopOrder.getShopOrderId());
  79. splitAccount.setSplitAccount(payOther);
  80. splitAccount.setProductType(6);
  81. splitAccount.setType(6);
  82. splitAccount.setSubUserNo(Constant.CUSTOMERNUM3);
  83. logger.info("付第三方分账参数------------->" + splitAccount);
  84. splitBillDetail.add(splitAccount);
  85. // 当前版本付第三方从供应商成本支出
  86. costPrice.updateAndGet(v -> MathUtil.sub(v, payOther).doubleValue());
  87. }
  88. if (costPrice.get() > 0) {
  89. SplitAccountPo splitAccount = new SplitAccountPo();
  90. splitAccount.setShopOrderId(shopOrder.getShopOrderId());
  91. splitAccount.setOrderId(shopOrder.getOrderId());
  92. splitAccount.setSplitAccount(costPrice.get());
  93. splitAccount.setProductType(1);
  94. // 该商品设置了商户号
  95. splitAccount.setType(4);
  96. splitAccount.setSubUserNo(shopOrder.getSplitCode());
  97. logger.info("成本分账参数------------->" + splitAccount.toString());
  98. splitBillDetail.add(splitAccount);
  99. }
  100. /** 成本分完,金额未尽,有组织佣金的情况,优先组织佣金
  101. * 当前版本,采美供应商上架丽格商城,会存在三成本均>0的情况
  102. * 若无组织佣金,所有剩余金额归于采美佣金
  103. */
  104. if (organizeCostPrice.get() > 0) {
  105. SplitAccountPo splitAccount = new SplitAccountPo();
  106. splitAccount.setShopOrderId(shopOrder.getShopOrderId());
  107. splitAccount.setOrderId(shopOrder.getOrderId());
  108. splitAccount.setSplitAccount(organizeCostPrice.get());
  109. splitAccount.setProductType(5);
  110. // 该商品设置了商户号
  111. splitAccount.setType(4);
  112. String code = newOrderDao.findSplitCodeByOrganize(shopOrder.getOrganizeId());
  113. if (StringUtils.isBlank(code)) {
  114. logger.info("组织的分帐号未空,无法分帐组织佣金!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
  115. return;
  116. }
  117. splitAccount.setSubUserNo(code);
  118. logger.info("组织佣金分账参数------------->" + splitAccount.toString());
  119. splitBillDetail.add(splitAccount);
  120. }
  121. /** 当前版本还要考虑分组织/集团佣金
  122. * 如果还有钱则为佣金,分到网络
  123. */
  124. if (cmCostPrice.get() > 0) {
  125. // 此处不考虑手续费,外部计算总额
  126. SplitAccountPo splitAccount = new SplitAccountPo();
  127. splitAccount.setOrderId(shopOrder.getOrderId());
  128. splitAccount.setShopOrderId(shopOrder.getShopOrderId());
  129. splitAccount.setSplitAccount(cmCostPrice.get());
  130. splitAccount.setProductType(3);
  131. splitAccount.setType(5);
  132. splitAccount.setSubUserNo(Constant.CUSTOMERNUM2);
  133. logger.info("佣金分账参数------------->" + splitAccount);
  134. splitBillDetail.add(splitAccount);
  135. }
  136. HashMap<String, BigDecimal> sbm = new HashMap<>();
  137. for (SplitAccountPo splitAccountPo : splitBillDetail) {
  138. String subUserNo = splitAccountPo.getSubUserNo();
  139. // 计算当前商户号总分账金额
  140. if (sbm.containsKey(subUserNo)) {
  141. BigDecimal v = MathUtil.add(sbm.get(subUserNo), splitAccountPo.getSplitAccount());
  142. sbm.put(subUserNo, v);
  143. } else {
  144. sbm.put(subUserNo, BigDecimal.valueOf(splitAccountPo.getSplitAccount()));
  145. }
  146. // splitcode相同的收款的时候已经是成本已分帐
  147. if (subUserNo.equals(shopOrder.getSplitCode())) {
  148. // 供应商自己收款,此部分金额留在自己商户号,作为成本分账
  149. splitAccountPo.setPayStatus(1);
  150. // 保存分账详情
  151. newOrderDao.insertSplitAccount(splitAccountPo);
  152. }
  153. }
  154. ArrayList<AccountPayOrder.AccountPayOrderExt.SplitBillRule> splitBillRules = new ArrayList<>();
  155. sbm.forEach((key, value) -> {
  156. /**
  157. * 不是自己的splitcode分走,是自己的不动
  158. * 把成本之外的金额加入splitRule分账参数,延续上面逻辑,供应商自己的成本由自己的商户号收款,
  159. * 自己成本部分不需要加入分账参数,仅需在else中做业务表参数处理
  160. */
  161. if (!key.equals(shopOrder.getSplitCode())) {
  162. // 佣金
  163. AccountPayOrder.AccountPayOrderExt.SplitBillRule splitBillRule = new AccountPayOrder.AccountPayOrderExt.SplitBillRule();
  164. // 分账计算的时候未处理手续费,总额处理手续费 x + x * 0.1% = value
  165. BigDecimal div = MathUtil.div(value, 1.001, 2);
  166. if (MathUtil.sub(value, div).doubleValue() < 0.01) {
  167. // 如果手续费小于最低手续费,取0.01
  168. div = MathUtil.round(MathUtil.sub(value, 0.01), 2);
  169. }
  170. splitBillRule.setSplitBillAmount(div);
  171. splitBillRule.setSplitBillMerchantNo(key);
  172. splitBillRules.add(splitBillRule);
  173. } else {
  174. // 修改子订单付款状态及付款金额
  175. newOrderDao.updateShopOrderByPayStatus(Integer.valueOf(i), value.doubleValue(), shopOrder.getPayStatus());
  176. SimpleDateFormat format2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  177. String currentTime2 = format2.format(new Date());
  178. // 保存付款单表
  179. PayShopPo payShop = new PayShopPo();
  180. payShop.setShopId(shopOrder.getShopId());
  181. payShop.setName("线上支付分账");
  182. payShop.setTotalAmount(value.doubleValue());
  183. payShop.setWipePayment(0d);
  184. payShop.setPayType(6);
  185. payShop.setStatus(1);
  186. payShop.setDelFlag(0);
  187. payShop.setApplyTime(currentTime2);
  188. payShop.setReviewTime(currentTime2);
  189. payShop.setPayTime(currentTime2);
  190. newOrderDao.insertPayShop(payShop);
  191. // 保存 付供应商记录
  192. PayShopRecordPo shopRecord = new PayShopRecordPo();
  193. shopRecord.setShopId(shopOrder.getShopId());
  194. shopRecord.setShopOrderId(shopOrder.getShopOrderId());
  195. shopRecord.setShopOrderNo(shopOrder.getShopOrderNo());
  196. shopRecord.setPayAmount(value.doubleValue());
  197. shopRecord.setWipePayment(0d);
  198. shopRecord.setPayType(6);
  199. shopRecord.setPayTime(currentTime2);
  200. shopRecord.setPayShopId(payShop.getId());
  201. shopRecord.setStatus(1);
  202. shopRecord.setDelFlag(0);
  203. newOrderDao.insertPayShopRecord(shopRecord);
  204. // 子订单是否全部付款
  205. List<String> payStatus = newShopOrderDao.findPayStatusByOrderID(shopOrder.getOrderId());
  206. boolean isPay = true;
  207. for (String shops : payStatus) {
  208. if (!"3".equals(shops)) {
  209. isPay = false;
  210. break;
  211. }
  212. }
  213. // 修改主订单付款状态
  214. if (isPay) {
  215. newOrderDao.updateOrderByPayStatus(shopOrder.getOrderId(), 3);
  216. } else {
  217. newOrderDao.updateOrderByPayStatus(shopOrder.getOrderId(), 2);
  218. }
  219. }
  220. });
  221. /**
  222. * 在上述else代码逻辑中已完成成本的业务表参数处理,在分账详情中排除成本,剩余佣金部分在分账结束收到
  223. * 成功返回码时处理(成本不参与分账)
  224. */
  225. splitBillDetail.removeIf(s -> s.getSubUserNo().equals(shopOrder.getSplitCode()));
  226. if (null != splitBillRules && splitBillRules.size() > 0) {
  227. //第三方分账接口
  228. try {
  229. AccountPayOrder accountPayOrder = new AccountPayOrder();
  230. accountPayOrder.setP1_bizType("AccountPaySub");
  231. accountPayOrder.setP2_signType("MD5");
  232. String format1 = new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss.SSS").format(new Date());
  233. accountPayOrder.setP3_timestamp(format1);
  234. String substring = format1.substring(20);
  235. // fz+当前微秒时间+原唯一订单号
  236. accountPayOrder.setP4_orderId("FZ" + substring + shopOrder.getShopOrderId());
  237. //付款账户子订单绑定商户号
  238. accountPayOrder.setP5_customerNumber(shopOrder.getSplitCode());
  239. AccountPayOrder.AccountPayOrderExt accountPayOrderExt = new AccountPayOrder.AccountPayOrderExt();
  240. //收款账户商编 填写splitBillRules时候不填写MerchantNo,Amount并且即使填写这两个参数不生效!!
  241. //accountPayOrderExt.setInMerchantNo(splitMoneyVo.getName());
  242. //accountPayOrderExt.setAmount(splitMoneyVo.getSplitMoney());
  243. accountPayOrderExt.setOrderType(AccountPayOrderType.TRANSFER);
  244. accountPayOrderExt.setServerCallbackUrl(Constant.prodSplit);
  245. accountPayOrderExt.setGoodsName("分账");
  246. if (null != splitBillRules && splitBillRules.size() > 0) {
  247. accountPayOrderExt.setSplitBillRules(splitBillRules);
  248. }
  249. String ext = JSON.toJSONString(accountPayOrderExt);
  250. logger.info("分账规则串json串:" + ext);
  251. accountPayOrder.setP6_ext(ext);
  252. // 生成签名
  253. StringBuilder builder = new StringBuilder();
  254. builder.append(Constant.SPLIT)
  255. .append(accountPayOrder.getP1_bizType()).append(Constant.SPLIT)
  256. .append(accountPayOrder.getP2_signType()).append(Constant.SPLIT)
  257. .append(accountPayOrder.getP3_timestamp()).append(Constant.SPLIT)
  258. .append(accountPayOrder.getP4_orderId()).append(Constant.SPLIT)
  259. .append(accountPayOrder.getP5_customerNumber()).append(Constant.SPLIT)
  260. .append(accountPayOrder.getP6_ext()).append(Constant.SPLIT)
  261. .append(Constant.XUNI);
  262. String sign = Disguiser.disguiseMD5(builder.toString().trim());
  263. Map<String, String> bean = convertBean(accountPayOrder);
  264. logger.info("--------------------> 发送分账参数: " + bean);
  265. Map<String, String> map = postForm(bean, Constant.FZ, sign, Map.class);
  266. logger.info("----------------分账返回数据: " + map.toString());
  267. if (map != null) {
  268. String code = map.get("rt5_retCode");
  269. if (!"0000".equals(code)) {
  270. String msg = map.get("rt6_retMsg");
  271. logger.info("【手动分账】>>>>>>>>>>手动分账失败>>>>>>>msg:" + msg);
  272. } else {
  273. for (SplitAccountPo splitAccount : splitBillDetail) {
  274. splitAccount.setPayStatus(1);
  275. // 保存分账详情
  276. newOrderDao.insertSplitAccount(splitAccount);
  277. }
  278. redisService.remove("XSFZMDS");
  279. logger.info("【手动分账】>>>>>>>>>>此订单分账结束");
  280. }
  281. }
  282. } catch (Exception e) {
  283. logger.error("【手动分账】>>>>>>>>>>错误信息", e);
  284. }
  285. }
  286. });
  287. }
  288. public void setSplitAccountDetail(AtomicReference<Double> costPrice, AtomicReference<Double> organizePrice,
  289. AtomicReference<Double> cmCostPrice, AtomicReference<Double> total,
  290. ShopOrderVo shopOrder, OrderReceiptRelationPo orderRelation) {
  291. // 待分账总金额
  292. double splitAmount = orderRelation.getAssociateAmount();
  293. total.updateAndGet(v -> MathUtil.add(v, splitAmount).doubleValue());
  294. // 总手续费
  295. double procedureFee;
  296. if (12 == orderRelation.getPayType()) {
  297. procedureFee = 10.00;
  298. } else if (17 == orderRelation.getPayType()) {
  299. //b2c 0.2%
  300. procedureFee = MathUtil.mul(splitAmount, 0.002, 2).doubleValue();
  301. //b2c最低手续费0.1
  302. if (procedureFee < 0.1) {
  303. procedureFee = 0.1;
  304. }
  305. } else if (13 == orderRelation.getPayType() || 15 == orderRelation.getPayType()) {
  306. //微信0.65%
  307. procedureFee = MathUtil.mul(splitAmount, 0.0065, 2).doubleValue();
  308. } else if (29 == orderRelation.getPayType()) {
  309. procedureFee = MathUtil.mul(splitAmount, 0.003, 2).doubleValue();
  310. if (procedureFee < 0.1) {
  311. procedureFee = 0.1;
  312. }
  313. } else if (30 == orderRelation.getPayType()) {
  314. procedureFee = MathUtil.mul(splitAmount, 0.006, 2).doubleValue();
  315. if (procedureFee < 0.1) {
  316. procedureFee = 0.1;
  317. }
  318. } else {
  319. //手续费 其他0.25%
  320. procedureFee = MathUtil.mul(splitAmount, 0.0025, 2).doubleValue();
  321. }
  322. if (MathUtil.compare(procedureFee, 0.01) <= 0) {
  323. procedureFee = 0.01;
  324. }
  325. // 商品数据
  326. List<OrderProductVo> orderProductList = newOrderDao.getOrderProductByShopOrderId(shopOrder.getShopOrderId());
  327. for (OrderProductVo orderProduct : orderProductList) {
  328. /** 价格 * 数量
  329. * 前版本为全比例成本,当前版本重新加回固定成本,新增字段cmCostPrice(平台服务费,分帐分采美),organizeCostPrice(组织/集团成本。分帐分组织)
  330. * 在此规则下,分帐方式取cop固定成本值。因存在成本修改,不能取cm_sku实时更新成本!
  331. */
  332. costPrice.updateAndGet(v -> MathUtil.add(v, MathUtil.mul(orderProduct.getCostPrice(), orderProduct.getNum(), 2)).doubleValue());
  333. organizePrice.updateAndGet(v -> MathUtil.add(v, MathUtil.mul(orderProduct.getOrganizeCostPrice(), orderProduct.getNum(), 2)).doubleValue());
  334. cmCostPrice.updateAndGet(v -> MathUtil.add(v, MathUtil.mul(orderProduct.getCmCostPrice(), orderProduct.getNum(), 2)).doubleValue());
  335. }
  336. //手续费承担方 线上支付手续费:默认1采美承担,2供应商承担
  337. Integer supportFlag = newOrderDao.findSupport(shopOrder.getShopOrderId());
  338. if (2 == supportFlag) {
  339. //供应商自己承担手续费
  340. costPrice.set(costPrice.get() - procedureFee);
  341. }
  342. }
  343. public <T> T postForm(Map<String, String> params, String url, String sign, Class<T> clazz) {
  344. FormBody.Builder builder = new FormBody.Builder();
  345. for (Map.Entry<String, String> entry : params.entrySet()) {
  346. builder.add(entry.getKey(), entry.getValue());
  347. }
  348. builder.add("sign", sign);
  349. Request request = new Request.Builder() // okHttp post
  350. .url(url)
  351. .post(builder.build())
  352. .build();
  353. Response response = null;
  354. try {
  355. response = client.newCall(request).execute();
  356. } catch (IOException e) {
  357. throw new IllegalStateException("请求出错", e);
  358. }
  359. if (!response.isSuccessful()) {
  360. try {
  361. logger.info(response.body().string());
  362. } catch (IOException e) {
  363. e.printStackTrace();
  364. }
  365. throw new RuntimeException("请求失败了: http response code: " + response.code());
  366. }
  367. ResponseBody body = response.body();
  368. String content = null;
  369. try {
  370. content = body.string();
  371. } catch (IOException e) {
  372. throw new IllegalStateException("IO异常", e);
  373. }
  374. JSONObject res = JSON.parseObject(content);
  375. if (!res.getBooleanValue("rt4_success")) {
  376. logger.error("error: " + res.getString("rt6_retMsg"));
  377. }
  378. /** rt4_success 为 true,需验签 **/
  379. return res.toJavaObject(clazz);
  380. }
  381. public Map<String, String> convertBean(Object bean) {
  382. Class clazz = bean.getClass();
  383. Field[] fields = clazz.getDeclaredFields();
  384. for (Field f : fields) {
  385. f.setAccessible(true);
  386. }
  387. try {
  388. Map<String, String> retMap = new LinkedHashMap<>();
  389. for (Field f : fields) {
  390. String key = f.toString().substring(f.toString().lastIndexOf(".") + 1);
  391. Object value = f.get(bean);
  392. if (value == null) {
  393. value = "";
  394. }
  395. retMap.put(key, (String) value);
  396. }
  397. return retMap;
  398. } catch (Exception e) {
  399. logger.info("分账", e);
  400. throw new IllegalStateException("分账异常", e);
  401. }
  402. }
  403. }