package com.pj.api.wx.service; import cn.hutool.cache.CacheUtil; import cn.hutool.cache.impl.TimedCache; import cn.hutool.core.date.DateUtil; import cn.hutool.core.net.URLDecoder; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.XmlUtil; import cn.hutool.crypto.SecureUtil; import cn.hutool.http.HttpUtil; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSON; import com.pj.api.wx.WxUtils; import com.pj.api.wx.bo.*; import com.pj.api.wx.vo.PrePayVO; import com.pj.current.config.MyConfig; import com.pj.current.config.PartConfig; import com.pj.current.config.WxConfig; import com.pj.project.tb_account.TbAccountService; import com.pj.project.tb_business.TbBusiness; import com.pj.project.tb_business.TbBusinessService; import com.pj.project.tb_business_car.TbBusinessCar; import com.pj.project.tb_business_car.TbBusinessCarService; import com.pj.project.tb_business_item.TbBusinessItem; import com.pj.project.tb_business_item.TbBusinessItemService; import com.pj.project.tb_charge_record.TbChargeRecord; import com.pj.project.tb_costomer.TbCostomer; import com.pj.project.tb_costomer.TbCostomerService; import com.pj.project.tb_fee_details.TbFeeDetailsService; import com.pj.project.tb_fee_statistics.TbFeeStatisticsService; import com.pj.project.tb_order.TbOrder; import com.pj.project.tb_order.TbOrderService; import com.pj.project.tb_pay_record.TbPayRecord; import com.pj.project.tb_pay_record.TbPayRecordService; import com.pj.project4sp.global.BusinessException; import com.pj.utils.cache.RedisUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Lazy; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import java.math.BigDecimal; import java.nio.charset.Charset; import java.util.*; @Service @Transactional @Slf4j public class WxService { @Resource @Lazy private TbBusinessService tbBusinessService; @Resource WxConfig wxConfig; @Resource MyConfig myConfig; @Resource PartConfig partConfig; @Resource WxUtils wxUtils; @Resource TbBusinessCarService tbBusinessCarService; @Resource @Lazy TbBusinessItemService tbBusinessItemService; @Resource TbFeeStatisticsService tbFeeStatisticsService; @Resource private TbAccountService tbAccountService; @Resource TbFeeDetailsService tbFeeDetailsService; @Resource private TbOrderService tbOrderService; private final List CAR_LIST = StrUtil.splitTrim("浙,粤,京,津,冀,晋,蒙,辽,黑,沪,吉,苏,皖,赣,鲁,豫,鄂,湘,桂,琼,渝,川,贵,云,藏, 陕, 甘, 青, 宁", ","); /** * 统一下单接口 */ public Map prePay(HttpServletRequest request) throws Exception { String tradeType = request.getParameter("tradeType"); String money = request.getParameter("money"); if (Double.valueOf(money) <= 0) { throw new BusinessException("金额不正确"); } String type = request.getParameter("type"); if (StrUtil.isEmpty(type)) { throw new BusinessException("参数异常"); } String openid = request.getParameter("openid"); String desc = request.getParameter("desc"); // String openid = "oDWvn5w-hVkzUuKeY7OBXBV_l1rU"; String businessId = request.getParameter("b"); String c = request.getParameter("c"); String a = request.getParameter("a"); Attach atchMap = new Attach(); atchMap.setC(c).setB(businessId).setA(a); String out_trade_no = RandomUtil.randomString(32); Map params = new HashMap<>(); params.put("appid", wxConfig.getAppId()); params.put("mch_id", wxConfig.getMachId()); params.put("openid", openid); params.put("nonce_str", RandomUtil.randomString(32)); params.put("body", desc); params.put("out_trade_no", out_trade_no); // params.put("attach", JSONUtil.toJsonStr(atchMap)); String total_free = partConfig.isTestEnv() ? "1" : NumberUtil.mul(money, 100 + "").intValue() + ""; log.info("pay free:{}", total_free); params.put("total_fee", total_free); // params.put("total_fee", "1"); params.put("spbill_create_ip", getIpAddress(request)); params.put("notify_url", myConfig.getDomain() + "/wx/notify"); params.put("trade_type", tradeType); params.put("scene_info", SceneInfoBO.getSceneInfo(myConfig.getDomain(), wxConfig.getTitle())); String sign = wxUtils.sign(params, wxConfig.getKey()); log.info("wx pay sign result:{}", sign); params.put("sign", sign); String prePayUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder"; String xmlParam = XmlUtil.mapToXmlStr(params); log.info("wx pay xml params:{}", xmlParam); String result = HttpUtil.createPost(prePayUrl).header("Content-Type", "text/xml").body(xmlParam) .execute().body(); Map resultMap = XmlUtil.xmlToMap(result); log.info("request pre pay url result:{}", resultMap); PrePayVO vo = new PrePayVO(); if ("SUCCESS".equals(resultMap.get("return_code")) && "SUCCESS".equals(resultMap.get("result_code"))) { log.info("生成预支付订单成功,mweb_url:{}", resultMap.get("mweb_url")); Object webUrl = resultMap.get("mweb_url"); Object prePayId = resultMap.get("prepay_id"); String preId = prePayId == null ? "" : prePayId.toString(); vo.setPayUrl(webUrl == null ? "" : webUrl.toString()) .setPrePayId(preId) .setTradeType(tradeType); Map payParams = getPayParams(openid, preId); payParams.put("outTradeNo", out_trade_no); TbOrder tbOrder = new TbOrder(); tbOrder.setAttach(JSONUtil.toJsonStr(atchMap)) .setOpenid(openid) .setOrderTime(new Date()).setBusinessType(Integer.valueOf(type)) .setOutTradeNo(out_trade_no).setPrice(money); return payParams; } throw new Exception("支付信息有误"); } public Map getPayParams(String openid, String prePayId) { Map signMap = new HashMap<>(10); signMap.put("appId", wxConfig.getAppId()); signMap.put("timeStamp", System.currentTimeMillis() / 1000 + ""); signMap.put("nonceStr", RandomUtil.randomString(32)); signMap.put("signType", "MD5"); signMap.put("package", "prepay_id=" + prePayId); String sign = wxUtils.sign(signMap, wxConfig.getKey()); signMap.put("paySign", sign); signMap.put("openId", openid); return signMap; } public Map getPayP(String timeStamp, String nonceStr, String openid, String pack) { Map signMap = new HashMap<>(10); signMap.put("appId", wxConfig.getAppId()); signMap.put("timeStamp", timeStamp); signMap.put("nonceStr", nonceStr); signMap.put("signType", "MD5"); signMap.put("package", pack); String sign = wxUtils.sign(signMap, wxConfig.getKey()); signMap.put("paySign", sign); signMap.put("openId", openid); return signMap; } public static String getIpAddress(HttpServletRequest request) { String ip = request.getHeader("x-forwarded-for"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } if (ip.contains(",")) { return ip.split(",")[0]; } else { return ip; } } public void WxNotify(NotifyBO notifyBO) { String outTradeNo = notifyBO.getOutTradeNo(); if (StrUtil.isNotEmpty(RedisUtil.get(outTradeNo))) { log.error("========订单已处理==========:{}", outTradeNo); return; } RedisUtil.setByHour(outTradeNo, DateUtil.now(), 1); TbOrder tbOrder = tbOrderService.findByOutTradeNo(outTradeNo); if (tbOrder == null) { log.error("========订单不存在==========:{}", outTradeNo); return; } if (StrUtil.equals("SUCCESS", tbOrder.getOrderStatus())) { log.error("========订单已支付==========:{}", outTradeNo); return; } String total_fee = notifyBO.getTotalFee(); BigDecimal money = new BigDecimal(total_fee).divide(new BigDecimal(100), 2, BigDecimal.ROUND_UP); String attachStr = tbOrder.getAttach(); String transactionId = notifyBO.getTransactionId(); Date now = new Date(); if (StrUtil.isNotEmpty(attachStr)) { Attach attach = JSONUtil.toBean(attachStr, Attach.class); List cars = JSONUtil.toList(attach.getC(), PriceBO.class); tbFeeStatisticsService.countParkFee(cars);//停车费日统计 for (PriceBO bo1 : cars) { TbBusinessCar car = tbBusinessCarService.getById(bo1.getId()); BigDecimal price = bo1.getP(); if (price == null || price.doubleValue() <= 0) { log.error("付款金额不正确:{}", JSONUtil.toJsonStr(notifyBO)); continue; } car.setPay(1).setMoney(car.getMoney().add(price)).setPayTime(now); tbBusinessCarService.updateById(car); TbBusiness business = tbBusinessService.getById(car.getBusinessId()); if (business != null) { business.setPayMoney(business.getPayMoney().add(price)); business.setPayStatus(business.getPayMoney().equals(business.getTotalMoney()) ? 3 : 4); tbBusinessService.updateById(business); } } tbFeeDetailsService.chargeParkFee(cars, transactionId, outTradeNo);//添加cars的收费明细 String businessId = attach.getB(); if (StrUtil.isNotEmpty(businessId)) { List items; List businessList = new ArrayList<>(); if (TbBusiness.BusinessType.CAR_DISINCLE.getCode().equals(tbOrder.getBusinessType())) { List businessIds = StrUtil.splitTrim(businessId, ","); items = tbBusinessItemService.findByBusinessIdList(businessIds); businessList = tbBusinessService.listByIds(businessIds); } else { items = tbBusinessItemService.findByBusinessId(businessId); TbBusiness tbBusiness = tbBusinessService.getById(businessId); businessList.add(tbBusiness); //整车越南车变成已支付=免费 List carList = tbBusinessCarService.findByBusinessId(businessId); carList.forEach(tbBusinessCar -> { String carNo = tbBusinessCar.getCarNo(); String carNoStr = StrUtil.sub(carNo, 0, 1); if (!CAR_LIST.contains(carNoStr)) { tbBusinessCar.setPay(1); tbBusinessCarService.updateById(tbBusinessCar); } }); } log.error("items size:{},{}", items.size(), JSONUtil.toJsonStr(items)); tbFeeStatisticsService.countBusinessFee(items, transactionId, outTradeNo);//业务费日统计 items.forEach(tbBusinessItem -> tbBusinessItem.setPayStatus(1).setPayTime(now)); tbBusinessItemService.updateBatchById(items); businessList.forEach(tbBusiness -> { tbBusiness.setPayTime(now).setPayType(3).setConfirmInput(1).setConfirmInputTime(now) .setPayMoney(tbBusiness.getPayMoney().add(tbBusiness.getItemPrice())) .setPayNo(transactionId); tbBusiness.setPayStatus(tbBusiness.getPayMoney().equals(tbBusiness.getTotalMoney()) ? 3 : 4); tbBusinessService.updateById(tbBusiness); }); } String a = attach.getA(); if (StrUtil.isNotEmpty(a)) { AccountChargeBO chargeBO = JSONUtil.toBean(a, AccountChargeBO.class); tbAccountService.recharge(chargeBO.getId(), chargeBO.getC(), TbChargeRecord.Ch.WECHAT.getType(), money); } } tbOrder.setOrderStatus("SUCCESS") .setTransactionId(notifyBO.getTransactionId()) .setCompleteDate(notifyBO.getTimeEnd()); tbOrderService.updateById(tbOrder); } public String getRedirectUrl(String path, String state) { String redirectUrl = myConfig.getWebDomain() + path; String encoderUrl = URLDecoder.decode(redirectUrl, Charset.forName("utf-8")); String url = wxConfig.getAuthLoginUrl().replace("REDIRECT_URI", encoderUrl); if (StrUtil.isNotEmpty(state)) { url = url.replace("STATE", state); } log.info("get redirect url:{}", url); return url; } public String getOpenidByCode(String code, String openid) { String openidUrl = wxConfig.getOpenidUrl().replace("CODE", code); String resp = HttpUtil.get(openidUrl); JSONObject result = JSONUtil.parseObj(resp); log.info("get openid result:{}", resp); String newOpenid = result.getStr("openid"); if (StrUtil.isNotEmpty(newOpenid)) { return newOpenid; } return openid; } public SortedMap getWxConfig(String url) { Long nowTime = System.currentTimeMillis() / 1000; String jsTicket = RedisUtil.get(wxConfig.getJsApiTicketKey()); SortedMap params = new TreeMap<>(); params.put("noncestr", RandomUtil.randomString(32)); params.put("jsapi_ticket", jsTicket); params.put("timestamp", nowTime); params.put("url", url); String sign = SecureUtil.sha1(params.toString().substring(1, params.toString().lastIndexOf("}")).replaceAll(", ", "&")); params.put("sign", sign); params.put("appId", wxConfig.getAppId()); return params; } @Async public void sendTemplateMsg(String templateId, String openid, MsgDataBO data) { String accessToken = RedisUtil.get(wxConfig.getAccessTokenKey()); BaseTemplate baseTemplate = new BaseTemplate(openid, templateId, data); String json = JSON.toJSONString(baseTemplate); String url = wxConfig.getSendMsgUrl().replace("ACCESS_TOKEN", accessToken); String resp = HttpUtil.post(url, json); log.info("send wx msg{},{},{};return :{}", templateId, openid, JSONUtil.toJsonStr(data), resp); } @Async public void sendTemplateMsg(String templateId, String openid, MsgDataBO data, String detailUrl) { String accessToken = RedisUtil.get(wxConfig.getAccessTokenKey()); BaseTemplate baseTemplate = new BaseTemplate(openid, templateId, detailUrl, data); String json = JSON.toJSONString(baseTemplate); String url = wxConfig.getSendMsgUrl().replace("ACCESS_TOKEN", accessToken); String resp = HttpUtil.post(url, json); log.info("send wx msg{},{},{};return :{}", templateId, openid, JSONUtil.toJsonStr(data), resp); } }