WxService.java 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. package com.pj.api.wx.service;
  2. import cn.hutool.cache.CacheUtil;
  3. import cn.hutool.cache.impl.TimedCache;
  4. import cn.hutool.core.date.DateUtil;
  5. import cn.hutool.core.net.URLDecoder;
  6. import cn.hutool.core.util.NumberUtil;
  7. import cn.hutool.core.util.RandomUtil;
  8. import cn.hutool.core.util.StrUtil;
  9. import cn.hutool.core.util.XmlUtil;
  10. import cn.hutool.crypto.SecureUtil;
  11. import cn.hutool.http.HttpUtil;
  12. import cn.hutool.json.JSONObject;
  13. import cn.hutool.json.JSONUtil;
  14. import com.alibaba.fastjson.JSON;
  15. import com.pj.api.wx.WxUtils;
  16. import com.pj.api.wx.bo.*;
  17. import com.pj.api.wx.vo.PrePayVO;
  18. import com.pj.current.config.MyConfig;
  19. import com.pj.current.config.PartConfig;
  20. import com.pj.current.config.WxConfig;
  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_costomer.TbCostomer;
  30. import com.pj.project.tb_costomer.TbCostomerService;
  31. import com.pj.project.tb_fee_details.TbFeeDetailsService;
  32. import com.pj.project.tb_fee_statistics.TbFeeStatisticsService;
  33. import com.pj.project.tb_order.TbOrder;
  34. import com.pj.project.tb_order.TbOrderService;
  35. import com.pj.project.tb_pay_record.TbPayRecord;
  36. import com.pj.project.tb_pay_record.TbPayRecordService;
  37. import com.pj.project4sp.global.BusinessException;
  38. import com.pj.utils.cache.RedisUtil;
  39. import lombok.extern.slf4j.Slf4j;
  40. import org.springframework.context.annotation.Lazy;
  41. import org.springframework.scheduling.annotation.Async;
  42. import org.springframework.stereotype.Service;
  43. import org.springframework.transaction.annotation.Transactional;
  44. import javax.annotation.Resource;
  45. import javax.servlet.http.HttpServletRequest;
  46. import java.math.BigDecimal;
  47. import java.nio.charset.Charset;
  48. import java.util.*;
  49. @Service
  50. @Transactional
  51. @Slf4j
  52. public class WxService {
  53. @Resource
  54. @Lazy
  55. private TbBusinessService tbBusinessService;
  56. @Resource
  57. WxConfig wxConfig;
  58. @Resource
  59. MyConfig myConfig;
  60. @Resource
  61. PartConfig partConfig;
  62. @Resource
  63. WxUtils wxUtils;
  64. @Resource
  65. TbBusinessCarService tbBusinessCarService;
  66. @Resource
  67. @Lazy
  68. TbBusinessItemService tbBusinessItemService;
  69. @Resource
  70. TbFeeStatisticsService tbFeeStatisticsService;
  71. @Resource
  72. private TbAccountService tbAccountService;
  73. @Resource
  74. TbFeeDetailsService tbFeeDetailsService;
  75. @Resource
  76. private TbOrderService tbOrderService;
  77. private final List<String> CAR_LIST = StrUtil.splitTrim("浙,粤,京,津,冀,晋,蒙,辽,黑,沪,吉,苏,皖,赣,鲁,豫,鄂,湘,桂,琼,渝,川,贵,云,藏, 陕, 甘, 青, 宁", ",");
  78. /**
  79. * 统一下单接口
  80. */
  81. public Map<String, String> prePay(HttpServletRequest request) throws Exception {
  82. String tradeType = request.getParameter("tradeType");
  83. String money = request.getParameter("money");
  84. if (Double.valueOf(money) <= 0) {
  85. throw new BusinessException("金额不正确");
  86. }
  87. String type = request.getParameter("type");
  88. if (StrUtil.isEmpty(type)) {
  89. throw new BusinessException("参数异常");
  90. }
  91. String openid = request.getParameter("openid");
  92. String desc = request.getParameter("desc");
  93. // String openid = "oDWvn5w-hVkzUuKeY7OBXBV_l1rU";
  94. String businessId = request.getParameter("b");
  95. String c = request.getParameter("c");
  96. String a = request.getParameter("a");
  97. Attach atchMap = new Attach();
  98. atchMap.setC(c).setB(businessId).setA(a);
  99. String out_trade_no = RandomUtil.randomString(32);
  100. Map<String, String> params = new HashMap<>();
  101. params.put("appid", wxConfig.getAppId());
  102. params.put("mch_id", wxConfig.getMachId());
  103. params.put("openid", openid);
  104. params.put("nonce_str", RandomUtil.randomString(32));
  105. params.put("body", desc);
  106. params.put("out_trade_no", out_trade_no);
  107. // params.put("attach", JSONUtil.toJsonStr(atchMap));
  108. String total_free = partConfig.isTestEnv() ? "1" : NumberUtil.mul(money, 100 + "").intValue() + "";
  109. log.info("pay free:{}", total_free);
  110. params.put("total_fee", total_free);
  111. // params.put("total_fee", "1");
  112. params.put("spbill_create_ip", getIpAddress(request));
  113. params.put("notify_url", myConfig.getDomain() + "/wx/notify");
  114. params.put("trade_type", tradeType);
  115. params.put("scene_info", SceneInfoBO.getSceneInfo(myConfig.getDomain(), wxConfig.getTitle()));
  116. String sign = wxUtils.sign(params, wxConfig.getKey());
  117. log.info("wx pay sign result:{}", sign);
  118. params.put("sign", sign);
  119. String prePayUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder";
  120. String xmlParam = XmlUtil.mapToXmlStr(params);
  121. log.info("wx pay xml params:{}", xmlParam);
  122. String result = HttpUtil.createPost(prePayUrl).header("Content-Type", "text/xml").body(xmlParam)
  123. .execute().body();
  124. Map<String, Object> resultMap = XmlUtil.xmlToMap(result);
  125. log.info("request pre pay url result:{}", resultMap);
  126. PrePayVO vo = new PrePayVO();
  127. if ("SUCCESS".equals(resultMap.get("return_code")) && "SUCCESS".equals(resultMap.get("result_code"))) {
  128. log.info("生成预支付订单成功,mweb_url:{}", resultMap.get("mweb_url"));
  129. Object webUrl = resultMap.get("mweb_url");
  130. Object prePayId = resultMap.get("prepay_id");
  131. String preId = prePayId == null ? "" : prePayId.toString();
  132. vo.setPayUrl(webUrl == null ? "" : webUrl.toString())
  133. .setPrePayId(preId)
  134. .setTradeType(tradeType);
  135. Map<String, String> payParams = getPayParams(openid, preId);
  136. payParams.put("outTradeNo", out_trade_no);
  137. TbOrder tbOrder = new TbOrder();
  138. tbOrder.setAttach(JSONUtil.toJsonStr(atchMap))
  139. .setOpenid(openid)
  140. .setOrderTime(new Date()).setBusinessType(Integer.valueOf(type))
  141. .setOutTradeNo(out_trade_no).setPrice(money);
  142. return payParams;
  143. }
  144. throw new Exception("支付信息有误");
  145. }
  146. public Map<String, String> getPayParams(String openid, String prePayId) {
  147. Map<String, String> signMap = new HashMap<>(10);
  148. signMap.put("appId", wxConfig.getAppId());
  149. signMap.put("timeStamp", System.currentTimeMillis() / 1000 + "");
  150. signMap.put("nonceStr", RandomUtil.randomString(32));
  151. signMap.put("signType", "MD5");
  152. signMap.put("package", "prepay_id=" + prePayId);
  153. String sign = wxUtils.sign(signMap, wxConfig.getKey());
  154. signMap.put("paySign", sign);
  155. signMap.put("openId", openid);
  156. return signMap;
  157. }
  158. public Map<String, String> getPayP(String timeStamp, String nonceStr, String openid, String pack) {
  159. Map<String, String> signMap = new HashMap<>(10);
  160. signMap.put("appId", wxConfig.getAppId());
  161. signMap.put("timeStamp", timeStamp);
  162. signMap.put("nonceStr", nonceStr);
  163. signMap.put("signType", "MD5");
  164. signMap.put("package", pack);
  165. String sign = wxUtils.sign(signMap, wxConfig.getKey());
  166. signMap.put("paySign", sign);
  167. signMap.put("openId", openid);
  168. return signMap;
  169. }
  170. public static String getIpAddress(HttpServletRequest request) {
  171. String ip = request.getHeader("x-forwarded-for");
  172. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
  173. ip = request.getHeader("Proxy-Client-IP");
  174. }
  175. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
  176. ip = request.getHeader("WL-Proxy-Client-IP");
  177. }
  178. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
  179. ip = request.getRemoteAddr();
  180. }
  181. if (ip.contains(",")) {
  182. return ip.split(",")[0];
  183. } else {
  184. return ip;
  185. }
  186. }
  187. public void WxNotify(NotifyBO notifyBO) {
  188. String outTradeNo = notifyBO.getOutTradeNo();
  189. if (StrUtil.isNotEmpty(RedisUtil.get(outTradeNo))) {
  190. log.error("========订单已处理==========:{}", outTradeNo);
  191. return;
  192. }
  193. RedisUtil.setByHour(outTradeNo, DateUtil.now(), 1);
  194. TbOrder tbOrder = tbOrderService.findByOutTradeNo(outTradeNo);
  195. if (tbOrder == null) {
  196. log.error("========订单不存在==========:{}", outTradeNo);
  197. return;
  198. }
  199. if (StrUtil.equals("SUCCESS", tbOrder.getOrderStatus())) {
  200. log.error("========订单已支付==========:{}", outTradeNo);
  201. return;
  202. }
  203. String total_fee = notifyBO.getTotalFee();
  204. BigDecimal money = new BigDecimal(total_fee).divide(new BigDecimal(100), 2, BigDecimal.ROUND_UP);
  205. String attachStr = tbOrder.getAttach();
  206. String transactionId = notifyBO.getTransactionId();
  207. Date now = new Date();
  208. if (StrUtil.isNotEmpty(attachStr)) {
  209. Attach attach = JSONUtil.toBean(attachStr, Attach.class);
  210. List<PriceBO> cars = JSONUtil.toList(attach.getC(), PriceBO.class);
  211. tbFeeStatisticsService.countParkFee(cars);//停车费日统计
  212. for (PriceBO bo1 : cars) {
  213. TbBusinessCar car = tbBusinessCarService.getById(bo1.getId());
  214. BigDecimal price = bo1.getP();
  215. if (price == null || price.doubleValue() <= 0) {
  216. log.error("付款金额不正确:{}", JSONUtil.toJsonStr(notifyBO));
  217. continue;
  218. }
  219. car.setPay(1).setMoney(car.getMoney().add(price)).setPayTime(now);
  220. tbBusinessCarService.updateById(car);
  221. TbBusiness business = tbBusinessService.getById(car.getBusinessId());
  222. if (business != null) {
  223. business.setPayMoney(business.getPayMoney().add(price));
  224. business.setPayStatus(business.getPayMoney().equals(business.getTotalMoney()) ? 3 : 4);
  225. tbBusinessService.updateById(business);
  226. }
  227. }
  228. tbFeeDetailsService.chargeParkFee(cars, transactionId, outTradeNo);//添加cars的收费明细
  229. String businessId = attach.getB();
  230. if (StrUtil.isNotEmpty(businessId)) {
  231. List<TbBusinessItem> items;
  232. List<TbBusiness> businessList = new ArrayList<>();
  233. if (TbBusiness.BusinessType.CAR_DISINCLE.getCode().equals(tbOrder.getBusinessType())) {
  234. List<String> businessIds = StrUtil.splitTrim(businessId, ",");
  235. items = tbBusinessItemService.findByBusinessIdList(businessIds);
  236. businessList = tbBusinessService.listByIds(businessIds);
  237. } else {
  238. items = tbBusinessItemService.findByBusinessId(businessId);
  239. TbBusiness tbBusiness = tbBusinessService.getById(businessId);
  240. businessList.add(tbBusiness);
  241. //整车越南车变成已支付=免费
  242. List<TbBusinessCar> carList = tbBusinessCarService.findByBusinessId(businessId);
  243. carList.forEach(tbBusinessCar -> {
  244. String carNo = tbBusinessCar.getCarNo();
  245. String carNoStr = StrUtil.sub(carNo, 0, 1);
  246. if (!CAR_LIST.contains(carNoStr)) {
  247. tbBusinessCar.setPay(1);
  248. tbBusinessCarService.updateById(tbBusinessCar);
  249. }
  250. });
  251. }
  252. log.error("items size:{},{}", items.size(), JSONUtil.toJsonStr(items));
  253. tbFeeStatisticsService.countBusinessFee(items, transactionId, outTradeNo);//业务费日统计
  254. items.forEach(tbBusinessItem -> tbBusinessItem.setPayStatus(1).setPayTime(now));
  255. tbBusinessItemService.updateBatchById(items);
  256. businessList.forEach(tbBusiness -> {
  257. tbBusiness.setPayTime(now).setPayType(3).setConfirmInput(1).setConfirmInputTime(now)
  258. .setPayMoney(tbBusiness.getPayMoney().add(tbBusiness.getItemPrice()))
  259. .setPayNo(transactionId);
  260. tbBusiness.setPayStatus(tbBusiness.getPayMoney().equals(tbBusiness.getTotalMoney()) ? 3 : 4);
  261. tbBusinessService.updateById(tbBusiness);
  262. });
  263. }
  264. String a = attach.getA();
  265. if (StrUtil.isNotEmpty(a)) {
  266. AccountChargeBO chargeBO = JSONUtil.toBean(a, AccountChargeBO.class);
  267. tbAccountService.recharge(chargeBO.getId(), chargeBO.getC(), TbChargeRecord.Ch.WECHAT.getType(), money);
  268. }
  269. }
  270. tbOrder.setOrderStatus("SUCCESS")
  271. .setTransactionId(notifyBO.getTransactionId())
  272. .setCompleteDate(notifyBO.getTimeEnd());
  273. tbOrderService.updateById(tbOrder);
  274. }
  275. public String getRedirectUrl(String path, String state) {
  276. String redirectUrl = myConfig.getWebDomain() + path;
  277. String encoderUrl = URLDecoder.decode(redirectUrl, Charset.forName("utf-8"));
  278. String url = wxConfig.getAuthLoginUrl().replace("REDIRECT_URI", encoderUrl);
  279. if (StrUtil.isNotEmpty(state)) {
  280. url = url.replace("STATE", state);
  281. }
  282. log.info("get redirect url:{}", url);
  283. return url;
  284. }
  285. public String getOpenidByCode(String code, String openid) {
  286. String openidUrl = wxConfig.getOpenidUrl().replace("CODE", code);
  287. String resp = HttpUtil.get(openidUrl);
  288. JSONObject result = JSONUtil.parseObj(resp);
  289. log.info("get openid result:{}", resp);
  290. String newOpenid = result.getStr("openid");
  291. if (StrUtil.isNotEmpty(newOpenid)) {
  292. return newOpenid;
  293. }
  294. return openid;
  295. }
  296. public SortedMap getWxConfig(String url) {
  297. Long nowTime = System.currentTimeMillis() / 1000;
  298. String jsTicket = RedisUtil.get(wxConfig.getJsApiTicketKey());
  299. SortedMap<String, Object> params = new TreeMap<>();
  300. params.put("noncestr", RandomUtil.randomString(32));
  301. params.put("jsapi_ticket", jsTicket);
  302. params.put("timestamp", nowTime);
  303. params.put("url", url);
  304. String sign = SecureUtil.sha1(params.toString().substring(1, params.toString().lastIndexOf("}")).replaceAll(", ", "&"));
  305. params.put("sign", sign);
  306. params.put("appId", wxConfig.getAppId());
  307. return params;
  308. }
  309. @Async
  310. public void sendTemplateMsg(String templateId, String openid, MsgDataBO data) {
  311. String accessToken = RedisUtil.get(wxConfig.getAccessTokenKey());
  312. BaseTemplate baseTemplate = new BaseTemplate(openid, templateId, data);
  313. String json = JSON.toJSONString(baseTemplate);
  314. String url = wxConfig.getSendMsgUrl().replace("ACCESS_TOKEN", accessToken);
  315. String resp = HttpUtil.post(url, json);
  316. log.info("send wx msg{},{},{};return :{}", templateId, openid, JSONUtil.toJsonStr(data), resp);
  317. }
  318. @Async
  319. public void sendTemplateMsg(String templateId, String openid, MsgDataBO data, String detailUrl) {
  320. String accessToken = RedisUtil.get(wxConfig.getAccessTokenKey());
  321. BaseTemplate baseTemplate = new BaseTemplate(openid, templateId, detailUrl, data);
  322. String json = JSON.toJSONString(baseTemplate);
  323. String url = wxConfig.getSendMsgUrl().replace("ACCESS_TOKEN", accessToken);
  324. String resp = HttpUtil.post(url, json);
  325. log.info("send wx msg{},{},{};return :{}", templateId, openid, JSONUtil.toJsonStr(data), resp);
  326. }
  327. }