這兩天,在移動APP上集成了支付寶支付功能,費了一些周折,除了其餘博客上提到的一些問題,這裏分享一下本身的經驗php
a 註冊支付寶商家帳號
b 開通移動支付功能
c 生成RSA私鑰和公鑰,上傳本身的公鑰給支付寶 官網方法前端
支付寶demo下載,將alipaysdk.jar放到本身的項目中。java
(我本身將支付集成在一個project,做爲android library使用)android
/** 支付寶類 */ public class Alipay { private Activity context;// 上下文 private String TAG = "Alipay"; private OnPayResultListener payResultListener; // 商戶PID public static final String PARTNER = "*"; // 商戶收款帳號 public static final String SELLER = "*"; // 商戶私鑰,pkcs8 格式 public static final String RSA_PRIVATE = "*"; // 支付寶公鑰 public static final String RSA_PUBLIC = ""; private static final int SDK_PAY_FLAG = 1; private Handler mHandler = new Handler() { @SuppressWarnings("unused") public void handleMessage(Message msg) { switch (msg.what) { case SDK_PAY_FLAG: { String result_string = (String) msg.obj; Log.w(TAG,result_string); PayResult payResult = new PayResult(result_string); payResultListener.OnPayResult(payResult);//回調 給 activity; break; } default: break; } }; }; public Alipay(Activity activity){ this.context = activity; } public OnPayResultListener getPayResultListener() { return payResultListener; } public void setPayResultListener(OnPayResultListener payResultListener) { this.payResultListener = payResultListener; } /** 根據訂單信息 請求支付寶*/ public void pay(final String payInfo) { Runnable payRunnable = new Runnable() { @Override public void run() { // 構造PayTask 對象 PayTask alipay = new PayTask(context); // 調用支付接口,獲取支付結果 Log.w(alipay.getVersion(),payInfo); String result = alipay.pay(payInfo, true); Message msg = new Message(); msg.what = SDK_PAY_FLAG; msg.obj = result; mHandler.sendMessage(msg); } }; Thread payThread = new Thread(payRunnable); payThread.start(); // 必須異步調用 } /** call alipay sdk pay. 調用SDK支付 */ public void pay(String subject, String body, String price,String trade_no) { if (TextUtils.isEmpty(PARTNER) || TextUtils.isEmpty(RSA_PRIVATE) || TextUtils.isEmpty(SELLER)) { Log.e(TAG,"須要配置PARTNER | RSA_PRIVATE| SELLER"); return; } //"測試的商品", "該測試商品的詳細描述", "0.01" String orderInfo = getOrderInfo(subject, body, price,trade_no); /** 特別注意,這裏的簽名邏輯須要放在服務端,切勿將私鑰泄露在代碼中! */ String sign = sign(orderInfo); try { sign = URLEncoder.encode(sign, "UTF-8");//僅需對sign 作URL編碼 } catch (UnsupportedEncodingException e) { e.printStackTrace(); } /** 完整的符合支付寶參數規範的訂單信息 */ final String payInfo = orderInfo + "&sign=\"" + sign + "\"&" + getSignType(); pay(payInfo); } /** create the order info. 建立訂單信息 */ private String getOrderInfo(String subject, String body, String price,String trade_no) { // 簽約合做者身份ID String orderInfo = "partner=" + "\"" + PARTNER + "\""; // 簽約賣家支付寶帳號 orderInfo += "&seller_id=" + "\"" + SELLER + "\""; // 商戶網站惟一訂單號 orderInfo += "&out_trade_no=" + "\"" + trade_no + "\""; // 商品名稱 orderInfo += "&subject=" + "\"" + subject + "\""; // 商品詳情 orderInfo += "&body=" + "\"" + body + "\""; // 商品金額 orderInfo += "&total_fee=" + "\"" + price + "\""; // 服務器異步通知頁面路徑 orderInfo += "¬ify_url=" + "\"" + "http://notify.msp.hk/notify.htm" + "\""; // 服務接口名稱, 固定值 orderInfo += "&service=\"mobile.securitypay.pay\""; // 支付類型, 固定值 orderInfo += "&payment_type=\"1\""; // 參數編碼, 固定值 orderInfo += "&_input_charset=\"utf-8\""; // 設置未付款交易的超時時間 // 默認30分鐘,一旦超時,該筆交易就會自動被關閉。 // 取值範圍:1m~15d。 // m-分鐘,h-小時,d-天,1c-當天(不管交易什麼時候建立,都在0點關閉)。 // 該參數數值不接受小數點,如1.5h,可轉換爲90m。 orderInfo += "&it_b_pay=\"30m\""; // extern_token爲通過快登受權獲取到的alipay_open_id,帶上此參數用戶將使用受權的帳戶進行支付 // orderInfo += "&extern_token=" + "\"" + extern_token + "\""; // 支付寶處理完請求後,當前頁面跳轉到商戶指定頁面的路徑,可空 // orderInfo += "&return_url=\"m.alipay.com\""; // 調用銀行卡支付,需配置此參數,參與簽名, 固定值 (須要簽約《無線銀行卡快捷支付》才能使用) // orderInfo += "&paymethod=\"expressGateway\""; return orderInfo; } /** sign the order info. 對訂單信息進行簽名 */ private String sign(String content) { return SignUtils.sign(content, RSA_PRIVATE); } /* get the sign type we use. 獲取簽名方式 */ private String getSignType() { return "sign_type=\"RSA\""; } /** get the sdk version. 獲取SDK版本號 */ public void getSDKVersion() { PayTask payTask = new PayTask(context); String version = payTask.getVersion(); } /** * 原生的H5(手機網頁版支付切natvie支付) 【對應頁面網頁支付按鈕】 * 沒有用到 * @param v */ public void h5Pay(View v) { Intent intent = new Intent();//this, H5PayDemoActivity.class); Bundle extras = new Bundle(); /** * url是測試的網站,在app內部打開頁面是基於webview打開的,demo中的webview是H5PayDemoActivity, * demo中攔截url進行支付的邏輯是在H5PayDemoActivity中shouldOverrideUrlLoading方法實現, * 商戶能夠根據本身的需求來實現 */ String url = "http://m.meituan.com"; // url能夠是一號店或者美團等第三方的購物wap站點,在該網站的支付過程當中,支付寶sdk完成攔截支付 extras.putString("url", url); intent.putExtras(extras); // startActivity(intent); } }
在這個Alipay中主要有兩個方法public void pay(final String payInfo)和public void pay(String subject, String body, String price,String trade_no) ,前者是在後臺生成訂單信息,後者是在app中生成,而後交給alipaysdk處理,而後反饋數據.關於反饋,我定義了一個類和一個接口,代碼以下:web
/** 支付反饋接口*/ public interface OnPayResultListener { void OnPayResult(PayResult result); } /** 支付結果*/ public class PayResult { private int PAY_TYPE = 1 ; // 1 alipay private String resultStatus; private String result; private String memo; public static final int PAY_ALIPAY = 1; public static final int PAY_WEIXIN = 2; public PayResult(String rawResult) { if (TextUtils.isEmpty(rawResult)) return; if(PAY_TYPE == PAY_ALIPAY){ String[] resultParams = rawResult.split(";"); for (String resultParam : resultParams) { if (resultParam.startsWith("resultStatus")) { resultStatus = gatValue(resultParam, "resultStatus"); } if (resultParam.startsWith("result")) { result = gatValue(resultParam, "result"); } if (resultParam.startsWith("memo")) { memo = gatValue(resultParam, "memo"); } } } } public boolean isSuccess(){ if(PAY_TYPE == PAY_ALIPAY) { return resultStatus.endsWith("9000"); } return false; } @Override public String toString() { return "resultStatus={" + resultStatus + "};memo={" + memo + "};result={" + result + "}"; } /** 對支付寶反饋結果的處理 */ private String gatValue(String content, String key) { String prefix = key + "={"; return content.substring(content.indexOf(prefix) + prefix.length(), content.lastIndexOf("}")); } public String getResultStatus() { return resultStatus; } public String getMemo() { return memo; } public String getResult() { return result; } public void setResultStatus(String resultStatus) { this.resultStatus = resultStatus; } public void setResult(String result) { this.result = result; } public void setMemo(String memo) { this.memo = memo; } }
其中支付寶在APP中的反饋的結果是這樣的:
resultStatus={9000};
memo={};
result={ partner="..." &seller_id="..." &out_trade_no="..." &sign="..." }
最後在Activity中添加支付功能express
Alipay pay = new Alipay(this); pay.setPayResultListener(this); pay.pay(bill_name,bill_body,bill_fee,bill_number); @Override public void OnPayResult(PayResult result){ // 支付寶移動端反饋 if(result.isSuccess()){ doTaskAsync(bill_id);//反饋給本身的服務器,訂單已支付 }else{ toast("支付未完成"); } }
若是參數設置正確的話,在android上集成支付寶就順利完成了json
進一步仔細研究,發現支付寶建議咱們將RSA簽名信息放到後臺。這裏作了進一步的集成,步驟以下:服務器
-> APP提供參數app
-> 服務器生成支付寶訂單信息框架
-> APP將信息提供給alipaysdk
-> 支付寶處理支付訂單
-> 支付寶請求notify_url
-> notify_url處理獲取支付寶的訂單反饋
咱們後臺使用的是PHP,一樣在以前的demo下載中有服務端代碼,複製php-uft8文件便可,結構以下:
其中,須要將key文件夾的rsa_private_key.pem換成本身生成的RSA密鑰便可。
修改配置文件(該處按照本身習慣有改動,可自行考慮),代碼以下:
/** alipay.config.php 支付寶配置文件*/ return array( 'partner' => '***', 'sellid' => '***', 'private_key_path' => '/../key/rsa_private_key.pem', 'ali_public_key_path' => '/../key/alipay_public_key.pem', 'sign_type' => strtoupper('RSA'), 'input_charset' => strtolower('utf-8'), 'cacert' => getcwd().'\\cacert.pem', 'transport' => 'http', 'notify_url' => 'http://../Alipay/notify_url.php' );
最後須要修改兩個重要的文件,一個是支付寶訂單信息的生成文件,一個是異步通知文件。
<?php /** AlipayFun.php * 支付寶輔助類 用於後臺生成 支付寶訂單信息 */ require_once(dirname(__FILE__) . '/' . 'alipay.config.php'); class AlipayFun{ /** 後臺生成 支付寶訂單 數據 */ function getAlipayOrderString($number,$subject,$content,$fee){ $alipay_config = require('alipay.config.php'); $parter = $alipay_config['partner']; $seller = $alipay_config['sellid']; $notify = $alipay_config['notify_url']; $prestr = 'partner="'.$parter.'"&seller_id="'.$seller.'"&out_trade_no="'.$number.'"&subject="'.$subject.'"&body="'.$subject.'"&total_fee="'.$fee.'"¬ify_url="'.$notify.'"'; $orderInfo =$prestr.'&service="mobile.securitypay.pay"&payment_type="1"&_input_charset="utf-8"&it_b_pay="30m"'; $key_path = $alipay_config['private_key_path']; $sign = $this->rsaSign($orderInfo,$key_path); //1, 加密 $sign = urlencode($sign); //2. 編碼 加密字符串 // $return_str = $orderInfo.'&sign="'.$sign.'"&sign_type="RSA"'; /** 一開始想在後臺一口氣所有生成訂單信息,發現簽名錯誤,這裏應該是urlencode的問題,php和java對urlencode(utf8)的處理可能不同;但奇怪的是,這裏須要php處理一遍urlencode,android再處理一遍urlencode,少了一方,都會報簽名錯誤 */ $info = array(); $info['order'] = $orderInfo; $info['rsa'] = $sign; return $info; } }
<?php /** notify_url.php * 功能:支付寶服務器異步通知頁面 */ ini_set('date.timezone','Asia/Shanghai'); require_once("lib/alipay_notify.class.php"); require_once("alipay.config.php"); // 須要配置文件和 功能文件 require("../index.php"); require("../billApi.php"); //須要添加框架中的index 和 controller $config = require('Alipay/alipay.config.php'); require_once("alipay.config.php"); require_once("lib/alipay_notify.class.php"); $alipayNotify = new AlipayNotify($config); $verify_result = $alipayNotify->verifyNotify(); //計算得出通知驗證結果 if($verify_result) {//驗證成功 //獲取支付寶的通知返回參數,可參考技術文檔中服務器異步通知參數列表 $out_trade_no = $_POST['out_trade_no']; //商戶訂單號 $trade_no = $_POST['trade_no']; //支付寶交易號 $trade_status = $_POST['trade_status']; //交易狀態 WAIT_BUYER_PAY $fee = $_POST['total_fee']; $buyer = $_POST['buyer_email']; logResult('success -> '.$out_trade_no.','.$trade_no.','.$trade_status.','.$fee.','.$buyer); if($_POST['trade_status'] == 'TRADE_FINISHED') { //判斷該筆訂單是否在商戶網站中已經作過處理 //若是沒有作過處理,根據訂單號(out_trade_no)在商戶網站的訂單系統中查到該筆訂單的詳細,並執行商戶的業務程序 //若是有作過處理,不執行商戶的業務程序 //注意: //退款日期超過可退款期限後(如三個月可退款),支付寶系統發送該交易狀態通知 //請判斷請求時的total_fee、seller_id與通知時獲取的total_fee、seller_id一致 } else if ($_POST['trade_status'] == 'TRADE_SUCCESS') { //注意: //付款完成後,支付寶系統發送該交易狀態通知 //請判斷請求時的total_fee、seller_id與通知時獲取的total_fee、seller_id一致 $bill = new billApi(); $pay_detail = array(); $pay_detail['number'] = $out_trade_no; $pay_detail['channel_no'] = $trade_no; $pay_detail['fee'] = $fee; $pay_detail['buyer'] = $buyer; $pay_detail['channel'] = 'alipay'; $bill->payDishBill($pay_detail); //處理後臺訂單的信息和狀態 } echo "success"; //請不要修改或刪除 } else { echo "fail"; // 驗證失敗 if(empty($_POST)) { logResult('fail-> post null'); } else { logResult('fail-> '.$_POST['trade_status'].$_POST['trade_no']); } }
此時,在後臺生成數據的方案中,android前端Activity請求代碼以下:
doTaskAsync(number,shopname,fee)); //向服務器請求數據 public void onJsonSuccess(String json) { //服務器返回訂單信息 Alipay pay = new Alipay(this); pay.setPayResultListener(this); try { JSONObject obj = new JSONObject(json); String orderInfo = obj.getString("order"); String sign = obj.getString("rsa"); sign = URLEncoder.encode(sign, "UTF-8"); String payInfo = orderInfo + "&sign=\"" + sign + "\"&sign_type=\"RSA\""; pay.pay(payInfo); } catch (Exception e) { e.printStackTrace(); } } @Override public void OnPayResult(PayResult result){ // 支付寶移動端反饋 if(result.isSuccess()){ finish(); }else{ toast("支付未完成"); } }
向alipaysdk發送的訂單數據必定要加「」,不然報錯
簽名、和簽名處理 必定不能有誤,不然報錯
後臺代碼中,必定要注意文件的路徑和權限。(主要是私鑰的路徑和log.txt的權限)
特別吐槽:rsa簽名,在後臺原本能夠直接生成訂單數據,但只能分別生成訂單前部分數據+rsa簽名數據,在客戶端對rsa簽名再進行URLEncoder,才能順利經過。