Android版-支付寶APP支付

此項目已開源 趕快來圍觀 Start支持下吧
客戶端開源地址-JPay】【服務端端開源地址-在com.javen.alipay
包名下

上一篇詳細介紹了微信APP支付 點擊這里

此篇文章來詳細介紹下支付寶APP支付

目錄
1、支付寶與微信對比(申請、費率、結算周期)
2、支付寶上線應用
3、支付寶App支付Android集成流程詳解
4、服務端實現(建議直接官方提供的服務端SDK)
5、客戶端實現
6、常見錯誤解決方案(ALI40247、AL38173)

補充(20170513) 支付寶APP支付可以使用沙箱環境測試。如需開啟測試模式只需要在OnCreate中添加如下代碼。沙箱環境測試APP支付中請使用沙箱版錢包測試:點擊開發者中心-沙箱環境-沙箱工具

EnvUtils.setEnv(EnvUtils.EnvEnum.SANDBOX);
使用沙箱環境測試

1、支付寶與微信對比

申請:【微信開發平臺】注冊的郵箱不能與騰訊其他產品同號,需要開發者資質認證(需要¥300,而且支付商戶與公眾號不公用)而支付寶只需要企業實名認證,其他質料類似(營業執照、組織機構代碼(現在都三證合一了))。

費率:
微信簽約匯率參考質料】【支付寶簽約匯率參考質料

結算周期:
微信結算周期: 話費通訊、財經資訊、股票軟件類、網絡虛擬服務以及互聯網相關行業 T+7 眾籌 T+3 其他T+1,結算款項自動提現到商戶的結算賬戶,一般從結算日起3個工作日到賬(具體到賬時間視不同銀行到賬情況而定)

支付寶結算周期:及時到支付寶賬號

2、支付寶上線應用

1、注冊賬號并實名認證

注冊賬號了沒有實名認證進入開發平臺的管理中心會提示如下圖

實名認證

為什么要實名認證

賬戶認證-商家認證流程介紹

商家認證流程
2、創建應用開通支付并上線

官方文檔有詳細的介紹-創建應用、配置應用、上線應用

資源下載:App支付DEMO&SDK生成與配置密鑰

資源下載匯總
簽名工具

注意簽名工具目錄不能包含中文

簽名工具的使用
上傳密鑰

這里上傳的是rsa_public_key.pem Java版簽名使用的密鑰是rsa_private_key_pkcs8.pem

3、支付寶App支付Android集成流程詳解

官方文檔 點擊這里

1、導入支付寶SDK 其中SDK在圖資源下載匯總WS.APP_PAY_SDK_BASE_20.zip

2、修改Manifest

在商戶應用工程的AndroidManifest.xml文件里面添加聲明:

<activity
            android:name="com.alipay.sdk.app.H5PayActivity"
            android:configChanges="orientation|keyboardHidden|navigation"
            android:exported="false"
            android:screenOrientation="behind" >
</activity>
<activity
            android:name="com.alipay.sdk.auth.AuthActivity"
            android:configChanges="orientation|keyboardHidden|navigation"
            android:exported="false"
            android:screenOrientation="behind" >
 </activity>

3、權限聲明

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

4、如需混洗、添加混淆規則

在商戶應用工程的proguard-project.txt里添加以下相關規則:
alipaySDK-xxxxx.jar根據下載的做響應的修改

-libraryjars libs/alipaySDK-xxxxx.jar
 
-keep class com.alipay.android.app.IAlixPay{*;}
-keep class com.alipay.android.app.IAlixPay$Stub{*;}
-keep class com.alipay.android.app.IRemoteServiceCallback{*;}
-keep class com.alipay.android.app.IRemoteServiceCallback$Stub{*;}
-keep class com.alipay.sdk.app.PayTask{ public *;}
-keep class com.alipay.sdk.app.AuthTask{ public *;}

5、支付接口調用

獲取PayTask支付對象調用支付(支付行為需要在獨立的非ui線程中執行
具體實現參考客戶端實現,調用支付需要使用到orderInfo 具體實現參考服務端實現

6、支付結果獲取和處理

調用pay方法支付后,將通過2種途徑獲得支付結果:

1、同步返回 商戶應用客戶端通過當前調用支付的Activity的Handler對象,通過它的回調函數獲取支付結果。
2、異步通知 商戶需要提供一個http協議的接口,包含在請求支付的入參中,其key對應notify_url。支付寶服務器在支付完成后,會以POST方式調用notify_url傳輸數據。

4、服務端實現

代碼來自Demo 并做簡單的封裝

具體實現截圖

設置支付寶業務參數

設置支付寶業務參數

注意: 支付寶App支付不支持沙盒模式(此坑糾結了很長時間),使用沙盒模式會出現ALI40247 錯誤,文末有錯誤的詳細說明。

核心實現代碼如下

/**
     * App支付
     */
    public void appPay(){
        String orderInfo;
        try {
            
            String body="我是測試數據";
            String passback_params="123";
            String subject="1";
            String total_amount="0.01";
            String notify_url="http://javen.ittun.com/alipay/pay_notify";
            
            String appId;
            String rsa_private;
            if (isDebug) {
                appId=prop.get("test_appId").trim();
                rsa_private=prop.get("test_rsa_private").trim();
System.out.println("test。。。。");
            }else {
                appId=prop.get("appId").trim();
                rsa_private=prop.get("rsa_private").trim();
            }
System.out.println("appId:"+appId);         
System.out.println("rsa_private:"+rsa_private);         
            
            BizContent content = new BizContent();
            content.setBody(body);
            content.setOut_trade_no(OrderInfoUtil2_0.getOutTradeNo());;
            
            content.setPassback_params(passback_params);
            
            content.setSubject(subject);
            
            content.setTotal_amount(total_amount);
            content.setProduct_code("QUICK_MSECURITY_PAY");
            
            
            Map<String, String> params = OrderInfoUtil2_0.buildOrderParamMap(appId,notify_url,content);
            String orderParam = OrderInfoUtil2_0.buildOrderParam(params);
            String sign = OrderInfoUtil2_0.getSign(params, rsa_private);
            orderInfo = orderParam + "&" + sign;
            log.info("orderInfo>"+orderInfo);
            result.success(orderInfo);
            renderJson(result);
            
        } catch (Exception e) {
            e.printStackTrace();
            result.addError("system error");
        }
    }

具體實現參考【開源項目

補充 使用支付服務端SDK實現獲取預付訂單

1、先獲取到AlipayClient

        alipayClient = new DefaultAlipayClient(serverUrl, appId, privateKey, format, charset, alipayPulicKey, signType);

2、使用AlipayTradeAppPayRequest封裝請求

/**
     * App支付
     * @param model
     * @param notifyUrl 
     * @return
     * @throws AlipayApiException
     */
    public static String startAppPayStr(AlipayTradeAppPayModel model, String notifyUrl) throws AlipayApiException{
        AlipayTradeAppPayResponse response = appPay(model,notifyUrl);
        return response.getBody();
    }
    
    /**
     * App 支付
     * https://doc.open.alipay.com/docs/doc.htm?treeId=54&articleId=106370&docType=1
     * @param model
     * @param notifyUrl 
     * @return
     * @throws AlipayApiException
     */
    public static AlipayTradeAppPayResponse appPay(AlipayTradeAppPayModel model, String notifyUrl) throws AlipayApiException{
        //實例化具體API對應的request類,類名稱和接口名稱對應,當前調用接口名稱:alipay.trade.app.pay
        AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
        //SDK已經封裝掉了公共參數,這里只需要傳入業務參數。以下方法為sdk的model入參方式(model和biz_content同時存在的情況下取biz_content)。
        request.setBizModel(model);
        request.setNotifyUrl(notifyUrl);
        //這里和普通的接口調用不同,使用的是sdkExecute
        AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
        return response;
    }

3、控制器封裝數據給客戶端

/**
     * app支付
     */
    public void appPay(){
        try {
            AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
            model.setBody("我是測試數據");
            model.setSubject("App支付測試-By Javen");
            model.setOutTradeNo(StringUtils.getOutTradeNo());
            model.setTimeoutExpress("30m");
            model.setTotalAmount("0.01");
            model.setPassbackParams("callback params");
            model.setProductCode("QUICK_MSECURITY_PAY");
            String orderInfo = AliPayApi.startAppPayStr(model,AliPayApi.notify_domain+"/alipay/app_pay_notify");
            result.success(orderInfo);
            renderJson(result);
            
        } catch (AlipayApiException e) {
            e.printStackTrace();
            result.addError("system error");
        }
    }

服務端異步通知

/**
     * App支付支付回調通知
     * https://doc.open.alipay.com/docs/doc.htm?treeId=54&articleId=106370&
     * docType=1#s3
     */
    public void app_pay_notify() {
        try {
            // 獲取支付寶POST過來反饋信息
            Map<String, String> params = AliPayApi.toMap(getRequest());
            for (Map.Entry<String, String> entry : params.entrySet()) {
                System.out.println(entry.getKey() + " = " + entry.getValue());
            }
            // 切記alipaypublickey是支付寶的公鑰,請去open.alipay.com對應應用下查看。
            // boolean AlipaySignature.rsaCheckV1(Map<String, String> params,
            // String publicKey, String charset, String sign_type)
            boolean flag = AlipaySignature.rsaCheckV1(params, AliPayApi.ALIPAY_PUBLIC_KEY, AliPayApi.CHARSET,
                    AliPayApi.SIGN_TYPE);
            if (flag) {
                // TODO
                System.out.println("success");
                renderText("success");
                return;
            } else {
                // TODO
                System.out.println("failure");
                renderText("failure");
            }
        } catch (AlipayApiException e) {
            e.printStackTrace();
            renderText("failure");
        }
    }

將異步通知的參數轉化為Map

/**
     * 將異步通知的參數轉化為Map
     * @param request
     * @return
     */
    public static Map<String, String> toMap(HttpServletRequest request) {
        System.out.println(">>>>" + request.getQueryString());
        Map<String, String> params = new HashMap<String, String>();
        Map<String, String[]> requestParams = request.getParameterMap();
        for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {
            String name = (String) iter.next();
            String[] values = (String[]) requestParams.get(name);
            String valueStr = "";
            for (int i = 0; i < values.length; i++) {
                valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
            }
            // 亂碼解決,這段代碼在出現亂碼時使用。
            // valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
            params.put(name, valueStr);
        }
        return params;
    }

5、客戶端實現

客戶端請求此action返回orderInfo,默認的訪問地址為http://[域名或者IP]:端口號/[項目名稱]/alipay/appPay

成功將返回:

{
  "code": 0,
  "data": "charset=utf-8&biz_content=%7B%22out_trade_no%22%3A%22120822453414812%22%2C%22total_amount%22%3A%220.01%22%2C%22subject%22%3A%221%22%2C%22body%22%3A%22%E6%88%91%E6%98%AF%E6%B5%8B%E8%AF%95%E6%95%B0%E6%8D%AE%22%2C%22product_code%22%3A%22QUICK_MSECURITY_PAY%22%2C%22passback_params%22%3A%22123%22%7D&method=alipay.trade.app.pay&format=json&notify_url=http%3A%2F%2Fjaven.ittun.com%2Falipay%2Fpay_notify&app_id=2016102000727659&sign_type=RSA&version=1.0&timestamp=2016-12-08+22%3A45%3A34&sign=m6fpNI58jkOIHROtm8Q2V1Ei7bxXc14JHtJEeqGM0B8dWlq3d%2FfAqpoQTZgeBb%2FVK%2B%2BydOJzVvSWc89dBxhYUq72KOeUwHiKlRJBFMnEMAZbJlRxqu9%2BRX2q7HSRmA6WRg75O68HZhkIhtO3bSNx3s710tMHmQCN230JoVWiwHw%3D",
  "message": null
}
/** 
 * 獲取支付寶App支付訂單信息 
 * @return 
 */ 
 public String getAliPayOrderInfo(Order order){
 String result=HttpKit.get(Constants.ALIPAY_URL);
 return result;
 } 

異步獲取orderInfo

public class AliPay extends AsyncTask<Object, Integer, String> {
    private Activity mContext;
    public AliPay(Activity context) {
        this.mContext = context;
    }
    
    @Override
    protected String doInBackground(Object... params) {
        return  IPayLogic.getIntance(mContext).getAliPayOrderInfo((Order)params[0]);
    }
    
    @Override
    protected void onPostExecute(String result) {
        try {
            if (result!=null) {
                System.out.println("AliPay result>"+result);
                JSONObject data = new JSONObject(result);
                String message = data.getString("message");
                int code = data.getInt("code");

                if(code == 0){
                    String orderInfo = data.getString("data");

                    Toast.makeText(mContext, "正在調起支付", Toast.LENGTH_SHORT).show();
                    IPayLogic.getIntance(mContext).startAliPay(orderInfo);

                }else{

                    Log.d("PAY_GET", "返回錯誤"+message);
//                  Toast.makeText(mContext, "返回錯誤:"+message, Toast.LENGTH_SHORT).show();
                }
            }else {
                System.out.println("get  AliPay exception, is null");
            }
        } catch (Exception e) {
            Log.e("PAY_GET", "異常:"+e.getMessage());
            Toast.makeText(mContext, "異常:"+e.getMessage(), Toast.LENGTH_SHORT).show();
        }
        super.onPostExecute(result);
    }

}

支付接口調用

public void startAliPay(final String orderInfo){
         Runnable payRunnable = new Runnable() {
             @Override 
             public void run() { 
                 PayTask alipay = new PayTask(mContext);
                 Map<String, String> result = alipay.payV2(orderInfo, true);
                 Message msg = new Message();
                 msg.obj = result;
                 mHandler.sendMessage(msg);
             } 
         }; 
         Thread payThread = new Thread(payRunnable);
         payThread.start();
     } 

客戶端通過回調函數獲取支付結果

private Handler mHandler = new Handler(Looper.getMainLooper()) {
        @SuppressWarnings("unchecked") 
        public void handleMessage(Message msg) { 
            PayResult payResult = new PayResult((Map<String, String>) msg.obj); 
            System.out.println("alipay call "+payResult.toString()); 
            String resultStatus = payResult.getResultStatus(); 
            String memo = payResult.getMemo(); 
            if (Constants.payListener !=null){ 
                //返回狀態以及詳細的描述參考 
                // https://doc.open.alipay.com/doc2/detail.htm?spm=a219a.7629140.0.0.xN1NnL&treeId=204&articleId=105302&docType=1 
                Constants.payListener.onPay(-2,resultStatus,memo); 
            } 
        } 
    }; 

客戶端具體使用方法

 public void testAliPay(View view){
        Toast.makeText(this, "支付寶測試", Toast.LENGTH_SHORT).show();
 
        Order order = new Order();
        order.setBody("會員充值中心");
        order.setParaTradeNo(System.currentTimeMillis()+"");
        order.setTotalFee(20);
        order.setAttach("json");//附加參數
        order.setNofityUrl("http://www.xxxx.com");//支付成功服務端回調通知的地址
 
        IPay.getIntance(MainActivity.this).toPay(IPay.PayMode.ALIPAY,order,new IPay.IPayListener() {
 
            @Override 
            public void onPay(int wxcode, String alicode, String message) {
                System.out.println("回調過來的狀態》"+alicode+" message>"+((message!=null && message.isEmpty())?"":message));
                Toast.makeText(MainActivity.this, "回調過來的狀態》" + alicode, Toast.LENGTH_SHORT).show();
            } 
        }); 
    } 

6、常見錯誤解決方案

APP支付報錯ALI40247處理方案

ALI40247的錯誤
**有2種情況 1、沒有權限 2、簽名失敗 **

可以查看論壇提供的這個解決方案

AL38173的錯誤一般是參數問題造成的,建議檢查請求參數,對照文檔查看,比如:參數少了、多了、亂碼、名稱不對,還有必傳參數是否都請求提交給支付寶了等等.

如果在網上查閱質料還未解決,可以尋求阿里的在線支持【支持中心

此項目已開源 趕快來圍觀 Start支持下吧

客戶端開源地址-JPay】【服務端端開源地址-在com.javen.alipay包名下

推薦閱讀
Android版-微信APP支付
極速開發微信公眾號之微信買單
極速開發微信公眾號之公眾號支付
極速開發微信公眾號之掃碼支付
極速開發微信公眾號之刷卡支付
極速開發微信公眾號之現金紅包

記錄學習的點滴,以此勉勵不斷奮斗的自己?????? 如果對你有幫助記得點喜歡告訴我哈

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,489評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,290評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,510評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,866評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,036評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,585評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,331評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,536評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,754評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,273評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,505評論 2 379

推薦閱讀更多精彩內容