官方文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1
官方的一张图
我们只需要开发图中红色的部分。
1.生成图文消息链接或二维码
网上随便找个网站就行,例如草料。
2.生成商户订单,调用统一下单API
调用统一下单API必须传用户的openid,openid是每个用户之于每个公众号的唯一标志。以下为获取的openid的步骤。获取openid的官方文档
a.设置网页授权回调域名
在以下路径”开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息”进行配置。
b.构造授权链接给用户去访问
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
各个参数的顺序不能改变
- APPID 公众号唯一标识
- REDIRECT_URI 授权后重定向的回调链接地址,请使用urlEncode对链接进行处理(请不要直接再此地址直接调起微信支付)。
- SCOPE 应用授权作用域,有两种方式
- snsapi_base 不会弹出要求用户授权页面,静默的获得code(只能获得openid)
- snsapi_userinfo 会弹出验证页面,可以获得用户公开信息
- STATE 重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节
如果用户同意授权,页面将跳转至 redirect_uri/?code=CODE&state=STATE。
c.根据code获得access_token,openid,refresh_token等
- APPID 公众号唯一标识
- SECRET 公众号的唯一凭证密钥(此密钥不应给用户接触,所有此链接应在服务器发起请求)
- CODE 上一步中获得的code(注意,每个code只能用一次)
微信返回数据
- access_token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
- expires_in access_token接口调用凭证超时时间,单位(秒)
- refresh_token 用户刷新access_token
- openid 用户唯一标识,请注意,在未关注公众号时,用户访问公众号的网页,也会产生一个用户和公众号唯一的OpenID
- scope 用户授权的作用域,使用逗号(,)分隔
如果只要获得用户的openid的,当这一步就已经结束了。
d.调用统一下单API
调用这个API可以自己根据文档要求构造请求,也可以直接只用微信封装好的sdk,以下介绍使用sdk的方法。
通过maven安装sdk
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
//这个官方demo的代码,调用统一下单接口
public static void main(String[] args) throws Exception {
MyConfig config = new MyConfig();
WXPay wxpay = new WXPay(config);
Map<String, String> data = new HashMap<String, String>();
data.put("body", "腾讯充值中心-QQ会员充值");
data.put("out_trade_no", "2016090910595900000012");
data.put("device_info", "");
data.put("fee_type", "CNY");
data.put("total_fee", "1");
data.put("spbill_create_ip", "123.12.12.123");
data.put("notify_url", "http://www.example.com/wxpay/notify");
data.put("trade_type", "NATIVE"); // 此处指定为扫码支付
data.put("product_id", "12");
try {
Map<String, String> resp = wxpay.unifiedOrder(data);
System.out.println(resp);
} catch (Exception e) {
e.printStackTrace();
}
}
把接口返回的数据取出需要的,然后进行签名,签名的Key为之前配置过的。
Map<String, String> result = new HashMap<>(6);
result.put("appId",config.getAppID());
result.put("timeStamp",String.valueOf(new Date().getTime()));
result.put("nonceStr",resp.get("nonce_str"));
result.put("package","prepay_id="+resp.get("prepay_id"));
result.put("signType","MD5");
result.put("paySign",WXPayUtil.generateSignature(result,config.getKey()));
签完名将数据传递给需要调起支付的前端页面。
JS调起微信支付
注意,调起支付的页面必须是在配置的支付授权目录的路径下的,不然调起支付失败。
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(result);
}
function onBridgeReady(t) {
//t为后台传过来的参数
WeixinJSBridge.invoke(
'getBrandWCPayRequest', t,
function (res) {
if (res.err_msg == "get_brand_wcpay_request:ok") {
window.location.href="${pageContext.request.contextPath}/result.htm?msg=success"
} // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。
});
}
后台接收微信的异步回调
一定要返回给微信已经收到通知的回复,一定要避免重复通知带来的业务逻辑错误。
微信通知是xml格式的数据。所以要先读出来,然后用密钥验证一下签名。
InputStreamReader isr=new InputStreamReader(request.getInputStream());
BufferedReader br=new BufferedReader(isr);
StringBuilder notifyData=new StringBuilder();
String line = null;
while ((line = br.readLine()) != null) {
notifyData.append(line);
}
Map<String, String> notifyMap = WXPayUtil.xmlToMap(notifyData.toString()); // 转换成map
if(wxPay.isPayResultNotifySignatureValid(notifyMap)) {
// 签名正确
// 进行处理。
// 注意特殊情况:订单已经退款,但收到了支付结果成功的通知,不应把商户侧订单状态从退款改成支付成功
if("SUCCESS".equals(notifyMap.get("result_code"))){
}
}else {
// 签名错误,如果数据里没有sign字段,也认为是签名错误
}
如果要展示支付结果的时候微信的通知还没来,可以主动的进行查询
public static void main(String[] args) throws Exception {
MyConfig config = new MyConfig();
WXPay wxpay = new WXPay(config);
Map<String, String> data = new HashMap<String, String>();
data.put("out_trade_no", "2016090910595900000012");
try {
Map<String, String> resp = wxpay.orderQuery(data);
System.out.println(resp);
} catch (Exception e) {
e.printStackTrace();
}
}