没啥好说的,直接淦!
一、前期准备
1. 微信支付商户号
2. 商户平台至API安全的API秘钥
3. 微信小程序appid(其它平台支付对应相应id即可)
二、本次对接的是微信支付JSAPI模式,后端java,前端微信小程序
官方文档说明大致如下:
1. 小程序内调用登录接口,获取到用户的openid,api参见公共api【小程序登录API】
2. 商户server调用支付统一下单,api参见公共api【统一下单API】
3. 商户server调用再次签名,api参见公共api【再次签名】
4. 商户server接收支付通知,api参见公共api【支付结果通知API】
5. 商户server查询支付结果,如未收到支付通知的情况,商户后台系统可调用【查询订单API】 (查单实现可参考:支付回调和查单实现指引)
ps:对于1、4、5都是基于基础以及调用支付成功之后的业务逻辑,较简单理解;可直接百度。这里只记录调用支付统一下单
四、统一下单
说明:调用统一下单接口主要是为了获取前端调用支付所需要的参数(prepay_id)信息
文档简述如下:
1. 应用场景:商户在小程序中先调用该接口在微信支付服务后台生成预支付交易单,返回正确的预支付交易后调起支付。
3. 是否需要证书:否
ps:此处参数都比较好得到,在官方给的SDK文件的方法中可以直接或者间接得到,下面会丢出SDK下载地址
注意:其中有个sign(签名)参数,需要根据特定算法拿到,同学们可以看着文档的示例理解获取sign值的原理,下面我也会贴出源码;文档地址pay.weixin.qq.com/wiki/doc/ap…
精简分析:
1. 填写所有参数信息
2. 通过算法得到sign参数(也是所有参数其中之一)
4. 请求回调中得到prepay_id参数信息
至此第一轮验证完成!
紧接第一轮验证得到prepay_id参数信息继续第二轮参数验证,目的为了得到小程序调起支付所需要的参数paySign;
5.第二次验证需要的参数不多(包括:appId小程序ID、timeStamp时间戳、nonceStr随机串、package数据包、signType签名方式);注意的是这里的timeStamp时间戳、nonceStr随机串要和第一轮验证相应的参数保持一致,不然会导致前端支付报错签名验证失败
ps:至此经过第一、第二请求验证后,已经全部得到前端支付所需要的参数信息,回调给前端既可。
五、代码部分
3.参数转换方法工具类(下载的官方SDK有提供)
package com.example.wxpaySDK;
import com.github.wxpay.sdk.WXPayConstants.SignType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.DocumentBuilder;
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.InputStream;
import java.io.StringWriter;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.*;
public class WXPayUtil {
private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final Random RANDOM = new SecureRandom();
public static Map<String, String> xmlToMap(String strXML) throws Exception {
try {
Map<String, String> data = new HashMap<String, String>();
DocumentBuilder documentBuilder = WXPayXmlUtil.newDocumentBuilder();
InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
org.w3c.dom.Document doc = documentBuilder.parse(stream);
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for (int idx = 0; idx < nodeList.getLength(); ++idx) {
Node node = nodeList.item(idx);
if (node.getNodeType() == Node.ELEMENT_NODE) {
org.w3c.dom.Element element = (org.w3c.dom.Element) node;
data.put(element.getNodeName(), element.getTextContent());
}
}
try {
stream.close();
} catch (Exception ex) {
}
return data;
} catch (Exception ex) {
WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
throw ex;
}
}
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();
try {
writer.close();
}
catch (Exception ex) {
}
return output;
}
public static String generateSignedXml(final Map<String, String> data, String key, com.example.wxpaySDK.WXPayConstants.SignType md5) throws Exception {
return generateSignedXml(data, key, SignType.MD5);
}
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);
}
public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
Map<String, String> data = xmlToMap(xmlStr);
if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
return false;
}
String sign = data.get(WXPayConstants.FIELD_SIGN);
return generateSignature(data, key).equals(sign);
}
public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
return isSignatureValid(data, key, SignType.MD5);
}
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);
}
public static String generateSignature(final Map<String, String> data, String key) throws Exception {
return generateSignature(data, key, SignType.MD5);
}
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)) {
System.err.println("参数MD5加密完成");
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));
}
}
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);
}
public static String MD5(String data) throws Exception {
java.security.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();
}
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();
}
public static Logger getLogger() {
Logger logger = LoggerFactory.getLogger("wxpay java sdk");
return logger;
}
public static long getCurrentTimestamp() {
return System.currentTimeMillis()/1000;
}
public static long getCurrentTimestampMs() {
return System.currentTimeMillis();
}
}
3.接口地址类
@PostMapping("pay")
public static Map<Object, Object> pay(String code, HttpServletRequest request) throws Exception {
Map<Object, Object> miniMap = new HashMap<Object, Object>();
WXPayUtil xmlUtil = null;
System.err.println("输出用户的code:" + code);
String grant_type = "authorization_code";
String param = "appid=" + APPID + "&secret=" + AppSecret + "&js_code=" + code + "&grant_type=" + grant_type;
String sr = HttpRequestUtil.sendGet("https://api.weixin.qq.com/sns/jscode2session", param);
JSONObject json = JSONObject.parseObject(sr);
String useropenid = (String) json.get("openid");
String sessionKey = (String) json.get("session_key");
String notify_url = "https://www.xxx.com";
String trade_type = "JSAPI";
String nonceStr = xmlUtil.generateNonceStr();
Map<String, String> data = new HashMap<String, String>();
data.put("appid", "xxxx55648xxxx");
data.put("mch_id", "xxxx56545");
data.put("nonce_str", nonceStr);
data.put("openid", useropenid);
data.put("body", "商品结算支付");
data.put("out_trade_no", RandomStringGenerator.getRandomStringByLength(18));
data.put("fee_type", "CNY");
data.put("total_fee", "1");
data.put("spbill_create_ip", request.getRemoteAddr());
data.put("notify_url", notify_url);
data.put("trade_type", trade_type);
data.put("sign_type", "MD5");
String newsign = xmlUtil.generateSignature(data, "填写你的商户API秘钥");
System.err.println("输出我的签名:" + newsign);
data.put("sign", newsign);
System.err.println(xmlUtil.mapToXml(data));
String result = HttpRequestUtil.sendPost("https://api.mch.weixin.qq.com/pay/unifiedorder",
xmlUtil.mapToXml(data));
System.err.println("支付统一下单回调结果:" + xmlUtil.xmlToMap(result));
Date d = new Date();
long timeStamp = d.getTime() / 1000;
String create_time = String.valueOf(timeStamp);
Map<String, String> successMap = xmlUtil.xmlToMap(result);
System.err.println("-----------开始支付二次验证------------");
System.err.println(successMap.get("prepay_id"));
String return_code = successMap.get("return_code");
String result_code = successMap.get("result_code");
String prepay_id = successMap.get("prepay_id");
if ("SUCCESS".equals(return_code) && return_code.equals(result_code)) {
HashMap<String, String> map = new HashMap<>();
map.put("appId", "填写你的小程序appid");
map.put("timeStamp", create_time);
map.put("nonceStr", nonceStr);
map.put("package", "prepay_id=" + prepay_id);
map.put("signType", "MD5");
System.err.println("二次验证签名参数 : " + map);
String sign = WXPayUtil.generateSignature(map, "填写你的商户号秘钥");
map.put("paySign", sign);
System.err.println("而成验证生成的签名paySign : " + sign);
miniMap.put("paySign", sign);
}
miniMap.put("timeStamp", create_time);
miniMap.put("nonceStr", nonceStr);
miniMap.put("package", xmlUtil.xmlToMap(result));
return miniMap;
}
ok,至此已完成后端微信支付的调用,别的平台调用方法大同小异,相应的修改既可。
六、前端调用支付
1.微信小程序调用
uni.request({
url: "http://192.168.1.4:8080/pay",
method: "POST",
data: {
code: loginsuccessRes.code,
},
header: {'content-type': 'application/x-www-form-urlencoded'},
success: (res) => {
console.log("res.data",res.data);
wx.requestPayment({
"appId":res.data.package.appid,
"timeStamp": res.data.timeStamp,
"nonceStr": res.data.nonceStr,
"package": "prepay_id="+res.data.package.prepay_id,
"signType": "MD5",
"paySign": res.data.paySign,
"success": (fail)=> {
console.log("调用支付成功02")
},
"fail": (fail)=> {
console.log("调用支付失败02",fail)
},
"complete": (fail)=> {}
})
}
});
ok!完成。