WxService.java 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. package com.pj.api.wx.service;
  2. import cn.hutool.core.date.DateUtil;
  3. import cn.hutool.core.util.NumberUtil;
  4. import cn.hutool.core.util.RandomUtil;
  5. import cn.hutool.core.util.StrUtil;
  6. import cn.hutool.core.util.XmlUtil;
  7. import cn.hutool.crypto.SecureUtil;
  8. import cn.hutool.http.HttpUtil;
  9. import cn.hutool.json.JSONObject;
  10. import cn.hutool.json.JSONUtil;
  11. import com.alibaba.fastjson.JSON;
  12. import com.pj.api.wx.WxUtils;
  13. import com.pj.api.wx.bo.*;
  14. import com.pj.api.wx.vo.PrePayVO;
  15. import com.pj.constants.business.CarEnum;
  16. import com.pj.constants.business.PayEnum;
  17. import com.pj.current.config.MyConfig;
  18. import com.pj.current.config.PartConfig;
  19. import com.pj.current.config.WxConfig;
  20. import com.pj.project.tb_account.AutomaticPay;
  21. import com.pj.project.tb_account.TbAccountService;
  22. import com.pj.project.tb_business.TbBusiness;
  23. import com.pj.project.tb_business.TbBusinessService;
  24. import com.pj.project.tb_business_car.TbBusinessCar;
  25. import com.pj.project.tb_business_car.TbBusinessCarService;
  26. import com.pj.project.tb_business_item.TbBusinessItem;
  27. import com.pj.project.tb_business_item.TbBusinessItemService;
  28. import com.pj.project.tb_charge_record.TbChargeRecord;
  29. import com.pj.project.tb_fee_details.TbFeeDetailsService;
  30. import com.pj.project.tb_fee_statistics.TbFeeStatisticsService;
  31. import com.pj.project.tb_goods.TbGoods;
  32. import com.pj.project.tb_goods.TbGoodsService;
  33. import com.pj.project.tb_invoice_order.TbInvoiceOrderService;
  34. import com.pj.project.tb_order.TbOrder;
  35. import com.pj.project.tb_order.TbOrderService;
  36. import com.pj.project.wx_send_msg.WxSendMsg;
  37. import com.pj.project.wx_send_msg.WxSendMsgService;
  38. import com.pj.project4sp.global.BusinessException;
  39. import com.pj.utils.cache.RedisUtil;
  40. import lombok.extern.slf4j.Slf4j;
  41. import org.springframework.context.annotation.Lazy;
  42. import org.springframework.scheduling.annotation.Async;
  43. import org.springframework.stereotype.Service;
  44. import org.springframework.transaction.annotation.Transactional;
  45. import javax.annotation.Resource;
  46. import javax.servlet.http.HttpServletRequest;
  47. import java.math.BigDecimal;
  48. import java.net.URLEncoder;
  49. import java.nio.charset.Charset;
  50. import java.util.*;
  51. import java.util.stream.Collectors;
  52. @Service
  53. @Transactional
  54. @Slf4j
  55. public class WxService {
  56. @Resource
  57. @Lazy
  58. private TbBusinessService tbBusinessService;
  59. @Resource
  60. WxConfig wxConfig;
  61. @Resource
  62. MyConfig myConfig;
  63. @Resource
  64. PartConfig partConfig;
  65. @Resource
  66. WxUtils wxUtils;
  67. @Resource
  68. TbBusinessCarService tbBusinessCarService;
  69. @Resource
  70. @Lazy
  71. TbBusinessItemService tbBusinessItemService;
  72. @Resource
  73. TbFeeStatisticsService tbFeeStatisticsService;
  74. @Resource
  75. private TbAccountService tbAccountService;
  76. @Resource
  77. TbFeeDetailsService tbFeeDetailsService;
  78. @Resource
  79. private TbOrderService tbOrderService;
  80. @Resource
  81. private TbGoodsService tbGoodsService;
  82. @Resource
  83. private WxSendMsgService wxSendMsgService;
  84. @Resource
  85. private TbInvoiceOrderService tbInvoiceOrderService;
  86. @Resource
  87. private AutomaticPay automaticPay;
  88. /**
  89. * 统一下单接口
  90. */
  91. public Map<String, String> prePay(HttpServletRequest request) throws Exception {
  92. String tradeType = request.getParameter("tradeType");
  93. String money = request.getParameter("money");
  94. if (Double.valueOf(money) <= 0) {
  95. throw new BusinessException("金额不正确");
  96. }
  97. String openid = request.getParameter("openid");
  98. String desc = request.getParameter("desc");
  99. // String openid = "oDWvn5w-hVkzUuKeY7OBXBV_l1rU";
  100. String businessId = request.getParameter("b");
  101. String c = request.getParameter("c");
  102. String a = request.getParameter("a");
  103. Attach atchMap = new Attach();
  104. atchMap.setC(c).setB(businessId).setA(a);
  105. String out_trade_no = RandomUtil.randomString(32);
  106. Map<String, String> params = new HashMap<>();
  107. params.put("appid", wxConfig.getAppId());
  108. params.put("mch_id", wxConfig.getMachId());
  109. params.put("openid", openid);
  110. params.put("nonce_str", RandomUtil.randomString(32));
  111. params.put("body", handlerDesc(desc));
  112. params.put("out_trade_no", out_trade_no);
  113. // params.put("attach", JSONUtil.toJsonStr(atchMap));
  114. String total_free = partConfig.isTestEnv() ? "1" : NumberUtil.mul(money, 100 + "").intValue() + "";
  115. log.info("pay free:{}", total_free);
  116. params.put("total_fee", total_free);
  117. // params.put("total_fee", "1");
  118. params.put("spbill_create_ip", getIpAddress(request));
  119. params.put("notify_url", myConfig.getDomain() + "/wx/notify");
  120. params.put("trade_type", tradeType);
  121. params.put("scene_info", SceneInfoBO.getSceneInfo(myConfig.getDomain(), wxConfig.getTitle()));
  122. String sign = wxUtils.sign(params, wxConfig.getKey());
  123. log.info("wx pay sign result:{}", sign);
  124. params.put("sign", sign);
  125. String prePayUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder";
  126. String xmlParam = XmlUtil.mapToXmlStr(params);
  127. log.info("wx pay xml params:{}", xmlParam);
  128. String result = HttpUtil.createPost(prePayUrl).header("Content-Type", "text/xml").body(xmlParam)
  129. .execute().body();
  130. Map<String, Object> resultMap = XmlUtil.xmlToMap(result);
  131. log.info("request pre pay url result:{}", resultMap);
  132. PrePayVO vo = new PrePayVO();
  133. if ("SUCCESS".equals(resultMap.get("return_code")) && "SUCCESS".equals(resultMap.get("result_code"))) {
  134. log.info("生成预支付订单成功,mweb_url:{}", resultMap.get("mweb_url"));
  135. Object webUrl = resultMap.get("mweb_url");
  136. Object prePayId = resultMap.get("prepay_id");
  137. String preId = prePayId == null ? "" : prePayId.toString();
  138. vo.setPayUrl(webUrl == null ? "" : webUrl.toString())
  139. .setPrePayId(preId)
  140. .setTradeType(tradeType);
  141. Map<String, String> payParams = getPayParams(openid, preId);
  142. payParams.put("outTradeNo", out_trade_no);
  143. TbOrder tbOrder = new TbOrder();
  144. tbOrder.setAttach(JSONUtil.toJsonStr(atchMap))
  145. .setOpenid(openid)
  146. .setOrderTime(new Date())
  147. .setOutTradeNo(out_trade_no).setPrice(money);
  148. return payParams;
  149. }
  150. throw new Exception("支付信息有误");
  151. }
  152. public Map<String, String> getPayParams(String openid, String prePayId) {
  153. Map<String, String> signMap = new HashMap<>(10);
  154. signMap.put("appId", wxConfig.getAppId());
  155. signMap.put("timeStamp", System.currentTimeMillis() / 1000 + "");
  156. signMap.put("nonceStr", RandomUtil.randomString(32));
  157. signMap.put("signType", "MD5");
  158. signMap.put("package", "prepay_id=" + prePayId);
  159. String sign = wxUtils.sign(signMap, wxConfig.getKey());
  160. signMap.put("paySign", sign);
  161. signMap.put("openId", openid);
  162. return signMap;
  163. }
  164. public Map<String, String> getPayP(String timeStamp, String nonceStr, String openid, String pack) {
  165. Map<String, String> signMap = new HashMap<>(10);
  166. signMap.put("appId", wxConfig.getAppId());
  167. signMap.put("timeStamp", timeStamp);
  168. signMap.put("nonceStr", nonceStr);
  169. signMap.put("signType", "MD5");
  170. signMap.put("package", pack);
  171. String sign = wxUtils.sign(signMap, wxConfig.getKey());
  172. signMap.put("paySign", sign);
  173. signMap.put("openId", openid);
  174. return signMap;
  175. }
  176. public static String getIpAddress(HttpServletRequest request) {
  177. String ip = request.getHeader("x-forwarded-for");
  178. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
  179. ip = request.getHeader("Proxy-Client-IP");
  180. }
  181. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
  182. ip = request.getHeader("WL-Proxy-Client-IP");
  183. }
  184. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
  185. ip = request.getRemoteAddr();
  186. }
  187. if (ip.contains(",")) {
  188. return ip.split(",")[0];
  189. } else {
  190. return ip;
  191. }
  192. }
  193. public void WxNotify(NotifyBO notifyBO) {
  194. String orderStatus = notifyBO.getTradeStatus();
  195. if (!"SUCCESS".equals(orderStatus) && !"FINISH".equals(orderStatus)) {
  196. log.error("支付订单回调失败:{}", JSONUtil.toJsonStr(notifyBO));
  197. return;
  198. }
  199. String outTradeNo = notifyBO.getOutTradeNo();
  200. if (StrUtil.isNotEmpty(RedisUtil.get(outTradeNo))) {
  201. log.error("========订单已处理==========:{}", outTradeNo);
  202. return;
  203. }
  204. RedisUtil.setByMINUTES(outTradeNo, DateUtil.now(), 10);
  205. TbOrder tbOrder = tbOrderService.findByOutTradeNo(outTradeNo);
  206. if (tbOrder == null) {
  207. log.error("========订单不存在==========:{}", outTradeNo);
  208. return;
  209. }
  210. String payopenid = tbOrder.getOpenid();
  211. String total_fee = notifyBO.getTotalFee();
  212. BigDecimal money = new BigDecimal(total_fee).divide(new BigDecimal(100), 2, BigDecimal.ROUND_UP);
  213. String attachStr = tbOrder.getAttach();
  214. String transactionId = notifyBO.getTransactionId();
  215. Date now = new Date();
  216. String timeEnd = notifyBO.getTimeEnd();
  217. Date payTime = now;
  218. if (StrUtil.isNotEmpty(timeEnd)) {
  219. payTime = DateUtil.parse(timeEnd, "yyyyMMddHHmmss");
  220. }
  221. if (StrUtil.isNotEmpty(attachStr)) {
  222. Attach attach = JSONUtil.toBean(attachStr, Attach.class);
  223. List<PriceBO> cars = JSONUtil.toList(attach.getC(), PriceBO.class);
  224. for (PriceBO bo1 : cars) {
  225. TbBusinessCar car = tbBusinessCarService.getById(bo1.getId());
  226. BigDecimal price = bo1.getP();
  227. if (price == null || price.doubleValue() <= 0) {
  228. log.error("付款金额不正确:{}", JSONUtil.toJsonStr(notifyBO));
  229. continue;
  230. }
  231. car.setPay(1).setMoney(price).setPayTime(payTime)
  232. .setPayType(CarEnum.PayTypeEnum.HAS_PAY_TYPE.getType())
  233. .setOutTradeNo(outTradeNo)
  234. .setPayOpenid(payopenid);
  235. tbBusinessCarService.updateById(car);
  236. //支付完之后要解绑
  237. automaticPay.unbindRun(car.getCarNo());
  238. }
  239. tbFeeDetailsService.chargeParkFee(cars, transactionId, outTradeNo, payTime, PayEnum.PayType.WX_PAY);//添加cars的收费明细
  240. String businessId = attach.getB();
  241. Date finalPayTime = payTime;
  242. if (StrUtil.isNotEmpty(businessId)) {
  243. List<String> businessIds = StrUtil.splitTrim(businessId, ",");
  244. List<TbBusiness> businessList = tbBusinessService.listByIds(businessIds);
  245. businessList = businessList.stream().filter(tbBusiness -> tbBusiness.getPayStatus() != PayEnum.PayStatusEnum.HAS_PAY_CONFIRM.getCode()).collect(Collectors.toList());
  246. businessIds = businessList.stream().map(TbBusiness::getId).collect(Collectors.toList());
  247. List<TbBusinessItem> items = tbBusinessItemService.findByBusinessIdList(businessIds);
  248. items = items.stream().filter(item -> item.getPayStatus() == 0).collect(Collectors.toList());
  249. for (TbBusiness tbBusiness : businessList) {
  250. tbBusiness.setPayTime(payTime).setPayType(PayEnum.PayType.WX_PAY.getCode()).setConfirmInput(1).setConfirmInputTime(payTime)
  251. .setPayMoney(tbBusiness.getItemPrice()).setPayOpenid(payopenid)
  252. .setCalculateId(tbOrder.getCalculateId())
  253. .setPayNo(transactionId).setOutTradeNo(outTradeNo);
  254. tbBusiness.setPayStatus(PayEnum.PayStatusEnum.HAS_PAY_CONFIRM.getCode());
  255. tbBusinessService.updateById(tbBusiness);
  256. TbGoods tbGoods = tbGoodsService.getById(tbBusiness.getGoodsId());
  257. List<TbBusinessCar> carList = tbBusinessCarService.findOtherBusinessCar(businessId);
  258. carList.forEach(tbBusinessCar -> {
  259. String carType = tbBusinessCar.getCarType();
  260. //
  261. if (CarEnum.CarTypeEnum.EMPTY_TYPE.getType().equals(carType) && tbGoods.getChinaCarPay() == 0
  262. || CarEnum.CarTypeEnum.WEIGHT_TYPE.getType().equals(carType) && tbGoods.getVietnamCarPay() == 0) {
  263. tbBusinessCar.setPay(1).setPayTime(finalPayTime).setPayOpenid(payopenid);
  264. tbBusinessCarService.updateById(tbBusinessCar);
  265. }
  266. });
  267. }
  268. items.forEach(tbBusinessItem -> tbBusinessItem.setPayStatus(1).setPayTime(finalPayTime).setCalculateId(tbOrder.getCalculateId()));
  269. tbFeeDetailsService.chargeBusinessFee(items, transactionId, outTradeNo, payTime, PayEnum.PayType.WX_PAY);//添加items的收费明细
  270. tbBusinessItemService.updateBatchById(items);
  271. }
  272. tbFeeStatisticsService.addOrUpdateStatistic(payTime);//更新当前日期的日统计
  273. String a = attach.getA();
  274. if (StrUtil.isNotEmpty(a)) {//充值的======>
  275. AccountChargeBO chargeBO = JSONUtil.toBean(a, AccountChargeBO.class);
  276. tbAccountService.recharge(chargeBO.getId(), chargeBO.getC(), TbChargeRecord.Ch.WECHAT.getType(), money);
  277. }
  278. }
  279. tbOrder.setOrderStatus(orderStatus)
  280. .setTransactionId(notifyBO.getTransactionId())
  281. .setCompleteDate(notifyBO.getTimeEnd());
  282. tbOrderService.updateById(tbOrder);
  283. tbInvoiceOrderService.addT(transactionId);//生成开票订单
  284. }
  285. public String getRedirectUrl(String path, String state) {
  286. String redirectUrl = myConfig.getWebDomain() + path;
  287. String encoderUrl = URLEncoder.encode(redirectUrl);
  288. String url = wxConfig.getAuthLoginUrl().replace("REDIRECT_URI", encoderUrl);
  289. if (StrUtil.isNotEmpty(state)) {
  290. url = url.replace("STATE", state);
  291. }
  292. log.info("get redirect url:{}", url);
  293. return url;
  294. }
  295. public String getOpenidByCode(String code, String openid) {
  296. String openidUrl = wxConfig.getOpenidUrl().replace("CODE", code);
  297. String resp = HttpUtil.get(openidUrl);
  298. JSONObject result = JSONUtil.parseObj(resp);
  299. log.info("get openid result:{}", resp);
  300. String newOpenid = result.getStr("openid");
  301. if (StrUtil.isNotEmpty(newOpenid)) {
  302. return newOpenid;
  303. }
  304. return openid;
  305. }
  306. public SortedMap getWxConfig(String url) {
  307. Long nowTime = System.currentTimeMillis() / 1000;
  308. String jsTicket = RedisUtil.get(wxConfig.getJsApiTicketKey());
  309. SortedMap<String, Object> params = new TreeMap<>();
  310. params.put("noncestr", RandomUtil.randomString(32));
  311. params.put("jsapi_ticket", jsTicket);
  312. params.put("timestamp", nowTime);
  313. params.put("url", url);
  314. String sign = SecureUtil.sha1(params.toString().substring(1, params.toString().lastIndexOf("}")).replaceAll(", ", "&"));
  315. params.put("sign", sign);
  316. params.put("appId", wxConfig.getAppId());
  317. return params;
  318. }
  319. @Async
  320. public void sendTemplateMsg(String templateId, String openid, MsgDataBO data) {
  321. String accessToken = RedisUtil.get(wxConfig.getAccessTokenKey());
  322. BaseTemplate baseTemplate = new BaseTemplate(openid, templateId, data);
  323. String json = JSON.toJSONString(baseTemplate);
  324. String url = wxConfig.getSendMsgUrl().replace("ACCESS_TOKEN", accessToken);
  325. String resp = HttpUtil.post(url, json);
  326. log.info("send wx msg{},{},{};return :{}", templateId, openid, JSONUtil.toJsonStr(data), resp);
  327. }
  328. @Async
  329. public void sendTemplateMsg(String templateId, String openid, Object data, String detailUrl) {
  330. if (StrUtil.isEmpty(openid)) {
  331. return;
  332. }
  333. String accessToken = RedisUtil.get(wxConfig.getAccessTokenKey());
  334. BaseTemplate baseTemplate = new BaseTemplate(openid, templateId, detailUrl, data);
  335. String json = JSON.toJSONString(baseTemplate);
  336. String url = wxConfig.getSendMsgUrl().replace("ACCESS_TOKEN", accessToken);
  337. String resp = HttpUtil.post(url, json);
  338. JSONObject result = JSONUtil.parseObj(resp);
  339. int code = result.getInt("errcode");
  340. log.info("send wx msg{},{},{};return :{}", templateId, openid, JSONUtil.toJsonStr(data), resp);
  341. WxSendMsg wxSendMsg = wxSendMsgService.findByOpenidAndDetailUrl(openid, detailUrl);
  342. if (code == 42001) {//token过期导致
  343. if (wxSendMsg == null) {
  344. wxSendMsg = new WxSendMsg();
  345. wxSendMsg.setCreateTime(DateUtil.now());
  346. }
  347. wxSendMsg.setCode(code).setDetailUrl(detailUrl)
  348. .setMsgData(JSONUtil.toJsonStr(data)).setOpenid(openid).setTemplateId(templateId)
  349. .setReturnMsg(result.getStr("errmsg"));
  350. wxSendMsgService.saveOrUpdate(wxSendMsg);
  351. } else if (wxSendMsg != null) {
  352. wxSendMsgService.delete(wxSendMsg.getId());
  353. }
  354. }
  355. private String handlerDesc(String desc) {
  356. if (StrUtil.isEmpty(desc)) {
  357. return "";
  358. }
  359. if (desc.getBytes(Charset.forName("utf-8")).length > 128) {
  360. desc = StrUtil.sub(desc, 0, desc.lastIndexOf("-"));
  361. desc = handlerDesc(desc);
  362. }
  363. return desc;
  364. }
  365. }