微信支付服务商版-java实战

最新开发了一版服务商版的微信支付,踩坑良多,一起与大家分享下。

 

先抛官方文档。多读文档还是有好处的。

总文档:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_sl_api.php?chapter=1_1

支付流程:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_sl_api.php?chapter=7_4&index=3

在线验签:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=20_1

一、开发前准备。

因为是服务商版的微信支付,首先就是申请一个实名认证的微信服务号A,然后开通微信服务商

申请特约服务商

公司B申请为特约商户,申请特约商户需要为有营业执照的公司或者个体。(申请流程一个工作日即可完成,不要被流程上的唬住)

 

二、开发开始。

先贴代码。

1、WeiXinUtils



import xxx.xxx.HttpClientUtil;
import com.alibaba.fastjson.JSONObject;
import org.apache.http.client.utils.HttpClientUtils;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.*;
/**
 * Created by xxx on 2019/12/27.
 * Desc:
 */

public class WeiXinUtils {

    public static void main(String [] args){

        toMchPay();
    }

    protected static Logger logger = LoggerFactory.getLogger(WeiXinUtils.class);

    /** appId*/
    public static final String APPID = "xxxx";

    /*** 服务商统一下单 url*/
    private static final String mchPayUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder";

    /*** 服务商id*/
    private static final String serverMchId="xxxx";

    /*** 服务号Appid*/
    private static final String serverAppId="xxxx";

    /*** 服务商ApiKey*/
    public static final String serverPayKey="xxxx";

    /** 交易类型*/
    private static final String tradeType="JSAPI";



    public static Map<String,String> toMchPay() {
        SortedMap<String,Object> map = new TreeMap<>();
        map.put("appid",serverAppId);
        map.put("mch_id",serverMchId);
        map.put("sub_appid",APPID);
        map.put("sub_mch_id","xxxx");
        map.put("nonce_str",PayUtil.create_nonce_str());
        map.put("body","shangpin01");
        //map.put("detail",detail);
        String outTradeNo="TDAYORDER"+System.currentTimeMillis();
        map.put("out_trade_no",outTradeNo);
        map.put("total_fee","3");
        map.put("trade_type",tradeType);
        map.put("sub_openid","xxxx");
        map.put("spbill_create_ip","xxxx");
        map.put("notify_url","https://www.baidu.com");
        map.put("sign",PayUtil.createSign(map));
        Map<String, String> headers = new HashMap<>();
        headers.put("content-type", "application/json;charset=utf-8");
        //String s = HttpClientUtils.sendPost(mchPayUrl, headers, PayUtil.getRequestXml(map), "utf-8");
        String s = HttpClientUtil.postXml(mchPayUrl, PayUtil.getRequestXml(map));
        //String s = PayUtil.goPostRequest(mchPayUrl, PayUtil.getRequestXml(map), "utf-8");
        Map<String,String> returnMap = new HashMap<>();

        try {
            returnMap = PayUtil.doXMLParse(s);
        } catch (JDOMException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        logger.error("request:"+PayUtil.getRequestXml(map));

        logger.error("return:"+JSONObject.toJSONString(returnMap));
        return returnMap;
    }


}

2、PayUtil


import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.*;


import xxx.xxx.MD5Util;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
/**
 * Created by xxx on 2019/12/27.
 * Desc:
 */
public class PayUtil {

    private static final String serverPayKey=WeiXinUtils.serverPayKey;

    public static String goPostRequest(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();
//            buffer = new StringBuffer(br);

            String line = null;
            while((line = br.readLine())!=null){
                buffer.append(line);
            }

            br.close();

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return buffer.toString();
    }

    /**
     * 获取随机字符串
     *
     * @return 2016年8月4日 上午9:26:07
     * @author Xuehao
     */
    public static String create_nonce_str() {
        String s = UUID.randomUUID().toString();
        // 去掉“-”符号
        return s.replaceAll("\\-", "").toUpperCase();
    }

    /**
     * 获取时间戳
     *
     * @return 2016年8月4日 上午9:26:20
     * @author Xuehao
     */
    public static String create_timestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }


    /**
     * 构造签名
     *
     * @param params
     * @param encode
     * @return
     * @throws UnsupportedEncodingException
     */
    public static String createSign(Map<String, String> params, boolean encode) throws UnsupportedEncodingException {
        Set<String> keysSet = params.keySet();
        Object[] keys = keysSet.toArray();
        Arrays.sort(keys);
        StringBuffer temp = new StringBuffer();
        boolean first = true;
        for (Object key : keys) {
            if (first) {
                first = false;
            } else {
                temp.append("&");
            }
            temp.append(key).append("=");
            Object value = params.get(key);
            String valueString = "";
            if (null != value) {
                valueString = value.toString();
            }
            if (encode) {
                temp.append(URLEncoder.encode(valueString, "UTF-8"));
            } else {
                temp.append(valueString);
            }
        }
        return temp.toString();
    }

    //生成签名
    public static String createSign(SortedMap<String, Object> parameters){
        StringBuilder sb = new StringBuilder();
        Set es = parameters.entrySet();
        for (Object e : es) {
            Map.Entry entry = (Map.Entry) e;
            String k = (String) entry.getKey();
            Object v = entry.getValue();
            if (null != v && !"".equals(v)
                    && !"sign".equals(k) && !"key".equals(k)) {
                sb.append(k).append("=").append(v).append("&");
            }
        }
        sb.append("key=").append(serverPayKey);
        System.out.println(sb.toString());
        return MD5Util.MD5Encode(sb.toString(), "UTF-8").toUpperCase();
    }

    //请求xml组装
    public static String getRequestXml(SortedMap<String, Object> parameters){
        StringBuilder sb = new StringBuilder();
        sb.append("<xml>");
        Set es = parameters.entrySet();
        for (Object e : es) {
            Map.Entry entry = (Map.Entry) e;
            String key = (String) entry.getKey();
            String value = (String) entry.getValue();
            if ("attach".equalsIgnoreCase(key) || "body".equalsIgnoreCase(key) || "sign".equalsIgnoreCase(key)) {
                sb.append("<").append(key).append(">").append("<![CDATA[").append(value).append("]]></").append(key).append(">");
            } else {
                sb.append("<").append(key).append(">").append(value).append("</").append(key).append(">");
            }
        }
        sb.append("</xml>");
        System.out.println(sb.toString());
        return sb.toString();
    }
    //xml解析
    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();
        for (Object aList : list) {
            Element e = (Element) aList;
            String k = e.getName();
            String v = "";
            List children = e.getChildren();
            if (children.isEmpty()) {
                v = e.getTextNormalize();
            } else {
                v = getChildrenText(children);
            }
            m.put(k, v);
        }
        //关闭流
        in.close();
        return m;
    }
    private static String getChildrenText(List children) {
        StringBuilder sb = new StringBuilder();
        if(!children.isEmpty()) {
            for (Object aChildren : children) {
                Element e = (Element) aChildren;
                String name = e.getName();
                String value = e.getTextNormalize();
                List list = e.getChildren();
                sb.append("<").append(name).append(">");
                if (!list.isEmpty()) {
                    sb.append(getChildrenText(list));
                }
                sb.append(value);
                sb.append("</").append(name).append(">");
            }
        }

        return sb.toString();
    }

    /**
     * 验证回调签名
     * @param map
     * @return
     */
    public static boolean isTenpaySign(Map<String, String> map) {
        String charset = "utf-8";
        String signFromAPIResponse = map.get("sign");
        if (signFromAPIResponse == null || signFromAPIResponse.equals("")) {
            System.out.println("API返回的数据签名数据不存在,有可能被第三方篡改!!!");
            return false;
        }
        System.out.println("服务器回包里面的签名是:" + signFromAPIResponse);
        //过滤空 设置 TreeMap
        SortedMap<String,String> packageParams = new TreeMap<>();
        for (String parameter : map.keySet()) {
            String parameterValue = map.get(parameter);
            String v = "";
            if (null != parameterValue) {
                v = parameterValue.trim();
            }
            packageParams.put(parameter, v);
        }
        StringBuilder sb = new StringBuilder();
        Set es = packageParams.entrySet();
        for (Object e : es) {
            Map.Entry entry = (Map.Entry) e;
            String k = (String) entry.getKey();
            String v = (String) entry.getValue();
            if (!"sign".equals(k) && null != v && !"".equals(v)) {
                sb.append(k).append("=").append(v).append("&");
            }
        }
        sb.append("key=").append(serverPayKey);
        //将API返回的数据根据用签名算法进行计算新的签名,用来跟API返回的签名进行比较
        //算出签名
        String tobesign = sb.toString();
        String resultSign = MD5Util.MD5Encode(tobesign, "utf-8").toUpperCase();
        String tenpaySign = packageParams.get("sign").toUpperCase();
        return tenpaySign.equals(resultSign);
    }
}

 

 

3、MD5Util


import java.security.MessageDigest;

public class MD5Util {

	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" };

	public static void main(String args[]) {
		String str = MD5Util.MD5Encode("000201503190041024", "UTF-8");
		System.out.print(str.toUpperCase());
	}

}

4、HttpClientUtil



import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.util.EntityUtils;
import org.omg.CORBA.SystemException;

import com.alibaba.fastjson.JSON;

/**
 * HTTP Client 工具类
 */
public class HttpClientUtil {

	protected static LogUtility logger = LogUtility.getInstance();

    private HttpClientUtil() {
    }

    private static HttpClient   httpClient      = null;

    private static HttpClient   httpsClient     = null;

    public static final String RETURN_CODE_FAIL = "request_fail";
    
    static {
        try {
            httpClient = getHttpClient();
            httpsClient = getHttpClient();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 默认的HTTP响应实体编码 = "UTF-8"
     */
    private static final String DEFAULT_CHARSET = "UTF-8";

    private synchronized static HttpClient getHttpClient() {
        PoolingClientConnectionManager cm = new PoolingClientConnectionManager();
        // 设置最大连接数
        cm.setMaxTotal(400);
        // 设置默认路由连接数
        cm.setDefaultMaxPerRoute(200);

        HttpParams httpParams = new BasicHttpParams();

        // 读超时30秒
        HttpConnectionParams.setSoTimeout(httpParams, 40 * 1000);
        // 连接超时30秒
        HttpConnectionParams.setConnectionTimeout(httpParams, 40 * 1000);

        // PoolingClientConnection timeout 30秒
        HttpClientParams.setConnectionManagerTimeout(httpParams, 40 * 1000);

        HttpClient httpClient = new DefaultHttpClient(cm, httpParams);
        return httpClient;
    }

    
    // /
    // <<Post>>

    /**
     * HTTP Post
     * 
     * @param url 请求url
     * @param params 请求参数
     * @return 响应内容实体
     * @throws SystemException 
     */
    public static String post(String url, Map<String, String> params) {
        return post(url, params, DEFAULT_CHARSET, DEFAULT_CHARSET);
    }


    /**
     * HTTP Post XML(使用默认字符集)
     * 
     * @param url 请求的URL
     * @param xml XML格式请求内容
     * @return 响应内容实体
     * @throws SystemException 
     */
    public static String postXml(String url, String xml) {
        return postXml(url, xml, DEFAULT_CHARSET, DEFAULT_CHARSET);
    }

    /**
     * HTTP Post XML
     * 
     * @param url 请求的URL
     * @param xml XML格式请求内容
     * @param requestCharset 请求内容字符集
     * @param responseCharset 响应内容字符集
     * @return 响应内容实体
     * @throws SystemException 
     */
    public static String postXml(String url, String xml, String requestCharset,
            String responseCharset) {
        return post(url, xml, "text/xml; charset=" + requestCharset, "text/xml", requestCharset,
                responseCharset);
    }

   

    /**
     * HTTP Post
     * 
     * @param url 请求URL
     * @param params 请求参数
     * @param paramEncoding 请求参数编码
     * @param responseCharset 响应内容字符集
     * @return 响应内容实体
     * @throws SystemException 
     */
    public static String post(String url, Map<String, String> params, String paramEncoding,
            String responseCharset) {
        HttpPost post = null;
        try {
            post = new HttpPost(url);
            if (params != null) {
                List<NameValuePair> paramList = new ArrayList<NameValuePair>();
                for (String key : params.keySet()) {
                    paramList.add(new BasicNameValuePair(key, params.get(key)));
                }
                UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(paramList, paramEncoding);
                post.setEntity(formEntity);
            }

            HttpResponse response = httpClient.execute(post);
            return consumeResponseEntity(response, responseCharset);
        } catch (Exception e) {
        	logger.logError("", e.getMessage(), e);
        } finally {
            if (post != null) {
                post.releaseConnection();
            }
        }
        return RETURN_CODE_FAIL;
    }

    /**
     * HTTP Post
     * 
     * @param url 请求的URL
     * @param content 请求内容
     * @param contentType 请求内容类型,HTTP Header中的<code>Content-type</code>
     * @param mimeType 请求内容MIME类型
     * @param requestCharset 请求内容字符集
     * @param responseCharset 响应内容字符集
     * @return 响应内容实体
     * @throws SystemException 
     */
    public static String post(String url, String content, String contentType, String mimeType,
            String requestCharset, String responseCharset) {
        HttpPost post = null;
        try {
            post = new HttpPost(url);
            post.setHeader("Content-Type", contentType);
            HttpEntity requestEntity = new StringEntity(content, ContentType.create(mimeType,
                    requestCharset));
            post.setEntity(requestEntity);

            HttpResponse response = httpClient.execute(post);
            return consumeResponseEntity(response, responseCharset);
        } catch (Exception e) {
        	logger.logError("", e.getMessage(), e);
        } finally {
            if (post != null) {
                post.releaseConnection();
            }
        }
        return RETURN_CODE_FAIL;
    }

   
}

 

5、WXController



import xxx.xxx.WXPayUtil;
import xxx.ApiResult;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * Created by xxx on 2019/12/28.
 * Desc:
 */
@RestController
@RequestMapping("/api/wx2")
@CrossOrigin
public class WXController {

    /**
     * timeStamp: payParam.timeStamp, //必填 当前的时间 时间戳
     nonceStr: payParam.nonceStr, //必填 随机字符串,长度为32个字符以下
     package: payParam.package, //必填 统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=***
     signType: payParam.signType, //非必填 签名算法
     paySign: payParam.paySign, //必填 签名,具体签名方案参见
     */
    @PostMapping("/palceOrder")
    public ApiResult placeOrede() throws Exception {

        Map<String,String> result = WeiXinUtils.toMchPay();

        SortedMap<String ,Object > resurnMap = new TreeMap<>();

        resurnMap.put("appId","xxx");
        resurnMap.put("timeStamp", System.currentTimeMillis()+"");
        resurnMap.put("package","prepay_id="+result.get("prepay_id"));
        resurnMap.put("nonceStr", WXPayUtil.generateNonceStr());
        resurnMap.put("signType","MD5");
        resurnMap.put("key","xxx");

        resurnMap.put("sign", PayUtil.createSign(resurnMap));


        return ApiResult.ok(resurnMap);
    }
}

 

代码结束。

 

三、最后需要注意的几个点。

1.一些参数要搞明白:

appid:服务号的appid(A)

mch_id:服务号的微信服务商的商户号(A)

sub_appid:当前小程序的appid(B)

sub_mch_id:特约商户申请通过后分配的商户号(B)

sub_openid:你当前用户对应当前小程序的openid(B)

serverPayKey:服务商的商户平台key

返回参数后给前端的时候二次加密,此时需要的appid是你小程序的appid(B)

 

2.可以在线验证你签名是否正确,初期开始,签名错误的概率还是很大的。

https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=20_1

 

 


文章结束。


                                                                                                                                           有需求可以一起研究,QQ:727880502

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值