編輯:關於Android編程
前言
現在的APP大部分需要接入支付功能,而支付的主流就是微信支付和支付寶支付,網上關於微信支付和支付支付資料很多,但是這些資料隨著官方的變動可能變得毫無用處,所以我建議直接看官方文檔,微信開放平台和支付寶開放平台。當然,一般官方不會閒的蛋疼隨便改,大的改動周期在2年左右,小的改動不會有太大影響,所以,如果你不習慣官方的文檔,那麼你找的資料的新鮮度很重要。因為本文主要目的是介紹服務端的一些操作放到客戶端,所以正常的接入就不重復造輪子了 。
疑問
相信很多同學看到標題就有疑問了,為什麼要將服務端的一些操作放到客戶端來?放到客戶端不安全,官方不是不推薦嗎?我也想啊,為什麼非要給自己制造麻煩,但是,我一個項目(外包)遇到了這樣的情況,這個項目做服務端告訴他不會集成,讓我放到客戶端來,當時也解釋了利害關系,但是對方執意如此。在此,我只想說……(此處省略一萬字),這裡我笑了,不會集成?那支付後那些回調操作你怎麼要做了?為什麼不也放到客戶端來?哦,對了,客戶端不方便修改數據庫數據。請原諒我的吐槽,這根本不是會不會集成的原因,這是態度問題啊,相信有很多同學遇到過類似的事情,在這裡還是希望大家吐槽可以,忍忍也就過去了。好像扯遠了,回歸正題吧。
支付寶服務端sign簽名
首先我們有必要來看一下支付寶支付的流程圖,以便了解服務端該做哪些操作:
注意第一點app攜帶支付信息(正常的流程這裡服務端有一個簽名過程,APP需要請求服務端提供的接口傳入對應的參數獲取簽名後的數據,本文將服務端簽名過程放到客戶端)調用支付接口請求支付寶客戶端調起支付界面。流程圖其他步驟可以參考前言中推薦的文章,裡面有詳細的解釋,這裡不再解釋了。
支付寶簽名支付寶簽名文檔,其實,支付寶的集成很簡單,所以簽名也很簡單,對照文檔一會兒就弄出來了,我們來看一看就明白了:
/**
* 獲取簽名
*/
public static String getSign(String content, String privateKey) {
try {
PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(Base64.decode(privateKey));
KeyFactory keyf = KeyFactory.getInstance("RSA");
PrivateKey priKey = keyf.generatePrivate(priPKCS8);
java.security.Signature signature = java.security.Signature.getInstance("SHA1WithRSA");
signature.initSign(priKey);
signature.update(content.getBytes("UTF-8"));
byte[] signed = signature.sign();
return Base64.encode(signed);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 創建訂單信息
* @param payEntity 訂單支付實體
*/
private static String getOrderInfo(PayEntity payEntity){
StringBuffer orderInfo = new StringBuffer();
// 簽約合作者身份ID
orderInfo.append("partner=" + "\"" + AliPayConstans.PARTNER + "\"");
// 簽約賣家支付寶賬號
orderInfo.append("&seller_id=" + "\"" + AliPayConstans.SELLER + "\"");
// 商戶網站唯一訂單號
orderInfo.append("&out_trade_no=" + "\"" + payEntity.getOut_trade_no() + "\"");
// 商品名稱
orderInfo.append("&subject=" + "\"" + payEntity.getSubject() + "\"");
// 商品詳情
orderInfo.append("&body=" + "\"" + payEntity.getBody() + "\"");
// 商品金額
orderInfo.append("&total_fee=" + "\"" + payEntity.getTotal_fee() + "\"");
// 服務器異步通知頁面路徑
orderInfo.append("¬ify_url=" + "\"" + AliPayConstans.NOTIFY_URL + "\"");
// 服務接口名稱, 固定值
orderInfo.append("&service=\"mobile.securitypay.pay\"");
// 支付類型, 固定值
orderInfo.append("&payment_type=\"1\"");
// 參數編碼, 固定值
orderInfo.append("&_input_charset=\"utf-8\"");
// 設置未付款交易的超時時間
// 默認30分鐘,一旦超時,該筆交易就會自動被關閉。
// 取值范圍:1m~15d。
// m-分鐘,h-小時,d-天,1c-當天(無論交易何時創建,都在0點關閉)。
// 該參數數值不接受小數點,如1.5h,可轉換為90m。
orderInfo.append("&it_b_pay=\"30m\"");
// extern_token為經過快登授權獲取到的alipay_open_id,帶上此參數用戶將使用授權的賬戶進行支付
// orderInfo += "&extern_token=" + "\"" + extern_token + "\"";
// 支付寶處理完請求後,當前頁面跳轉到商戶指定頁面的路徑,可空
orderInfo.append("&return_url=\"m.alipay.com\"");
// 調用銀行卡支付,需配置此參數,參與簽名, 固定值 (需要簽約《無線銀行卡快捷支付》才能使用)
// orderInfo += "&paymethod=\"expressGateway\"";
return orderInfo.toString();
}
AliPayConstans相關:
/** 商戶PID */
public static final String PARTNER = "";
/** 商戶收款賬號 */
public static final String SELLER = "";
/** 商戶私鑰,pkcs8格式 */
public static final String RSA_PRIVATE = "";
/** 支付寶公鑰 */
public static final String RSA_PUBLIC = "";
/** 支付回調接口,需要服務器端支持 */
public static final String NOTIFY_URL = "";
微信服務端統一下單及簽名
老規矩先看微信支付流程圖,不得不說微信支付的流程還挺復雜的,這也許是大家吐槽微信支付的原因之一吧:
<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjxjb2RlIGNsYXNzPQ=="hljs java">我們需要注意2-7之間的步驟,這是關鍵所在,其中會有兩次簽名。
1. 構建支付訂單信息(第一次簽名)
2. 調用統一下單接口獲取預付ID(prepay_id)
3. 生成帶簽名的客戶端支付信息(第二次簽名)
1、構建支付訂單信息
/**
* 微信支付,構建統一下單請求參數
*/
public String genEntity() {
String nonceStr = genNonceStr();
List packageParams = new ArrayList();
// APPID
packageParams
.add(new BasicNameValuePair("appid", WeChatConstans.APP_ID));
// 商品描述
packageParams.add(new BasicNameValuePair("body", body));
// 商戶ID
packageParams.add(new BasicNameValuePair("mch_id",
WeChatConstans.PARTNER_ID));
// 隨機字符串
packageParams.add(new BasicNameValuePair("nonce_str", nonceStr));
// 回調接口地址
packageParams.add(new BasicNameValuePair("notify_url",
WeChatConstans.NOTIFY_URL));
// 我們的訂單號
packageParams.add(new BasicNameValuePair("out_trade_no", out_trade_no));
// 提交用戶端ip
packageParams.add(new BasicNameValuePair("spbill_create_ip",
getIPAddress()));
BigDecimal totalFeeBig = new BigDecimal(total_fee);
int totalFee = totalFeeBig.multiply(new BigDecimal(100)).intValue();
// 總金額,單位為 分 !
packageParams.add(new BasicNameValuePair("total_fee", String
.valueOf(totalFee)));
// 支付類型, APP
packageParams.add(new BasicNameValuePair("trade_type", "APP"));
// 生成簽名
String sign = genPackageSign(packageParams);
packageParams.add(new BasicNameValuePair("sign", sign));
String xmlstring = XmlUtil.toXml(packageParams);
try {
//避免商品描述中文字符編碼格式造成支付失敗
return new String(xmlstring.toString().getBytes(), "ISO-8859-1");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
生成簽名:
/**
* 生成簽名
*/
public static String genPackageSign(List params) {
try {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < params.size(); i++) {
sb.append(params.get(i).getName());
sb.append('=');
sb.append(params.get(i).getValue());
sb.append('&');
}
sb.append("key=");
sb.append(WeChatConstans.PARTNER_KEY);
String packageSign = MD5Util.getMessageDigest(
sb.toString().getBytes("utf-8")).toUpperCase();
return packageSign;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
}
隨機字符串:
/**
* 微信支付調用統一下單接口,隨機字符串
*/
public static String genNonceStr() {
try {
Random random = new Random();
String rStr = MD5Util.getMessageDigest(String.valueOf(
random.nextInt(10000)).getBytes("utf-8"));
return rStr;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
}
因為微信接收參數的格式只支持xml格式,所以我們要將參數轉為xml格式,具體參照統一下單 接口,生成xml格式:
/**
* 生成 XML
*/
public static String toXml(List params) {
StringBuilder sb = new StringBuilder();
sb.append("");
for (int i = 0; i < params.size(); i++) {
sb.append("<" + params.get(i).getName() + ">");
sb.append(params.get(i).getValue());
sb.append("");
}
sb.append(" ");
return sb.toString();
}
2、調用統一下單接口獲取預付ID
將我們第一步構建支付訂單信息作為參數請求統一下單接口:
/**
* 異步網絡請求獲取預付Id
*/
private class GetPrepayIdTask extends AsyncTask {
@Override
protected void onPreExecute() {
}
@Override
protected void onPostExecute(String result) {
// 第三步, 發送支付請求
sendPayReq(result);
}
@Override
protected String doInBackground(String... params) {
// 網絡請求獲取預付Id
String url = String.format(WeChatConstans.WECHAT_UNIFIED_ORDER);
String entity = genEntity();
byte[] buf = WeChatHttpClient.httpPost(url, entity);
if (buf != null && buf.length > 0) {
try {
Map map = XmlUtil.doXMLParse(new String(buf));
return (String) map.get("prepay_id");
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
}
由於返回的參數格式是xml格式,所以我們要解析數據,獲取prepay_id:
/**
* 解析xml,返回第一級元素鍵值對。如果第一級元素有子節點,則此節點的值是子節點的xml數據。
*
* @param strxml
* @return
* @throws JDOMException
* @throws IOException
*/
public static Map doXMLParse(String strxml) throws Exception {
if (null == strxml || "".equals(strxml)) {
return null;
}
Map m = new HashMap();
InputStream in = new ByteArrayInputStream(strxml.getBytes());
SAXBuilder builder = new SAXBuilder();
Document doc = builder.build(in);
Element root = doc.getRootElement();
List list = root.getChildren();
Iterator it = list.iterator();
while (it.hasNext()) {
Element e = (Element) it.next();
String k = e.getName();
String v = "";
List children = e.getChildren();
if (children.isEmpty()) {
v = e.getTextNormalize();
} else {
v = getChildrenText(children);
}
m.put(k, v);
}
// 關閉流
in.close();
return m;
}
/**
* 獲取子結點的xml
*
* @param children
* @return String
*/
public static String getChildrenText(List children) {
StringBuffer sb = new StringBuffer();
if (!children.isEmpty()) {
Iterator it = children.iterator();
while (it.hasNext()) {
Element e = (Element) it.next();
String name = e.getName();
String value = e.getTextNormalize();
List list = e.getChildren();
sb.append("<" + name + ">");
if (!list.isEmpty()) {
sb.append(getChildrenText(list));
}
sb.append(value);
sb.append("");
}
}
return sb.toString();
}
* 3、生成帶簽名的客戶端支付信息(直接調起支付)*
/**
* 發送支付請求
* @param prepayId 預付Id
*/
private void sendPayReq(String prepayId) {
PayReq req = new PayReq();
req.appId = WeChatConstans.APP_ID;
req.partnerId = WeChatConstans.PARTNER_ID;
req.prepayId = prepayId;
req.nonceStr = genNonceStr();
req.timeStamp = String.valueOf(genTimeStamp());
req.packageValue = "Sign=WXPay";
List signParams = new LinkedList();
signParams.add(new BasicNameValuePair("appid", req.appId));
signParams.add(new BasicNameValuePair("noncestr", req.nonceStr));
signParams.add(new BasicNameValuePair("package", req.packageValue));
signParams.add(new BasicNameValuePair("partnerid", req.partnerId));
signParams.add(new BasicNameValuePair("prepayid", req.prepayId));
signParams.add(new BasicNameValuePair("timestamp", req.timeStamp));
//再次簽名
req.sign = genPackageSign(signParams);
// 傳遞的額外信息,字符串信息,自定義格式
// req.extData = type +"#" + out_trade_no + "#" +total_fee;
// 微信支付結果界面對調起支付Activity的處理
// APPCache.payActivity.put("調起支付的Activity",(調起支付的Activity)context);
// 在支付之前,如果應用沒有注冊到微信,應該先調用IWXMsg.registerApp將應用注冊到微信
api.registerApp(WeChatConstans.APP_ID);
api.sendReq(req);
// 支付完成後微信會回調 wxapi包下 WXPayEntryActivity 的public void onResp(BaseResp
// resp)方法,所以後續操作,放在這個回調函數中操作就可以了
}
微信支付成功調起支付後回調到WXPayEntryActivity就是正常微信支付流程了。
總結
文章重點介紹了微信支付服務端操作放到客戶端而支付寶的服務端操作比較簡單,由於篇幅原因,所以只貼了部分關鍵代碼,下面會給出模板demo(Eclispce和Studio都有),Demo不能直接調起支付,但是替換為具體項目非常方便。通過本文可以了解到,其實不管客戶端還是服務端,只要按照官方文檔來做,一般還是能實現的,不過官方文檔也有不足的地方,文檔排版不好、內容混雜、文檔更新不及時、demo雜而亂(PS:這裡只覺得微信demo太雜太亂了)。但是,只要你用心,這些都不是問題。
如果……有同學遇到服務端蠻不講理的把這些操作丟給你,你可以和他理論一番,他改正也就好了,如果還是一意孤心,你還是忍忍吧……畢竟,你要做一個大度的人,哈哈哈!
The good seaman is known in bad weather.
Demo傳送門
在項目中做了列表頁面和詳情頁面,用到了以下幾個知識點,在這裡和大家分享一下:1.數據庫模塊的完善:1.1升級數據庫,抽出版本字段;如果xxx.db 數據庫已經存在了,之後
在使用Eclipse的時代,我們很少去在style文件給整個應用或者Activity去設定顏色,那是因為即使設置也不會提升用戶的視覺效果。但是材料設計號稱讓沒有設計功底的
最近碰到個項目要使用到滾動選擇器,原生的NumberPicker可定制性太差,不大符合UI要求。網上開源的WheelView是用ScrollView寫的,不能循環滾動,而
I9300相信已經有很多人入手了,不知道大家知道多少I9300的使用技巧在這裡總結了一下,希望對大家有幫助。如果還有什麼不全的地方,歡迎在下方留言。1. 鎖