简介:这几天公司有个活动需要用到微信公众号支付,最后成功了完成支付。但第一次使用微信的支付中间遇到一些问题方便以后参考,下面总结整个调用的过程。
- 开发环境:java
- 开发系统:win10
- 开发工具:idea2018
- 2018-11-26
在开发之前我们要准备以下资料:
- 开发者ID(AppID) 这个资料在微信公众号开发配置里查看
- (AppSecret) 这个资料在微信公众号开发配置里查看
- 接口权限网页服务 网页白名单(这个地方要备案域名)
支付界面设置这里要开通 JSAPI支付 -https://pay.weixin.qq.com
产品中心
-配置好授权的支付页面(此处主要是支付的回调和页面授权)
下面是操作环节和代码code核心部分
- 统一下单
接口地址 URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder
- 下面是请求的参数
公众账号ID appid 是 String(32) wxd678efh567hg6787 微信支付分配的公众账号ID(企业号corpid即为此appId)
官方地址 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
字段名 | 变量名 | 必填 | 类型 | 描述 | |
---|---|---|---|---|---|
公众账号ID | appid | 是 | String(21) | 这里就是你公众号appid | |
商户号 | mch_id | 是 | String(32) | 这个是支付商户号 | |
随机字符串 | nonce_str | 是 | String(32) | 这可以用自带的例子里面生成 | |
签名 | sign | 是 | String(32) | 详见签名生成算法 | |
商品描述 | body | 是 | String(128) | 这是简单的描述支持中文 | |
商户订单号 | out_trade_no | 是 | String(32) | 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-* 且在同一个商户号下唯一 | |
标价金额 | total_fee | 是 | Int | 订单总金额,单位为分 | |
终端IP | spbill_create_ip | 是 | String(16) | 提交的IP123.12.12.123 | |
通知地址 | notify_url | 是 | String(256) | http://www.weixin.qq.com/wxpay/pay.php这个后面通知用的 | |
交易类型 | trade_type | 是 | String(16) | JSAPI JSAPI支付 NATIVE Native支付 APP APP支付https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_2 |
以上参数是必填项目
从上面的格式我们看到很多东西都是自己定义的,唯一的一个东西就是 签名
设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
也就是说 上面你要发送的这些信息都要拼接成一个字符串
比如你要发送这些
mch_id: 10000100
device_info: 1000
body: test
nonce_str: ibuaiVcKdpRxkhJA
最后拼接后的格式是
string A="appid=wxd930ea5d5a258f4f&body=test&device_info=1000&mch_id=10000100&nonce_str=ibuaiVcKdpRxkhJA";
按照参数名ASCII码从小到大排序(字典序) 使用下面的函数拼接比较方便
public static String formatUrlMap(Map<String, String> paramsMap, boolean urlEncode, boolean keyToLower) {
String buff = "";
Map<String, String> tmpMap = paramsMap;
try {
List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>(tmpMap.entrySet());
//对所有传入参数按照字段名的ASCII码从小到大排序(字典序)
Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>() {
public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) {
return (o1.getKey()).toString().compareTo(o2.getKey());
}
});
//构造URL 键值对的格式
StringBuffer buf = new StringBuffer();
for (Map.Entry<String, String> item : infoIds) {
if (StringUtils.isNotBlank(item.getKey())) {
String key = item.getKey();
String value = item.getValue();
if (urlEncode) {
value = URLEncoder.encode(value, "utf-8");
}
if (keyToLower) {
buf.append(key.toLowerCase() + "=" + value);
} else {
buf.append(key + "=" + value);
}
buf.append("&");
}
}
buff = buf.toString();
if (StringUtils.isNotEmpty(buff)) {
buff = buff.substring(0, buff.length() - 1);
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return buff;
}
第二步 key 在商户注册里面设置 https://pay.weixin.qq.com
stringSignTemp=stringA+"&key=192006250b4c09247ec02edce69f6a2d" //注:key为商户平台设置的密钥key
第三步:拼接API密钥: 有二个方式 我用的是md5
string SignTemp=A+"&key=192006250b4c09247ec02edce69f6a2d" //注:key为商户平台设置的密钥key
sign=MD5(SignTemp).toUpperCase()="9A0A8659F005D6984697E2CA0A9CF3B7" //注:MD5签名方式
sign=hash_hmac("sha256",SignTemp,key).toUpperCase()="6A9AE1657590FD6257D693A078E1C3E4BB6BA4DC30B23E0EE2496E54170DACD6" //注:HMAC-SHA256签名方式
这里注意都要转成大写
到这里第一大步骤就完成了。可以post数据了,如果post成功会得到类似下面的数据
<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg><appid><![CDATA[wx**********7c26]]></appid><mch_id><![CDATA[14******02]]></mch_id><device_info><![CDATA[WEB]]></device_info><nonce_str><![CDATA[rwo6Cw6mCS3tVeBi]]></nonce_str><sign><![CDATA[CF0D8*******8F56AE8401433]]></sign><result_code><![CDATA[SUCCESS]]></result_code><prepay_id><![CDATA[wx26125**********b3701824863]]></prepay_id><trade_type><![CDATA[JSAPI]]></trade_type></xml>
这里最主要的是 prepay_id=wx26125**********b3701824863 这样的一个类型
然后二次签名
参数需要重新进行签名计算,参与签名的参数为:appId、timeStamp、nonceStr、package、signType,参数区分大小写
下面是我的二次签名代码
Map<String, String> paraMap = new HashMap<String, String>();
paraMap.put("appId",wappId);
paraMap.put("timeStamp", timeStamp);
paraMap.put("nonceStr", nonceStr);
paraMap.put("package",spackage);
paraMap.put("signType",signType);
String SinSring = StringUtil.formatUrlMap(paraMap,false,false);//按照参数名ASCII码从小到大排序(字典序)
String paySign= WXPayUtil.MD5((SinSring+"&key="+key));
paraMap.put("paySign",paySign);
Map<String, Object> mapky = new HashMap<>();
mapky.put("appId", wappId);
mapky.put("timeStamp", timeStamp);
mapky.put("nonceStr", nonceStr);
mapky.put("package", spackage);
mapky.put("signType", signType);
mapky.put("paySign",paySign);
将二次签名的信息发送到客户端
这个是官方给的唤醒 支付界面
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6
注:JS API的返回结果get_brand_wcpay_request:ok仅在用户成功完成支付时返回。由于前端交互复杂,get_brand_wcpay_request:cancel或者get_brand_wcpay_request:fail可以统一处理为用户遇到错误或者主动放弃,不必细化区分。
示例代码如下:
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":"wx2421b1c4370ec43b", //公众号名称,由商户传入
"timeStamp":"1395712654", //时间戳,自1970年以来的秒数
"nonceStr":"e61463f8efa94090b1f366cccfbbb444", //随机串
"package":"prepay_id=u802345jgfjsdfgsdg888",
"signType":"MD5", //微信签名方式:
"paySign":"70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名
},
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();
}
最后支付完成你会收到一个 微信支付的 支付结果通知
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7&index=8
就是上面 统一下单 那个地址会得到一个通知成功的信息,只要返回 下面格式的代码就可以了
<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
</xml>
以上是腾讯的微信支付过程