[5] 微信公众号开发 - 微信支付功能开发(网页JSAPI调用)
1、微信支付的流程
- 点击支付按钮时,执行一个Ajax到后台
- 后台通过前台的部分信息(如商品名额,金额等),将其组装成符合微信要求格式的xml,然后调用微信的“统一下单接口”
- 调用成功后微信会返回一个组装好的xml,我们提取之中的消息(预支付id也在其中)以JSON形式返回给前台
- 前台将该JSON传参给微信内置JS的方法中,调其微信支付
- 支付成功后,微信会将本次支付相关信息返回给我们的服务器
2、微信支付功能开发详解
2.1 设置支付目录和授权域名
2.2 组装xml,调用统一下单接口,获取prepay_id
2.2.1 组装xml
参数 | 说明 | 备注 |
appId | 开发者应用ID,在 “开发 - 基本配置” 查看 | |
mch_id | 微信支付的商户号,在 “微信支付 - 商户信息” 查看 | |
device_info | 终端设备号(门店号或收银设备ID) | PC网页或公众号内支付,则传 “WEB” |
body | 商品或支付的简单描述 | |
trade_type | 可取值JSAPI,NATIVE,APP等,我们这里使用的是JSAPI | JSAPI 公众号支付;NATIVE 原生扫码支付;APP app支付 |
nonce_str | 随机字符串 | 参考算法:《微信支付官方文档 - 安全规范》 |
notify_url | 通知地址,微信支付成功后,微信服务器会发送信息到该url | |
out_trade_no | 商户系统内部订单号,由商户自定义,订单号要保持唯一性 | |
total_fee | 订单总金额,单位:分 | |
openid | 用户标识,用户在该公众号下的唯一身份标识 | |
sign | 签名 | 参考算法:《微信支付官方文档 - 安全规范》 |
key | API密钥,在 “微信商户平台 - 账户中心 - API安全 - API密钥” |
- nonce_str 随机字符串,用于保证签名不可预测
- 算法:
- 官方建议调用随机数函数生成,然后转为字符串
- sign 签名
- 算法:
- 所有发送或接收的数据按参数名ASCII码从小到大排序,使用键值对形式拼接为字符串(如 key1=value1&key2=value2…)
- ASCII码的字典排序,可以利用TreeMap帮我们自动实现
- 将拼接好的字符串最后,再拼接上API密钥,即key,得到新的字符串
- 将新的字符串进行MD5加密,并将加密后字符串全部转换为大写
String appId = WeChatAPI.getAppID();
String body = "JSAPI支付测试";
String merchantId = WeChatAPI.getMerchantID();
String tradeNo = String.valueOf(new Date().getTime());
String nonceStr1 = SignUtil.createNonceStr();
String notifyUrl = "http://k169710n05.51mypc.cn/pay/do/afterPaySuccess.q";
String openId = "okAkc0muYuSJUtvMf25UQHnqYvM4";
String totalFee = "1";
TreeMap<String, String> map = new TreeMap<String, String>();
map.put("appid", appId);
map.put("mch_id", merchantId);
map.put("device_info", "WEB");
map.put("body", body);
map.put("trade_type", "JSAPI");
map.put("nonce_str", nonceStr1);
map.put("notify_url", notifyUrl);
map.put("out_trade_no", tradeNo);
map.put("total_fee", totalFee);
map.put("openid", openId);
String sign = SignUtil.createSign(map);
String xml = "<xml>" +
"<appid>" + appId + "</appid>" +
"<body>" + body +"</body>" +
"<device_info>WEB</device_info>" +
"<mch_id>" + merchantId + "</mch_id>" +
"<nonce_str>" + nonceStr1 + "</nonce_str>" +
"<notify_url>" + notifyUrl +"</notify_url>" +
"<openid>" + openId + "</openid>" +
"<out_trade_no>" + tradeNo + "</out_trade_no>" +
"<total_fee>" + totalFee + "</total_fee>" +
"<trade_type>JSAPI</trade_type>" +
"<sign>" + sign + "</sign>" +
"</xml>";
String appId = WeChatAPI.getAppID();
String body = "JSAPI支付测试";
String merchantId = WeChatAPI.getMerchantID();
String tradeNo = String.valueOf(new Date().getTime());
String nonceStr1 = SignUtil.createNonceStr();
String notifyUrl = "http://k169710n05.51mypc.cn/pay/do/afterPaySuccess.q";
String openId = "okAkc0muYuSJUtvMf25UQHnqYvM4";
String totalFee = "1";
TreeMap<String, String> map = new TreeMap<String, String>();
map.put("appid", appId);
map.put("mch_id", merchantId);
map.put("device_info", "WEB");
map.put("body", body);
map.put("trade_type", "JSAPI");
map.put("nonce_str", nonceStr1);
map.put("notify_url", notifyUrl);
map.put("out_trade_no", tradeNo);
map.put("total_fee", totalFee);
map.put("openid", openId);
String sign = SignUtil.createSign(map);
String xml = "<xml>" +
"<appid>" + appId + "</appid>" +
"<body>" + body +"</body>" +
"<device_info>WEB</device_info>" +
"<mch_id>" + merchantId + "</mch_id>" +
"<nonce_str>" + nonceStr1 + "</nonce_str>" +
"<notify_url>" + notifyUrl +"</notify_url>" +
"<openid>" + openId + "</openid>" +
"<out_trade_no>" + tradeNo + "</out_trade_no>" +
"<total_fee>" + totalFee + "</total_fee>" +
"<trade_type>JSAPI</trade_type>" +
"<sign>" + sign + "</sign>" +
"</xml>";
body参数如果直接填写中文,在调用接口时会出现“签名错误”,要以ISO8859-1编码所以 String body = new String("body内容字符串".getBytes("ISO8859-1"));但即便如此,在支付完成后微信推送的“微信支付凭证”中,商品详情中的中文也依然显示的乱码
- body参数内容如果包含中文,那么在调用接口时会出现“签名错误”
- 在网上找了很多方法,有了如上删除线部分的方法,但是仍然是有问题的,因为支付成功后的凭证里中文是乱码
- 后来终于在网上各种倒腾,找到了原因,确实是编码问题,但问题不在body是否使用ISO8859-1,而在MD5的加密算法中是否使用UTF-8
- 所以 md.update(sourceStr.getBytes("UTF-8"));
/**
* 生成随机数
* <p>算法参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3</p>
* @return 随机数字符串
*/
public static String createNonceStr() {
SecureRandom random = new SecureRandom();
int randomNum = random.nextInt();
return Integer.toString(randomNum);
}
/**
* 生成签名,用于在微信支付前,获取预支付时候需要使用的参数sign
* <p>算法参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3</p>
* @param params 需要发送的所有数据设置为的Map
* @return 签名sign
*/
public static String createSign(TreeMap<String, String> params) {
String signValue = "";
String stringSignTemp = "";
String stringA = "";
//获得stringA
Set<String> keys = params.keySet();
for (String key : keys) {
stringA += (key + "=" + params.get(key) + "&");
}
stringA = stringA.substring(0, stringA.length() - 1);
//获得stringSignTemp
stringSignTemp = stringA + "&key=" + WeChatAPI.getMerchantKey();
//获得signValue
signValue = encryptByMD5(stringSignTemp).toUpperCase();
log.debug("预支付签名:" + signValue);
return signValue;
}
/**
* MD5加密
*
* @param sourceStr
* @return
*/
public static String encryptByMD5(String sourceStr) {
String result = "";
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(sourceStr.getBytes("UTF-8"));
byte b[] = md.digest();
int i;
StringBuffer buf = new StringBuffer("");
for (int offset = 0; offset < b.length; offset++) {
i = b[offset];
if (i < 0)
i += 256;
if (i < 16)
buf.append("0");
buf.append(Integer.toHexString(i));
}
result = buf.toString();
} catch (NoSuchAlgorithmException e) {
System.out.println(e);
}
return result;
}
/**
* 生成随机数
* <p>算法参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3</p>
* @return 随机数字符串
*/
public static String createNonceStr() {
SecureRandom random = new SecureRandom();
int randomNum = random.nextInt();
return Integer.toString(randomNum);
}
/**
* 生成签名,用于在微信支付前,获取预支付时候需要使用的参数sign
* <p>算法参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3</p>
* @param params 需要发送的所有数据设置为的Map
* @return 签名sign
*/
public static String createSign(TreeMap<String, String> params) {
String signValue = "";
String stringSignTemp = "";
String stringA = "";
//获得stringA
Set<String> keys = params.keySet();
for (String key : keys) {
stringA += (key + "=" + params.get(key) + "&");
}
stringA = stringA.substring(0, stringA.length() - 1);
//获得stringSignTemp
stringSignTemp = stringA + "&key=" + WeChatAPI.getMerchantKey();
//获得signValue
signValue = encryptByMD5(stringSignTemp).toUpperCase();
log.debug("预支付签名:" + signValue);
return signValue;
}
/**
* MD5加密
*
* @param sourceStr
* @return
*/
public static String encryptByMD5(String sourceStr) {
String result = "";
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(sourceStr.getBytes("UTF-8"));
byte b[] = md.digest();
int i;
StringBuffer buf = new StringBuffer("");
for (int offset = 0; offset < b.length; offset++) {
i = b[offset];
if (i < 0)
i += 256;
if (i < 16)
buf.append("0");
buf.append(Integer.toHexString(i));
}
result = buf.toString();
} catch (NoSuchAlgorithmException e) {
System.out.println(e);
}
return result;
}
2.2.2 调用统一下单接口,获取prepay_id
<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
<appid><![CDATA[wx2421b1c4370ec43b]]></appid>
<mch_id><![CDATA[10000100]]></mch_id>
<nonce_str><![CDATA[IITRi8Iabbblz1Jc]]></nonce_str>
<openid><![CDATA[oUpF8uMuAJO_M2pxb1Q9zNjWeS6o]]></openid>
<sign><![CDATA[7921E432F65EB8ED0CE9755F0E86D72F]]></sign>
<result_code><![CDATA[SUCCESS]]></result_code>
<prepay_id><![CDATA[wx201411101639507cbf6ffd8b07629950874]]></prepay_id>
<trade_type><![CDATA[JSAPI]]></trade_type>
</xml>
<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
<appid><![CDATA[wx2421b1c4370ec43b]]></appid>
<mch_id><![CDATA[10000100]]></mch_id>
<nonce_str><![CDATA[IITRi8Iabbblz1Jc]]></nonce_str>
<openid><![CDATA[oUpF8uMuAJO_M2pxb1Q9zNjWeS6o]]></openid>
<sign><![CDATA[7921E432F65EB8ED0CE9755F0E86D72F]]></sign>
<result_code><![CDATA[SUCCESS]]></result_code>
<prepay_id><![CDATA[wx201411101639507cbf6ffd8b07629950874]]></prepay_id>
<trade_type><![CDATA[JSAPI]]></trade_type>
</xml>
String url = WeChatAPI.getUrl_prePay();
String result = NetUtil.sendRequest(url, "POST", xml);
String reg = "<prepay_id><!\\[CDATA\\[(\\w+)\\]\\]></prepay_id>";
Pattern pattern = Pattern.compile(reg);
Matcher matcher = pattern.matcher(result);
String prepayId = "";
while (matcher.find()) {
prepayId = matcher.group(1);
log.debug("预支付ID:" + prepayId);
}
String url = WeChatAPI.getUrl_prePay();
String result = NetUtil.sendRequest(url, "POST", xml);
String reg = "<prepay_id><!\\[CDATA\\[(\\w+)\\]\\]></prepay_id>";
Pattern pattern = Pattern.compile(reg);
Matcher matcher = pattern.matcher(result);
String prepayId = "";
while (matcher.find()) {
prepayId = matcher.group(1);
log.debug("预支付ID:" + prepayId);
}
2.3 回传参数,调起微信支付JS
2.3.1 回传参数
这时候,已经有了预支付ID,但是后台的处理还没有结束,我们还没有把该有的信息返回给前台。那么前台需要哪些东西呢?《微信支付官方文档 - 微信内H5调起支付》有详细的解释,这里再贴一下:
参数 | 说明 | 备注 |
appId | 开发者应用ID,在 “开发 - 基本配置” 查看 | |
timeStamp | 时间戳,标准北京时间,秒级(10位数字) | |
nonceStr | 随机字符串 | 参考算法:《微信支付官方文档 - 安全规范》 |
package | 订单详情扩展字符串,其实就是预支付ID | 示例: prepay_id=*** |
signType | 签名方式,暂支持MD5 | |
paySign | 签名 | 参考算法:《微信支付官方文档 - 安全规范》 |
- package的值是 “prepay_id=***”,而不是 "***" 的方式(***表示之前获取的prepay_id)
- timeStamp注意使用标准北京时间,可以使用Calendar设置Locale为CHINA,因为是秒级所以记得除以1000
- paySign签名要重新生成,算法还是之前的,但是参数需要除自己以外的 appId、timeStamp、nonceStr、package、signType
- 之前xml中参数appid是小写,这里的appId是大写的I
Date beijingDate = Calendar.getInstance(Locale.CHINA).getTime();
String nonceStr2 = SignUtil.createNonceStr();
JSONObject json = new JSONObject();
json.put("appId", appId);
json.put("timeStamp", beijingDate.getTime() / 1000);
json.put("nonceStr", nonceStr2);
json.put("package", "prepay_id=" + prepayId);
json.put("signType", "MD5");
TreeMap<String, String> map2 = new TreeMap<String, String>();
map2.put("appId", appId);
map2.put("timeStamp", String.valueOf(beijingDate.getTime() / 1000));
map2.put("nonceStr", nonceStr2);
map2.put("package", "prepay_id=" + prepayId);
map2.put("signType", "MD5");
String paySign = SignUtil.createSign(map2);
json.put("paySign", paySign);
String re = json.toJSONString();
AjaxSupport.sendSuccessText(null, re);
Date beijingDate = Calendar.getInstance(Locale.CHINA).getTime();
String nonceStr2 = SignUtil.createNonceStr();
JSONObject json = new JSONObject();
json.put("appId", appId);
json.put("timeStamp", beijingDate.getTime() / 1000);
json.put("nonceStr", nonceStr2);
json.put("package", "prepay_id=" + prepayId);
json.put("signType", "MD5");
TreeMap<String, String> map2 = new TreeMap<String, String>();
map2.put("appId", appId);
map2.put("timeStamp", String.valueOf(beijingDate.getTime() / 1000));
map2.put("nonceStr", nonceStr2);
map2.put("package", "prepay_id=" + prepayId);
map2.put("signType", "MD5");
String paySign = SignUtil.createSign(map2);
json.put("paySign", paySign);
String re = json.toJSONString();
AjaxSupport.sendSuccessText(null, re);
2.3.2 使用微信内置的JS调起微信支付
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":"wx2421bk1c4370c43b", //公众号名称,由商户传入
"timeStamp":"1395712654", //时间戳,自1970年以来的秒数
"nonceStr":"e61463f8efa94090b1f366cccfbbb444", //随机串
"package":"prepay_id=u802345jfgjsdfgsdg888",
"signType":"MD5", //微信签名方式:
"paySign":"70EA570631E4B79628FBCS90534C63FF7FADD89" //微信签名
},
function(res){
if(res.err_msg == "get_brand_wcpay_request:ok" ) {}
// 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
}
);
}
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();
}
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":"wx2421bk1c4370c43b", //公众号名称,由商户传入
"timeStamp":"1395712654", //时间戳,自1970年以来的秒数
"nonceStr":"e61463f8efa94090b1f366cccfbbb444", //随机串
"package":"prepay_id=u802345jfgjsdfgsdg888",
"signType":"MD5", //微信签名方式:
"paySign":"70EA570631E4B79628FBCS90534C63FF7FADD89" //微信签名
},
function(res){
if(res.err_msg == "get_brand_wcpay_request:ok" ) {}
// 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
}
);
}
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();
}
$().invoke("/pay/do/pay.q", null, function (re) {
var result = JSON.parse(re);
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', result, function(res){
alert(JSON.stringify(res));
if(res.err_msg == "get_brand_wcpay_request:ok" ) {
//doit 这里处理支付成功后的逻辑,通常为页面跳转
}
}
);
}
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();
}
});
$().invoke("/pay/do/pay.q", null, function (re) {
var result = JSON.parse(re);
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', result, function(res){
alert(JSON.stringify(res));
if(res.err_msg == "get_brand_wcpay_request:ok" ) {
//doit 这里处理支付成功后的逻辑,通常为页面跳转
}
}
);
}
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();
}
});
- 如果你在 var result = JSON.parse(re); 之前再添加一个用于debug的输出语句 alert(re);
- 你可以看到传过来的各项参数,其中timeStamp的值是没有双引号的,这会导致在iOS中支付出现错误,提示缺少timeStamp参数
$().invoke("/pay/do/pay.q", null, function (re) {
var result = JSON.parse(re);
result['timeStamp'] = result['timeStamp'] + "";
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', result, function(res){
alert(JSON.stringify(res));
if(res.err_msg == "get_brand_wcpay_request:ok" ) {
//doit 这里处理支付成功后的逻辑,通常为页面跳转
}
}
);
}
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();
}
});
$().invoke("/pay/do/pay.q", null, function (re) {
var result = JSON.parse(re);
result['timeStamp'] = result['timeStamp'] + "";
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', result, function(res){
alert(JSON.stringify(res));
if(res.err_msg == "get_brand_wcpay_request:ok" ) {
//doit 这里处理支付成功后的逻辑,通常为页面跳转
}
}
);
}
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();
}
});
2.4 校验信息的正确性
...
String notifyUrl = "http://k169710n05.51mypc.cn/pay/do/afterPaySuccess.q";
...
String xml = "<xml>" +
"<appid>" + appId + "</appid>" +
"<body>" + body +"</body>" +
"<device_info>WEB</device_info>" +
"<mch_id>" + merchantId + "</mch_id>" +
"<nonce_str>" + nonceStr1 + "</nonce_str>" +
"<notify_url>" + notifyUrl +"</notify_url>" +
"<openid>" + openId + "</openid>" +
"<out_trade_no>" + tradeNo + "</out_trade_no>" +
"<total_fee>" + totalFee + "</total_fee>" +
"<trade_type>JSAPI</trade_type>" +
"<sign>" + sign + "</sign>" +
"</xml>";
...
String notifyUrl = "http://k169710n05.51mypc.cn/pay/do/afterPaySuccess.q";
...
String xml = "<xml>" +
"<appid>" + appId + "</appid>" +
"<body>" + body +"</body>" +
"<device_info>WEB</device_info>" +
"<mch_id>" + merchantId + "</mch_id>" +
"<nonce_str>" + nonceStr1 + "</nonce_str>" +
"<notify_url>" + notifyUrl +"</notify_url>" +
"<openid>" + openId + "</openid>" +
"<out_trade_no>" + tradeNo + "</out_trade_no>" +
"<total_fee>" + totalFee + "</total_fee>" +
"<trade_type>JSAPI</trade_type>" +
"<sign>" + sign + "</sign>" +
"</xml>";
- 解析微信发来的信息,通过重新签名的方式验证信息的正确性,确认信息是否是微信所发
- return_code和result_code都是SUCCESS的话,处理商户自己的业务逻辑
- 应答微信,告诉它说我们收到信息了,不用再发了(如果不进行应答,则微信服务器会通过一定的策略定期重新发起通知)
<xml>
<appid><![CDATA[wx2421b1c4370ec43b]]></appid>
<attach><![CDATA[支付测试]]></attach>
<bank_type><![CDATA[CFT]]></bank_type>
<fee_type><![CDATA[CNY]]></fee_type>
<is_subscribe><![CDATA[Y]]></is_subscribe>
<mch_id><![CDATA[10000100]]></mch_id>
<nonce_str><![CDATA[5d2b6c2a8db53831f7eda20af46e531c]]></nonce_str>
<openid><![CDATA[oUpF8uMEb4qRXf22hE3X68TekukE]]></openid>
<out_trade_no><![CDATA[1409811653]]></out_trade_no>
<result_code><![CDATA[SUCCESS]]></result_code>
<return_code><![CDATA[SUCCESS]]></return_code>
<sign><![CDATA[B552ED6B279343CB493C5DD0D78AB241]]></sign>
<sub_mch_id><![CDATA[10000100]]></sub_mch_id>
<time_end><![CDATA[20140903131540]]></time_end>
<total_fee>1</total_fee>
<trade_type><![CDATA[JSAPI]]></trade_type>
<transaction_id><![CDATA[1004400740201409030005092168]]></transaction_id>
</xml>
<xml>
<appid><![CDATA[wx2421b1c4370ec43b]]></appid>
<attach><![CDATA[支付测试]]></attach>
<bank_type><![CDATA[CFT]]></bank_type>
<fee_type><![CDATA[CNY]]></fee_type>
<is_subscribe><![CDATA[Y]]></is_subscribe>
<mch_id><![CDATA[10000100]]></mch_id>
<nonce_str><![CDATA[5d2b6c2a8db53831f7eda20af46e531c]]></nonce_str>
<openid><![CDATA[oUpF8uMEb4qRXf22hE3X68TekukE]]></openid>
<out_trade_no><![CDATA[1409811653]]></out_trade_no>
<result_code><![CDATA[SUCCESS]]></result_code>
<return_code><![CDATA[SUCCESS]]></return_code>
<sign><![CDATA[B552ED6B279343CB493C5DD0D78AB241]]></sign>
<sub_mch_id><![CDATA[10000100]]></sub_mch_id>
<time_end><![CDATA[20140903131540]]></time_end>
<total_fee>1</total_fee>
<trade_type><![CDATA[JSAPI]]></trade_type>
<transaction_id><![CDATA[1004400740201409030005092168]]></transaction_id>
</xml>
<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
</xml>
<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
</xml>
/**
* 支付成功后的处理
* <p>微信支付成功后,对微信返回的信息进行校验</p>
* @return
*/
public String afterPaySuccess() {
HttpServletRequest request = ServletActionContext.getRequest();
HttpServletResponse response = ServletActionContext.getResponse();
TreeMap<String, String> map = new TreeMap<String, String>();
try {
//解析xml,存入map
InputStream inputStream = request.getInputStream();
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(inputStream);
Element rootElement = document.getRootElement();
List<Element> elements = rootElement.elements();
String reg = "<!\\[CDATA\\[(.+)\\]\\]>";
Pattern pattern = Pattern.compile(reg);
for (Element element : elements) {
String key = element.getName();
String value = element.getText();
Matcher matcher = pattern.matcher(value);
while (matcher.find()) {
value = matcher.group(1);
}
map.put(key, value);
}
//如果微信结果通知为失败
if ("FAIL".equals(map.get("return_code"))) {
log.debug(map.get("return_msg"));
return NONE;
}
//doit 处理商户业务逻辑
//签名对比,应答微信服务器
String signFromWechat = map.get("sign");
map.remove("sign");
String sign = SignUtil.createSign(map);
if (sign.equals(signFromWechat)) {
String responseXml = "<xml>" +
"<return_code><![CDATA[SUCCESS]]></return_code>" +
"<return_msg><![CDATA[OK]]></return_msg>" +
"</xml>";
response.getWriter().write(responseXml);
}
} catch (IOException e) {
e.printStackTrace();
} catch (DocumentException e) {
e.printStackTrace();
}
return NONE;
}
/**
* 支付成功后的处理
* <p>微信支付成功后,对微信返回的信息进行校验</p>
* @return
*/
public String afterPaySuccess() {
HttpServletRequest request = ServletActionContext.getRequest();
HttpServletResponse response = ServletActionContext.getResponse();
TreeMap<String, String> map = new TreeMap<String, String>();
try {
//解析xml,存入map
InputStream inputStream = request.getInputStream();
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(inputStream);
Element rootElement = document.getRootElement();
List<Element> elements = rootElement.elements();
String reg = "<!\\[CDATA\\[(.+)\\]\\]>";
Pattern pattern = Pattern.compile(reg);
for (Element element : elements) {
String key = element.getName();
String value = element.getText();
Matcher matcher = pattern.matcher(value);
while (matcher.find()) {
value = matcher.group(1);
}
map.put(key, value);
}
//如果微信结果通知为失败
if ("FAIL".equals(map.get("return_code"))) {
log.debug(map.get("return_msg"));
return NONE;
}
//doit 处理商户业务逻辑
//签名对比,应答微信服务器
String signFromWechat = map.get("sign");
map.remove("sign");
String sign = SignUtil.createSign(map);
if (sign.equals(signFromWechat)) {
String responseXml = "<xml>" +
"<return_code><![CDATA[SUCCESS]]></return_code>" +
"<return_msg><![CDATA[OK]]></return_msg>" +
"</xml>";
response.getWriter().write(responseXml);
}
} catch (IOException e) {
e.printStackTrace();
} catch (DocumentException e) {
e.printStackTrace();
}
return NONE;
}
3、参考链接
附件列表
[5] 微信公众号开发 - 微信支付功能开发(网页JSAPI调用)的更多相关文章
- JAVA版开源微信管家—JeeWx捷微3.2版本发布,支持微信公众号,微信企业号,支付窗、小程序
JeeWx捷微3.2微信企业号升级版本发布^_^ JeeWx捷微V3.2——多触点管理平台(支持微信公众号,微信企业号,支付窗.小程序) JeeWx捷微V3.2.0版本引入了更多新特性,支持微信公 ...
- JeeWx捷微3.1小程序版本发布,支持微信公众号,微信企业号,支付窗——JAVA版开源微信管家
支持小程序,JeeWx捷微3.1小程序版本发布^_^ JeeWx捷微V3.1——多触点小程序版本管理平台(支持微信公众号,微信企业号,支付窗) JeeWx捷微V3.1.0版本紧跟微信小程序更新,在 ...
- JAVA版开源微信管家—JeeWx捷微3.1小程序版本发布,支持微信公众号,微信企业号,支付窗
支持小程序,JeeWx捷微3.1小程序版本发布^_^ JeeWx捷微V3.1--多触点小程序版本管理平台(支持微信公众号,微信企业号,支付窗) JeeWx捷微V3.1.0版本紧跟微信小程序更新,在原有 ...
- JAVA开源微信管家平台——JeeWx捷微V3.3版本发布(支持微信公众号,微信企业号,支付窗)
JeeWx捷微V3.3版本紧跟微信小程序更新,在原有多触点版本基础上,引入了更多的新亮点:支持微信公众号.微信企业号.支付宝服务窗等多触点开发:采用微服务框架实现,可插拔可集成,轻量级开发:对小程序的 ...
- CabloyJS的微信API对接模块:当前支持微信公众号和微信小程序
Cabloy-微信是什么 Cabloy-微信是基于CabloyJS全栈业务开发框架开发的微信接口模块,当前整合了微信公众号和微信小程序的接口,达到开箱即用的使用效果.在Cabloy-微信的基础上,可以 ...
- thinkphp5.0 微信公众号接入支付宝支付
---恢复内容开始--- 真是无力吐槽这个需求了,想骂客户,好端端的非要在微信公众号接入支付宝,都知道微信公众号是拒绝支付宝的,屏蔽了支付宝,所以在微信公众号接入支付宝的话就必须手动复制链接跳出微信内 ...
- 微信公众号授权,支付,退款总结【shoucang】
1.支付前准备 1.1首先两个平台接入账户. 商户平台:https://pay.weixin.qq.com/index.php/core/home/login?return_url=%2F 公众平台: ...
- php 微信公众号接入支付宝支付
真是无力吐槽这个需求了,好端端的非要在微信公众号接入支付宝,都知道微信公众号是拒绝支付宝的,屏蔽了支付宝,所以在微信公众号接入支付宝的话就必须手动复制链接跳出微信内置浏览器,强制性打开web浏览器完成 ...
- 使用flask搭建微信公众号:实现签到功能
终于到了实战阶段.用微信公众号实现一个简单的签到功能. 前情提要: 微信公众号token验证失败 使用flask搭建微信公众号:完成token的验证 使用flask搭建微信公众号:接收与回复消息 程序 ...
- 上篇: php 微信公众号 基于Thinkphp3.2框架开发
说明:本教程是自己自学+自己的理解+扩展(包括学习过程中遇到的一些问题) 参考教程:麦子学院--李忠益--http://www.maiziedu.com/u/70409/ 微盟: http://www ...
随机推荐
- 初次搭建vue环境(最基础的)
一直以来觉得搭建环境是自己的短板,恰巧老大跟我说他刚才面试一个有4年工作经验的人,给那面试的人出了到机试题,给了1小时的时间连环境都没搭好.且不说那人的工作经验是否掺有水分,自己还是有点尴尬的,以前的 ...
- HIVE—索引、分区和分桶的区别
一.索引 简介 Hive支持索引,但是Hive的索引与关系型数据库中的索引并不相同,比如,Hive不支持主键或者外键. Hive索引可以建立在表中的某些列上,以提升一些操作的效率,例如减少MapRed ...
- MySQL57安装教程
MySQL57安装教程... --------------------------- 首先需要下载MySQL57安装包: --------------------------------------- ...
- java 5年规划---
偶然看到别人,觉得写的很好,尤其对刚出来工作的人,不一定是最好,但至少可以给你一个方向,所以就把这篇文章放到自己博客来,时刻提醒自己 第一部分 在搭建SSM的过程中,可能会经常接触到一个叫maven的 ...
- 残差网络resnet学习
Deep Residual Learning for Image Recognition 微软亚洲研究院的何凯明等人 论文地址 https://arxiv.org/pdf/1512.03385v1.p ...
- 使用Java连接Redis
下载redis的驱动包:Jedis.下载地址:https://mvnrepository.com/artifact/redis.clients/jedis/2.9.0 打开IDE,我使用的是Eclip ...
- 查询session内容
Enumeration enumsession = request.getSession().getAttributeNames(); while(enumsession.hasMoreElement ...
- mysql存储过程 基本语法
话不多说 一.MySQL 创建存储过程 "pr_add" 是个简单的 MySQL 存储过程,这个存储过程有两个 int 类型的输入参数 "a"."b& ...
- AVAssetWriter 硬编码bug解决
一.需求 直播助手在录屏过程中,产品要求跟随用户手机屏幕旋转,录屏的视频跟随旋转 二.实施方案 目前触手录,iTools PC端均已经实现该功能,并且该功能只适配iOS9和iOS10系统.猜测实现方案 ...
- Java 随笔记录
1. java对象转json Message msg = generateMessage();ObjectMapper mapper = new ObjectMapper();String json ...