1.登录微信支付商户平台
https://pay.weixin.qq.com/index.php/partner/public/home
2.点击开发文档
3.进入如下界面点击小程序支付
4.微信公众平台支付账户认证
认证流程如下:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_sl_api.php?chapter=3_1
认证完成后,微信开发支付参数说明如下:
appid为和服务商商户号绑定的服务商appid,一般情况为认证的服务号appid;
mch_id为服务商商户号,目前仅在认证服务号后台(mp.weixin.qq.com)开放申请服务商商户号,申请开通后即在微信支付系统创建绑定关系;
sub_mch_id为和服务商商户号有父子绑定关系的子商户号;
sub_appid为服务商模式的场景appid,在小程序中拉起支付时该字段必传;
trade_type请填写JSAPI;
openid为appid对应的微信用户标识;
sub_openid为sub_appid对应的微信用户标识,小程序服务商模式下单中的openid和sub_openid必须至少传其中一个,在小程序中拉起支付一般情况下只能获取到sub_openid,即使用wx.login接口获得的openid。
5.小程序业务流程
商户系统和微信支付系统主要交互:
1)小程序内调用登录接口,获取到用户的openid,api参见公共api【小程序登录API】
2)商户server调用支付统一下单,api参见公共api【统一下单API】
3)商户server调用再次签名,api参见公共api【再次签名】
4)商户server接收支付通知,api参见公共api【支付结果通知API】
5)商户server查询支付结果,api参见公共api【查询订单API】
小程序支付的交互图如下:
6.java程序接口API实现:
1) 生成订单
packagecom.jingweiiot.smart_home.utils.weixin.WXPay;
importjava.util.Map;
importjava.util.SortedMap;
importjava.util.TreeMap;
importjavax.servlet.http.HttpServletRequest;
importcom.fasterxml.jackson.core.JsonProcessingException;
importcom.fasterxml.jackson.databind.ObjectMapper;
importcom.jingweiiot.smart_home.model.UserOrderInfo;
publicclass CreateUserOrderInfo {
/**
* 生成订单
* @paramuserOrderInfo
* @paramrequest
*@return
*@throws JsonProcessingException
*/
public static Map<String, String>createPayInfo(UserOrderInfo userOrderInfo,HttpServletRequest request) throwsJsonProcessingException {
SortedMap<String, String>params = new TreeMap<String, String>();
//自定义不重复的长度不长于32位
String nonceStr =StringUtil.getRandomString(30);
params.put("openid",userOrderInfo.getOpenId());
//微信开放平台审核通过的应用APPID
params.put("appid",WXPramsContent.appId);
//微信支付分配的商户号
params.put("mch_id",WXPramsContent.mchId);
//随机字符串
// params.put("nonce_str","1233212131221121212");
params.put("nonce_str",nonceStr);
//商品描述
params.put("body",WXPramsContent.body);
//商户订单号
params.put("out_trade_no",userOrderInfo.getOrderNum());
//总金额
params.put("total_fee","1");
// params.put("total_fee",userOrderInfo.getPayAmount().multiply(new BigDecimal(100)).toString());
//终端IP
params.put("spbill_create_ip",WXPramsContent.getIpAddr(request));
//通知地址
params.put("notify_url",WXPramsContent.notifyUrl);
//交易类型
params.put("trade_type",WXPramsContent.tradeType);
//MD5签名
String sign =WeChat.createSign(params);
params.put("sign", sign);
//统一下单支付接口
String xmlStr =WeChat.httpsRequest(WXPramsContent.payUrl,"POST",WeChat.map2XmlString(params));
// System.err.println("xml:" +WeChat.map2XmlString(params));
//微信商户平台返回支付结果以及预支付id等
Map<String, String> response =WeChat.readXmlString2Map(xmlStr);
//二次签名
//拼接签名需要的参数
ObjectMapper mapper = newObjectMapper();
System.err.println("response:" +mapper.writeValueAsString(response));
SortedMap<String, String>signParams = new TreeMap<String, String>();
signParams.put("appId",WXPramsContent.appId);//app_id
signParams.put("nonceStr", nonceStr);//自定义不重复的长度不长于32位
signParams.put("package","prepay_id="+response.get("prepay_id"));//预付订单id
System.out.println("package:"+response.get("prepay_id"));
signParams.put("signType","MD5");
signParams.put("timeStamp",String.valueOf(System.currentTimeMillis()));//北京时间时间戳
String signAgain =WeChat.createSign(signParams);
signParams.put("paySign",signAgain);
return signParams;
}
}
2) 工具类
packagecom.jingweiiot.smart_home.utils.weixin.WXPay;
importorg.dom4j.Document;
importorg.dom4j.DocumentException;
importorg.dom4j.DocumentHelper;
importorg.dom4j.Element;
importcom.fasterxml.jackson.core.JsonProcessingException;
importcom.fasterxml.jackson.databind.ObjectMapper;
importjava.io.BufferedReader;
importjava.io.InputStream;
importjava.io.InputStreamReader;
importjava.io.OutputStream;
importjava.net.ConnectException;
importjava.net.HttpURLConnection;
importjava.net.URL;
importjava.security.MessageDigest;
importjava.util.HashMap;
importjava.util.Iterator;
importjava.util.List;
importjava.util.Map;
importjava.util.Set;
importjava.util.SortedMap;
importjava.util.TreeMap;
/**
*dom4j解析xml
* 利用HttpClient进行post请求的工具类
*/
publicclass WeChat {
// 微信支付请求
static String httpsRequest(StringrequestUrl, String requestMethod, String outputStr) {
try {
URL url = new URL(requestUrl);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
// 设置请求方式(GET/POST)
conn.setRequestMethod(requestMethod);
conn.setRequestProperty("content-type","application/x-www-form-urlencoded");
// 当outputStr不为null时向输出流写数据
if (null != outputStr) {
OutputStream outputStream =conn.getOutputStream();
// 注意编码格式
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
// 从输入流读取返回内容
InputStream inputStream =conn.getInputStream();
InputStreamReader inputStreamReader= new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = newBufferedReader(inputStreamReader);
String str;
StringBuffer buffer = newStringBuffer();
while ((str = bufferedReader.readLine())!= null) {
buffer.append(str);
}
// 释放资源
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
conn.disconnect();
return buffer.toString();
} catch (ConnectException ce) {
} catch (Exception e) {
}
return null;
}
/**
* map转xml格式字符串
*/
static Stringmap2XmlString(SortedMap<String, String> params) {
String xmlResult;
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
for (String key : params.keySet()) {
String value ="<![CDATA[" + params.get(key) + "]]>";
sb.append("<" + key +">" + value + "</" + key + ">");
}
sb.append("</xml>");
xmlResult = sb.toString();
return xmlResult;
}
/**
* 将xml字符串转换成map
*/
static HashMap<String, String>readXmlString2Map(String xml) {
HashMap<String,String> map = new HashMap<String, String>();
Document doc;
try {
doc =DocumentHelper.parseText(xml); // 将字符串转为XML
Element rootElt =doc.getRootElement(); // 获取根节点
List<Element> list =rootElt.elements();// 获取根节点下所有节点
for (Element element : list) { // 遍历节点
map.put(element.getName(),element.getText()); // 节点的name为map的key,text为map的value
}
} catch (DocumentException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
public static String createSign(SortedMap<String,String> params) {
StringBuffer sb = new StringBuffer();
Set es = params.entrySet();//字典序
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String k = (String) entry.getKey();
String v = (String)entry.getValue();
//为空不参与签名、参数名区分大小写
if (null != v &&!"".equals(v) && !"sign".equals(k)
&&!"key".equals(k)) {
sb.append(k + "=" + v+ "&");
}
}
//第二步拼接key,key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置
sb.append("key="+WXPramsContent.key);
String sign = MD5Util.MD5Encode(sb.toString(),"utf-8")
.toUpperCase();//MD5加密
return sign;
}
static class MD5Util {
private static StringbyteArrayToHexString(byte[] b) {
StringBuffer resultSb = newStringBuffer();
for (int i = 0; i < b.length;i++)
resultSb.append(byteToHexString(b[i]));
return resultSb.toString();
}
private static StringbyteToHexString(byte b) {
int n = b;
if (n < 0)
n += 256;
int d1 = n / 16;
int d2 = n % 16;
return hexDigits[d1] +hexDigits[d2];
}
static String MD5Encode(String origin,String charsetname) {
String resultString = null;
try {
resultString = newString(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= new String[]{"0", "1", "2", "3","4", "5","6", "7", "8","9", "a", "b", "c", "d","e", "f"};
}
public static boolean isTenpaySign(StringcharacterEncoding, SortedMap<Object, Object> packageParams, StringAPI_KEY) {
StringBuffer sb = newStringBuffer();
Set es = packageParams.entrySet();
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry =(Map.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();
//System.out.println(tenpaySign +" " + mysign);
return tenpaySign.equals(mysign);
}
}
3) 订单支付完成回调更新订单状态
public void PayNotifyAction(HttpServletRequest request,HttpServletResponseresponse) throws Exception{
System.err.println("已进入微信支付回调接口-----订单支付完成回调更新订单状态");
//读取参数
InputStream inputStream ;
StringBuffer sb = new StringBuffer();
inputStream= request.getInputStream();
String s ;
BufferedReader in = new BufferedReader(newInputStreamReader(inputStream, "UTF-8"));
while((s = in.readLine()) != null){
sb.append(s);
}
in.close();
inputStream.close();
//解析xml成map
Map<String, String> map = new HashMap<String,String>();
map= XMLUtil.doXMLParse(sb.toString());
System.err.println("map:" +map);
//过滤空 设置TreeMap
SortedMap<Object,Object> packageParams = newTreeMap<Object,Object>();
Iteratorit = map.keySet().iterator();
while(it.hasNext()) {
String parameter = (String) it.next();
String parameterValue = map.get(parameter);
String v = "";
if(null!= parameterValue) {
v = parameterValue.trim();
}
packageParams.put(parameter,v);
}
// 账号信息
String key = WXPramsContent.key;// key
logger.info(packageParams);
//判断签名是否正确
if(WeChat.isTenpaySign("UTF-8",packageParams,key)) {
String mch_id = (String)packageParams.get("mch_id");
String openid = (String)packageParams.get("openid");
String is_subscribe = (String)packageParams.get("is_subscribe");
String out_trade_no = (String)packageParams.get("out_trade_no");
String total_fee = (String)packageParams.get("total_fee");
logger.info("mch_id:"+mch_id);
logger.info("openid:"+openid);
logger.info("is_subscribe:"+is_subscribe);
logger.info("out_trade_no:"+out_trade_no);
logger.info("total_fee:"+total_fee);
String resXml = "";
if("SUCCESS".equals((String)packageParams.get("result_code"))){
//--------------您的业务逻辑处理部分---------------
logger.info("支付成功");
//通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.
resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
+ "<return_msg><![CDATA[OK]]></return_msg>"+ "</xml> ";
}
} else {
logger.info("支付失败,错误信息:" + packageParams.get("err_code"));
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
+ "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml>";
}
BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
out.write(resXml.getBytes());
out.flush();
out.close();
System.err.println("resXml:" + resXml.getBytes());
} else{
logger.info("通知签名验证失败");
}
}