前言:想调用微信支付的小伙伴们,在看我给予的案例之前,我们先看懂微信的支付流程。
我总结了一下,就比较简单了(要看明细流转,就到微信官网)【微信签名这一块我们拿出来单独简介】【报酬求助联系:1124904642】
1.客户下单,该单据保存在自己的库存中
2.在点击确认支付的时候,调用微信的统一下单接口
3.统一下单接口会根据你提供的回调接口反馈统一下单信息,自己去解析返回的XML术语对比是否成功,成功与否,把信息返回给微信(微信会反复回调你的接口至少两次,确保统一下单成功)
4.告诉微信,统一下单成功后,微信会返回成功之后的信息,自己解析XML术语,根据术语中的字段,生成微信调用SDK的必要参数.
一.微信统一下单
1.统一下单接口讲解
统一下单接口:https://api.mch.weixin.qq.com/pay/unifiedorder
请求参数
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
---|---|---|---|---|---|
公众账号ID | appid | 是 | String(32) | wxd678efh567hg6787 | 微信支付分配的公众账号ID(企业号corpid即为此appId) |
商户号 | mch_id | 是 | String(32) | 1230000109 | 微信支付分配的商户号 |
设备号 | device_info | 否 | String(32) | 013467007045764 | 自定义参数,可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB" |
随机字符串 | nonce_str | 是 | String(32) | 5K8264ILTKCH16CQ2502SI8ZNMTM67VS | 随机字符串,长度要求在32位以内。推荐随机数生成算法 |
签名 | sign | 是 | String(32) | C380BEC2BFD727A4B6845133519F3AD6 | 通过签名算法计算得出的签名值,详见签名生成算法 |
签名类型 | sign_type | 否 | String(32) | MD5 | 签名类型,默认为MD5,支持HMAC-SHA256和MD5。 |
商品描述 | body | 是 | String(128) | 腾讯充值中心-QQ会员充值 |
商品简单描述,该字段请按照规范传递,具体请见参数规定 |
商品详情 | detail | 否 | String(6000) | 商品详细描述,对于使用单品优惠的商户,改字段必须按照规范上传,详见“单品优惠参数说明” | |
附加数据 | attach | 否 | String(127) | 深圳分店 | 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。 |
商户订单号 | out_trade_no | 是 | String(32) | 20150806125346 | 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。详见商户订单号 |
标价币种 | fee_type | 否 | String(16) | CNY | 符合ISO 4217标准的三位字母代码,默认人民币:CNY,详细列表请参见货币类型 |
标价金额 | total_fee | 是 | Int | 88 | 订单总金额,单位为分,详见支付金额 |
终端IP | spbill_create_ip | 是 | String(16) | 123.12.12.123 | APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。 |
交易起始时间 | time_start | 否 | String(14) | 20091225091010 | 订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则 |
交易结束时间 | time_expire | 是 | String(14) | 20091227091010 |
订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。订单失效时间是针对订单号而言的,由于在请求支付的时候有一个必传参数prepay_id只有两小时的有效期,所以在重入时间超过2小时的时候需要重新请求下单接口获取新的prepay_id。其他详见时间规则 建议:最短失效时间间隔大于1分钟 |
订单优惠标记 | goods_tag | 否 | String(32) | WXG | 订单优惠标记,使用代金券或立减优惠功能时需要的参数,说明详见代金券或立减优惠 |
通知地址 | notify_url | 是 | String(256) | http://www.weixin.qq.com/wxpay/pay.php | 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 |
交易类型 | trade_type | 是 | String(16) | JSAPI | 取值如下:JSAPI,NATIVE,APP等,说明详见参数规定 |
商品ID | product_id | 否 | String(32) | 12235413214070356458058 | trade_type=NATIVE时(即扫码支付),此参数必传。此参数为二维码中包含的商品ID,商户自行定义。 |
指定支付方式 | limit_pay | 否 | String(32) | no_credit | 上传此参数no_credit--可限制用户不能使用信用卡支付 |
用户标识 | openid | 否 | String(128) | oUpF8uMuAJO_M2pxb1Q9zNjWeS6o | trade_type=JSAPI时(即公众号支付),此参数必传,此参数为微信用户在商户对应appid下的唯一标识。openid如何获取,可参考【获取openid】。企业号请使用【企业号OAuth2.0接口】获取企业号内成员userid,再调用【企业号userid转openid接口】进行转换 |
+场景信息 | scene_info | 否 | String(256) |
{"store_info" : { |
该字段用于上报场景信息,目前支持上报实际门店信息。该字段为JSON对象数据,对象格式为{"store_info":{"id": "门店ID","name": "名称","area_code": "编码","address": "地址" }} ,字段详细说明请点击行前的+展开 |
将参数构造成XML字符串写入到请求微信接口的请求正文中(xml字符串示例)
<xml>
<appid>wx2421b1c4370ec43b</appid>
<attach>支付测试</attach>
<body>JSAPI支付测试</body>
<mch_id>10000100</mch_id>
<detail><![CDATA[{
"goods_detail":[
{
"goods_id":"iphone6s_16G",
"wxpay_goods_id":"1001",
"goods_name":"iPhone6s 16G",
"quantity":1,
"price":528800,
"goods_category":"123456",
"body":"苹果手机"
},
{
"goods_id":"iphone6s_32G",
"wxpay_goods_id":"1002",
"goods_name":"iPhone6s 32G",
"quantity":1,
"price":608800,
"goods_category":"123789",
"body":"苹果手机"
}
]
}]]></detail>
<nonce_str>1add1a30ac87aa2db72f57a2375d8fec</nonce_str>
<notify_url>http://wxpay.wxutil.com/pub_v2/pay/notify.v2.php</notify_url>
<openid>oUpF8uMuAJO_M2pxb1Q9zNjWeS6o</openid>
<out_trade_no>1415659990</out_trade_no>
<spbill_create_ip>14.23.150.211</spbill_create_ip>
<total_fee>1</total_fee>
<trade_type>JSAPI</trade_type>
<sign>0CB01533B8C1EF103065174F50BCA001</sign>
</xml>
2.notify_url:回调(通知地址接口)
微信解析XML字符串信息之后会调用notify_url,回调接口,告诉客户同意下下单是否成功。
3.统一下单成功之后,根据微信返回的信息中构造调用微信SDK需要用到的参数,返回给前端,前端利用SDK调用支付的接口。
代码案例:
//微信支付需要的参数返回
public static boolean weChat(Map<String,Object> map,Map<String,Object> errorMap){
//微信统一下单
if(!PublicUtil.unifiedOrder(map,errorMap)){
errorMap.put("error",ResMessage.Server_Abnormal.code);
return true;
}
//下单成构造四个参数调用SDK
JSONObject jsonObject= weChatMaps(map);
map.clear();
jsonObjectConversionMap(jsonObject, map);
return false;
}
unifiedOrder:
/**
*
* @Title: unifiedOrder
* @Description: TODO
* @param @return
* @return boolean
* @throws
* 微信统一下单接口
*/
public static boolean unifiedOrder(Map<String,Object> map,Map<String,Object> errorMap){
Map<String,Object> mapstem = new HashMap<String,Object>();
map.put("body","******");
map.put("time_start",DateForamtUtil.to_YYYYMMddHHmmss_str(new Date())); //交易起始时间
map.put("time_expire",DateForamtUtil.to_YYYYMMddHHmmss_str(DateForamtUtil.addMinute(new Date(), 10))); //交易結束起始时间
//封装微信端要求的参数
Map<String,Object> mapParameter =encapsulatedData(map);//封装微信端要求的参数
//封装成XML,转成字符串
String spliceXml=spliceXml(mapParameter);
try{
//访问微信接口
String strResult =getHttpRequest(Configure.UNIFIEDORDER_API, null, spliceXml);
//解析微信返回的XML
mapstem=XMLParser.getMapFromXML(spliceXml);
if(mapstem==null || mapstem.size()==0){
errorMap.put("error",ResMessage.comment_autograph_notnull.code);
return true;
}
map.put("prepay_id",String.valueOf(mapstem.get("mapstem")));
Map<String,Object> maps=weChatMaps(map);
return false;
} catch (MalformedURLException e){
e.printStackTrace();
return true;
} catch (IOException e){
e.printStackTrace();
return true;
} catch (ParserConfigurationException e){
// TODO Auto-generated catch block
e.printStackTrace();
return true;
} catch (SAXException e){
// TODO Auto-generated catch block
e.printStackTrace();
return true;
}
}
encapsulatedData:
/**
*
* @Title: encapsulatedData
* @Description: TODO
* @param
* @return void
* @throws
* 构造微信统一下单的数据参数
*/
public static Map<String,Object> encapsulatedData(Map<String,Object> map){
Map<String,Object> mapParameter = new HashMap<String,Object>();
mapParameter.put("appid", Configure.getAppid());//微信分配的公众号ID
mapParameter.put("mch_id", Configure.getMchid());//微信支付分配的商户号ID
mapParameter.put("nonce_str",UUID.randomUUID().toString().replace("-",""));//随机字符串32位
mapParameter.put("body", String.valueOf(map.get("body")));//商品描述
mapParameter.put("out_trade_no", String.valueOf(map.get("strorder")));//微信支付分配的商户号ID
String dbmoney=String.valueOf(Float.parseFloat(String.valueOf(map.get("dbmoney")))*100);
mapParameter.put("total_fee",dbmoney.substring(0,dbmoney.length()-2));//标价金额
mapParameter.put("time_start", String.valueOf(map.get("time_start")));
mapParameter.put("time_expire", String.valueOf(map.get("time_expire")));
mapParameter.put("openid", String.valueOf(map.get("struserid")));//用户的openid
mapParameter.put("spbill_create_ip", String.valueOf(map.get("spbill_create_ip")));//终端IP request.getRemoteAddr()
mapParameter.put("notify_url", "http://autotest.xiaobaoche.net/weixin/paynt");//通知地址
mapParameter.put("trade_type", "JSAPI");//支付类型
String sign=sign(mapParameter);
mapParameter.put("sign",sign);//签名(通过签名算法计算得出的签名值,详见签名生成算法)
return mapParameter;
}
构造XNL字符串 spliceXml:
/**
*
* @Title: spliceXml
* @Description: TODO
* @param @return
* @return String
* @throws
* 拼接微信要求的XML格式,凭借XML时,最好用转义符号 <![CDATA[]]>
*/
public static String spliceXml(Map<String,Object> map){
StringBuffer spliceXml=new StringBuffer();
spliceXml.append("<xml>");
spliceXml.append("<appid><![CDATA[").append(String.valueOf(map.get("appid"))).append("]]</appid>");
spliceXml.append("<mch_id><![CDATA[").append(String.valueOf(map.get("mch_id"))).append("]]</mch_id>");
spliceXml.append("<nonce_str><![CDATA[").append(String.valueOf(map.get("nonce_str"))).append("]]</nonce_str>");
spliceXml.append("<body><![CDATA[").append(String.valueOf(map.get("body"))).append("]]</body>");
spliceXml.append("<out_trade_no><![CDATA[").append(String.valueOf(map.get("out_trade_no"))).append("]]</out_trade_no>");
spliceXml.append("<total_fee><![CDATA[").append(String.valueOf(map.get("total_fee"))).append("]]</total_fee>");
spliceXml.append("<time_start><![CDATA[").append(String.valueOf(map.get("time_start"))).append("]]</time_start>");
spliceXml.append("<time_expire><![CDATA[").append(String.valueOf(map.get("time_expire"))).append("]]</time_expire>");
spliceXml.append("<appid><![CDATA[").append(String.valueOf(map.get("spbill_create_ip"))).append("]]</appid>");
spliceXml.append("<spbill_create_ip><![CDATA[").append(String.valueOf(map.get("notify_url"))).append("]]</spbill_create_ip>");
spliceXml.append("<trade_type><![CDATA[").append(String.valueOf(map.get("trade_type"))).append("]]</trade_type>");
spliceXml.append("<sign><![CDATA[").append(String.valueOf(map.get("sign"))).append("]]</sign>");
spliceXml.append("</xml>");
return spliceXml.toString();
}
访问微信接口的方法
/**
* 获取http请求的数据
* @return
* @throws IOException
* @throws MalformedURLException
* 发起请求的方法
*/
public static String getHttpRequest(String url, String param, String contentType) throws MalformedURLException, IOException{
String result = "";
HttpURLConnection connection = null;
connection = (HttpURLConnection) new URL(url).openConnection();
connection.setDoOutput(true);// 设置连接输出流为true,默认false (post
// 请求是以流的方式隐式的传递参数)
connection.setDoInput(true); // 设置连接输入流为true
connection.setRequestProperty("Accept-Charset", "utf-8");
connection.setRequestProperty("contentType", "utf-8");
connection.setRequestProperty("contentType", "utf-8");
connection.setUseCaches(false); // post请求缓存设为false
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); // application/x-www-form-urlencoded->表单数据
connection.connect(); // 建立连接
// application/json;charset=UTF-8 application/x-www-form-urlencoded
if(contentType != null){
connection.setRequestProperty("contentType", contentType);
}
//参数字符串
if(param != null){
OutputStream out = connection.getOutputStream(); // 创建输入输出流
out.write(param.getBytes("UTF-8")); // 将参数输出到连接
out.flush(); // 输出完成后刷新并关闭流
out.close(); // 重要且易忽略步骤 (关闭流,切记!)
}
InputStream fin = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(fin, "utf-8"));
String str = reader.readLine();
while (str != null){
result += str;
str = reader.readLine();
}
return result;
}
getMapFromXML:解析微信端返回的XML
/**
*
* @Title: getMapFromXML
* @Description: TODO
* @param @param xmlString
* @param @return
* @param @throws ParserConfigurationException
* @param @throws IOException
* @param @throws SAXException
* @return Map<String,Object>
* @throws
* 解析XML,返回MAP
*/
public static Map<String,Object> getMapFromXML(String xmlString) throws ParserConfigurationException, IOException, SAXException {
//这里用Dom的方式解析回包的最主要目的是防止API新增回包字段
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
InputStream is = Util.getStringStream(xmlString);
Document document = null;
try{
document = builder.parse(is);
}catch(Exception e){
}
//获取到document里面的全部结点
NodeList allNodes = document.getFirstChild().getChildNodes();
Node node;
Map<String, Object> map = new HashMap<String, Object>();
int i=0;
while (i < allNodes.getLength()) {
node = allNodes.item(i);
if(node instanceof Element){
map.put(node.getNodeName(),node.getTextContent());
}
i++;
}
return map;
}
构造微信调用SDK支付需要用到的参数
//------------------构造微信支付需要的参数开始--------------------------------------------------------------
public static JSONObject weChatMaps(Map<String,Object> maps){
//构造做微信支付时需要用到的参数
JSONObject jsonObject=new JSONObject();
jsonObject.put("appId",Configure.getAppid());//公众号APPID
jsonObject.put("timeStamp",String.valueOf(System.currentTimeMillis() / 1000));
jsonObject.put("nonceStr",PublicTool.getRandomUUID(32));
jsonObject.put("package","prepay_id="+ String.valueOf(maps.get("prepay_id")));
jsonObject.put("signType","md5");
Map<String,Object> map = jsonObject;
String signs = sign(map);//签名
jsonObject.put("paySign",signs);
return jsonObject;
}
//------------------构造微信支付需要的参数结束--------------------------------------------------------------