实现功能
:公众号内H5页面调用微信支付实现充值,
1.开发流程
1) 申请公众号 并且认证 (这个是前提 自行百度有很多方法)
2)获取用户授权 获取openid (上一篇文章有介绍怎么获取)
3)调用统一下单接口获取预支付id 微信统一接口API https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
4)H5调起微信支付的内置JS
5)支付完成后,微信回调URL的处理
第1).2)步就不在介绍了....
2.统一接口参数介绍
appid ==应用ID==登陆微信公众号后台-开发-基本配置
mch_id == 微信支付商户号==登陆微信支付后台,即可看到
body==商品描述==商品或支付单简要描述
spbill_create_ip
==
获取发起电脑ip
代码如下
String spbill_create_ip = LLPayUtil.getIpAddr(request).replace("_", ".");
/**
*
* 功能描述:获取真实的IP地址
* @param request
* @return
* @author guoyx
*/
public static String getIpAddr(HttpServletRequest request)
{
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = request.getRemoteAddr();
}
if (!isnull(ip) && ip.contains(","))
{
String[] ips = ip.split(",");
ip = ips[ips.length - 1];
}
//转换IP 格式
if(!isnull(ip)){
ip=ip.replace(".", "_");
}
return ip;
}
trade_type==交易类型==取值如下:JSAPI,NATIVE,APP。我们这里使用的JSAPI。标题已经说了,是微信公众号支付。他们的区别,请参考https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_2
ps:JSAPI--公众号支付、NATIVE--原生扫码支付、APP--app支付,统一下单接口trade_type的传参可参考这里。MICROPAY--刷卡支付,刷卡支付有单独的支付接口,不调用统一下单接口
nonce_str==随机字符串==随机字符串,不长于32位
代码如下
/**
* 获取随机字符串 Nonce Str
*
* @return String 随机字符串
*/
public static String generateNonceStr() {
return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
}
notify_url==通知地址==接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数。
out_trade_no==商户订单号==商户系统内部的订单号,32个字符内、可包含字母
代码如下
/**
* 生成订单号,格式:XX(前缀) + yyyyMMddHHmmss + 10位数
* @param prefix
* @return
*/
public static String generate(String prefix) {
synchronized(locker) {
if (sn == 999999999) {
sn = 0;
} else {
sn ++;
}
String str = String.format("%010d", sn);
return prefix + sdf.format(new Date()) + str;
}
}
total_fee==总金额==订单总金额
代码如下 money金额
String.valueOf((int)(money*100))
openid
==用户标识==trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识
sign
==签名==官方给的签名算法
代码如下
key
==key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置
String sign = PayCommonUtil.createSign("UTF-8", packageParams, key);
/**
* @author
* @date 2016-4-22
* @Description:sign签名
* @param characterEncoding
* 编码格式
* @param parameters
* 请求参数
* @return
*/
public static String createSign(String characterEncoding, SortedMap<Object, Object> packageParams, String API_KEY) {
StringBuffer sb = new StringBuffer();
Set es = packageParams.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + API_KEY);
String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
return sign;
}
封装成XML参数
SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>();
packageParams.put("appid", appid);
packageParams.put("mch_id", mch_id);
packageParams.put("nonce_str",nonce_str );
packageParams.put("body", body);
packageParams.put("out_trade_no", out_trade_no);
packageParams.put("total_fee", String.valueOf((int)(money*100)));// 价格(注意:价格的单位是分)
packageParams.put("spbill_create_ip", spbill_create_ip);
packageParams.put("notify_url", notify_url);
packageParams.put("trade_type", "JSAPI"); //交易类型 JSAPI--公众号支付
packageParams.put("openid", openid); //微信ID
packageParams.put("sign", sign)
准备好以上参数之后,封装成XML
格式如下:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <xml> <appid>wxb1427ebebexxxxxx</appid> <body>XXX费用</body> <device_info>WEB</device_info> <mch_id>132186xxxx</mch_id> <nonce_str>6AED000AF86A084F9CB0264161E29DD3</nonce_str> <notify_url>https://一个域名/api/wechatPay/jsapiPayNotify</notify_url> <openid>oo8WUt0taCqjt552htW1vw-xxxxx</openid> <out_trade_no>1</out_trade_no> <sign>各种排序+key生成的那个sign</sign> <total_fee>1</total_fee> <trade_type>JSAPI</trade_type> </xml>
代码如下
String requestXML = PayCommonUtil.getRequestXml(packageParams);
/**
* @author
* @date 2016-4-22
* @Description:将请求参数转换为xml格式的string
* @param parameters
* 请求参数
* @return
*/
public static String getRequestXml(SortedMap<Object, Object> parameters) {
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
Set es = parameters.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k) || "sign".equalsIgnoreCase(k)) {
sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">");
} else {
sb.append("<" + k + ">" + v + "</" + k + ">");
}
}
sb.append("</xml>");
return sb.toString();
}
调用微信的统一下单地址:https://api.mch.weixin.qq.com/pay/unifiedorder调用代码如下String resXml = HttpUtil.postData(PropertiesHandler.getConfigValue("UFDODER_URL").toString(), requestXML);
见证奇迹的时刻。如果以上参数都神奇的对了,那么会收到微信返回的XML字符串,格式如下
<xml><return_code><![CDATA[SUCCESS]]></return_co de> <return_msg><![CDATA[OK]]></return_msg><appid><![CDATA[wxb1427ebebexxxxxx]]></appid><mch_id><![CDATA[132186xxxx]]></mch_id><device_info><![CDATA[WEB]]></device_info><nonce_str><![CDATA[Hh4LFHUUvtDYtNdp]]></nonce_str><sign><![CDATA[079F8A915FD3044F4A17D75F4945E955]]></sign><result_code><![CDATA[SUCCESS]]></result_co de> <prepay_id><![CDATA[wx20160617155030d9e6a0e48b0533061255]]></prepay_id><trade_type><![CDATA[JSAPI]]></trade_type></xml>
我们需要的,就是这货
prepay_id
拿到prepay_id后 进行下一步 生成paySign 签名 代码如下
SortedMap<Object, Object> packagePara = new TreeMap<Object, Object>();
//自1970年1月1日 0点0分0秒以来的秒数。注意:部分系统取到的值为毫秒级,需要转换成秒(10位数字)。
long timeSt = (LLPayUtil.getPhoneCurrentDateTimeStr());
String timeStr = Long.toString(timeSt);
packagePara.put("appId", appid); //公众号名称,由商户传入
packagePara.put("timeStamp",timeStr); //时间戳,自1970年以来的秒数
packagePara.put("nonceStr", nonce_str); // 随机串
packagePara.put("package", "prepay_id="+urlCode); //prepay_id
packagePara.put("signType", "MD5"); //微信签名方式
String paySign = PayCommonUtil.createSign("UTF-8", packagePara, key); // 微信签名
/**
* @author
* @date 2016-4-22
* @Description:paySign 签名
* @param characterEncoding
* 编码格式
* @param parameters
* 请求参数
* @return
*/
public static String createSign(String characterEncoding, SortedMap<Object, Object> packageParams, String API_KEY) {
StringBuffer sb = new StringBuffer();
Set es = packageParams.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + API_KEY);
String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
return sign;
}
拼接好参数之后 传回给前台
if(null != paySign){
model.put("res", true);
model.put("appId", appid);
model.put("timeStamp", timeStr);
model.put("nonceStr", nonce_str);
model.put("packag","prepay_id="+urlCode);
model.put("signType", "MD5");
model.put("paySign", paySign);
return model;
}
注意:记得添加授权目录
前台JSP代码
//微信充值
function wechat(){
var phone = '${userInfo.phone}';
var money = $("#money").val();
if(money == "" || money == 0){
layer.msg("充值的金额不能为空");
return false;
}
var moneyCheck = /^[0-9]+(.[0-9]{1,2})?$/;
if(!moneyCheck.test(money)){
layer.msg("请保留两位小数!");
return;
}
$.ajax({
url: '<%=WEBPATH%>/fund/phoenWechatpay',
type: "post",
async: false,
da ta: {"money" : money},
success: function(da ta){
if(da ta.res){
callPay(da ta.appId,da ta.timeStamp,da ta.nonceStr,
da ta.packag,da ta.signType,da ta.paySign);
}else{
alert("充值失败!");
}
}
})
}
//调用微信内置浏览器的微信支付
function callPay(appId,timeStamp,nonceStr,
packag,signType,paySign) {
if (typeof WeixinJSBridge == "undefined") {
if (document.addEventListener) {
document.addEventListener('WeixinJSBridgeReady', on BridgeReady,
false);
} else if (document.attachEvent) {
document.attachEvent('WeixinJSBridgeReady', on BridgeReady);
document.attachEvent('on WeixinJSBridgeReady', on BridgeReady);
}
} else {
on BridgeReady(appId,timeStamp,nonceStr,
packag,signType,paySign);
}
}
//提交微信充值
function on BridgeReady(appId,timeStamp,nonceStr,
packag,signType,paySign) {
// alert("appId:"+appId+",timeStamp:"+timeStamp+",nonceStr:"+nonceStr+",packag:"+packag+",signType:"+signType+",paySign:"+paySign);
WeixinJSBridge.invoke('getBrandWCPayRequest', {
"appId" : appId, //公众号名称,由商户传入
"timeStamp" : timeStamp, //时间戳,自1970年以来的秒数
"nonceStr" : nonceStr, //随机串
"package" : packag, //"prepay_id=u802345jgfjsdfgsdg888",
"signType" : signType, //微信签名方式:
"paySign" : paySign //微信签名
}, function(res) { // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。
if (res.err_msg == "get_brand_wcpay_request:ok") {
//充值成功后返回页面
window.location.replace("<%=WEBPATH %>/fund/phone_return_url");
}
if (res.err_msg == "get_brand_wcpay_request:cancel") {
layer.msg("交易取消");
return false;
}
if (res.err_msg == "get_brand_wcpay_request:fail") {
layer.msg("支付失败");
return false;
}
});
}
效果图如下