JSAPI微信公众号支付(基于Java、uni-app)
个人第一遍博客,写的不好还请海涵指正。
一、开发前期工作
微信官方文档:https://pay.weixin.qq.com/wiki/doc/api/index.html
1、进入公众号(服务号)获取到AppID跟AppSecret两个参数。
2、进入商户号获取到商户ID跟API密钥。
3、进入商户平台进行JSAPI支付配置授权目录,该目录路径填写你支付页面的路径,如:http://*****/pay/。
4、支付还需要进行网页授权获取到openid,所以还需要配置公众号的网页授权域名。
以上步骤完成后直接上代码
二、后台代码
/**
* @Description 微信浏览器内微信支付/公众号支付(JSAPI)
* @param request
* @return Map
*/
@ApiOperation(value ="微信支付",notes = "微信支付",httpMethod = "POST")
@RequestMapping(value="/payment", method = RequestMethod.POST)
@ResponseBody
public Map payment(HttpServletRequest request, String price, String openId, Integer id) {
try {
//拼接统一下单地址参数
Map<String, String> paraMap = new HashMap<String, String>();
//获取请求ip地址
String ip = request.getHeader("x-forwarded-for");
if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
ip = request.getHeader("Proxy-Client-IP");
}
if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
ip = request.getHeader("WL-Proxy-Client-IP");
}
if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
ip = request.getRemoteAddr();
}
if(ip.indexOf(",")!=-1){
String[] ips = ip.split(",");
ip = ips[0].trim();
}
paraMap.put("appid", WXPayUtil.appid);
paraMap.put("body", "物业费用");
paraMap.put("mch_id", WXPayUtil.mch_id);
paraMap.put("nonce_str", WXPayUtil.generateNonceStr());
paraMap.put("openid", openId);
paraMap.put("out_trade_no", WXPayUtil.getOrderCode());//订单号
paraMap.put("spbill_create_ip", ip);
paraMap.put("total_fee",price);
paraMap.put("trade_type", "JSAPI");
paraMap.put("notify_url","http://****/WechatPayController/callback?id=" + id);// 此路径是微信服务器调用支付结果通知路径随意写
String sign = WXPayUtil.generateSignature(paraMap, WXPayUtil.mch_key);//paternerKey 密钥
paraMap.put("sign", sign);
String xml = WXPayUtil.mapToXml(paraMap);//将所有参数(map)转xml格式
// 统一下单 https://api.mch.weixin.qq.com/pay/unifiedorder
String unifiedorder_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
String xmlStr = HttpRequest.sendPost(unifiedorder_url, xml);//发送post请求"统一下单接口"返回预支付id:prepay_id
//以下内容是返回前端页面的json数据
String prepay_id = "";//预支付id
if (xmlStr.indexOf("SUCCESS") != -1) {
Map<String, String> map = WXPayUtil.xmlToMap(xmlStr);
prepay_id = (String) map.get("prepay_id");
}
Map<String, String> payMap = new HashMap<String, String>();
payMap.put("appId", WXPayUtil.appid);
payMap.put("timeStamp", WXPayUtil.getCurrentTimestamp()+"");
payMap.put("nonceStr", WXPayUtil.generateNonceStr());
payMap.put("signType", "MD5");
payMap.put("package", "prepay_id=" + prepay_id);
String paySign = WXPayUtil.generateSignature(payMap, WXPayUtil.mch_key);
payMap.put("paySign", paySign);
return payMap;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
调取该方法后,会返回以下参数给前端:
<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
<result_code><![CDATA[SUCCESS]]></result_code>
<mch_id><![CDATA[******]]></mch_id>
<appid><![CDATA[********]]></appid>
<nonce_str><![CDATA[********]]></nonce_str>
<sign><![CDATA[*********]]></sign>
<prepay_id><![CDATA[*********]]></prepay_id>
<trade_type><![CDATA[JSAPI]]></trade_type>
</xml>
当支付成功后,回调该方法进行相关业务处理
@RequestMapping("/callback")
public void callBack(HttpServletRequest request,HttpServletResponse response){
//System.out.println("微信支付成功,微信发送的callback信息,请注意修改订单信息");
InputStream is = null;
try {
is = request.getInputStream();//获取请求的流信息(这里是微信发的xml格式所有只能使用流来读)
String xml = WXPayUtil.inputStream2String(is, "UTF-8");
Map<String, String> notifyMap = WXPayUtil.xmlToMap(xml);//将微信发的
if(notifyMap.get("return_code").equals("SUCCESS")){
if(notifyMap.get("result_code").equals("SUCCESS")){
//当支付成功时,在以下代码中可做对应的业务处理
String id = notifyMap.get("id");//物业费用id
String ordersSn = notifyMap.get("out_trade_no");//商户订单号
//根据物业缴费id查询出对应的费用信息
TbPropertyFee tbPropertyFee =
tbPropertyFeeService.selectTbPropertyFeeById(Long.valueOf(id));
if (!StringUtils.isNull(tbPropertyFee)){
if (tbPropertyFee.getType().equals("未缴费")){
tbPropertyFee.setType("已缴费");
tbPropertyFee.setPaytime(new Date());
tbPropertyFee.setOuttradeno(ordersSn);
tbPropertyFeeService.updateTbPropertyFee(tbPropertyFee);
}
}
}
}
//告诉微信服务器收到信息了,不要在调用回调action了========这里很重要回复微信服务器信息用流发送一个xml即可
response.getWriter().write("<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>");
is.close();
} catch (Exception e) {
e.printStackTrace();
}
}
相关工具类
基于post、get转发的工具类
public class HttpRequest {
/**
* 向指定URL发送GET方法的请求
*
* @param url
* 发送请求的URL
* @param param
* 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return URL 所代表远程资源的响应结果
*/
public static String sendGet(String url, String param) {
String result = "";
BufferedReader in = null;
try {
String urlNameString = url + "?" + param;
System.out.println(urlNameString);
URL realUrl = new URL(urlNameString);
// 打开和URL之间的连接
URLConnection connection = realUrl.openConnection();
// 设置通用的请求属性
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 建立实际的连接
connection.connect();
// 获取所有响应头字段
Map<String, List<String>> map = connection.getHeaderFields();
// 遍历所有的响应头字段
for (String key : map.keySet()) {
System.out.println(key + "--->" + map.get(key));
}
// 定义 BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(
connection.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送GET请求出现异常!" + e);
e.printStackTrace();
}
// 使用finally块来关闭输入流
finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
return result;
}
/**
* 向指定 URL 发送POST方法的请求
*
* @param url
* 发送请求的 URL
* @param param
* 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return 所代表远程资源的响应结果
*/
public static String sendPost(String url, String param) {
PrintWriter out = null;
BufferedReader in = null;
String result = "";
try {
URL realUrl = new URL(url);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
// 获取URLConnection对象对应的输出流
out = new PrintWriter(conn.getOutputStream());
// 发送请求参数
out.print(param);
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送 POST 请求出现异常!"+e);
e.printStackTrace();
}
//使用finally块来关闭输出流、输入流
finally{
try{
if(out!=null){
out.close();
}
if(in!=null){
in.close();
}
}
catch(IOException ex){
ex.printStackTrace();
}
}
return result;
}
/**
* get请求
*
* @param url
* @return
*/
public static String get(String url) {
String result = null;
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet(url);
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(50000).setConnectTimeout(50000).build();
httpget.setConfig(requestConfig);
try {
System.out.println("executing request " + httpget.getURI());
CloseableHttpResponse response = httpclient.execute(httpget);
try {
HttpEntity entity = response.getEntity();
// log.info("--------------------------------------");
// log.info(response.getStatusLine()+"");
if (entity != null) {
result = EntityUtils.toString(entity, "UTF-8");
// log.info("--------------------------------------");
// log.info("get====> Response content: " + result);
// log.info("--------------------------------------");
}
// log.info("------------------------------------");
} finally {
response.close();
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
httpget.releaseConnection();
}
return result;
}
}
基于WxPayUtil的工具类(也可去微信官方平台网站进行SDK与DEMO下载,里面有该工具类)
public class WXPayUtil {
private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final Random RANDOM = new SecureRandom();
public static final String FIELD_SIGN = "sign";
/*微信公众号的appid*/
public static final String appid = "*************";
/*微信公众号的AppSecret*/
public static final String secret = "*************";
/*微信支付分配的商户号*/
public static final String mch_id = "***********";
/*商户平台的秘钥*/
public static final String mch_key="**********";
/**
* XML格式字符串转换为Map
*
* @param strXML XML字符串
* @return XML数据转换后的Map
* @throws Exception
*/
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) {
// do nothing
}
return data;
} catch (Exception ex) {
WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
throw ex;
}
}
/**
* 将Map转换为XML格式的字符串
*
* @param data Map类型数据
* @return XML格式的字符串
* @throws Exception
*/
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(); //.replaceAll("\n|\r", "");
try {
writer.close();
}
catch (Exception ex) {
}
return output;
}
/**
* 获取随机字符串 Nonce Str
*
* @return String 随机字符串
*/
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);
}
/**
* 生成签名
*
* @param data 待签名数据
* @param key API密钥
* @return 签名
*/
public static String generateSignature(final Map<String, String> data, String key) throws Exception {
return generateSignature(data, key, SignType.MD5);
}
/**
* 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
*
* @param data 待签名数据
* @param key API密钥
* @param signType 签名方式
* @return 签名
*/
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(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)) {
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));
}
}
/**
* 生成 MD5
*
* @param data 待处理数据
* @return MD5结果
*/
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();
}
/**
* 生成 HMACSHA256
* @param data 待处理数据
* @param key 密钥
* @return 加密结果
* @throws Exception
*/
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();
}
/**
* 日志
* @return
*/
public static Logger getLogger() {
Logger logger = LoggerFactory.getLogger("wxpay java sdk");
return logger;
}
/**
* 获取当前时间戳,单位秒
* @return
*/
public static long getCurrentTimestamp() {
return System.currentTimeMillis()/1000;
}
/**
* 获取当前时间戳,单位毫秒
* @return
*/
public static long getCurrentTimestampMs() {
return System.currentTimeMillis();
}
public static String inputStream2String(InputStream inStream, String encoding){
String result = null;
ByteArrayOutputStream outStream = null;
try {
if(inStream != null){
outStream = new ByteArrayOutputStream();
byte[] tempBytes = new byte[1024];
int count = 0;
while((count = inStream.read(tempBytes)) != -1){
outStream.write(tempBytes, 0, count);
}
tempBytes = null;
outStream.flush();
result = new String(outStream.toByteArray(), encoding);
outStream.close();
}
} catch (Exception e) {
result = null;
}
return result;
}
//订单编号前缀
public static final String PREFIX = "WL";
//订单编号后缀(核心部分)
private static long code;
// 生成订单编号
public static synchronized String getOrderCode() {
code++;
String str = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
long m = Long.parseLong((str)) * 10000;
m += code;
return PREFIX + m;
}
}
三、前端代码
<view class="btn" style="bottom: -10rpx;" @click="pay">确认缴费</view>
//缴费(进行微信支付)
pay() {
uni.request({
url: 'http://******/WechatPayController/payment',
method: 'POST',
header: {
'content-type': 'application/x-www-form-urlencoded'
},
data: {
'openId': uni.getStorageSync('userInfo').openid,
'price': parseInt(this.infoList.propertyfee * 100),
'id': this.infoList.id
},
success: res => {
//调起支付
this.callPay(res);
}
});
},
callPay: function(response) {
if (typeof WeixinJSBridge === "undefined") {
if (document.addEventListener) {
document.addEventListener('WeixinJSBridgeReady', this.onBridgeReady(response), false);
} else if (document.attachEvent) {
document.attachEvent('WeixinJSBridgeReady', this.onBridgeReady(response));
document.attachEvent('onWeixinJSBridgeReady', this.onBridgeReady(response));
}
} else {
this.onBridgeReady(response);
}
},
onBridgeReady: function(response) {
let that = this;
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId": response.data.appId, //公众号名称,由商户传入
"timeStamp": response.data.timeStamp, //时间戳,自1970年以来的秒数
"nonceStr": response.data.nonceStr, //随机串
"package": response.data.package,
"signType": response.data.signType, //微信签名方式:
"paySign": response.data.paySign //微信签名
},
function(res) {
if (res.err_msg === "get_brand_wcpay_request:ok") {
// 使用以上方式判断前端返回,微信团队郑重提示:
//res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
uni.showLoading({
title: '支付成功'
});
setTimeout(function() {
uni.hideLoading();
//支付成功后跳转的页面
uni.redirectTo({
url: '../myMessage/index'
});
}, 1000);
} else {
uni.hideLoading();
}
WeixinJSBridge.log(response.err_msg);
}
);
},
如果不出意外的话,到这都可进行成功支付。
最后感谢以下参考资料:
https://blog.csdn.net/wang122412655/article/details/90613759
https://blog.csdn.net/weixin_39706415/article/details/111588950