专栏简介 | ||
个人主页 本栏目录 |
心灵鸡汤 不是成功离我们太远,而是我们坚持的太少。 |
✍相关博文✍ 微信支付配置 |
微信支付-JASPI:准备工作[微信公众平台配置,微信商户平台配置]
微信支付所需的条件配置已经完成,接下来就开始实现(以下仅供参考)
一,实现前准备
①,阅读官方支付文档,主要是业务流程这一点https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_4
②,官方demo下载 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1
③,js支付请求方式选择(我选择的是chooseWXPay,这个根据自身情况选择)
1.商户平台:网页通过JavaScript调用getBrandWCPayRequest接口,发起微信支付请求
3.两者区别:选择getBrandWCPayRequest的话,支付页面不需要引入任何的js,选用chooseWXPay的话,需要引入JS文件
二,代码实现
1.前端页面核心代码
①,引入js
②,config接口注入权限验证(requestUrl为当前页面的路由地址)
var reUrl =window.location.href;
$.ajax({
url: "/wxPay/getWXConfig?requestUrl="+encodeURIComponent(reUrl),
dataType: "json",
async: false,
type: "GET",
success: function (data) {
wx.config({
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: data.appId, // 必填,公众号的唯一标识
timestamp:data.timestamp, // 必填,生成签名的时间戳
nonceStr: data.nonceStr, // 必填,生成签名的随机串
signature: data.signature,// 必填,签名
jsApiList: ['chooseWXPay'] // 必填,需要使用的JS接口列表
});
}
});
geWxConfig的实现方法可参考:微信分享开发:代码实现[前端+后端](二)
③,支付调用统一下单接口(请根据自身业务定义后端接口)
function SaveOrWxPay() {
$.ajax({
url: "/wxPay/pay2?openId="+ GetPar("token")+"&totalFee="+$("#labTotalCost").text()+"&operationNO="+$("#labOperationNO").text()+"&userName="+$("#UserName").val()+"&userPhone="+$("#UserPhone").val(),
dataType: "json",
async: false,
type: "GET",
success: function (data) {
wx.chooseWXPay({
timestamp: data.timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
nonceStr: data.nonceStr, // 支付签名随机串,不长于 32 位
package: data.package, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)
signType: data.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
paySign: data.paySign, // 支付签名
success: function (res) {
//此处为支付成功后点击[完成]后的回调,可根据自身业务需求做相应处理
},
});
}
});
}
2.后端核心代码
根据统一订单API,必备的参数有:
appid:公众账号ID mch_id:商户号 nonce_str:随机字符串 sign:签名
body:商品描述 out_trade_no:商户订单号 total_fee:标价金额 spbill_create_ip:终端IP
trade_type:交易类型 openid:用户标识,授权登录公众号获得
notify_url:通知地址(我们可以在这个里面处理支付成功后的业务逻辑,比如修改支付状态)
①openid获取可参考微信授权登录:移动端[unionid](一),自行修改
②接下来就是关于支付的核心代码了
/**
* @description: 微信支付-统一下单
* @author: lvyq
* @date: 2020/9/16 16:30
* @version 1.0
*/
@ApiOperation(value = "微信支付-统一下单")
@RequestMapping(value = "/pay2", method = {RequestMethod.POST, RequestMethod.GET})
public Object Orders(HttpServletRequest request, String openId, String tradeNo, String totalFee,String operationNO,String userName,String userPhone) {
CommonUtils commonUtils = new CommonUtils();
totalFee=String.format("%.0f", Float.valueOf(totalFee)*100);
//TODO 订单号暂自生成
tradeNo = new CommonUtils().getRandomStringByLength(32);
String nonce_str = WXPayUtil.generateNonceStr();
try {
Map paraMap = new HashMap<>();
//获取请求ip地址
String ip = commonUtils.getIPAddress(request);
paraMap.put("appid", appid);
paraMap.put("mch_id", mchid);
paraMap.put("nonce_str", nonce_str);
//签名
String sign = WXPayUtil.generateSignature(paraMap, paternerKey);
paraMap.put("sign", sign);
paraMap.put("body", "维护管理服务费结算");
paraMap.put("out_trade_no", tradeNo);
paraMap.put("total_fee", totalFee);
paraMap.put("spbill_create_ip", ip);
// TODO 支付异步回调地址
//此处对中文进行转码
String RuserName=URLEncoder.encode(userName, "UTF-8");
paraMap.put("notify_url", commonUtils.getDomainName(request).toString() + "/wxPay/callback2/"+operationNO+"/"+RuserName+"/"+userPhone);
System.out.print("notify_url==="+commonUtils.getDomainName(request).toString() + "/wxPay/callback2/"+operationNO+"/"+RuserName+"/"+userPhone);
paraMap.put("trade_type", "JSAPI");
paraMap.put("openid", openId);
paraMap.put("sign_type", "MD5");
//将所有参数(map)转xml格式
// String xml = WXPayUtil.mapToXml(paraMap);
String xml = WXPayUtil.generateSignedXml(paraMap, paternerKey);
//发送post请求"统一下单接口"返回预支付id:prepay_id
String xmlStr = HttpRequest.sendPost(unifiedorderUrl, xml);
String prepay_id = "";//预支付id
if (xmlStr.indexOf("SUCCESS") != -1) {
Map map = WXPayUtil.xmlToMap(xmlStr);
prepay_id = (String) map.get("prepay_id");
}
Map payMap = new HashMap();
payMap.put("appId", 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, paternerKey);
payMap.put("paySign", paySign);
return payMap;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* @description: 支付回调地址
* @author: lvyq
* @date: 2020/9/17 16:06
* @version 1.0
*/
@ApiOperation(value = "支付回调地址")
@RequestMapping(value = "/callback2/{operationNO}/{userName}/{userPhone}", method = {RequestMethod.POST, RequestMethod.GET})
public String CallBack(HttpServletRequest request, HttpServletResponse response,@PathVariable("operationNO") String operationNO,@PathVariable("userName") String userName,@PathVariable("userPhone") String userPhone) throws Exception {
InputStream is = null;
//解码
String RuserName=URLDecoder.decode(userName,"UTF-8");
System.out.print("RuserName==="+RuserName+"userName=="+userName);
try {
//获取请求的流信息(这里是微信发的xml格式所有只能使用流来读)
is = request.getInputStream();
String xml = commonUtils.nputStream2String(is);
//将微信发的xml转map
Map notifyMap = WXPayUtil.xmlToMap(xml);
if (WXPayUtil.isSignatureValid(notifyMap,paternerKey)) {
if (notifyMap.get("return_code").equals("SUCCESS")) {
if (notifyMap.get("result_code").equals("SUCCESS")) {
//TODO 业务代码,对数据增删改查等。。。
}
}
}else {
// 签名错误,如果数据里没有sign字段,也认为是签名错误
}
//告诉微信服务器收到信息了,防止微信重复回调
response.getWriter().write(" ");
is.close();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
③,以下附上commonUtil部分代码
package com.jmdz.util;
import com.alibaba.fastjson.JSONObject;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.ConnectException;
import java.net.URL;
import java.util.Random;
/**
* @description: 公共工具
* @author: lvyq
* @date: 2020/9/15 11:35
* @version 1.0
*/
public class CommonUtils {
/**
* @description: 获取域名(协议+域名)
* @author: lvyq
* @date: 2020/9/15 11:36
* @version 1.0
*/
public String getDomainName(HttpServletRequest request){
// return request.getServerName()+":"+request.getServerPort();
return request.getScheme()+"://"+request.getServerName();
}
public String getIPAddress(HttpServletRequest request){
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();
}
return ip;
}
public static String getRandomStringByLength(int length) {
String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
public static String nputStream2String(InputStream in) {
InputStreamReader reader = null;
try {
reader = new InputStreamReader(in, "UTF-8");
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}
BufferedReader br = new BufferedReader(reader);
StringBuilder sb = new StringBuilder();
String line = "";
try {
while ((line = br.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
}
return sb.toString();
}
}
③.HttpRequest
package com.jmdz.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
import java.util.Map;
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> 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;
}
}
④,WXPayUtil (这个官网提供的demo里面就有,直接拿来用就行。我就不贴代码了)
三,查看效果
==============================分割线==============================
1.为了notify_url接口里面接受参数,我们的接口采用restful。伪造一个不带参数的url
2.回调地址含中文的话无法回调地址,故在统一下单时,对中文参数进行编码,接收时再解码即可