前言:本人是從android 轉java的,第一次加入項目就遇到了微信支付對接,微信支付的對接文檔讓我感覺很差,也遇到了很多的坑,所以在此記錄一下。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
一:先了解微信小程序支付的流程,官方給的流程圖還是可以看的
我們後臺要做的就只有三點:
1.通過小程序的appid 祕鑰 code 三個參數拿到小程序的openid(每個登錄小程序的用戶openid是唯一的)
2.然後通過openid,還有訂單參數,小程序appid,商戶mch_id等(開發文檔有明確必傳參數,下面會給代碼)拿到支付id prepay_id返回給前端
(此處需要注意的就是兩次簽名的生成)
3.對微信後臺返回的支付結果進行數據處理
二:主要代碼部分
1.如何拿到小程序的openid
puclic class WeiXinUtil{
public static String getOpenid(String code){
Map<String, String> params = new HashMap<>();
params.put("appid", "微信小程序開發平臺註冊信息");
params.put("secret", "微信小程序開發平臺的祕鑰");
params.put("grant_type", "authorization_code");
params.put("js_code", "微信小程序登錄後傳來的code參數");
Map map = JSON.parseObject(HttpClientUtil.postMap("https://api.weixin.qq.com/sns/jscode2session", params),Map.class);
System.out.println(map);
assert map != null;
if (map.get("openid") == null) {
throw new BusinessException(map.get("errmsg").toString());
}
return map.get("openid").toString();
}
}
代碼段解釋:params中的參數都是小程序那邊給出的,grant_type:參數固定爲autorization_code,然後將參數使用post請求發到微信的接口中https://api.weixin.qq.com/sns/jscode2session,從返回值中拿到openid
2.如何將商品信息簽名,去微信後臺驗證拿到支付id prepay_id(這一步的操作就叫統一下單)
統一下單的參數給了很多,我們只傳他必填參數
接口代碼
@ApiOperation("微信支付,預下單")
@PostMapping("/xxxxx")
public RestResult wxpay(@RequestBody PayRequestBodyVo vo) {
try {
String unifiedorder = WeiXinUtil.getUnifiedorder(openid, outTradeNo, Double.valueOf(vo.getSumValue()));
Map<String, String> map = PayUtil.doXMLParse(unifiedorder);
SortedMap<Object, Object> param = WeiXinUtil.prepayId(map);
return new RestResult("微信支付", param);
} catch (Exception e) {
e.printStackTrace();
}
return new RestResult("微信支付");
}
public class WeiXinUtil{
public static String getUnifiedorder(String openid, String outTradeNo, double money) throws Exception {
SortedMap<Object, Object> params = new TreeMap<>();
params.put("appid", "小程序appid");
params.put("body", "商品名稱");
params.put("mch_id", "商戶mch_id");
params.put("nonce_str", PayUtil.makeUUID(32));
params.put("notify_url", "支付結果返回接口地址,需要自己寫個接口,然後做外網映射(外網映射建議做http的,https有的人因爲缺少ssl證書會導致接受不到返回值)");
params.put("openid", "上一步獲取到的openid");
params.put("out_trade_no", PayUtil.generateOrderNo());//商品訂單號
params.put("spbill_create_ip", PayUtil.getLocalIp());//服務部署的ip
params.put("total_fee", PayUtil.moneyToIntegerStr(money));//費用的參數轉型
params.put("trade_type", "JSAPI");//對接類型
params.put("sign", PayUtil.createSign("UTF-8", params, "商戶祕鑰"));//MD5簽名
//轉換成xml
String xmlData = PayUtil.getRequestXml(params);
//請求微信後臺,獲取支付id
String resXml = HttpClientUtil.httpsRequest("https://api.mch.weixin.qq.com/pay/unifiedorder", "POST", xmlData);
return resXml;
}
public static SortedMap<Object, Object> prepayId(Map<String, String> map) {
SortedMap<Object, Object> parameters = new TreeMap<>();
parameters.put("appId", "");
parameters.put("timeStamp", PayUtil.create_timestamp());
parameters.put("nonceStr", map.get("nonce_str"));
parameters.put("package", "prepay_id=" + map.get("prepay_id"));
parameters.put("signType", "MD5");
String sign = PayUtil.createSign("UTF-8", parameters, "商戶祕鑰");
parameters.put("prepay_id", "prepay_id=" + map.get("prepay_id"));
parameters.put("paySign", sign);
return parameters;
}
}
然後給出這裏使用到的工具方法
public class PayUtil{
/**
* 獲取當前機器的ip
*/
public static String getLocalIp() {
InetAddress ia = null;
String localip = null;
try {
ia = ia.getLocalHost();
localip = ia.getHostAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
}
return localip;
}
@SuppressWarnings("rawtypes")
public static String getRequestXml(SortedMap<Object, Object> parameters) {
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
Set es = parameters.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 ("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
*/
@SuppressWarnings("rawtypes")
public static String createSign(String characterEncoding,SortedMap<Object, Object> parameters, String key) {
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();
Iterator<?> it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
if (entry.getValue() != null || !"".equals(entry.getValue())) {
String v = String.valueOf(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()).toUpperCase();
return sign;
}
/**
* 生成隨機數
*/
public static String makeUUID(int len) {
return UUID.randomUUID().toString().replaceAll("-", "").substring(0, len);
}
/**
* 生成訂單號
*/
public static String generateOrderNo() {
SimpleDateFormat sdf = new SimpleDateFormat("yyMMdd");
return sdf.format(new Date()) + makeUUID(16);
}
/**
* 解析xml
*/
public static Map doXMLParse(String strxml) throws Exception {
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();
org.jdom2.Document doc = builder.build(in);
org.jdom2.Element root = doc.getRootElement();
List list = root.getChildren();
Iterator it = list.iterator();
while (it.hasNext()) {
org.jdom2.Element e = (org.jdom2.Element) it.next();
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;
}
/**
* 獲取子節點的xml
*/
public static String getChildrenText(List children) {
StringBuffer sb = new StringBuffer();
if (!children.isEmpty()) {
Iterator it = children.iterator();
while (it.hasNext()) {
org.jdom2.Element e = (org.jdom2.Element) it.next();
String name = e.getName();
String value = e.getTextNormalize();
List list = e.getChildren();
sb.append("<" + name + ">");
if (!list.isEmpty()) {
sb.append(getChildrenText(list));
}
sb.append(value);
sb.append("</" + name + ">");
}
}
return sb.toString();
}
/**
* 轉換金額到整型
*/
public static String moneyToIntegerStr(Double money) {
BigDecimal decimal = new BigDecimal(money);
int amount = decimal.multiply(new BigDecimal((100)))
.setScale(0, BigDecimal.ROUND_HALF_UP).intValue();
return String.valueOf(amount);
}
/**
* 微信下單,map to xml
* @param params 參數
* @return String
*/
public static String mapToXml(Map<String, String> params) {
StringBuilder xml = new StringBuilder();
xml.append("<xml>");
for (Map.Entry<String, String> entry : params.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
// 略過空值
if (StringUtils.isEmpty(value)) continue;
xml.append("<").append(key).append("><![CDATA[");
xml.append(entry.getValue());
xml.append("]]></").append(key).append(">");
}
xml.append("</xml>");
return xml.toString();
}
public static String create_timestamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}
}
本人在此踩過的坑,1.由於是兩個人合作,商戶mch_id寫錯了,請求下單返回結果 商戶有違規,限制支付功能,改正商戶id即解決
2.簽名錯誤:此處代碼已經嘗試過了 簽名沒有錯誤,但是本人在做這個地方的時候也遇到了些坑,請求結果爲簽名錯誤,然後去微信簽名驗證工具中驗證:
將轉化後的xml簽名信息放入,然後將商戶平臺設置的祕鑰放入商戶key中,若驗證通過,兩個key相同 即你的簽名沒有問題,
若此處代碼中還返回簽名錯誤,請重新到商戶賬號中,重新設置祕鑰,(這個時候一定是你設置的祕鑰,和你代碼總驗證的祕鑰不一致,這裏的錯誤就多種原因了:可能設置後複製粘貼的錯誤,可能是商戶那邊操作人員給你的時候就是錯的,所以在這裏請重新設置一次)
三:微信返回結果接口
返回結果的接口就不上代碼了,這裏都是項目的個人操作,唯一需要注意的就是能否拿到微信後臺返回的數據
1.返回數據是null的:本人遇到的原因是(使用花生殼做了這個接口的外網映射,由於微信開發平臺要求說要https的,但測試的時候就拿不到返回值),解決方式:將映射類型變成http就可以拿到返回值了
-------------------------------------------------------------------------
---------------若有地方表達不明白,歡迎加QQ:574774295 詢問,請備註驗證消息