您的位置:首页 > 移动开发 > 微信开发

第三方APP微信支付Java服务端构建步骤

2016-06-13 10:44 501 查看
因日前工作需要,做了一次微信支付。其中一些关键点记录下来,以备不时之需,也拿出来交流下,如有不足之处,还望多多指教。

第三方APP微信支付时序图

详见:"https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_3"

1.准备工作获取appid和appkey(api密匙)

微信支付申请审核通过后,您就可以收到微信发给您的邮件,里面有您账户相关参数。

第一个坑:公众平台的密钥和商户号的密钥是不一样的!!!微信支付审核成功之后会收到一封邮件,邮件中有appid
商户号,商户后台登录上号和密码,登录到商户后台:账户设置-安全设置-切换到API安全,下载证书,下面有一个api密匙,进去填写一个字符串,保存,后续两次签名都是用的这个手动设置的key!!!



2.服务端工作流程

         这里跳过流程图中的前三步

2.1调用统一下单接口,获取prepayid

2.1.1准备所需参数

统一下单接口必需参数:appid,mch_id,nonce_str,body,out_trade_no,total_fee,spbill_create_ip,notify_url,trade_type,这九个参数部分是客户端传来的,其中notify_url是异步通知(微信通知应用服务端的URL地址)

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

第二个坑:以上所需九个参数变量名必是小写,total_fee以分为单位,是大于0的整数;同一个应用的安卓和苹果分配的appid不一样,可以让客户端携带appid来请求

2.1.1对参数进行签名

按照接口文档给的签名说明:需要先对参数按照ascii码进行排序,值为空的不参与排序,参与排序的字段不包括appkey(api密匙)。排完序后,将排好序的字段塞进xml中,同时用排好序的字符串与key及其值通过&拼接起来,之后再使用MD5加密,然后将小写转大写,这就是生成的sign(签名)值。最后将sign塞进xml中。然后就可以开始向微信统一下单接口发起请求了,接收prepayid。

以下是我自己的代码,大部分和网上的一样:

//这个是将参数从reqInfo对象放进一个map中

public StringgetXmlStr(WCPayGetPrePayIdReqInfo reqInfo) {

Map<String,Object>params = new HashMap<String,Object>();

params.put("appid",reqInfo.getAppId()); //上面的appid,注意大小写

params.put("mch_id",reqInfo.getMchId()); //商户id

params.put("key",reqInfo.getKey()); //appkey(api密匙)

params.put("nonce_str",WCPayUtils.getRandomNumber(32)); //32位随机数

params.put("body",reqInfo.getBody()); //商品描述

params.put("out_trade_no",reqInfo.getOutTradeNo()); //应用后台生成的订单id

params.put("total_fee",reqInfo.getTotalFee()); //总金额

params.put("spbill_create_ip",reqInfo.getSpbillCreateIp()); //用户终端ip

params.put("notify_url","application/test"); //异步通知URL

params.put("trade_type",reqInfo.getTradeType()); //交易方式,参见微信接口文档

try{

returnWCPayUtils.getXmlFromParamsMap(params);

}catch (Exception e) {

LogFactory.getLog("Message").debug("生成xml字符串出错");

}

return null;

}

2.1.2调用统一下单接口

将以上参xml字符串准备妥当后,用httpclient向微信统一下单接口发起请求,拿到返回的数据后,对其进行签名认证,认证成功后封装到WCPayGetPrePayIdRespInfo对象中,请求方法如下

private void getPrepayId(StringxmlStr,WCPayGetPrePayIdRespInfo respInfo) {
try {
HttpClientBuilderhttpClientBuilder = HttpClientBuilder.create();
CloseableHttpClientcloseableHttpClient = httpClientBuilder.build();
HttpPost httpPost =new HttpPost("https://api.mch.weixin.qq.com/pay/unifiedorder");
HttpClientContextcontext = HttpClientContext.create();
StringEntity se = newStringEntity(xmlStr);
se.setContentType("text/xml");
se.setContentEncoding(newBasicHeader(HTTP.CONTENT_TYPE, "application/xml"));
httpPost.setEntity(se);
httpPost.setConfig(RequestConfig.DEFAULT);
HttpResponsehttpResponse = closeableHttpClient.execute(httpPost, context);
HttpEntity httpEntity= httpResponse.getEntity();
if (httpEntity !=null) {
// 打印响应内容
InputStreamcontent = httpEntity.getContent();
Map<String,String> params = WCPayUtils.getParamsMapFromXml(content);
params.put("key",pContect.getWcPayKey());
if(params.containsKey("sign")&& params.get("prepay_id") != null &&
!"".equals(params.get("prepay_id"))&& !"null".equals(params.get("prepay_id"))){
if(WCPayUtils.checkSign(params)){//签名认证成功
for(Map.Entry<String, String> param : params.entrySet()) {
if("appid".equals(param.getKey()))
respInfo.setAppId(param.getValue());
elseif("mch_id".equals(param.getKey()))
respInfo.setPartnerId(param.getValue());
elseif("prepay_id".equals(param.getKey()))
respInfo.setPrepayId(param.getValue());//将prepayid放进WCPayGetPrePayIdRespInfo对象中
else
continue;
}
}
}else{
LogFactory.getLog("system").info("第一次响应签名认证失败");
}
}
closeableHttpClient.close();
} catch (Exception e) {
e.printStackTrace();
}
}
  2.2 给客户端返回数据

对WCPayGetPrePayIdRespInfo对象中的其他字段值进行设置,WCPayGetPrePayIdRespInfo对象是返回给app端的对象,这里有app端调起支付接口所需的七个必需参数:

appid,partnerid(商户id),prepayid(预支付标识,微信返回给服务端的xml数据中有),noncestr(32位随机字符串,建议新生成一个),package(取固定值 Sign=WXPay), timestamp(时间戳),sign(签名)

第三个坑这里的sign还是通过要对上面六个(不包括sign)参数进行排序,然后拼接appkey后在进行md5加密,再小写变大写获取),

之后将sign也放进WCPayGetPrePayIdRespInfo对象中,然后传递给app客户端,

2.3接收微信异步通知并处理相应业务逻辑

当app客户端向微信发起支付请求,并付款成功后,微信会向异步通知URL也就是notify_url上面传递支付接口信息,也是xml字符串。我们需要将之解析完成后,并再次生成sign,然后将生成的sign与传来的sign进行比对认证,认证成功则说明是微信发来的信息。然后从里面拿到result_code,如果result_code是“SUCCESS”说明支付成功。处理相应业务逻辑。

 以上,就是第三方APP微信支付Java服务端构建步骤。如有不对的之处,还请指正交流。

下面附上上面所用到的两个工具类

WCPayUtils工具类

import java.io.InputStream;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

public class WCPayUtils {
/**
* 将微信支付所需参数拼接为xml字符串
* @param <T>
*
* @param paramsMap
* @return
* @throws Exception
*/
public static <T> String getXmlFromParamsMap(Map<String, T> paramsMap) throws Exception {
if (paramsMap != null && paramsMap.size() > 0) {
Map<String, Object> params = new TreeMap<String, Object>(new Comparator<String>() {
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
});
params.putAll(paramsMap);
StringBuffer ss = new StringBuffer("<xml>");
for (Entry<String, Object> param : params.entrySet()) {
if (!"key".equals(param.getKey())) {
ss.append("<" + param.getKey() + "><![CDATA[").append(param.getValue())
.append("]]></" + param.getKey() + ">");
}
}
String sign = getSignFromParamMap(params);
ss.append("<sign>" + sign + "</sign>");
ss.append("</xml>");
String xmlString = ss.toString();
return new String(xmlString.getBytes(), "ISO-8859-1");
}
return null;
}

/**
* 从xml字符串中解析参数
* @param xml
* @return
* @throws Exception
*/
public static Map<String, String> getParamsMapFromXml(InputStream xml) throws Exception {
Map<String, String> params = new HashMap<String, String>(0);
SAXReader saxReader = new SAXReader();
Document read = saxReader.read(xml);
Element node = read.getRootElement();
listNodes(node, params);
return params;
}

/**
* 生成count位的随机数
*
* @param count
* @return
*/
public static String getRandomNumber(int count) {
StringBuffer ss = new StringBuffer(count);
for (int i = 0; i < count; i++) {
int a = (int) (Math.random() * 10);
ss.append(a);
}
return ss.toString();
}

@SuppressWarnings({ "unchecked" })
public static void listNodes(Element node, Map<String, String> params) {
// 获取当前节点的所有属性节点
List<Attribute> list = node.attributes();
// 遍历属性节点
if ((list == null || list.size() == 0) && !(node.getTextTrim().equals(""))) {
if(node.getTextTrim().contains("<![CDATA[")){
String[] split = node.getTextTrim().split("<![CDATA[");
split[1].replaceAll("]]>", "");
params.put(node.getName(), split[1]);
}else{
params.put(node.getName(),node.getTextTrim());
}
}
// 当前节点下面子节点迭代器
Iterator<Element> it = node.elementIterator();
// 遍历
while (it.hasNext()) {
// 获取某个子节点对象
Element e = it.next();
// 对子节点进行遍历
listNodes(e, params);
}
}

/**
* 从map中获取签名sign
* @param paramsMap
* @return
* @throws Exception
*/
public static <T> String getSignFromParamMap(Map<String, T> paramsMap) throws Exception{
if (paramsMap != null && paramsMap.size() > 0) {
Map<String, T> params = new TreeMap<String, T>(new Comparator<String>() {
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}

});
params.putAll(paramsMap);
StringBuffer tempStr = new StringBuffer();
for (Entry<String, T> param : params.entrySet()) {
if (!"sign".equals(param.getKey()) && !"key".equals(param.getKey())
&& !"".equals(param.getValue()) && param.getValue() != null) {
tempStr.append(param.getKey() + "=" + param.getValue() + "&");
}
}
String temp = tempStr.toString().concat("key="+params.get("key"));
return MD5Utils.getMD5(temp).toUpperCase();
}
return null;
}

/**
* 签名认证
* @param paramsMap
* @return
* @throws Exception
*/
public static <T> boolean checkSign(Map<String, T> paramsMap) throws Exception {
String sign = getSignFromParamMap(paramsMap);
return paramsMap.get("sign").equals(sign);
}

}

MD5Utils工具类

import java.security.MessageDigest;

public class MD5Utils {
// 十六进制下数字到字符的映射数组
private final static String[] HEXDIGITS = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d","e", "f" };

public final static String getMD5(String str){
if (str != null) {
try {
// 创建具有指定算法名称的信息摘要
MessageDigest md = MessageDigest.getInstance("MD5");
// 使用指定的字节数组对摘要进行最后更新,然后完成摘要计算
byte[] results = md.digest(str.getBytes()); // 将得到的字节数组变成字符串返回
StringBuffer resultSb = new StringBuffer();
String a = "";
for (int i = 0; i < results.length; i++) {
int n = results[i];
if (n < 0)
n = 256 + n;
int d1 = n / 16;
int d2 = n % 16;
a = HEXDIGITS[d1] + HEXDIGITS[d2];
resultSb.append(a);
}
return resultSb.toString();
} catch (Exception ex) {
ex.printStackTrace();
}
}
return null;
}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: