目录
一、准备工作
经过跌跌撞撞地探索,终于完成了微信支付功能,现将微信支付开发重要步骤及代码记录下来,以备不时之需。相信很多童鞋要开始开发微信支付功能的时候,都是一脸懵逼、无从下手,不妨回顾一下,使用者角度,一个完整的微信支付流程是怎样的呢:
(1)首先要有一个“我要充值”的按钮,点击该按钮
(2)跳转到一个支付页面
(3)选择好金额之后,点击“立即充值”,弹出密码键盘
(4)密码输入无误,充值完成之后,商品发货,钱包瘪了
1、所以在开发时要事先准备好这些东西
(1)跳转到充值(下单)界面的一个按钮
(2)设计好的一个充值页面
2、开发时,要配置好这些参数
(1)登录公众号平台,设置【授权回调页面域名】,就是你充值页面的url,哪个页面需要获取openID,就写哪个页面的url。
(2)准备好参数:
appID:公众号appid
appSecret:公众号密钥
mch_id:商户id
key:API密钥
(由于微信开发完成一段时间之后才补的博客,所以有一些细节有点忘记了,这些参数如何配置可以参看这篇博客:https://blog.csdn.net/javaYouCome/article/details/79473743)
二、开始写代码
在编程时,我将微信支付分成三大部分,一是获取openID,二是支付,三是接收支付结果。
1、获取用户openID
获取openid的时机其实没有明确要求,只要在下单之前将这个参数准备好就行了。我所做的项目中,用户通过账户密码登录之后,便马上获取openid。这里我们假设用户在按下“我要充值”按键时,获取用户openID。
获取openID步骤:先获取code,再用code换取openid。
(1)获取code
用户按下【我要充值】按键时,不直接进入充值页面,而是先进入一个“包含了我们appId和充值页面链接的一个url”,作用就是先获取code,然后再携带获取的code,自动跳转到我们真正的目的地——充值页面。用户感觉不到这其中的跳转过程~
步骤 ①,将【我要充值】按键和获取code的url关联
如果【我要充值】位于公众号底部的快捷菜单键
则登录微信公众平台,在自定义菜单中,在【我要交费】的url改为:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=你的appID&redirect_uri=等会儿要跳转的url&response_type=code&scope=snsapi_base&state=123#wechat_redirect
等会儿要跳转的url,一般情况下,就是充值页面的url啦
步骤 ②:解析url得到code
经过步骤1,来到充值页面时,此时充值页面的url中携带了code的值,例如:http://chongzhiyemian.htm?code=XXXXXXXX&DisplayTile=1
在前端,通过正则匹配将code提取出来,写在函数GetQueryString()中
function GetQueryString(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");//构造一个含有目标参数的正则表达式对象
var r = window.location.search.substr(1).match(reg);//匹配目标参数
if (r != null) return unescape(r[2]); return null; //返回参数值
}
(2)用code换取openid
由于code的有效期只有五分钟,所以要尽快换取openid
==前端:
window.onload = function () {
var code = GetQueryString("code"); //获取code
$.post("User/getOpenid", {code: code}, function (msg) { //将code传到后台用于获取openid
if (msg.type == 1) {
var openid = msg.data;
alert("您的openID是" + openid);
}
else if (msg.type == 0)
{
alert("获取openid失败");
return;
}
else {
alert("异常!")
}
})
}
==后台
@RequestMapping("/getOpenid")
@ResponseBody
public String getOpenid(HttpServletRequest request)
{
String code = request.getParameter("code");
String appId = "你的appID";
String appSecret = "你的公众号密钥";
String result;
try {
String URL = "https://api.weixin.qq.com/sns/oauth2/access_token?grant_type=authorization_code";
String getDataStr = "&appid=" + appId + "&secret=" + appSecret+"&code="+code;
String str = templateMsgService.HttpGet(URL, getDataStr);
net.sf.json.JSONObject json = net.sf.json.JSONObject.fromObject(str);
String openid = (String)json.get("openid");
if(openid!=null){
result=openid;
}else{
//获取失败的处理
}
} catch (Exception e) {
// 异常的处理
}
return result;
}
==涉及到的工具方法:templateMsgService.HttpGet()
//HttpGet方法
public String HttpGet(String URL, String GetDataStr){
String getUrl = URL+GetDataStr;
StringBuffer sb = new StringBuffer();
InputStreamReader isr = null;
BufferedReader br = null;
try
{
java.net.URL url = new URL(getUrl);
URLConnection urlConnection = url.openConnection();
urlConnection.setAllowUserInteraction(false);
isr = new InputStreamReader(url.openStream());
br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null)
{
sb.append(line);
}
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
try {
br.close();
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return sb.toString();
}
2、支付主体
(1)业务流程概述
现在正式开始支付,步骤很多,坑很多,先来看一下这整个流程是怎样的,我们需要做的有哪些~
微信支付官方给的业务时序图如下,乍一看很头大,过程太多了!但需要我们做的只有其中标红的那部分(https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_4)
把我们要做的和支付这个过程串起来,就是:
① 【1. 生成图文消息链接】:其实就是 充值页面。
② 用户选择充值金额,点击<确认充值>按键之后,我们【4. 生成商户订单】(没有固定格式要求,主要是给开发者和管理者看的,根据业务需要,例如用户账号、金额、下单时间),然后【5. 调用“统一下单API”】,生成微信能看得懂的订单。
③ 【6. 生成支付参数及签名】完成后,在支付页面根据支付配置及微信统一订单的prepay_id,发起微信支付。
④ 用户输入密码,确认密码之后,微信会通过异步的方式,通知我们的系统,【10.通知支付的结果】。
⑤ 程序中,处理支付的结果,处理好之后,【11.告知微信通知处理结果】
(2)开始写支付部分代码
写代码时,分这么几步走:
第一步:凑齐一堆参数,封装成xml,发给微信支付系统的统一下单接口地址,然后返回一个prepay_id
第二步:凑齐一堆包括prepay_id在内的参数,发到前端,由前端调用支付API
(微信API和用户交互:调起密码键盘、扣钱啥的就不关我们的事啦)
第三步:等待支付系统发回的支付结果,然后进行业务处理
第四步:后台发货,前端提醒充值成功
① 第一步:生成商户订单、调用统一下单API、获取prepay_id
准备必填参数(具体规定见:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1)
将这些参数转化为xml,再发给统一下单接口,接收prepay_id。
参数 | 是啥 | 有啥规定 | 咱们填啥 |
appid | APPID |
| XXXX |
mch_id | 商户ID |
| XXXXX |
nonce_str | 随机字符串 | 可以通过微信提供的随机数生成算法生成 | WXPayUtil.generateNonceStr() |
sign | 签名 | 可以通过微信提供的签名算法生成 | WXPayUtil.generateSignature(paraMap, paternerKey) |
body | 支付的名称 | 格式:商家名称-销售商品类目 | "XXShop-Apple" |
out_trade_no | 订单号 | 建议:当前时间+随机序列 | 当前时间+随机序列,自己写了个函数 generateOrderId() |
total_fee | 订单金额 | 单位:分,int类型 | 后台来处理一下 |
spbill_create_ip | 用户的终端IP地址 |
| 后台处理 |
notify_url | 回调地址 | 接收微信反馈结果。 微信支付系统到时候带着一堆参数请求这个地址,异步通知我们支付成功没,然后我们再在这个地址做一些修改订单状态,赠送积分等事务
| 例如“www.XXXX.com/callback”
|
trade_type | 支付类型 |
| JSAPI |
openid | 当前用户的openid |
| 之前获取过的 |
② 第二步:后台准备第二堆参数,存到map中,发给前端,前端用这些数据去调支付API
参数 | 是啥 | 有啥规定 | 咱们填啥 |
appId | APPID |
| XXXX |
timeStamp | 时间戳 | 标准北京时间,时区为东八区,自1970年1月1日 0点0分0秒以来的秒数。注意:部分系统取到的值为毫秒级,需要转换成秒(10位数字)。 微信有提供这个方法 | WXPayUtil.getCurrentTimestamp() |
nonceStr | 随机字符串 | 同上,通过微信提供的随机数生成算法生成 | WXPayUtil.generateNonceStr() |
signType | 签名方式 | 默认为MD5。需和统一下单的类型一致 | MD5 |
package | 订单详情扩展字符串 | 统一下单接口返回的prepay_id参数值, 提交格式:prepay_id=*** | "prepay_id=" + prepay_id |
paySign | 签名 | 同上,采用签名生成算法 | XPayUtil.generateSignature(payMap, paternerKey) |
①②部分的后台程序,都在一个路径中完成
// 第二步,用户点击“确认充值”按钮后,生成用户订单,存入数据库,然后生成一堆信息,向微信支付系统请求prepay_id
// 得到prepay_id后,将一堆信息打包发送到前端,由前端调起支付界面
@RequestMapping(path = {"/pay/order"}, method = {RequestMethod.POST,RequestMethod.GET})
@ResponseBody
public ResultInfo order(HttpServletRequest request)
{
String orderAccount= request.getParameter("accountID"); //充值账户
String orderFee1= request.getParameter("orderFee"); //充值金额(单位:分)
String paternerKey="你的API密钥";
String appId = "你的appId";
String openId= "当前用户的openid";
// 将充值金额的单位由元转换为分
int index = orderFee1.indexOf(".");
int length = orderFee1.length();
Long amLong = 0l;
if(index == -1){
amLong = Long.valueOf(orderFee1+"00");
}else if(length - index >= 3){
amLong = Long.valueOf((orderFee1.substring(0, index+3)).replace(".", ""));
}else if(length - index == 2){
amLong = Long.valueOf((orderFee1.substring(0, index+2)).replace(".", "")+0);
}else{
amLong = Long.valueOf((orderFee1.substring(0, index+1)).replace(".", "")+"00");
}
String orderFee= amLong.toString();
try {
// ---------------生成用户订单-----------------
String orderId=generateOrderId();//生成订单编号
// 最好设置一个订单状态标志位,在第三步“接收交易状态”时再修改标志位
// 将用户订单存入数据库等等操作
// --------------------------------------------
// ---------------获取用户的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();
}
// -------------------------------------------
// ----- 统一下单参数------
// 注意,参数的顺序不能错!!!!否则无法成功下单
Map<String, String> paraMap = new HashMap<String, String>();
paraMap.put("appid", appId);
paraMap.put("body", "XXShop-Apple");
paraMap.put("mch_id", "你的商户ID");
paraMap.put("nonce_str", WXPayUtil.generateNonceStr());
paraMap.put("openid", openId);
paraMap.put("out_trade_no",orderId );//订单号
paraMap.put("spbill_create_ip", ip);
paraMap.put("total_fee",orderFee);
paraMap.put("trade_type", "JSAPI");
paraMap.put("notify_url","www.XXXXXXX.com/callback");// 此路径是微信服务器调用支付结果通知路径
String sign = WXPayUtil.generateSignature(paraMap, paternerKey);
System.out.println("签名是"+sign);
paraMap.put("sign", sign);
String xml = WXPayUtil.mapToXml(paraMap);//将所有参数(map)转xml格式
// 统一下单接口
String unifiedorder_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
//发送post请求"统一下单接口"返回预支付id:prepay_id
String xmlStr = HttpRequest.sendPost(unifiedorder_url, xml);
//System.out.println("xml是"+xmlStr);
//以下内容是返回前端页面的json数据
String prepay_id = "";//预支付id
if (xmlStr.indexOf("SUCCESS") != -1) {
System.out.println("支付系统返回了prepay_id");
Map<String, String> map = WXPayUtil.xmlToMap(xmlStr);
prepay_id =map.get("prepay_id");
}else {
System.out.println("prepay_id获取失败");
}
// =============至此已成功获取到prepay_id================
//System.out.println("您的prepay_id的值是:"+prepay_id);
// 将“微信内H5调起支付”需要的参数打包成JSON,发给前端
Map<String, String> payMap = new HashMap<String, String>();
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);
if(payMap!=null){
resultInfo.setType(1);
resultInfo.setData(payMap);
resultInfo.setMessage("成功获得prepay_id,且将数据发送到前端" );
}else{
resultInfo.setType(0);
resultInfo.setMessage("获取prepayId失败" );
resultInfo.setData("0");
}
} catch (Exception e) {
resultInfo.setType(2);
resultInfo.setMessage("异常:" + e.toString());
resultInfo.setData("000");
}
System.out.println(resultInfo.getType()+"...."+resultInfo.getMessage()+"...."+resultInfo.getData());
return resultInfo;
}
==涉及到的工具:
说明:微信官方给了一个工具库,不妨下载之后导入,下载地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1
a. 生成用户订单号:利用微信官方给的WXPayUtil工具类,新建一个函数generateOrderId()
public static String generateOrderId() {
char[] nonceChars = new char[6]; //6个随机字符
for (int index = 0; index < nonceChars.length; ++index) {
nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
}
SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");//设置日期格式
String orderId1=df.format(new Date());
String orderI2=new String(nonceChars);
String orderId =orderId1+orderI2;
return orderId;
}
b. 生成nonce_str:WXPayUtil.generateNonceStr()
c. 生成sign签名:WXPayUtil.generateSignature(paraMap, paternerKey)
d. 发送post请求,访问统一下单接口:sendPost( url, xml)
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;
}
}
③ 前端收到后台传来的第二堆参数,然后唤起支付页面
这里特别气,因为官方参考文档中,没说该怎么调用,第一次做微信支付的菜鸟眼泪掉下来。。。经过参考网上优秀大佬的博客,以及数次探索和实验。。。终于知道该咋用了:
function pay(){
var accountID=document.getElementById("accountID").value;
var orderFee = document.getElementById("orderFee").value;
// 将用户的金额
$.post("pay/order", { orderFee: orderFee ,accountID:accountID}, function (result) {
// 以下是后台发来的“第二堆参数”,
var appId=result.appId;
var timeStamp=result.timeStamp;
var nonceStr=result.nonceStr;
var packageStr=result.package;
var signType=result.signType;
var paySign=result.paySign;
if (typeof WeixinJSBridge == "undefined") {
if (document.addEventListener) {
document.addEventListener('WeixinJSBridgeReady',
onBridgeReady, false);
} else if (document.attachEvent) {
document.attachEvent('WeixinJSBridgeReady',
onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady',
onBridgeReady);
}
} else {
// 唤起支付页面
onBridgeReady(appId,timeStamp,nonceStr,packageStr,signType,paySign);
}
});
//微信JSDK唤起支付
function onBridgeReady (appId,timeStamp,nonceStr,packageStr,signType,paySign) {
//放到pay里了,这些数据啥时候用?zx
WeixinJSBridge.invoke('getBrandWCPayRequest', {
"appId": appId,
"timeStamp": timeStamp,
"nonceStr": nonceStr,
"package": packageStr,
"signType": signType,
"paySign": paySign
},
function (msg) {
if (msg.message == "get_brand_wcpay_request:ok") {
console.log('支付成功');
//支付成功后跳转的页面
window.location.href = "/rechargeSuccess.html";
} else if (msg.message == "get_brand_wcpay_request:cancel") {
console.log('支付取消');
} else if (msg.message == "get_brand_wcpay_request:fail") {
console.log('支付失败');
WeixinJSBridge.call('closeWindow');
} //使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
}
);
}
}
3、接收支付系统交易结果通知
怎么知道用户有没有支付成功,进而决定要不要发货呢~当然就是写个接口用来接收微信支付系统的消息啦
这个接口在上述步骤中其实已经提到过了,就是统一下单参数中的notify_url,要求这个回调地址外网能够访问,在调试时,要放在公网上才行。这个接口有两大作用:一是接收微信支付系统的消息;二是进行一些业务处理,例如修改订单状态、安排发货!
菜鸟表示,刚看到这个回调地址的说明时,很费解,那先看程序吧
(1)后台程序:主要包括以下逻辑:
① 从输入流中获得微信支付系统发来的消息,是xml格式的,各参数的含义参见:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7,但最重要的参数主要是return_code和return_msg
② 解析消息,可以得到某订单号是否成功,然后进行业务处理
③ 给微信支付系统回复“收到”确认信息,否则微信支付系统会不停地发同样的通知给回调接口。。。。
@RequestMapping("/callback")
@ResponseBody
public ResultInfo payCallBack(HttpServletRequest request, HttpServletResponse response){
ResultInfo resultInfo =new ResultInfo();
InputStream inputStream = null;
try {
inputStream = request.getInputStream();
String xml = WXPayUtil.inputStream2String(inputStream, "UTF-8");
Map<String, String> notifyMap = WXPayUtil.xmlToMap(xml);//将微信发的xml转map
//System.out.println("支付系统返回支付结果"+xml);
if(notifyMap.get("return_code").equals("SUCCESS")){
System.out.println("return_code是:"+notifyMap.get("return_code"));
// 交易成功
if(notifyMap.get("result_code").equals("SUCCESS")){
// 接下来进行一些业务处理
}
}else{
// 交易失败的处理
}
response.getWriter().write("<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>"); //告知微信支付系统已收到消息
inputStream.close();
} catch (Exception e) {
// 异常的处理
}
return resultInfo;
}
至此,微信支付的主体程序和流程就结束了。
在微信支付的开发过程中,参看了以下优秀大佬的博客,贴出来特此感谢:
https://blog.csdn.net/javaYouCome/article/details/79473743
https://www.cnblogs.com/imeng/p/4792043.html
https://blog.csdn.net/jrainbow/article/details/49904065?utm_source=blogxgwz3