JAVA WeChat Pay

1. Brief description

  Now many projects need to use the WeChat payment interface, the official document is also a simple description, it is really difficult to understand if the technology is not advanced (I can’t understand it when I read the official document), I still need to collect and summarize it myself.

Watched a lot online

Some of them still couldn't succeed in the end. Next, I will share my WeChat payment. The WeChat official account or mini program of this WeChat payment requires WeChat authentication, otherwise you can’t apply for WeChat payment.

Now that we have reached this point, I believe all the premises are ready. Go directly to the code~

2. Java WeChat payment

One, first understand the meaning of each parameter

  https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1 This is the interception part of the official WeChat description. Please check this link for details.

2. WeChat payment code

First create the service layer and insert the following code:

public interface WeiXinPayService {
 
    Object WeiXinPay(String outTradeNo,String openid, String body, int total_fee) throws UnsupportedEncodingException;
}
@Service
public class WeiXinPayServiceImpl implements WeiXinPayService {
    private static final Logger LOGGER = LoggerFactory
            .getLogger(WeiXinPayServiceImpl.class);
 
    @Override
    public Object WeiXinPay(String outTradeNo,String openid, String body, int total_fee) {
 
        String appid = "" // 公众号--》“开发者ID”  微信小程序,或者公众号的APPID
        String mch_id = "" // 商户号,将该值赋值给partner
        String key =  "" // 微信支付商户平台登录)--》“API安全”--》“API密钥”--“设置密钥”(设置之后的那个值就是partnerkey,32位)
        LOGGER.debug(appid);
        LOGGER.debug(mch_id);
       LOGGER.debug(key);
//        String body = body; // 描述           int total_fee = total_fee; // 支付金额
        String notify_url = ""; // 回调链接
//        String out_trade_no = IdUtils.genOrderName();//生成订单号
//        LOGGER.debug("outTradeNo---:"+out_trade_no);
        LOGGER.debug("openid是----"+openid);
        LOGGER.debug("appid---"+appid);
        LOGGER.debug("mch_id---"+mch_id);
        Map<Object, Object> map = null;
        try {
            map = WeiXinAtcion.me.weixinPlay(mch_id, appid,
                    key, openid, total_fee, outTradeNo, notify_url, body);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        return map;
    }
}

Then create a corcontroller layer:

/**
 * <p>Title: WeiXinPayController</p>  
  * <p>Description: 微信支付</p>  
  * @author SpringRoot  
  * @date 2020/3/4-11:17
 */
@Controller
public class WeiXinPayController {
    private static final Logger LOGGER = LoggerFactory
            .getLogger(WeiXinPayController.class);
 
    @Autowired
    private WeiXinPayService weiXinPayService;
 
    /**
     * 支付接口
     *
     * @param openid
     * @param body      说明
     * @param total_fee 总价
     * @return
     */
    @RequestMapping("/WeiXinPay")
    public @ResponseBody
    Object WeiXinPay(String outTradeNo,String openid, String body, int total_fee) {
        LOGGER.debug("outTradeNo-------------"+outTradeNo);
        LOGGER.debug("openid-------------"+openid);
        LOGGER.debug("body-------------"+body);
        LOGGER.debug("total_fee-------------"+total_fee);
        try {
            return weiXinPayService.WeiXinPay(outTradeNo,openid, body, total_fee);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return TTResult.fail();
    }
}

Tools used in the above code: First of all WeiXinAtcion

/**
 * <p>Title: WeiXinAtcion</p>  
  * <p>Description: </p>  
  * @author SpringRoot
  * @date 2020/3/4-11:17
 */
@Component
public class WeiXinAtcion {
    //密钥
    public static final WeiXinAtcion me = new WeiXinAtcion();
    private static final Logger LOGGER = LoggerFactory
            .getLogger(WeiXinAtcion.class);
 
    /**
     * 生成微信订单
     *
     * @param mch_id
     * @param appid
     * @param key
     * @param openid
     * @param total_fee
     * @param out_trade_no
     * @param notify_url
     * @param body
     * @return
     * @throws UnsupportedEncodingException
     * @throws DocumentException
     */
    public SortedMap<Object, Object> weixinPlay(String mch_id, String appid, String key, String openid, int total_fee, String out_trade_no, String notify_url, String body) throws UnsupportedEncodingException, DocumentException, DocumentException {
 
        SortedMap<Object, Object> paymentPo = new TreeMap<Object, Object>();
        paymentPo.put("appid", appid);
        paymentPo.put("mch_id", mch_id);
        paymentPo.put("nonce_str", WXUtil.generate());
        paymentPo.put("body", body);
        paymentPo.put("out_trade_no", out_trade_no);
        paymentPo.put("total_fee", String.valueOf(total_fee));
        paymentPo.put("spbill_create_ip","服务器的ip地址");//此处是公网ip
        paymentPo.put("notify_url", notify_url);
        paymentPo.put("trade_type", "JSAPI");
        paymentPo.put("openid", openid);
        String sign = WXUtil.createSign_ChooseWXPay("UTF-8", paymentPo, key);
        paymentPo.put("sign", sign);
        String param = WXUtil.getRequestXml(paymentPo);
        //将参数通过post请求传给微信端
        String request = WXUtil.httpRequest("https://api.mch.weixin.qq.com/pay/unifiedorder", "POST", param);
        Map<String, String> map = new HashMap<String, String>();         // 将解析结果存储在HashMap中
        InputStream in = new ByteArrayInputStream(request.getBytes());
        SAXReader reader = new SAXReader();                              // 读取输入流
        Document document = reader.read(in);
 
        Element root = document.getRootElement();                        // 得到xml根元素
        @SuppressWarnings("unchecked")                                   // 得到根元素的所有子节点
                List<Element> elementList = root.elements();
        for (Element element : elementList) {
            map.put(element.getName(), element.getText());
        }
        SortedMap<Object, Object> result = new TreeMap<Object, Object>();
 
        LOGGER.debug("第一次签名返回码" + map.get("return_code"));
        LOGGER.debug("第一次签名返回结果" + map.get("return_msg"));
        //第一次签名成功
        if (map.get("return_code").equals("SUCCESS")) {                  // 业务结果
            String nonceStr = WXUtil.generate();
            Long timeStamp = System.currentTimeMillis() / 1000;
            SortedMap<Object, Object> params = new TreeMap<Object, Object>();
 
            params.put("appId", appid);
            params.put("nonceStr", nonceStr);
            params.put("package", "prepay_id=" + map.get("prepay_id"));
            params.put("signType", "MD5");
            params.put("timeStamp", timeStamp);
 
            //第二次签名成功
            LOGGER.debug("开始第二次签名");
            String paySign = WXUtil.createSign_ChooseWXPay("UTF-8", params, key);
            result.put("paySign", paySign);
            result.put("timeStamp", timeStamp + "");
            result.put("nonceStr", nonceStr);
            result.put("out_trade_no", paymentPo.get("out_trade_no"));
            result.put("package", "prepay_id=" + map.get("prepay_id"));
            result.put("return_code", "SUCCESS");
        } else {
            result.put("return_code", "Fail");
            result.put("return_msg", map.get("return_msg"));
 
        }
        return result;
    }
}

Tools: WXUtil

public class WXUtil {
     
    private static final Logger LOGGER = LoggerFactory.getLogger(WXUtil.class);
    /**
     * 随机字符串
     * @return
     */
    public static String generate() {
        return UUID.randomUUID().toString().trim().replaceAll("-", "");
    }
     
    /**
     * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
     * @param strxml
     * @return
     * @throws JDOMException
     * @throws IOException
     */
    public static Map doXMLParse(String strxml) throws JDOMException, IOException {
        strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
 
        if(null == strxml || "".equals(strxml)) {
            return null;
        }
        Map m = new HashMap();
        InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
        SAXBuilder builder = new SAXBuilder();
        Document doc = builder.build(in);
        Element root = doc.getRootElement();
        List list = root.getChildren();
        Iterator it = list.iterator();
        while(it.hasNext()) {
            Element e = (Element) it.next();
            String k = e.getName();
            String v = "";
            List children = e.getChildren();
            if(children.isEmpty()) {
                v = e.getTextNormalize();
            } else {
                v = WXUtil.getChildrenText(children);
            }
            m.put(k, v);
        }
         
        //关闭流
        in.close();
         
        return m;
    }
     
    /**
     * 获取子结点的xml
     * @param children
     * @return String
     */
    public static String getChildrenText(List children) {
        StringBuffer sb = new StringBuffer();
        if(!children.isEmpty()) {
            Iterator it = children.iterator();
            while(it.hasNext()) {
                Element e = (Element) it.next();
                String name = e.getName();
                String value = e.getTextNormalize();
                List list = e.getChildren();
                sb.append("<" + name + ">");
                if(!list.isEmpty()) {
                    sb.append(WXUtil.getChildrenText(list));
                }
                sb.append(value);
                sb.append("</" + name + ">");
            }
        }
        return sb.toString();
    }
    /**
     * 将请求参数转换为xml格式的string字符串,微信服务器接收的是xml格式的字符串
     * @param parameters
     * @return
     */
    public static String getRequestXml(SortedMap<Object, Object> parameters) {
        StringBuffer sb = new StringBuffer();
        sb.append("<xml>");
        Set<Entry<Object, Object>> es = parameters.entrySet();
        Iterator<Entry<Object, Object>> it = es.iterator();
        while (it.hasNext()) {
            Entry<Object, Object> entry = (Entry<Object, Object>) it.next();
            String k = (String) entry.getKey();
            String v = (String) entry.getValue();
             
            if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k) || "sign".equalsIgnoreCase(k)) {
                sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">");
            } else {
                sb.append("<" + k + ">" + v + "</" + k + ">");
            }
        }
        sb.append("</xml>");
        return sb.toString();
    }
 
 
     
    /**
     * sign签名,必须使用MD5签名,且编码为UTF-8
     * @param characterEncoding
     * @param parameters
     * @return
     */
    public static String createSign_ChooseWXPay(String characterEncoding, SortedMap<Object, Object> parameters, String key) {
        StringBuffer sb = new StringBuffer();
        Set<Entry<Object, Object>> es = parameters.entrySet();
        Iterator<Entry<Object, Object>> it = es.iterator();
        while (it.hasNext()) {
            Entry<Object, Object> entry = (Entry<Object, Object>) it.next();
            String k = (String) entry.getKey();
            Object v = entry.getValue();
            if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
                sb.append(k + "=" + v + "&");
            }
        }
        /** 支付密钥必须参与加密,放在字符串最后面 */
        sb.append("key=" + key);
        String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
        return sign;
    }
 
 
 
 
 
    public static String httpRequest(String requestUrl,String requestMethod,String outputStr){
        // 创建SSLContext
        StringBuffer buffer=null;
        try{
        URL url = new URL(requestUrl);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod(requestMethod);
        conn.setDoOutput(true);
        conn.setDoInput(true);
        conn.connect();
 
        //往服务器端写内容
        if(null !=outputStr){
            OutputStream os=conn.getOutputStream();
            os.write(outputStr.getBytes("utf-8"));
            os.close();
        }
        // 读取服务器端返回的内容
        InputStream is = conn.getInputStream();
        InputStreamReader isr = new InputStreamReader(is, "utf-8");
        BufferedReader br = new BufferedReader(isr);
        buffer = new StringBuffer();
        String line = null;
        while ((line = br.readLine()) != null) {
             buffer.append(line);
         }
        }catch(Exception e){
            e.printStackTrace();
        }
        return buffer.toString();
        }   
    public static String urlEncodeUTF8(String source){
        String result=source;
        try {
            result=java.net.URLEncoder.encode(source, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return result;
    }
         
    /**
     * 退款和企业付款到银行卡
     */
   public static Map<String, String> doRefund(HttpServletRequest request,String url,String data,String partner,String apiclient_certLocation) throws Exception {
    // p12证书的位置
            // 微信公众平台:“微信支付”--》“商户信息”--》“交易数据”--》“详情请登录微信支付商户平台查看”(登录)--》“API安全”--》“API证书”--》“下载证书”
            // 下载证书后将apiclient_cert.p12放在src目录下面(出于安全考虑,请自行下载自己的证书)
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            String url2 = request.getSession().getServletContext().getRealPath("/")
                    + "cert/" + apiclient_certLocation;
            LOGGER.debug("url2--->"+url2);
            File file=new File(url2);
            FileInputStream instream = new FileInputStream(file);// P12文件目录
        try {
            keyStore.load(instream, partner.toCharArray());
        } finally {
            instream.close();
        }
        SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, partner.toCharArray()).build();
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext,new String[] { "TLSv1" }, null,SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
        CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
        try {
            HttpPost httpost = new HttpPost(url); // 设置响应头信息
            httpost.addHeader("Connection", "keep-alive");
            httpost.addHeader("Accept", "*/*");
            httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
//          httpost.addHeader("Host", "api.mch.weixin.qq.com");
            httpost.addHeader("X-Requested-With", "XMLHttpRequest");
            httpost.addHeader("Cache-Control", "max-age=0");
            httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
            httpost.setEntity(new StringEntity(data, "UTF-8"));
            CloseableHttpResponse response = httpclient.execute(httpost);
            try {
                HttpEntity entity = response.getEntity();
                String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
                EntityUtils.consume(entity);
                return WXUtil.doXMLParse(jsonStr);
            } finally {
                response.close();
            }
        } finally {
            httpclient.close();
        }
    }
 
 
    /**
     * 得到公钥
     * @param request
     * @param url 请求微信端的链接
     * @param data//请求的数据
     * @param partner
     * @param apiclient_certLocation
     * @return
     * @throws Exception
     */
    public static Object getPublicKey(HttpServletRequest request,String url,String data,String partner,String apiclient_certLocation) throws Exception {
        // p12证书的位置
        // 微信公众平台:“微信支付”--》“商户信息”--》“交易数据”--》“详情请登录微信支付商户平台查看”(登录)--》“API安全”--》“API证书”--》“下载证书”
        // 下载证书后将apiclient_cert.p12放在src目录下面(出于安全考虑,请自行下载自己的证书)
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        String url2 = request.getSession().getServletContext().getRealPath("/")
                + "cert/" + apiclient_certLocation;
        LOGGER.debug("url2--->"+url2);
        File file=new File(url2);
        FileInputStream instream = new FileInputStream(file);// P12文件目录
        try {
            keyStore.load(instream, partner.toCharArray());
        } finally {
            instream.close();
        }
        SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, partner.toCharArray()).build();
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext,new String[] { "TLSv1" }, null,SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
        CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
        try {
            HttpPost httpost = new HttpPost(url); // 设置响应头信息
            httpost.addHeader("Connection", "keep-alive");
            httpost.addHeader("Accept", "*/*");
            httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
            httpost.addHeader("X-Requested-With", "XMLHttpRequest");
            httpost.addHeader("Cache-Control", "max-age=0");
            httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
            httpost.setEntity(new StringEntity(data, "UTF-8"));
            CloseableHttpResponse response = httpclient.execute(httpost);
            try {
                HttpEntity entity = response.getEntity();
                String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
                EntityUtils.consume(entity);
                return jsonStr;
            } finally {
                response.close();
            }
        } finally {
            httpclient.close();
        }
    }
 
    /** 
     * 是否签名正确,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。 
     * @return boolean 
     */ 
    public static boolean isTenpaySign(String characterEncoding, SortedMap<Object, Object> packageParams, String API_KEY) {  
        StringBuffer sb = new StringBuffer();  
        Set es = packageParams.entrySet();  
        Iterator it = es.iterator();  
        while(it.hasNext()) {  
            Entry entry = (Entry)it.next();
            String k = (String)entry.getKey();  
            String v = (String)entry.getValue();  
            if(!"sign".equals(k) && null != v && !"".equals(v)) {  
                sb.append(k + "=" + v + "&");  
            }  
        }  
        sb.append("key=" + API_KEY);  
        //算出摘要  
        String mysign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toLowerCase();  
        String tenpaySign = ((String)packageParams.get("sign")).toLowerCase();  
           
        return tenpaySign.equals(mysign);
    }
 
    public static Map<String, String> doRefund(String string, String xML, String mCH_ID, String cERT) {
        // TODO Auto-generated method stub
        return null;
    }
}

Tool class that returns results: TTResult

public class CommonResult {
    // 定义jackson对象
    private static final ObjectMapper MAPPER = new ObjectMapper();
 
    // 响应业务状态
    private Integer status;  // 200 代表成功, 500 代表失败
 
    // 响应消息
    private String msg;
 
    // 响应中的数据
    private Object data;
 
    public static CommonResult build(Integer status, String msg, Object data) {
        return new TTResult(status, msg, data);
    }
 
    public static CommonResult ok(Object data) {
        return new TTResult(data);
    }
     
     
    public static CommonResult ok() {
        return new TTResult(null);
    }
 
    public static CommonResult fail(){
        return new TTResult(500,"fail",null);
    }
     
    public static CommonResult fail(Object data){
        return new TTResult(500,"fail",data);
    }
     
    public TTResult() {
 
    }
 
    public static CommonResult build(Integer status, String msg) {
        return new TTResult(status, msg, null);
    }
     
    public TTResult(Integer status, String msg, Object data) {
        this.status = status;
        this.msg = msg;
        this.data = data;
    }
 
    public TTResult(Object data) {
        this.status = 200;
        this.msg = "OK";
        this.data = data;
    }
     
 
    // public Boolean isOK() {
    // return this.status == 200;
    // }
 
    public Integer getStatus() {
        return status;
    }
 
    public void setStatus(Integer status) {
        this.status = status;
    }
 
    public String getMsg() {
        return msg;
    }
 
    public void setMsg(String msg) {
        this.msg = msg;
    }
 
    public Object getData() {
        return data;
    }
 
    public void setData(Object data) {
        this.data = data;
    }
 
    /**
     * 将json结果集转化为TTResult对象
     * 
     * @param jsonData
     *            json数据
     * @param clazz
     *            TTResult中的object类型
     * @return
     */
    public static CommonResult formatToPojo(String jsonData, Class<?> clazz) {
        try {
            if (clazz == null) {
                return MAPPER.readValue(jsonData, TTResult.class);
            }
            JsonNode jsonNode = MAPPER.readTree(jsonData);
            JsonNode data = jsonNode.get("data");
            Object obj = null;
            if (clazz != null) {
                if (data.isObject()) {
                    obj = MAPPER.readValue(data.traverse(), clazz);
                } else if (data.isTextual() || data.isNumber()) {
                    obj = MAPPER.readValue(data.asText(), clazz);
                }
            }
            return build(jsonNode.get("status").intValue(), jsonNode.get("msg")
                    .asText(), obj);
        } catch (Exception e) {
            return null;
        }
    }
 
    /**
     * 没有object对象的转化
     * 
     * @param json
     * @return
     */
    public static CommonResult format(String json) {
        try {
            return MAPPER.readValue(json, TTResult.class);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
 
    /**
     * Object是集合转化
     * 
     * @param jsonData
     *            json数据
     * @param clazz
     *            集合中的类型
     * @return
     */
    public static CommonResult formatToList(String jsonData, Class<?> clazz) {
        try {
            JsonNode jsonNode = MAPPER.readTree(jsonData);
            JsonNode data = jsonNode.get("data");
            Object obj = null;
            if (data.isArray() && data.size() > 0) {
                obj = MAPPER.readValue(data.traverse(), MAPPER.getTypeFactory()
                        .constructCollectionType(List.class, clazz));
            }
            return build(jsonNode.get("status").intValue(), jsonNode.get("msg")
                    .asText(), obj);
        } catch (Exception e) {
            return null;
        }
    }
}

For the parameters involved in the above code, go to the official WeChat documentation to see the instructions. Each parameter has an explanation. According to me, WeChat Pay will definitely work.

Guess you like

Origin blog.csdn.net/baidu_39322753/article/details/104672848