微信支付,说起来容易,做起来真不容易,网上资料很多,但是随着微信支付的更新,有用的不多,bug也很多,调试了好久,终于弄明白如何让微信支付调通。这里涉及前后端代码,从而更方便直接用。
首先微信支付,需要弄明白一些流程,从而快速了解。
1、由于是h5页面支付,需要跳转一个地址,获取openid,可以是单独一个index.html,里面只有一个跳转地址,或者也可以直接在微信公众号的子菜单中直接增加这个地址:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=填写appid&redirect_uri=填写&response_type=code&scope=snsapi_base#wechat_redirect
这样跳转的时候就直接可以获取到openid,其中appid需要填写,redirect_uri需要填写,这个地址就是这个跳转地址成功后会获得一个code,然后传给这个地址,我是写到了后台的controller中,从而可以根据这个code,解析出openid。当然也可以单独一个页面,然后通过这个页面进入controller中,然后再跳转到支付页面。
2、由于是后台解析获得openid,所以从后台需要跳转到业务支付页面pay.html,在那里完成支付逻辑,代码后面统一上。
3、支付完成后调出支付窗口,完成支付,并跳到到支付通知地址。
核心流程是上面,但是关键的配置很多,一个不能少,准备工作如下:
1、微信公众号配置:公众号设置-》功能设置-》JS接口安全域名(请求controller的地址前缀),网页授权域名(可以是一级域名,在这个域名下请求的页面可以获得授权)
2、微信公众号配置:微信公众号-》基本设置-》APPID,APPSecert(用于获取openid的参数使用)
3、微信支付商户平台:产品中心-》开发配置-》JSAPI支付目录配置(用于支付页面在这个位置调起,本文是pay.html放在这个目录)
4、微信支付商户平台:账户中心-》API安全-》API证书(*.p12格式,用于代码配置证书用),API密钥(产生签名需要使用这个,会进行两次签名)
开始代码
1、下载最新版的微信官方java版本demo,地址为:
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1
由于WXPayConfig是抽象类,需要继承,同目录下新建一个类MyConfig,代码如下:
package com.wxpay.demo.sdk;
import com.wxpay.demo.sdk.WXPayConfig;
import java.io.*;
public class MyConfig extends WXPayConfig{
private byte[] certData;
public MyConfig() throws Exception {
String certPath = "C:\\cert\\apiclient_cert.p12";//填写具体路径
File file = new File(certPath);
InputStream certStream = new FileInputStream(file);
this.certData = new byte[(int) file.length()];
certStream.read(this.certData);
certStream.close();
}
public String getAppID() {
return "wx***";//填写具体的公众号中的APPID
}
public String getMchID() {
return "160***";//微信支付商户平台中的商户号
}
public String getKey() {
return "d2ab1****";//微信公众号中的APPSecert
}
public String getAPIKey() {
return "Ling****";//微信支付商户平台中的API密钥
}
public InputStream getCertStream() {
ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
return certBis;
}
public int getHttpConnectTimeoutMs() {
return 8000;
}
public int getHttpReadTimeoutMs() {
return 10000;
}
@Override
IWXPayDomain getWXPayDomain() {
// TODO Auto-generated method stub
IWXPayDomain iwxPayDomain = new IWXPayDomain() {
public void report(String domain, long elapsedTimeMillis, Exception ex) {
}
public DomainInfo getDomain(WXPayConfig config) {
return new IWXPayDomain.DomainInfo(WXPayConstants.DOMAIN_API, true);
}
};
return iwxPayDomain;
}
}
开始核心的后台java逻辑,涉及三个,一个是/wx/openid是开篇说的,重要:通过一个链接跳转后的回调地址,从而后台获得openid,然后跳转到自己的支付页面,第二是/wx/pay,就是支付页面需要ajax调用这个接口,从而在这个接口里面完成微信预支付的接口,获得prepay_id,然后返回给前端,前端再调起支付接口,成功后,进入第三步,/wx/notify,这个接口是成功后会调用这个接口,进行告诉微信成功,并调用自己的业务逻辑。
package com.wxpay.demo.controller;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.wxpay.demo.sdk.MyConfig;
import com.wxpay.demo.sdk.WXPayConstants.SignType;
import com.wxpay.demo.sdk.WXPayUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import com.alibaba.fastjson.JSON;
@Controller
@RequestMapping("/wx")
public class InterfaceController {
private String m_openid;
private String m_prepayid;
private String m_nonce_str;
//http中请求接口
public 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());
// 获取URLConnection对象对应的输出流(进行编码)
out = new PrintWriter(new OutputStreamWriter(conn.getOutputStream(),"UTF-8"));
// 发送请求参数
out.print(param);
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(
conn.getInputStream(), "utf-8"));
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;
}
@RequestMapping("/openid")
public void wet(HttpServletRequest request,HttpServletResponse response) throws Exception{
MyConfig myconfig = new MyConfig();
String json1 = "";
String code = request.getParameter("code");
String httpurl = "https://api.weixin.qq.com/sns/oauth2/access_token?"+"appid="+myconfig.getAppID()+"&secret="+myconfig.getKey()+"&code="+code+"&grant_type=authorization_code";//
//网页授权获取用户信息时用于获取access_token以及openid的请求路径 https://api.weixin.qq.com/sns/oauth2/access_token?
try {
json1 = HttpUtil.get(httpurl);
JSONObject jsonToken = new JSONObject(json1);
m_openid = jsonToken.getStr("openid");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
response.sendRedirect("https://**/pay.html");//需要填写自己的支付页面路径
}
@RequestMapping("/pay")
public void pay(HttpServletRequest request, HttpServletResponse response)
throws Exception {
MyConfig myconfig = new MyConfig();
request.setCharacterEncoding("UTF-8");
//网页授权后获取传递的参数
String money = request.getParameter("money");//分为单位
String ip = request.getParameter("ip");
int intMoney = Integer.parseInt(money);
//用于获取随机数
String strReq = WXPayUtil.generateNonceStr();//10位序列号,可以自行调整
String orderNo=myconfig.getAppID()+Long.toString(WXPayUtil.getCurrentTimestamp());//随机生成了一个订单号
Map data = new HashMap();
data.put("appid", myconfig.getAppID());
data.put("body", "商品名称");//填写自己的商品名称
data.put("mch_id", myconfig.getMchID());
data.put("nonce_str",strReq);
data.put("notify_url", "需要填写回调地址");//如:https://**/wx/notify
data.put("out_trade_no", orderNo);
data.put("openid",m_openid);
data.put("spbill_create_ip", ip);
data.put("total_fee", money);
data.put("trade_type", "JSAPI"); //
try {
String sign = WXPayUtil.generateSignature(data, myconfig.getAPIKey(),SignType.MD5);
data.put("sign", sign);
String xml = WXPayUtil.mapToXml(data);
String wxUnifiedorderUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder";
String xmlStr = sendPost(wxUnifiedorderUrl, xml);
Map map = WXPayUtil.xmlToMap(xmlStr);
if (xmlStr.indexOf("SUCCESS") != -1) {
m_prepayid = (String) map.get("prepay_id");
m_nonce_str = (String) map.get("nonce_str");
}
System.out.println("---rush wxpay/pay,m_prepayid="+m_prepayid+",nonce_str="+m_nonce_str);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Map json = new HashMap();
json.put("appId", myconfig.getAppID());
json.put("timeStamp", WXPayUtil.getCurrentTimestamp() + "");
json.put("nonceStr", m_nonce_str);//需要用第一签名返回的值
//json.put("nonceStr", WXPayUtil.generateNonceStr());
json.put("package", "prepay_id=" + m_prepayid);
json.put("signType", "MD5");
try {
String sign = WXPayUtil.generateSignature(json, myconfig.getAPIKey(),SignType.MD5);
json.put("paySign", sign);
}catch(Exception e){
e.printStackTrace();
}
String s = JSON.toJSONString(json);
response.setCharacterEncoding("utf-8");
response.setHeader("Access-Control-Allow-Origin", "*");
response.setContentType("application/json; charset=utf-8");//返回的格式必须设置为application/json
response.getWriter().write(s);//写入到返回结果中
System.out.println("---rush wxpay/pay,json="+json);
}
@RequestMapping("/notify")
public void notify(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("--rush wxpay/notify--------------------");
response.getWriter()
.write(" ");
// response.sendRedirect("https://***/payover.html");//可以进入自己的后续业务处理
}
}
其中涉及的pay.html核心代码如下:
支付