集成第三方-支付寶那些事

這兩天,在移動APP上集成了支付寶支付功能,費了一些周折,除了其餘博客上提到的一些問題,這裏分享一下本身的經驗php

Android客戶端代碼集成

一、準備

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 += "&notify_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


PHP服務器端代碼集成

進一步仔細研究,發現支付寶建議咱們將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.'"&notify_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客戶端 添加支付

此時,在後臺生成數據的方案中,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("支付未完成");
            }
    }

吐槽

  1. 向alipaysdk發送的訂單數據必定要加「」,不然報錯

  2. 簽名、和簽名處理 必定不能有誤,不然報錯

  3. 後臺代碼中,必定要注意文件的路徑和權限。(主要是私鑰的路徑和log.txt的權限)

  4. 特別吐槽:rsa簽名,在後臺原本能夠直接生成訂單數據,但只能分別生成訂單前部分數據+rsa簽名數據,在客戶端對rsa簽名再進行URLEncoder,才能順利經過。

相關文章
相關標籤/搜索