流程梳理:
- 获取用户授权: -> code -> openid
- 统一支付下单:组参 -> sign -> 调用统一支付下单接口 -> prepay_id
- 唤起微信内置支付页面:组参 -> sign -> js页面唤起
- 获取openid:
-
- js页面请求获取 code:
-
- 配置:
-
-
- 请求微信链接
-
https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
-
-
- 所需参数(5个):
-
- appid: 应用唯一标识,在微信开放平台提交应用审核通过后获得
- redirect_uri:微信回调路径(域名未备案不影响测试,域名未备案不影响测试, 域名未备案不影响测试,重要的事情多说几遍,不要再怀疑域名的问题)
- response_type:固定值 code
- scope: 应用授权作用域(拥有多个作用域用逗号(,)分隔)
-
- snsapi_base:隐式授权,用户无感知,仅限于获取code等基本信息。
- snsapi_login:需要用户点击确认授权,获取更多用户信息。
- state: 授权请求后原样带回给第三方。
- 返回说明:
-
- 用户允许授权后,将会重定向到redirect_uri的网址上,并且带上code和state参数。
-
- redirect_uri?code=CODE&state=STATE
- 若用户禁止授权,则重定向后不会带上code参数,仅会带上state参数
-
- redirect_uri?state=STATE
- js 代码:
-
var url = "http://wangy0228.imwork.net/weixin/order.shtml?parkId=" + $("#parkId").val();
var appid = $("#wxappid").val();
var weixinUrl = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + appid + "&redirect_uri=" + encodeURI(url) + "&response_type=code&scope=snsapi_base&state=" + plate + "#wechat_redirect";
window.location.href = encodeURI(weixinUrl);
var appid = $("#wxappid").val();
var weixinUrl = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + appid + "&redirect_uri=" + encodeURI(url) + "&response_type=code&scope=snsapi_base&state=" + plate + "#wechat_redirect";
window.location.href = encodeURI(weixinUrl);
-
- 用code换取openid:
-
- 请求的微信链接:
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
-
-
- 所需参数(4个):
-
- appid:应用唯一标识,在微信开放平台提交应用审核通过后获得
- secret: 应用**AppSecret,在微信开放平台提交应用审核通过后获得
- code: 填写第一步获取的code参数
- grant_type: 填写第一步获取的code参数
- 返回说明:
-
- access_token: 接口调用凭证
- expires_in: access_token接口调用凭证超时时间,单位(秒)
- refresh_token: 用户刷新access_token
- openid: 授权用户唯一标识
- scope: 用户授权的作用域,使用逗号(,)分隔
- unionid: 当且仅当该网站应用已获得该用户的userinfo授权时,才会出现该字段。
-
正确返回样例:
{
"access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE",
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
错误返回样例:
{
"errcode":40029,
"errmsg":"invalid code"
}
-
-
- 代码:
-
String openIdUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appid + "&secret=" + secret + "&code=" + code + "&grant_type=authorization_code";
HttpPost post = new HttpPost(openIdUrl); // 设置响应头信息
post.addHeader("Connection", "keep-alive");
post.addHeader("Accept", "*/*");
post.addHeader("Content-Type", "application/json");
post.addHeader("Host", "api.mch.weixin.qq.com");
post.addHeader("X-Requested-With", "XMLHttpRequest");
post.addHeader("Cache-Control", "max-age=0");
post.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
HttpResponse response = httpclient.execute(post);
String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
Map<String, Object> resultMap = (Map<String, Object>) JSONObject.parse(jsonStr);
String openid = resultMap.get("openid").toString();
2、统一下单支付:
-
- 官方文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
- 网站配置:(路径为支付路径的上一级目录)
-
-
- 调用的微信统一下单支付接口:
https://api.mch.weixin.qq.com/pay/unifiedorder
-
- 用 SortedMap 拼参数(11个)换取 prepay_id:
-
- appid: 微信支付分配的公众账号ID
- mch_id: 微信支付分配的商户号
- nonce_str: 随机字符串,长度要求在32位以内
UUID.randomUUID().toString().replace("-", "");
-
-
- body: 商品简单描述
- out_trade_no: 订单号
- total_fee: 总金额,单位为分
- spbill_create_ip: APP和网页支付提交用户端ip
-
InetAddress ip = InetAddress.getLocalHost().getHostAddress();
-
-
- notify_url: 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
- trade_type: 取值如下:JSAPI,NATIVE,APP等(公众号支付此值为JSAPI)
- openid: trade_type=JSAPI时(即公众号支付),此参数必传,此参数为微信用户在商户对应appid下的唯一标识。
- sign: 通过签名算法计算得出的签名值
-
- 签名算法需要用到商户的Appsecret:( 微信商户平台(pay.weixin.qq.com)-->账户中心-->账户设置-->API安全-->**设置)
-
/**
* 创建md5摘要,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
* packageParams 为除 sign 外所有参数
*/
public String createSign(SortedMap<String, String> packageParams, String appSecret) {
StringBuffer sb = new StringBuffer();
SortedMap<String, String> treeMap = new TreeMap<>();
for (Map.Entry<String, String> entry : treeMap.entrySet()){
String k = entry.getKey();
String v = entry.getValue();
if (null != v && !"".equals(v) && !"sign".equals(k)
&& !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + appSecret);
return MD5Util.MD5Encode(sb.toString(), this.charset).toUpperCase();
}
-
- 将以上参数组参,拼接成 xml 格式字符串,调用统一支付接口换取 prepay_id。
-
- 当 return_code 和 result_code 都为 SUCCESS 时返回
-
- trade_type:支付类型(此处返回为JSAPI)
- prepay_id:微信预支付会话标记
- code_url:无
- 其他返回参数说明见微信官方文档,此处用不到所有就不做过多分析。
3、微信内H5调起支付:
-
-
- 用微信浏览器内置对象 WeixinJSBridge 调起支付(WeixinJSBridge 在其他浏览器无法识别):
- 参数(6个):
-
- appId:商户id
- timeStamp:时间戳
- nonceStr:随机字符串
- package:订单扩展字段(此处为前面获取的prepay_id,格式:prepay_id=***)
- signType:签名方式(MD5)
- paySign:签名
- 返回结果:
-
- get_brand_wcpay_request:ok:支付成功
- get_brand_wcpay_request: cancel:用户取消支付
- get_brand_wcpay_request:fail:支付失败
- 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(data.data);
}
if (document.addEventListener) {
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
} else if (document.attachEvent) {
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
} else {
onBridgeReady(data.data);
}
function onBridgeReady(data) {
var total = $("#total").val();
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId": data.appId, //公众号名称,由商户传入
"timeStamp": data.timeStamp, //时间戳,自1970年以来的秒数
"nonceStr": data.nonceStr, //随机串
"package": data.package,
"signType": data.signType, //微信签名方式:
"paySign": data.paySign //微信签名
}, function (res) {
if (res.err_msg == "get_brand_wcpay_request:ok") {
window.location.href="${ctx}/weixin/success.shtml?total=" + total;
} else if (res.err_msg == "get_brand_wcpay_request:cancel") {
alert("用户取消支付!");
} else {
alert("支付失败!");
}
});
var total = $("#total").val();
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId": data.appId, //公众号名称,由商户传入
"timeStamp": data.timeStamp, //时间戳,自1970年以来的秒数
"nonceStr": data.nonceStr, //随机串
"package": data.package,
"signType": data.signType, //微信签名方式:
"paySign": data.paySign //微信签名
}, function (res) {
if (res.err_msg == "get_brand_wcpay_request:ok") {
window.location.href="${ctx}/weixin/success.shtml?total=" + total;
} else if (res.err_msg == "get_brand_wcpay_request:cancel") {
alert("用户取消支付!");
} else {
alert("支付失败!");
}
});
};