微信支付遇到的坑(移动支付)

在介绍之前先抱怨一番,对于腾讯的支付文档和技术主持,我只想说一个字:坑。跟阿里的支付宝比较,差距。。。项目中需要支付宝的移动支付和即时到帐,连研究文档带开放,一共3个小时就搞定,但微信一个接口,从开始看文档到整个接口测通上线,中间跨度将近5天,最恶心的是,交流困难,支付宝有专门的在线客服,有问题通过QQ就可以及时得到解答,但是微信,只能发邮件联系,并且回复时间不定。总之,就一个字坑。不啰嗦,上代码。

1:移动支付在支付之前,一般由服务端请求接口,生成预支付交易单https://api.mch.weixin.qq.com/pay/unifiedorder

     请求参数为xml格式



需要注意的是生存sign(坑),生成签名的模式跟一般接口的签名基本类似,都是将空值的参数去除之后,将参数名参数值拼接按照字典排序,之后加密。具体参考https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_3

签名生成之后,微信有提供专门在线验签工具,进行验证https://pay.weixin.qq.com/wiki/tools/signverify/

坑一:我按照规则生成签名之后,并跟在线签名验证工具进行验证,验证通过了,但是在请求接口时,返回


然后就是郁闷,发邮件去技术支持,一会问上传的密钥是否正确,最后特没说出个所以然。之后自己搜索,发现很多人遇到同样的问题,都是body中的中文问题导致,我试着将body中的中文描述改为英文,果然正常了。然后确定问题,着手解决,无非是在post请求时中文的编码问题。大家可以参考以下代码

public static String httpsRequest(String requestUrl, String xmlStr) {  
        try{  
            URL url = new URL(requestUrl);  
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();  
            connection.setDoOutput(true);  
            connection.setDoInput(true);  
            connection.setUseCaches(false);  
            connection.setRequestMethod("POST");  
            if (null != output) {  
                OutputStream outputStream = connection.getOutputStream();  
                outputStream.write(xmlStr.getBytes("UTF-8"));  
                outputStream.close();  
            }  
            // 从输入流读取返回内容  
            InputStream inputStream = connection.getInputStream();  
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");  
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);  
            String str = null;  
            StringBuffer buffer = new StringBuffer();  
            while ((str = bufferedReader.readLine()) != null) {  
                buffer.append(str);  
            }  
            bufferedReader.close();  
            inputStreamReader.close();  
            inputStream.close();  
            inputStream = null;  
            connection.disconnect();  
            return buffer.toString();  
        }catch(Exception ex){  
            ex.printStackTrace();  
        }  
  
        return "";  
    }

正常请求的返回结果为:


之后就是调起支付接口,此接口需要用到上个接口返回的prepay_id,按照文档生成参数xml

返回给app,由app 调起支付接口

2.notify_url 回调通知

在生成与支付订单这个接口中,需要一个参数为notify_url,这个地址是微信在支付成功后的回调地址,通常用了执行相应的业务逻辑,比如在支付成功后,修改订单状态为已支付。

同样的notify_url地址,支付宝一次测试通过,但是微信却是怎么搞都调用不到,发邮件询问把,刚开始我的地址是<notify_url>http://61.51.80.138:8008/apppay/notify/weixin_ticket_notify.hlt</notify_url>,微信的技术支持答复是不能加端口号,好吧,让网管做了个外网映射,改成80端口,将地址换为http://61.51.80.138/viptrip365/apppay/notify/weixin_ticket_notify.hlt ,继续测试,然后依然是收不到回调请求,在从头到尾看一遍文档,仍然是没有头绪,好吧继续发邮件,回复是因为我没有按照文档返回success,请查看文档,请详细阅读文档,然后我就xx了,请求都没收到怎么返回success?

解决:发现在靠微信提供的技术支持不能解决问题之后,我试着自己用之前生成与支付订单的请求方式,请求我这个url,终于发现了问题所在,原来,微信在回调通知时,参数类型为二进制流的xml,而我的struts2的package集成的拦截器并没有相关的拦截,导致不能正常请求,但是这个地址在浏览器却是能正常访问。

解决方法:地址(struts2的拦截器) 配置为 extends="struts-default"

回调中验证可参考以下方法:

String preStr = PayUtil.createLinkString_weixin(map);
key = PayUtil.getKey("weixin_key");
String singStr = preStr +"&key="+key;
String sign = MD5Util.MD5Encode(singStr, "utf-8").toUpperCase();
if(!sign.equals(map.get("sign"))){
System.out.println("签名错误-----");
System.out.println("mySign:----"+sign);
System.out.println("wxSign:----"+map.get("sign"));
return false;
}
if(!"SUCCESS".equals(map.get("result_code"))){
System.out.println("交易状态不正确-----"+map.get("err_code"));
return false;//交易状态不正确
}
if(!PayUtil.getKey("mch_id").equals(map.get("mch_id"))){
System.out.println("收款账号不正确-----"+map.get("mch_id"));
return false;
}
String totalPrice = map.get("total_fee");
DecimalFormat df = new DecimalFormat("0.00");//保留两位小数
String total_fee =df.format(Double.valueOf(totalPrice)/100);
if(!pahManager.checkOrderPrice(map.get("out_trade_no"), total_fee,type)){
System.out.println("支付金额不正确-----"+total_fee);
return false;
}



下面是payUtil,可参考其中的build_weixin()方法,该方法返回的map即为app端,调起支付需要用到的参数。仅供参考

package com.viptrip.app.appInterface.common.util;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.PropertyResourceBundle;
import java.util.TreeMap;
import java.util.UUID;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

import com.viptrip.ticket.util.PaymentUtil;

public class PayUtil {
public static Map<String, String> build_weixin(int serviceFlag,String out_trade_no, String total_fee,String ip) {
Map<String,String> map=new LinkedHashMap<String,String>();
//获取service参数
map.put("appid",getKey("appid"));
//获取partnerid参数
map.put("mch_id",getKey("mch_id"));
//获取_input_charset参数
map.put("nonce_str",UUID.randomUUID().toString().substring(0,20));
//支付类型
map.put("trade_type", getKey("trade_type"));
String notify_url = null;
String body = null;
if(serviceFlag==1){//机票
body="机票付款";
notify_url = getKey("ticket_notify_weixin");
}else if(serviceFlag==2){//酒店
body="酒店付款";
notify_url = getKey("hotel_notify_weixin");
}else if(serviceFlag==3){//机加酒
body="机加酒付款";
notify_url = getKey("pah_notify_weixin");
}
map.put("body", body);///订单描述

map.put("notify_url", notify_url);

map.put("spbill_create_ip", ip);
map.put("out_trade_no", out_trade_no);
map.put("total_fee", total_fee);

//去除空参数,生成sign 并构建参数xml
map=PaymentUtil.paraFilter(map);
String key = "";
String preStr = createLinkString_weixin(map);
key = getKey("weixin_key");
String singStr = preStr +"&key="+key;
String sign = MD5Util.MD5Encode(singStr, "utf-8").toUpperCase();
StringBuffer xmlStr = buildXml(map,sign);
System.out.println(xmlStr);
//调起统一下单接口,获取prepay_id
String responseStr = HttpRequestUtils.httpsRequest("https://api.mch.weixin.qq.com/pay/unifiedorder",xmlStr.toString());
System.out.println(responseStr);
map = PayUtil.xmlToMap(responseStr);
if("SUCCESS".equals(map.get("return_code"))){
String prepayid = map.get("prepay_id");
//生成 调起支付接口 需要用到的参数
map = PayUtil.create_WX_paramMap(prepayid);
}
return map;
}
/**
* 加载属性文件
* @param param
* @return
*/
public static String getKey(String key) {
String app_key = PropertyResourceBundle.getBundle(
"com.viptrip.app.appInterface.common.properties.app").getString(key);
return app_key;
}
private static StringBuffer buildXml(Map<String, String> map, String sign) {

List<String> keys = new ArrayList<String>(map.keySet());
Collections.sort(keys);
StringBuffer str = new StringBuffer();
String key = "";
String value="";
str.append("<xml>");
for (int i = 0; i < keys.size(); i++) {
key = keys.get(i);
value = map.get(key);
str.append("<"+key+">"+value+"</"+key+">");
}
str.append("<sign>"+sign+"</sign>");
str.append("</xml>");
return str;
}


public static Map<String,String> xmlToMap(String xmlStr){
Map<String, String> map = new HashMap<String, String>();
Document doc = null;
try {
doc = DocumentHelper.parseText(xmlStr);
} catch (DocumentException e) {
e.printStackTrace();
} // 将字符串转为XML
Element rootElement = doc.getRootElement(); // 获取根节点
Iterator iter = rootElement.elementIterator(); // 获取t节点下的子节点
while (iter.hasNext()) {
Element recordEle = (Element) iter.next();
String key = recordEle.getName(); // 拿到t节点下的子节点entry的key值
String value = recordEle.getTextTrim(); // 拿到t节点下的子节点entry的value值
map.put(key, value);
}
return map;
}
/**
* 生成预订单后,创建 调起支付 接口所需的参数
* @param prepayid
* @return
*/
public static Map<String, String> create_WX_paramMap(String prepayid) {
Map<String, String> map = new HashMap<String, String>();
map.put("appid",getKey("appid"));
map.put("partnerid",getKey("mch_id"));
map.put("prepayid",prepayid);
map.put("package","Sign=WXPay");
map.put("noncestr",UUID.randomUUID().toString().substring(0,20));
map.put("timestamp",new Date().getTime()/1000+"");
String preStr = PaymentUtil.createLinkString(map);
String key = getKey("weixin_key");
String singStr = preStr +"&key="+key;
String sign = MD5Util.MD5Encode(singStr, "utf-8").toUpperCase();
map.put("sign", sign);
return map;
}
/**
* 参数排序组合拼接
* @param params
* @return
*/
public static String createLinkString_weixin(Map<String, String> params) {

List<String> keys = new ArrayList<String>(params.keySet());
Collections.sort(keys);

String prestr = "";
String key = "";
String value = "";
for (int i = 0; i < keys.size(); i++) {
key = keys.get(i);
value = params.get(key);
if(!"sign".equals(key)&&!"sign_type".equals(key)){
if (i == keys.size() - 1) {//拼接时,不包括最后一个&字符
prestr = prestr + key + "=" + value;
} else {
prestr = prestr + key + "=" + value + "&";
}
}
}

return prestr;
}

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值