微信支付之前的統一下單
前言:想調用微信支付的小夥伴們,在看我給予的案例之前,我們先看懂微信的支付流程。
我總結了一下,就比較簡單了(要看明細流轉,就到微信官網)【微信簽名這一塊我們拿出來單獨簡介】【報酬求助聯系: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;
}
//------------------構造微信支付需要的參數結束--------------------------------------------------------------
微信支付之前的統一下單