WxService.java 17 KB

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