微信支付
微信支付(公众号)开发流程
-
openId是每个用户在公众号中的唯一编号,商户后台获取预支付单时要用到openId(参与MD5计算)
-
获取openId之前,应先获取code码
-
注意设置微信支付商户平台的支付授权目录,设置的路径应该为:支付页面的上一级目录,如支付页面url为:www.xxx.com/app/pay.html,那么,支付授权目录则为:www.xxx.com/app/,记得以“/”结尾
总体流程
- 前端获取code码
- 后端使用code获取用户openId
- 商户后台拿到openId,整合其他参数后向微信服务器发起预支付请求,得到预支付单
- 商户后台抽取预支付单的参数,整合其他参数后将数据返回给用户微信客户端
- 用户微信客户端使用返回的参数调起微信支付内置方法,若参数正确,将弹出支付窗口,用户输入密码完成支付
细节
1、前端获取code码
function userPay() {
var redirect_uri = 'https://localhost:8488/emsb/APP/userPay.html';
//调用网页授权接口链接,注意,redirect_uri必须进行encode
var url = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=xxxxxxxxxxxx&redirect_uri='+encodeURI(redirect_uri)+
'&response_type=code&scope=snsapi_base&state=1#wechat_redirect';
//执行跳转
window.location = url;
}
执行此方法,注意
-
将url 中 appid换成自己公众号的appid
-
redirect_uri 应进行encodeURI编码
-
scope=snsapi_base:获取用户基本信息:openId,不需要用户授权
-
scope=snsapi_userinfo:获取openId和其他用户资料(昵称、头像、国、省、城市、性别、权限),需要用户手动授权
-
window.location 执行完成后,将自动跳转到redirect_uri页面,并将所请求的信息附加在url地址后方,可使用以下方法获取
let url = location.search.substr(1); let list = url.split("&"); let code = list[0].split("=")[1]; let state = list[1].split("=")[1];
2、通过code码获取openId
因获取openId时还需要使用一些敏感的参数(appsecret),应将此步骤放到商户后台
String appid = "xxxxxxxxxx";
String appsecret = "xxxxxxxxxxxx";
//使用HttpClient
String praiseUrl = "https://api.weixin.qq.com/sns/oauth2/access_token";
HttpClient client = new HttpClient();
PostMethod postMethod = new PostMethod(praiseUrl);
// 必须设置下面这个Header
postMethod.addRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36");
postMethod.addParameter("appid", appid);
postMethod.addParameter("secret", appsecret);
postMethod.addParameter("code", code);
postMethod.addParameter("grant_type", "authorization_code");
//发送请求,并获取返回结果
int res_code = client.executeMethod(postMethod);
if (res_code == 200) {
String res = postMethod.getResponseBodyAsString();
//转为Map,以便获取openId
responseMap = (Map<String, String>) JSON.parse(res);
//responseMap.get("openid");
}
这里使用HttpClient向微信接口发送请求,应在pom.xml中添加如下依赖
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
</dependency>
同时使用到了JSON,在pom.xml中添加如下依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
3、向微信接口请求预支付单
此过程较复杂,为节省时间,直接用微信官方的SDK:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1,下载java版本后,将 src\main\java\com\github\wxpay\sdk 包下所有文件导入当前工程,并引入pom.xml中所有的依赖
- IWXPayDomain
- MyConfig
- WXPay
- WXPayConfig
- WXPayConstants
- WXPayReport
- WXPayRequest
- WXPayUtil
- WXPayXmlUtil
根据REAEME.md中的提示,新建MyConfig继承WXPayConfig,实现其中的方法
public class MyConfig extends WXPayConfig {
private byte[] certData;
public MyConfig() throws Exception {
try {
ClassPathResource classPathResource = new ClassPathResource("config/apiclient_cert.p12");
//获取文件流
InputStream certStream = classPathResource.getInputStream();
this.certData = IOUtils.toByteArray(certStream);
certStream.read(this.certData);
certStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
String getAppID() {
return "xxxxxxxxxxxxxxx";
}
//商户号
@Override
String getMchID() {
return "xxxxxxxxxxxxxx";
}
//key 密钥 在微信支付商户平台->产品中心->开发配置中设置支付密钥
@Override
String getKey() {
return "xxxxxxxxxxxxxxxx";
}
@Override
InputStream getCertStream() {
ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
return certBis;
}
@Override
IWXPayDomain getWXPayDomain() {
IWXPayDomain iwxPayDomain = new IWXPayDomain() {
@Override
public void report(String domain, long elapsedTimeMillis, Exception ex) {
}
@Override
public DomainInfo getDomain(WXPayConfig config) {
return new IWXPayDomain.DomainInfo(WXPayConstants.DOMAIN_API, true);
}
};
return iwxPayDomain;
}
public int getHttpConnectTimeoutMs() {
return 8000;
}
public int getHttpReadTimeoutMs() {
return 10000;
}
}
平常使用SpringBoot打包后,外部证书文件获取可能失败,这里已经做了优化,使用IOUtils,应导入如下依赖
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-io -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
4、获取预支付单
MyConfig config = new MyConfig();
WXPay wxpay = new WXPay(config);
Map<String, String> data = new HashMap<>();
data.put("appid", "xxxxxxxxxx");
data.put("mch_id", "xxxxxxxx");
data.put("nonce_str", WXPayUtil.generateNonceStr()); //随机串,这里使用了微信SDK提供的工具
data.put("sign_type", "MD5");
data.put("body", "1");
data.put("out_trade_no", WXPayUtil.generateNonceStr());
data.put("total_fee", "1");
data.put("spbill_create_ip", "123.12.12.123"); //暂时随便指定,不影响
data.put("notify_url", "http://www.baidu.com"); //暂时随便指定,不影响
data.put("trade_type", "JSAPI"); //公众号支付为JSAPI
data.put("device_info", "WEB"); //公众号支付为WEB
data.put("openid", responseMap.get("openid")); //拿到用户的openId
//为订单信息签名,防止篡改:签名的内容为以上data中的全部数据,注意map中key的大小写
String sign = WXPayUtil.generateSignature(data, "xxxxxxxxxxxxxxxxxxx", WXPayConstants.SignType.MD5);
//将签名放入map中,一并发送给微信接口
data.put("sign", sign);
//调用微信统一下单接口,接收返回数据(Map),其中包含用户支付的核心数据
Map<String, String> resp = wxpay.unifiedOrder(data);
可以到https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=20_1测试数据签名是否正确
5、为预支付单数据签名,发送给用户微信客户端
//签名返回数据
Map<String, String> map = new HashMap<>();
//注意resp为调用统一下单接口后的返回数据map
//注意key的大小写
map.put("appId", resp.get("appid"));
map.put("package", "prepay_id=" + resp.get("prepay_id"));
map.put("nonceStr", resp.get("nonce_str"));
map.put("signType", "MD5");
map.put("timeStamp", String.valueOf(WXPayUtil.getCurrentTimestamp())); //时间戳单位为秒,直接使用SDK提供的工具
//使用支付密钥为以上五条数据签名,签名类型为MD5,防止数据被篡改
String paySign = WXPayUtil.generateSignature(map, "xxxxxxxxxxx", WXPayConstants.SignType.MD5);
//将签名一并发送给客户端
map.put("paySign", paySign);
//将map返回给前台
return map;
6、微信客户端调起支付窗口
这是从微信官方文档中拿到的代码
//将服务端返回的数据填充到此方法
function onBridgeReady(res){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":res.appId, //公众号名称,由商户传入
"timeStamp":res.timeStamp, //时间戳,自1970年以来的秒数
"nonceStr":res.nonceStr, //随机串
"package":res.package,
"signType":"MD5", //微信签名方式
"paySign":res.paySign //微信签名
},
function(res){
if(res.err_msg == "get_brand_wcpay_request:ok" ) {} // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
}
);
}
//以下可以注释掉,手动调用onBridgeReady方法即可,若参数校验正确,将拉起支付密码框
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();
}
到这里之后,可能即使你经过反复确认,数据完全正确,也无法成功拉起支付密码输入框,系统提示支付验证失败之类的,其实,微信的官方SDK中有一个坑:找到 WXPay.java
public WXPay(final WXPayConfig config, final String notifyUrl, final boolean autoReport, final boolean useSandbox) throws Exception {
this.config = config;
this.notifyUrl = notifyUrl;
this.autoReport = autoReport;
this.useSandbox = useSandbox;
if (useSandbox) {
this.signType = SignType.MD5; // 沙箱环境
}
else {
// this.signType = SignType.HMACSHA256;
this.signType = SignType.MD5;
}
this.wxPayRequest = new WXPayRequest(config);
}
在这个构造方法中,
我注释了 this.signType = SignType.HMACSHA256; ,
应改为this.signType = SignType.MD5;
现在应该能够成功调起支付窗口了。

浙公网安备 33010602011771号