小言_互联网的博客

JSAPI网页授权-微信支付-微信退款-商户平台Java对接

314人阅读  评论(0)

简介:

首先,对接微信支付要知道需要什么参数,然后等我们拿到这些需要的参数。就要开始怎么去做。微信有专门的支付dome,可惜我才疏学浅,没怎么看懂(哭泣~)。不过里面的有一些工具类是我们所需要的。

 这里我们可以看到所需的参数:

公众账号ID appid:这个是从商户公众号后台里面获取的

商户号 mch_id:这个也是从商户后台获取的,申请退款的时候这商户号就是退款证书的AES加解密算法的key

随机字符串 nonce_str:这个可以在微信官网的dome里面使用工具类

终端IP spbill_create_ip:官网的dome中获得

通知地址 notify_url:比如下单成功之后的回调地址。通知url必须为外网可访问的url,不能携带参数

用户标识 openid:trade_type=JSAPI时(即JSAPI支付),此参数必传,此参数为微信用户在商户对应appid下的唯一标识

交易类型 trade_type:JSAPI -JSAPI支付 NATIVE -Native支付 APP -APP支付

签名 sign:通过签名算法计算得出的签名值,详见签名生成算法

这里我们所作的是JSAPI支付方式,所以这里的openid必需要获得:如何获得,该怎么获得。

首先,要知道我们使用的是微信里面自带的浏览器,我们通过微信浏览器给当前用户进行用户授权。看JS 代码

var code = "";
$(function(){
        /**
            如果你之前从上一个页面已经获得code,你可以直接从url带过来
            这个code是从微信浏览器获得的,随后通过这个code去后台请求微信端
            获取openid 
        */
	code=getUrlParam("code");
	if(code==undefined || code==""){
		if(is_weixn()){
			$(location).attr("href","https://open.weixin.qq.com/connect/oauth2/authorize?appid=xxx&redirect_uri=xxx&response_type=code&scope=snsapi_base&state=1#wechat_redirect");			
		}else{
			//跳转让他关注公众号
			//Alert3('不是微信浏览器');
			$(location).attr("href","#");
		}
	}
})
/**
*判断是否微信浏览器
*/
function is_weixn(){
    var ua = navigator.userAgent.toLowerCase();
    if(ua.match(/MicroMessenger/i)=="micromessenger") {
        return true;
    } else {
        return false;
    }
}
/**
*获取url中的参数
*/
function getUrlParam(name) {
  var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); 
  var r = window.location.search.substr(1).match(reg);  
  if (r != null) return unescape(r[2]); return null;
}

这里有几个参数需要注意以下:

获取code 的请求地址:https://open.weixin.qq.com/connect/oauth2/authorize?

后面带参数appid=商户后台获取的appid

redirect_uri=当前需要支付授权的页面的完整地址(跳转回调redirect_uri,应当使用https链接来确保授权code的安全性。),需要https,需要对地址进行urlEncode处理,需要注意参数顺序。

后面参数固定不变。

这个code 在前端获取之后就有这个值,需请求后台即可拿到openid

订单支付

/**
*微信支付
*/
$("#wxpay").click(function(){	
	var parameter={};
	if(code !=null && code!=undefined && code!=""){
		parameter["code"]=code;
	}else{
		Alert3('微信授权失败,无法支付');
		return;
	}
        //支付方式0/1、wx/zfb
	parameter["payMethod"]="0";
        parameter["amount"]=价格;
	var data=getData2(parameter);
	var requestDate=ajax(url + "/xxx",data);
	if(requestDate["code"]==10000){	
		res(requestDate["prepayId"]);	//prepayId微信支付成功的返回参数,需要带到前台
	}else{
		alert(requestDate["msg"]);	
	}
})
function getData2(parameter){
	var data={};
	data["parameter"]=parameter;
	return JSON.stringify(data);
}
/**
*支付标识验证
*/
function res(prepayId){
	var appId="xxx";//商户appid
	var timestamp=Math.round(new Date().getTime()/1000).toString();
	var nonceStr=getMachine();
	var package="prepay_id="+prepayId;
	var signType="MD5";
	var stringSignTemp="appId={0}&nonceStr={1}&package={2}&signType={3}&timeStamp={4}&key=qwertyuiopasdfghjklzxcvbqwertyui";
	stringSignTemp=String.format(stringSignTemp,appId,nonceStr,package,signType,timestamp);
	var paySign=$.md5(stringSignTemp).toUpperCase();
	onBridgeReady(appId,timestamp,nonceStr,package,signType,paySign);
}
/**
*支付参数校验
*/
function onBridgeReady(appId,timestamp,nonceStr,package,signType,paySign){
   WeixinJSBridge.invoke(
      'getBrandWCPayRequest', {
         "appId":appId,     //公众号名称,由商户传入     
         "timeStamp":timestamp,         //时间戳,自1970年以来的秒数     
         "nonceStr":nonceStr, //随机串     
         "package":package,     
         "signType":signType,         //微信签名方式:     
         "paySign":paySign //微信签名 
      },
      function(res){
      if(res.err_msg == "get_brand_wcpay_request:ok" ){
		  alert("支付成功");
     	 // 使用以上方式判断前端返回,微信团队郑重提示:
            //res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
      } else{
			alert("支付失败");
		}
   }); 
}
/**
*获得随机数
*/
function getMachine(){
	var chars = ['0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'
				 ,'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'];
	var machine="";
	for(var i=0; i<16;i++){
		var t=parseInt(61*Math.random())
		machine=machine+chars[t];
	}
	return machine;
}

 后台调用

//支付调用
            String payMethod = object.optString("payMethod"); //支付方式
//            String orderNo = "xxx";
            if (payMethod.trim().equals("0")){
                String code = object.optString("code");

                String wx_url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + SysConfig.getWx_appid() + "&secret=" + SysConfig.getWx_secret() + "&code=" + code + "&grant_type=authorization_code";

                String sb2 = HttpUtil.httpsGet(wx_url);
                if(sb2 == null || "".equals(sb2)){
                    map.put("code",30001);
                    map.put("msg","网络繁忙,请稍后重试");
                    return map;
                }
                JSONObject object2 = JSONObject.fromObject(sb2);
                System.out.println("object2==="+object2);
                openid = object2.optString("openid");
                if (openid == ""){
                    map.put("code", 31002);
                    map.put("msg", "授权失败,请稍后重试");
                    return map;
                }
                Map<String, String> data = new HashMap<String, String>();
                data.put("appid", SysConfig.getWx_appid());//商户appid
                data.put("body", "xxx支付:");
                data.put("detail", "xxx描述");
                data.put("mch_id", SysConfig.getWx_mch_id());
                data.put("nonce_str",WXPayUtil.generateNonceStr());//随机串
                data.put("notify_url", SysConfig.getWx_notify_verify());//回调地址
                data.put("openid",openid);
                data.put("out_trade_no", orderNo);//商户订单
                data.put("sign_type", "MD5");
                data.put("spbill_create_ip", GetIpUtil.getIp(request));
                data.put("total_fee", String.valueOf((int)(amount*100)));//金额:分
                data.put("trade_type", "JSAPI");
                String xml = WXPayUtil.generateSignedXml(data,SysConfig.getWx_key());//这里是微信商户的支付key,也就是api密钥
                System.out.println("xml==="+xml);
                String respn = HttpUtil.sendJsonPost(SysConfig.getWx_payMent(),xml).toString();
                if (respn == null){
                    map.put("code", 31001);
                    map.put("msg", "系统繁忙,请稍后重试");
                    return map;
                }
                Map resp = JSONObject.fromObject(WXPayUtil.parseXml(respn).get("xml"));
                if("SUCCESS".equals(resp.get("return_code"))){
                    prepayId = resp.get("prepay_id").toString();
                }else{
                    map.put("code", 31003);
                    map.put("msg", "系统繁忙,请稍后重试");
                    return map;
                }
            }else{
                map.put("code", 10004);
                map.put("msg", "支付方式异常!");
            }

下面是支付所需的工具类:

发起请求的POST和GET

    public static String httpsGet(String urlStr) {
        StringBuilder result = new StringBuilder();
        BufferedReader reader = null;
        try {
            // 创建连接
            URL url = new URL(urlStr);
            HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
            connection.connect();

            // 读取响应
            reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
            String line;
            System.out.println("Https返回:");
            while ((line = reader.readLine()) != null) {
                result.append(line.trim());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            try {
                if (reader != null) {
                    reader.close();
                }
            } catch (IOException ex) {
                LogUtil.error("io流关闭错误!" + ex);
                ex.printStackTrace();
            }
        }
        return result.toString();
    }
//发送json请求
    public static StringBuilder sendJsonPost(String url, String param) {
        if (url == null) {
            return new StringBuilder("NETWORKURLERROR");
        }
        PrintWriter out = null;
        BufferedReader in = null;
        StringBuilder result = new StringBuilder("");
        try {
            LogUtil.info(url + ">>>>>>" + param);
            URL realUrl = new URL(url);// 创建连接
            HttpURLConnection connection = (HttpURLConnection) realUrl.openConnection();
            connection.setDoOutput(true);
            connection.setDoInput(true);
            connection.setUseCaches(false);
            connection.setInstanceFollowRedirects(true);
            connection.setRequestMethod("POST"); // 设置请求方式
            connection.setRequestProperty("Accept", "application/json"); // 设置接收数据的格式
            connection.setRequestProperty("Content-Type", "application/json"); // 设置发送数据的格式
            connection.connect();
            OutputStreamWriter out1 = new OutputStreamWriter(
                    connection.getOutputStream(), "UTF-8"); // utf-8编码
            out1.append(param);
            out1.flush();
            out1.close();
            // 读取响应
            // 定义BufferedReader输入流来读取URL的响应
            in = new BufferedReader(
                    new InputStreamReader(connection.getInputStream(), "UTF-8"));
            String line;
            while ((line = in.readLine()) != null) {
                result.append(line);
            }

        } catch (Exception e) {
            LogUtil.error("发送 POST 请求出现异常!" + e);
            e.printStackTrace();
            return new StringBuilder("NETWORKERROR");
        }
        //使用finally块来关闭输出流、输入流
        finally {
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (IOException ex) {
                LogUtil.error("io流关闭错误!" + ex);
                ex.printStackTrace();
            }
        }
        return result;
    }

 微信配置类SysConfig

/**
 * Created by jy on 2019/8/21.
 * 系统级公共参数配置
 */
public class SysConfig {
    private static String ip = "xxxx";
    private static String ip2 = "xxxx";

    //微信
    private static String wx_certPath = "D:\\cer\\apiclient_cert.p12";//退款需要
    private static String wx_appid = "xxx";//商户获得
    private static String wx_mch_id = "xxx";//商户获得
    private static String wx_paymentKey = "xxx";//商户获得
    private static String wx_secret = "xxx";//商户获得
    private static String wx_notify_verify = "xxx";//微信回调地址

    //微信接口地址
    private static String wx_payMent = "https://api.mch.weixin.qq.com/pay/unifiedorder";
    private static String wx_refund = "https://api.mch.weixin.qq.com/secapi/pay/refund";
    private static String wx_query = "https://api.mch.weixin.qq.com/pay/orderquery";

    public static String getWx_payMent() {
        return wx_payMent;
    }

    public static String getWx_query() {
        return wx_query;
    }

    public static String getWx_refund() {
        return wx_refund;
    }

    public static String getIp() {
        return ip;
    }

    public static String getIp2() {
        return ip2;
    }

    public static String getWx_certPath() {
        return wx_certPath;
    }

    public static String getWx_appid() {
        return wx_appid;
    }

    public static String getWx_mch_id() {
        return wx_mch_id;
    }

    public static String getWx_key() {
        return wx_paymentKey;
    }

    public static String getWx_secret() {
        return wx_secret;
    }

    public static String getWx_notify_verify() {
        return wx_notify_verify;
    }
}

 微信支付的工具类WXPayUtil

import com.github.wxpay.sdk.WXPayConstants;
import com.github.wxpay.sdk.WXPayConstants.SignType;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayInputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.*;

/**
 * Created with IDEA
 * author:Jy
 * Date:2019/8/30
 * Time:10:11
 */
public class WXPayUtil {

    private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

    private static final Random RANDOM = new SecureRandom();

    /**
     * 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。
     *
     * @param data Map类型数据
     * @param key API密钥
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
        return isSignatureValid(data, key, SignType.MD5);
    }

    /**
     * 判断签名是否正确,必须包含sign字段,否则返回false。
     *
     * @param data Map类型数据
     * @param key API密钥
     * @param signType 签名方式
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(Map<String, String> data, String key, SignType signType) throws Exception {
        if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
            return false;
        }
        String sign = data.get(WXPayConstants.FIELD_SIGN);
        return generateSignature(data, key, signType).equals(sign);
    }

    /**
     * 生成签名
     *
     * @param data 待签名数据
     * @param key API密钥
     * @return 签名
     */
    public static String generateSignature(final Map<String, String> data, String key) throws Exception {
        return generateSignature(data, key, SignType.MD5);
    }

    /**
     * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
     *
     * @param data 待签名数据
     * @param key API密钥
     * @param signType 签名方式
     * @return 签名
     */
    public static String generateSignature(final Map<String, String> data, String key, SignType signType) throws Exception {
        Set<String> keySet = data.keySet();
        String[] keyArray = keySet.toArray(new String[keySet.size()]);
        Arrays.sort(keyArray);
        StringBuilder sb = new StringBuilder();
        for (String k : keyArray) {
            if (k.equals(WXPayConstants.FIELD_SIGN)) {
                continue;
            }
            if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
                sb.append(k).append("=").append(data.get(k).trim()).append("&");
        }
        sb.append("key=").append(key);
        if (SignType.MD5.equals(signType)) {
            return MD5(sb.toString()).toUpperCase();
        }
        else if (SignType.HMACSHA256.equals(signType)) {
            return HMACSHA256(sb.toString(), key);
        }
        else {
            throw new Exception(String.format("Invalid sign_type: %s", signType));
        }
    }


    /**
     * 获取随机字符串 Nonce Str
     *
     * @return String 随机字符串
     */
    public static String generateNonceStr() {
        char[] nonceChars = new char[32];
        for (int index = 0; index < nonceChars.length; ++index) {
            nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
        }
        return new String(nonceChars);
    }


    /**
     * 生成 MD5
     *
     * @param data 待处理数据
     * @return MD5结果
     */
    public static String MD5(String data) throws Exception {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] array = md.digest(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }

    /**
     * 生成 HMACSHA256
     * @param data 待处理数据
     * @param key 密钥
     * @return 加密结果
     * @throws Exception
     */
    public static String HMACSHA256(String data, String key) throws Exception {
        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
        sha256_HMAC.init(secret_key);
        byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }
    /**
     * 获取当前时间戳,单位秒
     * @return
     */
    public static long getCurrentTimestamp() {
        return System.currentTimeMillis()/1000;
    }

    /**
     * 获取当前时间戳,单位毫秒
     * @return
     */
    public static long getCurrentTimestampMs() {
        return System.currentTimeMillis();
    }
    /**
     * 生成带有 sign 的 XML 格式字符串
     *
     * @param data Map类型数据
     * @param key API密钥
     * @return 含有sign字段的XML
     */
    public static String generateSignedXml(final Map<String, String> data, String key) throws Exception {
        return generateSignedXml(data, key, SignType.MD5);
    }

    /**
     * 生成带有 sign 的 XML 格式字符串
     *
     * @param data Map类型数据
     * @param key API密钥
     * @param signType 签名类型
     * @return 含有sign字段的XML
     */
    public static String generateSignedXml(final Map<String, String> data, String key, SignType signType) throws Exception {
        String sign = generateSignature(data, key, signType);
        data.put(WXPayConstants.FIELD_SIGN, sign);
        return mapToXml(data);
    }
    /**
     * 将Map转换为XML格式的字符串
     *
     * @param data Map类型数据
     * @return XML格式的字符串
     * @throws Exception
     */
    public static String mapToXml(Map<String, String> data) throws Exception {
        org.w3c.dom.Document document = WXPayXmlUtil.newDocument();
        org.w3c.dom.Element root = document.createElement("xml");
        document.appendChild(root);
        for (String key: data.keySet()) {
            String value = data.get(key);
            if (value == null) {
                value = "";
            }
            value = value.trim();
            org.w3c.dom.Element filed = document.createElement(key);
            filed.appendChild(document.createTextNode(value));
            root.appendChild(filed);
        }
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        DOMSource source = new DOMSource(document);
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        StringWriter writer = new StringWriter();
        StreamResult result = new StreamResult(writer);
        transformer.transform(source, result);
        String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
        try {
            writer.close();
        }
        catch (Exception ex) {
        }
        return output;
    }
    /**
     * xml转为map,map中有list(节点相同时候),list中有map
     * @param xml
     * @return
     * @throws DocumentException
     */
    public static Map<String, Object> parseXml(String xml) throws DocumentException {
        Map map = new HashMap();
        try {
            SAXReader saxReader = new SAXReader();
            Document document = saxReader.read(new ByteArrayInputStream(xml.getBytes("UTF-8")));
            Element root = document.getRootElement();
            elementTomap(root, map);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return map;
    }
    @SuppressWarnings("unchecked")
    public static Map<String, Object> elementTomap(Element outele, Map<String, Object> outmap) {
        List<Element> list = outele.elements();
        int size = list.size();
        if (size == 0) {
            outmap.put(outele.getName(), outele.getTextTrim());
        } else {
            Map<String, Object> innermap = new HashMap<String, Object>();
            int i = 1;
            for (Element ele1 : list) {
                String eleName = ele1.getName();
                String value = ele1.getText();
                Object obj = innermap.get(eleName);
                if (obj == null) {
                    elementTomap(ele1, innermap);
                } else {
                    if (obj instanceof java.util.Map) {
                        List<Map<String, Object>> list1 = new ArrayList<Map<String, Object>>();
                        list1.add((Map<String, Object>) innermap.remove(eleName));
                        elementTomap(ele1, innermap);
                        list1.add((Map<String, Object>) innermap.remove(eleName));
                        innermap.put(eleName, list1);
                    } else if (obj instanceof String) {
                        innermap.put(eleName + i, value);
                        i++;
                    } else {
                        elementTomap(ele1, innermap);
                        Map<String, Object> listValue = (Map<String, Object>) innermap.get(eleName);
                        ((List<Map<String, Object>>) obj).add(listValue);
                        innermap.put(eleName, obj);
                    }
                }
            }
            outmap.put(outele.getName(), innermap);
        }
        return outmap;
    }
}

 微信获取IP的工具类GetIpUtil

package com.cn.hjsj.util.http;

import javax.servlet.http.HttpServletRequest;

/**
 * Created by jy on 2019/8/21.
 */
public class GetIpUtil {

    public static String getIp(HttpServletRequest request) {
        String ip = null;

        try{
            ip = request.getHeader("x-forwarded-for");
//            System.out.println("ip0=" + ip);
            if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("Proxy-Client-IP");
//                System.out.println("ip1=" + ip);
            }
            if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
//                System.out.println("ip2=" + ip);
            }
            if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
//                System.out.println("ip3=" + ip);
            }
        } catch(Exception e){
            e.printStackTrace();
        }

        return ip;
    }

}

 微信xml转化的工具类WXPayXmlUtil

import org.w3c.dom.Document;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

/**
 * 2018/7/3
 */
public final class WXPayXmlUtil {
    public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
        documentBuilderFactory.setXIncludeAware(false);
        documentBuilderFactory.setExpandEntityReferences(false);

        return documentBuilderFactory.newDocumentBuilder();
    }

    public static Document newDocument() throws ParserConfigurationException {
        return newDocumentBuilder().newDocument();
    }
}

 可能需要用到的maven依赖

<dependency>
    <groupId>com.mashape.unirest</groupId>
    <artifactId>unirest-java</artifactId>
    <version>1.4.9</version>
</dependency>

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.6</version>
</dependency>

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpasyncclient</artifactId>
    <version>4.0.2</version>
</dependency>

<!--微信 -->
<dependency>
    <groupId>com.github.wxpay</groupId>
    <artifactId>wxpay-sdk</artifactId>
    <version>0.0.3</version>
</dependency>

至此,支付就基本上可以使用了。

支付回调返回的参数是xml形式,直接贴xml返回串,具体根据自己的业务需求做响应的变化

<xml><appid><![CDATA[xxxx]]></appid>
<bank_type><![CDATA[CFT]]></bank_type>
<cash_fee><![CDATA[1]]></cash_fee>
<fee_type><![CDATA[CNY]]></fee_type>
<is_subscribe><![CDATA[Y]]></is_subscribe>
<mch_id><![CDATA[xxxxx]]></mch_id>
<nonce_str><![CDATA[aydmDbuhMAYW7b9jhyvsJqBcQs2X1XRwXk]]></nonce_str>
<openid><![CDATA[oGTD2jo4F9ElXBmdbz66FAFxRrcE]]></openid>
<out_trade_no><![CDATA[OL201909170944228907100003]]></out_trade_no>
<result_code><![CDATA[SUCCESS]]></result_code>
<return_code><![CDATA[SUCCESS]]></return_code>
<sign><![CDATA[3764A45AFE8F110EC4359B3C268A7DBE]]></sign>
<time_end><![CDATA[20190917095333]]></time_end>
<total_fee>1</total_fee>
<trade_type><![CDATA[JSAPI]]></trade_type>
<transaction_id><![CDATA[4200000408201909173245179213]]></transaction_id>
</xml>

订单退款

订单退款首先需要商户号证书,这个证书是从商户平台下载下来的。

我这里封装查询订单和退款的工具类WxInterfaceUtil

import com.cn.hjsj.base.cache.SysConfig;
import com.cn.hjsj.util.http.HttpUtil;
import net.sf.json.JSONObject;

import java.io.FileInputStream;
import java.security.KeyStore;
import java.util.HashMap;
import java.util.Map;

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLContext;

/**
 * 微信接口调用
 * Created with IDEA
 * author:Jy
 * Date:2019/8/30
 * Time:9:58
 */
public class WxInterfaceUtil {
    /**
     * 微信申请退款
     *
     * @param orderNo
     * @param amount
     * @return
     */
    public static Map<String, Object> reWxRefund(String orderNo, int amount) {
        Map<String, Object> map = new HashMap<String, Object>();
        try {
            Map<String, String> data = new HashMap<String, String>();
            data.put("appid", SysConfig.getWx_appid());
            data.put("mch_id", SysConfig.getWx_mch_id());
            data.put("notify_url", SysConfig.getWx_notify_verify());
            data.put("nonce_str", WXPayUtil.generateNonceStr());
            data.put("out_trade_no", orderNo);
            data.put("out_refund_no","RE" + orderNo);
            data.put("sign_type", "MD5");
            data.put("total_fee", String.valueOf(amount));
            data.put("refund_fee", String.valueOf(amount));
            data.put("refund_desc", "预订单审核不通过");
            String xml = WXPayUtil.generateSignedXml(data, SysConfig.getWx_key());
            String respn = WxInterfaceUtil.doRefund(SysConfig.getWx_mch_id(), SysConfig.getWx_refund(), xml);
            System.out.println(respn);
            if (respn == null) {
                map.put("code", 31001);
                map.put("msg", "系统繁忙,请稍后重试");
                return map;
            }
            Map resp = JSONObject.fromObject(WXPayUtil.parseXml(respn).get("xml"));
            if ("SUCCESS".equals(resp.get("return_code"))) {
                if ("SUCCESS".equals(resp.get("result_code"))) {
                    map.put("code", 10000);
                    map.put("msg", "申请退款成功");
                } else {
                    map.put("code", 31003);
                    map.put("msg", resp.get("err_code_des"));
                    return map;
                }
            } else {
                map.put("code", 31003);
                map.put("msg", "系统繁忙,请稍后重试");
                return map;
            }
        } catch (Exception e) {
            e.printStackTrace();
            map.put("code", 30007);
            map.put("msg", e.getMessage());
        }
        return map;
    }

    /**
     * 微信查询订单
     *
     * @param orderNo
     * @return
     */
    public static Map<String, Object> orWxQuery(String orderNo) {
        HashMap<String, Object> map = new HashMap<String, Object>();
        try {
            Map<String, String> data = new HashMap<String, String>();
            data.put("appid", SysConfig.getWx_appid());
            data.put("mch_id", SysConfig.getWx_mch_id());
            data.put("nonce_str", WXPayUtil.generateNonceStr());
            data.put("out_trade_no", orderNo);
            data.put("sign_type", "MD5");
            String xml = WXPayUtil.generateSignedXml(data, SysConfig.getWx_key());
            String respn = HttpUtil.sendJsonPost(SysConfig.getWx_query(), xml).toString();
            if (respn == null) {
                map.put("code", 31001);
                map.put("msg", "系统繁忙,请稍后重试");
                return map;
            }
            Map resp = JSONObject.fromObject(WXPayUtil.parseXml(respn).get("xml"));
            if ("SUCCESS".equals(resp.get("return_code"))) {
                map.put("amount", resp.get("total_fee"));
                map.put("code", 10000);
                map.put("msg", "查询金额成功");
            } else {
                map.put("code", 31003);
                map.put("msg", "系统繁忙,请稍后重试");
                return map;
            }
        } catch (Exception e) {
            e.printStackTrace();
            map.put("code", 30007);
            map.put("msg", e.getMessage());
        }
        return map;
    }

    /**
     * 退款工具
     *
     * @param mchId
     * @param url
     * @param data
     * @return
     * @throws Exception
     */
    public static String doRefund(String mchId, String url, String data) throws Exception {
        /**
         * 注意PKCS12证书 是从微信商户平台-》账户设置-》 API安全 中下载的
         */
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        //P12文件目录 证书路径,这里需要你自己修改,linux下还是windows下的根路径
        FileInputStream instream = new FileInputStream(SysConfig.getWx_certPath());
        try {
            keyStore.load(instream, mchId.toCharArray());//这里写密码..默认是你的MCHID
        } finally {
            instream.close();
        }
        // Trust own CA and all self-signed certs
        SSLContext sslcontext = SSLContexts.custom()
                .loadKeyMaterial(keyStore, mchId.toCharArray())//这里也是写密码的
                .build();
        // Allow TLSv1 protocol only
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                sslcontext,
                SSLConnectionSocketFactory.getDefaultHostnameVerifier());
        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 jsonStr;
            } finally {
                response.close();
            }
        } finally {
            httpclient.close();
        }
    }
}

发起微信退款的时候需要校验证书,发起成功之后我们会收到退款回调:参数如下

<xml>
<return_code>SUCCESS</return_code>
<appid><![CDATA[xxxxx]]></appid>
<mch_id><![CDATA[xxxxxx]]></mch_id>
<nonce_str><![CDATA[e748ab147wef242f34f342a2a5b91998]]></nonce_str>
<req_info><![CDATA[AES加密内容]]></req_info>
</xml>

这里我们需要使用使用工具类进行解密,解密

过程由于使用的AES解密,jdk中的jre自带的AES解密算法和正式的解密有一点偏差,会出现

AES加解密报错:Illegal key size or default parameters

推荐查看:https://blog.csdn.net/tomatocc/article/details/85096911

微信退款工具类Base64


/**
 * Created with IDEA
 * author:Jy
 * Date:2019/9/16
 * Time:10:13
 */

import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;

public class Base64 {

    private static final char S_BASE64CHAR[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
            'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
            'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
    private static final byte S_DECODETABLE[];

    static {
        S_DECODETABLE = new byte[128];
        for (int i = 0; i < S_DECODETABLE.length; i++)
            S_DECODETABLE[i] = 127;

        for (int i = 0; i < S_BASE64CHAR.length; i++)
            S_DECODETABLE[S_BASE64CHAR[i]] = (byte) i;

    }

    /**
     * @param ibuf
     * @param obuf
     * @param wp
     * @return
     */
    private static int decode0(char ibuf[], byte obuf[], int wp) {
        int outlen = 3;
        if (ibuf[3] == '=')
            outlen = 2;
        if (ibuf[2] == '=')
            outlen = 1;
        int b0 = S_DECODETABLE[ibuf[0]];
        int b1 = S_DECODETABLE[ibuf[1]];
        int b2 = S_DECODETABLE[ibuf[2]];
        int b3 = S_DECODETABLE[ibuf[3]];
        switch (outlen) {
            case 1: // '\001'
                obuf[wp] = (byte) (b0 << 2 & 252 | b1 >> 4 & 3);
                return 1;

            case 2: // '\002'
                obuf[wp++] = (byte) (b0 << 2 & 252 | b1 >> 4 & 3);
                obuf[wp] = (byte) (b1 << 4 & 240 | b2 >> 2 & 15);
                return 2;

            case 3: // '\003'
                obuf[wp++] = (byte) (b0 << 2 & 252 | b1 >> 4 & 3);
                obuf[wp++] = (byte) (b1 << 4 & 240 | b2 >> 2 & 15);
                obuf[wp] = (byte) (b2 << 6 & 192 | b3 & 63);
                return 3;
        }
        throw new RuntimeException("Internal error");
    }

    /**
     * @param data
     * @param off
     * @param len
     * @return
     */
    public static byte[] decode(char data[], int off, int len) {
        char ibuf[] = new char[4];
        int ibufcount = 0;
        byte obuf[] = new byte[(len / 4) * 3 + 3];
        int obufcount = 0;
        for (int i = off; i < off + len; i++) {
            char ch = data[i];
            if (ch != '=' && (ch >= S_DECODETABLE.length || S_DECODETABLE[ch] == 127))
                continue;
            ibuf[ibufcount++] = ch;
            if (ibufcount == ibuf.length) {
                ibufcount = 0;
                obufcount += decode0(ibuf, obuf, obufcount);
            }
        }

        if (obufcount == obuf.length) {
            return obuf;
        } else {
            byte ret[] = new byte[obufcount];
            System.arraycopy(obuf, 0, ret, 0, obufcount);
            return ret;
        }
    }

    /**
     * @param data
     * @return
     */
    public static byte[] decode(String data) {
        char ibuf[] = new char[4];
        int ibufcount = 0;
        byte obuf[] = new byte[(data.length() / 4) * 3 + 3];
        int obufcount = 0;
        for (int i = 0; i < data.length(); i++) {
            char ch = data.charAt(i);
            if (ch != '=' && (ch >= S_DECODETABLE.length || S_DECODETABLE[ch] == 127))
                continue;
            ibuf[ibufcount++] = ch;
            if (ibufcount == ibuf.length) {
                ibufcount = 0;
                obufcount += decode0(ibuf, obuf, obufcount);
            }
        }

        if (obufcount == obuf.length) {
            return obuf;
        } else {
            byte ret[] = new byte[obufcount];
            System.arraycopy(obuf, 0, ret, 0, obufcount);
            return ret;
        }
    }

    /**
     * @param data
     * @param off
     * @param len
     * @param ostream
     * @throws IOException
     */
    public static void decode(char data[], int off, int len, OutputStream ostream) throws IOException {
        char ibuf[] = new char[4];
        int ibufcount = 0;
        byte obuf[] = new byte[3];
        for (int i = off; i < off + len; i++) {
            char ch = data[i];
            if (ch != '=' && (ch >= S_DECODETABLE.length || S_DECODETABLE[ch] == 127))
                continue;
            ibuf[ibufcount++] = ch;
            if (ibufcount == ibuf.length) {
                ibufcount = 0;
                int obufcount = decode0(ibuf, obuf, 0);
                ostream.write(obuf, 0, obufcount);
            }
        }

    }

    /**
     * @param data
     * @param ostream
     * @throws IOException
     */
    public static void decode(String data, OutputStream ostream) throws IOException {
        char ibuf[] = new char[4];
        int ibufcount = 0;
        byte obuf[] = new byte[3];
        for (int i = 0; i < data.length(); i++) {
            char ch = data.charAt(i);
            if (ch != '=' && (ch >= S_DECODETABLE.length || S_DECODETABLE[ch] == 127))
                continue;
            ibuf[ibufcount++] = ch;
            if (ibufcount == ibuf.length) {
                ibufcount = 0;
                int obufcount = decode0(ibuf, obuf, 0);
                ostream.write(obuf, 0, obufcount);
            }
        }

    }

    /**
     * @param data
     * @return
     */
    public static String encode(byte data[]) {
        return encode(data, 0, data.length);
    }

    /**
     * @param data
     * @param off
     * @param len
     * @return
     */
    public static String encode(byte data[], int off, int len) {
        if (len <= 0)
            return "";
        char out[] = new char[(len / 3) * 4 + 4];
        int rindex = off;
        int windex = 0;
        int rest;
        for (rest = len - off; rest >= 3; rest -= 3) {
            int i = ((data[rindex] & 255) << 16) + ((data[rindex + 1] & 255) << 8) + (data[rindex + 2] & 255);
            out[windex++] = S_BASE64CHAR[i >> 18];
            out[windex++] = S_BASE64CHAR[i >> 12 & 63];
            out[windex++] = S_BASE64CHAR[i >> 6 & 63];
            out[windex++] = S_BASE64CHAR[i & 63];
            rindex += 3;
        }

        if (rest == 1) {
            int i = data[rindex] & 255;
            out[windex++] = S_BASE64CHAR[i >> 2];
            out[windex++] = S_BASE64CHAR[i << 4 & 63];
            out[windex++] = '=';
            out[windex++] = '=';
        } else if (rest == 2) {
            int i = ((data[rindex] & 255) << 8) + (data[rindex + 1] & 255);
            out[windex++] = S_BASE64CHAR[i >> 10];
            out[windex++] = S_BASE64CHAR[i >> 4 & 63];
            out[windex++] = S_BASE64CHAR[i << 2 & 63];
            out[windex++] = '=';
        }
        return new String(out, 0, windex);
    }

    /**
     * @param data
     * @param off
     * @param len
     * @param ostream
     * @throws IOException
     */
    public static void encode(byte data[], int off, int len, OutputStream ostream) throws IOException {
        if (len <= 0)
            return;
        byte out[] = new byte[4];
        int rindex = off;
        int rest;
        for (rest = len - off; rest >= 3; rest -= 3) {
            int i = ((data[rindex] & 255) << 16) + ((data[rindex + 1] & 255) << 8) + (data[rindex + 2] & 255);
            out[0] = (byte) S_BASE64CHAR[i >> 18];
            out[1] = (byte) S_BASE64CHAR[i >> 12 & 63];
            out[2] = (byte) S_BASE64CHAR[i >> 6 & 63];
            out[3] = (byte) S_BASE64CHAR[i & 63];
            ostream.write(out, 0, 4);
            rindex += 3;
        }

        if (rest == 1) {
            int i = data[rindex] & 255;
            out[0] = (byte) S_BASE64CHAR[i >> 2];
            out[1] = (byte) S_BASE64CHAR[i << 4 & 63];
            out[2] = 61;
            out[3] = 61;
            ostream.write(out, 0, 4);
        } else if (rest == 2) {
            int i = ((data[rindex] & 255) << 8) + (data[rindex + 1] & 255);
            out[0] = (byte) S_BASE64CHAR[i >> 10];
            out[1] = (byte) S_BASE64CHAR[i >> 4 & 63];
            out[2] = (byte) S_BASE64CHAR[i << 2 & 63];
            out[3] = 61;
            ostream.write(out, 0, 4);
        }
    }

    /**
     * @param data
     * @param off
     * @param len
     * @param writer
     * @throws IOException
     */
    public static void encode(byte data[], int off, int len, Writer writer) throws IOException {
        if (len <= 0)
            return;
        char out[] = new char[4];
        int rindex = off;
        int rest = len - off;
        int output = 0;
        do {
            if (rest < 3)
                break;
            int i = ((data[rindex] & 255) << 16) + ((data[rindex + 1] & 255) << 8) + (data[rindex + 2] & 255);
            out[0] = S_BASE64CHAR[i >> 18];
            out[1] = S_BASE64CHAR[i >> 12 & 63];
            out[2] = S_BASE64CHAR[i >> 6 & 63];
            out[3] = S_BASE64CHAR[i & 63];
            writer.write(out, 0, 4);
            rindex += 3;
            rest -= 3;
            if ((output += 4) % 76 == 0)
                writer.write("\n");
        }
        while (true);
        if (rest == 1) {
            int i = data[rindex] & 255;
            out[0] = S_BASE64CHAR[i >> 2];
            out[1] = S_BASE64CHAR[i << 4 & 63];
            out[2] = '=';
            out[3] = '=';
            writer.write(out, 0, 4);
        } else if (rest == 2) {
            int i = ((data[rindex] & 255) << 8) + (data[rindex + 1] & 255);
            out[0] = S_BASE64CHAR[i >> 10];
            out[1] = S_BASE64CHAR[i >> 4 & 63];
            out[2] = S_BASE64CHAR[i << 2 & 63];
            out[3] = '=';
            writer.write(out, 0, 4);
        }
    }

}

MD5工具类

/**
 * Created with IDEA
 * author:Jy
 * Date:2019/9/16
 * Time:10:41
 */

import java.security.MessageDigest;

public class MD5Util {
    public final static String MD5(String s) {
        char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        try {
            byte[] btInput = s.getBytes();
            // 获得MD5摘要算法的 MessageDigest 对象
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            // 使用指定的字节更新摘要
            mdInst.update(btInput);
            // 获得密文
            byte[] md = mdInst.digest();
            // 把密文转换成十六进制的字符串形式
            int j = md.length;
            char str[] = new char[j * 2];
            int k = 0;
            for (int i = 0; i < j; i++) {
                byte byte0 = md[i];
                str[k++] = hexDigits[byte0 >>> 4 & 0xf];
                str[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(str);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private static String byteArrayToHexString(byte b[]) {
        StringBuffer resultSb = new StringBuffer();
        for (int i = 0; i < b.length; i++)
            resultSb.append(byteToHexString(b[i]));

        return resultSb.toString();
    }

    private static String byteToHexString(byte b) {
        int n = b;
        if (n < 0)
            n += 256;
        int d1 = n / 16;
        int d2 = n % 16;
        return hexDigits[d1] + hexDigits[d2];
    }

    public static String MD5Encode(String origin, String charsetname) {
        String resultString = null;
        try {
            resultString = new String(origin);
            MessageDigest md = MessageDigest.getInstance("MD5");
            if (charsetname == null || "".equals(charsetname))
                resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
            else
                resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));
        } catch (Exception exception) {
        }
        return resultString;
    }

    private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};

}

AES工具类

import com.cn.hjsj.base.cache.SysConfig;
import com.github.wxpay.sdk.WXPayUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.security.Security;
import java.util.Map;

/**
 * Created with IDEA
 * author:Jy
 * Date:2019/9/16
 * Time:10:42
 */
public class AESUtil {


    /**
     * 密钥算法
     */
    private static final String ALGORITHM = "AES";
    /**
     * 加解密算法/工作模式/填充方式
     */
    private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS7Padding";
    /**
     * 生成key
     */
    //微信支付API密钥设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置
    private static String paySign = SysConfig.getWx_key();
    //对商户key做md5,得到32位小写key*
    private static SecretKeySpec key = new SecretKeySpec(MD5Util.MD5Encode(paySign, "UTF-8").toLowerCase().getBytes(), ALGORITHM);

    /**
     * AES加密
     *
     * @param data
     * @return
     * @throws Exception
     */
    public static String encryptData(String data) throws Exception {
        Security.addProvider(new BouncyCastleProvider());
        // 创建密码器
        Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING, "BC");
        // 初始化
        cipher.init(Cipher.ENCRYPT_MODE, key);
        return Base64.encode(cipher.doFinal(data.getBytes()));
    }

    /**
     * AES解密
     * <p>
     * (1)对加密串A做base64解码,得到加密串B
     * (2)用key*对加密串B做AES-256-ECB解密(PKCS7Padding)
     *
     * @param base64Data
     * @return
     * @throws Exception
     */
    public static String decryptData(String base64Data) throws Exception {
        Security.addProvider(new BouncyCastleProvider());
        Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING, "BC");
        cipher.init(Cipher.DECRYPT_MODE, key);
        return new String(cipher.doFinal(Base64.decode(base64Data)));
    }
}

 到这里微信退款就基本完成了,当然微信api里面也有更方便的发起方式,代码也会更加简洁。当初使用的是微信的api发起方式老是出现签名不通过,随后没用了,自己封装才可以用。错误也知道是什么原因了。是我创建WxPay对象的时候把支付key弄错了。

 


转载:https://blog.csdn.net/weixin_42102798/article/details/100925448
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场