1、什麼是[統一下單接口]?
首先我們要明白這個問題,需要先行看一下微信的官方文檔:
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
2.開發
//微信支付,生成統一訂單
private void wxPay(String order_id) {
String appid = WeChatInfo.WX_APP_ID;//app在微信申請的appid
String mch_id = WeChatInfo.WX_MCH_ID;//申請支付功能的商戶号
String nonce_str = WeChatField.getRandomString();//随機碼
String body = "黃金月卡會員";//商品描述,随便寫
// String out_trade_no = UtilData.generateOrderSN();//訂單編号
String out_trade_no = order_id;//訂單編号
// (Math.Round((decimal)order.Amount * 100, 0)).ToString()
String total_fee = "1";//總金額
// String time_start = UtilData.getCurrentTime();//時間戳,格式yyyyMMddHHmmss
String attach = "會員";//附加資料:深圳分店
String trade_type = "APP";//交易類型
String notify_url = "http://www.xxxx.net";//通知回調位址,然後背景做個回調,無參的,必須放到商戶平台上配置添加
String spbill_create_ip = WeChatField.getIPAddress(mContext);//裝置ip
SortedMap<String, String> params = new TreeMap<String, String>();
params.put("appid", appid);
params.put("mch_id", mch_id);
params.put("nonce_str", nonce_str);
params.put("attach", attach);
params.put("body", body);
params.put("out_trade_no", out_trade_no);
params.put("total_fee", total_fee );
params.put("trade_type", trade_type);
params.put("notify_url", notify_url);
params.put("spbill_create_ip", spbill_create_ip);
XLog.e(XLog.getTag(),"---------out_trade_no:"+out_trade_no);
//擷取簽名sign,文檔有些參數是必須傳的,但是少了還是能請求成功xml
String sign = WeChatField.getSign(params);
//參數xml化
String xmlParams = WeChatField.parseString2Xml(params, sign);
XLog.e(XLog.getTag(),"------下單xml化---->" + "\n" + xmlParams);
String urlStr = "https://api.mch.weixin.qq.com/pay/unifiedorder";//下單統一接口
try {
final byte[] xmlbyte = xmlParams.getBytes("UTF-8");
URL url = new URL(urlStr);
final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
conn.setDoOutput(true);// 允許輸出
conn.setDoInput(true);
conn.setUseCaches(false);// 不使用緩存
conn.setRequestMethod("POST");
conn.setRequestProperty("Connection", "Keep-Alive");// 維持長連接配接
conn.setRequestProperty("Charset", "UTF-8");
conn.setRequestProperty("Content-Length",
String.valueOf(xmlbyte.length));
conn.setRequestProperty("Content-Type", "text/xml; charset=UTF-8");
conn.setRequestProperty("X-ClientType", "2");//發送自定義的頭資訊
conn.getOutputStream().write(xmlbyte);
conn.getOutputStream().flush();
conn.getOutputStream().close();
if (conn.getResponseCode() != 200){
XLog.e(XLog.getTag(),"----------請求url失敗-");
return;
}
InputStream is = conn.getInputStream();// 擷取傳回資料
// 使用輸出流來輸出字元(可選)
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int len;
while ((len = is.read(buf)) != -1) {
out.write(buf, 0, len);
}
String string = out.toString("UTF-8");
System.out.println(string);
XLog.e(" 微信傳回資料 ", " --- " + string);
out.close();
is.close();
String xmlStr = ((String) string);
XLog.e(XLog.getTag(),"----result--->" + string);
XLog.e(XLog.getTag(),"----下單傳回--->" + "\n" + xmlStr);
Map<String, String> map = WeChatField.decodeXml(xmlStr);
Set set = map.entrySet();
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
XLog.e(XLog.getTag(),"-----entry.getKey()/entry.getValue()---->" + entry.getKey() + "/" + entry.getValue());
}
if (map.get("return_code").equalsIgnoreCase("SUCCESS")) {
if (map.get("return_msg").equalsIgnoreCase("OK")) {
PayReq req = new PayReq();
req.appId = WeChatInfo.WX_APP_ID;//APPID
req.partnerId = WeChatInfo.WX_MCH_ID;//商戶号
req.prepayId = map.get("prepay_id");
req.nonceStr = map.get("nonce_str");
String time = System.currentTimeMillis() / 1000 + "";
req.timeStamp = time;//時間戳,這次是截取long類型時間的前10位
req.packageValue = "Sign=WXPay";//參數是固定的,寫死的
SortedMap<String, String> sortedMap = new TreeMap<String, String>();//一定要注意鍵名,去掉下劃線,字母全是小寫
sortedMap.put("appid", WeChatInfo.WX_APP_ID);
sortedMap.put("partnerid", WeChatInfo.WX_MCH_ID);
sortedMap.put("prepayid", map.get("prepay_id"));
sortedMap.put("noncestr", map.get("nonce_str"));
sortedMap.put("timestamp", time);
sortedMap.put("package", "Sign=WXPay");
req.sign = WeChatField.getSign(sortedMap);//重新存除了sign字段之外,再次簽名,要不然喚起微信支付會傳回-1,特别坑爹的是,鍵名一定要去掉下劃線,不然傳回-1
//進行支付
doWxpay(req);
} else {
Toast.makeText(mContext, "簽名失敗", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(mContext, "交易失敗", Toast.LENGTH_SHORT).show();
}
} catch (MalformedURLException e) {
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
}
}
WeChatField.class
public class WeChatField {
/**
* 解析傳回的XML為鍵值對
* @param content
* @return
*/
public static Map<String,String> decodeXml(String content) {
try {
Map<String, String> xml = new HashMap<String, String>();
XmlPullParser parser = Xml.newPullParser();
parser.setInput(new StringReader(content));
int event = parser.getEventType();
while (event != XmlPullParser.END_DOCUMENT) {
String nodeName=parser.getName();
switch (event) {
case XmlPullParser.START_DOCUMENT:
break;
case XmlPullParser.START_TAG:
if("xml".equals(nodeName)==false){
//執行個體化student對象
xml.put(nodeName,parser.nextText());
}
break;
case XmlPullParser.END_TAG:
break;
}
event = parser.next();
}
return xml;
} catch (Exception e) {
XLog.e(XLog.getTag(),e.toString());
}
return null;
}
/***
* 将鍵值對xml化
* @param map
* @param sign
* @return
*/
public static String parseString2Xml(SortedMap<String, String> map, String sign) {
StringBuffer sb = new StringBuffer();
map.put("sign",sign);
sb.append("<xml>");
Set es = map.entrySet();
Iterator iterator = es.iterator();
while(iterator.hasNext()){
Map.Entry entry = (Map.Entry)iterator.next();
String k = (String)entry.getKey();
String v = (String)entry.getValue();
sb.append("<"+k+">"+v+"</"+k+">");
}
sb.append("</xml>");
return sb.toString();
}
/**
* 簽名獲得sign字段
* @param params
* @return
*/
public static String getSign(SortedMap<String, String> params) {
StringBuffer sb = new StringBuffer();
Set es = params.entrySet();
Iterator iterator = es.iterator();
while (iterator.hasNext()){
Map.Entry entry = (Map.Entry) iterator.next();
String key = (String) entry.getKey();
String value = (String) entry.getValue();
if (null != value && !TextUtils.isEmpty(value) && !key.equals("key")){
sb.append(key + "=" + value + "&");
}
}
sb.append("key="+WeChatInfo.WX_PRIVATE_KEY);//商品平台API密鑰,32位的字母數字,找申請支付功能的人要,就在商戶平台那裡
XLog.e(XLog.getTag(),"-------------------------------sign-------------->"+"\n"+sb.toString());
String packageSign = MD5.getMessageDigest(sb.toString().getBytes()).toUpperCase();
return packageSign;
}
/**
* MD5加密
* @return
*/
public static String getRandomString() {
Random random = new Random();
return MD5.getMessageDigest(String.valueOf(random.nextInt(10000)).getBytes());
}
/**擷取裝置ip*/
public static String getIPAddress(Context context) {
NetworkInfo info = ((ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
if (info != null && info.isConnected()) {
if (info.getType() == ConnectivityManager.TYPE_MOBILE) {//目前使用2G/3G/4G網絡
try {
//Enumeration<NetworkInterface> en=NetworkInterface.getNetworkInterfaces();
for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
NetworkInterface intf = en.nextElement();
for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
InetAddress inetAddress = enumIpAddr.nextElement();
if (!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address) {
return inetAddress.getHostAddress();
}
}
}
} catch (SocketException e) {
e.printStackTrace();
}
} else if (info.getType() == ConnectivityManager.TYPE_WIFI) {//目前使用無線網絡
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
String ipAddress = intIP2StringIP(wifiInfo.getIpAddress());//得到IPV4位址
return ipAddress;
}
} else {
//目前無網絡連接配接,請在設定中打開網絡
}
return null;
}
/**
* 将得到的int類型的IP轉換為String類型
*
* @param ip
* @return
*/
public static String intIP2StringIP(int ip) {
return (ip & 0xFF) + "." +
((ip >> 8) & 0xFF) + "." +
((ip >> 16) & 0xFF) + "." +
(ip >> 24 & 0xFF);
}
}
PayReq.class
public class PayReq extends BaseReq {
private static final String TAG = "MicroMsg.PaySdk.PayReq";
private static final int EXTDATA_MAX_LENGTH = 1024;
public String appId;
public String partnerId;
public String prepayId;
public String nonceStr;
public String timeStamp;
public String packageValue;
public String sign;
public String extData;
public PayReq.Options options;
public String signType;
public PayReq() {
}
public boolean checkArgs() {
if (this.appId != null && this.appId.length() != 0) {
if (this.partnerId != null && this.partnerId.length() != 0) {
if (this.prepayId != null && this.prepayId.length() != 0) {
if (this.nonceStr != null && this.nonceStr.length() != 0) {
if (this.timeStamp != null && this.timeStamp.length() != 0) {
if (this.packageValue != null && this.packageValue.length() != 0) {
if (this.sign != null && this.sign.length() != 0) {
if (this.extData != null && this.extData.length() > 1024) {
Log.e("MicroMsg.PaySdk.PayReq", "checkArgs fail, extData length too long");
return false;
} else {
return true;
}
} else {
Log.e("MicroMsg.PaySdk.PayReq", "checkArgs fail, invalid sign");
return false;
}
} else {
Log.e("MicroMsg.PaySdk.PayReq", "checkArgs fail, invalid packageValue");
return false;
}
} else {
Log.e("MicroMsg.PaySdk.PayReq", "checkArgs fail, invalid timeStamp");
return false;
}
} else {
Log.e("MicroMsg.PaySdk.PayReq", "checkArgs fail, invalid nonceStr");
return false;
}
} else {
Log.e("MicroMsg.PaySdk.PayReq", "checkArgs fail, invalid prepayId");
return false;
}
} else {
Log.e("MicroMsg.PaySdk.PayReq", "checkArgs fail, invalid partnerId");
return false;
}
} else {
Log.e("MicroMsg.PaySdk.PayReq", "checkArgs fail, invalid appId");
return false;
}
}
public void toBundle(Bundle var1) {
super.toBundle(var1);
var1.putString("_wxapi_payreq_appid", this.appId);
var1.putString("_wxapi_payreq_partnerid", this.partnerId);
var1.putString("_wxapi_payreq_prepayid", this.prepayId);
var1.putString("_wxapi_payreq_noncestr", this.nonceStr);
var1.putString("_wxapi_payreq_timestamp", this.timeStamp);
var1.putString("_wxapi_payreq_packagevalue", this.packageValue);
var1.putString("_wxapi_payreq_sign", this.sign);
var1.putString("_wxapi_payreq_extdata", this.extData);
var1.putString("_wxapi_payreq_sign_type", this.signType);
if (this.options != null) {
this.options.toBundle(var1);
}
}
public void fromBundle(Bundle var1) {
super.fromBundle(var1);
this.appId = a.b(var1, "_wxapi_payreq_appid");
this.partnerId = a.b(var1, "_wxapi_payreq_partnerid");
this.prepayId = a.b(var1, "_wxapi_payreq_prepayid");
this.nonceStr = a.b(var1, "_wxapi_payreq_noncestr");
this.timeStamp = a.b(var1, "_wxapi_payreq_timestamp");
this.packageValue = a.b(var1, "_wxapi_payreq_packagevalue");
this.sign = a.b(var1, "_wxapi_payreq_sign");
this.extData = a.b(var1, "_wxapi_payreq_extdata");
this.signType = a.b(var1, "_wxapi_payreq_sign_type");
this.options = new PayReq.Options();
this.options.fromBundle(var1);
}
public int getType() {
return 5;
}
public static class Options {
public static final int INVALID_FLAGS = -1;
public String callbackClassName;
public int callbackFlags = -1;
public Options() {
}
public void toBundle(Bundle var1) {
var1.putString("_wxapi_payoptions_callback_classname", this.callbackClassName);
var1.putInt("_wxapi_payoptions_callback_flags", this.callbackFlags);
}
public void fromBundle(Bundle var1) {
this.callbackClassName = a.b(var1, "_wxapi_payoptions_callback_classname");
this.callbackFlags = a.a(var1, "_wxapi_payoptions_callback_flags");
}
}
}
定義訂單編号
/**
* 要求外部訂單号必須唯一。
* @return
*/
public static String getOutTradeNo() {
SimpleDateFormat format = new SimpleDateFormat("MMddHHmmss", Locale.getDefault());
Date date = new Date();
String key = format.format(date);
Random r = new Random();
key = key + r.nextInt();
key = key.substring(0, 15);
return key;
}
有什麼不懂,歡迎留言;
統一訂單接口,坑太多了,花了一天時間 搞定;