Android 支付宝App支付集成

时间:2024-03-26 22:50:18

Android 支付宝App支付集成

 

 

总结

Android 支付流程:(与android前端有关的步骤,下面有详细流程图)

 (1) 请求生成订单(签名)

(2) 返回签名后订单信息

---------------------------------------后台

(3) 调用支付宝SDK,进行支付

(4) 返回支付结果

               -----------------------------------------支付宝

(5) 将支付结果返回给后台,验签,解析

(6) 返回最终支付结果

---------------------------------------后台

 

Android 实际操作:

(1) 创建应用并申请APPID

(2) 配置应用,添加支付功能(签约),配置双方秘钥

(3) 搭建开发环境,添加依赖SDK

(4) 修改Manifest.xml

(5) 初始化支付宝client

AlipayClient alipayClient = new DefaultAlipayClient(URL, APP_ID, APP_PRIVATE_KEY, FORMAT, CHARSET, ALIPAY_PUBLIC_KEY, SIGN_TYPE);

(6) 支付接口调用

final String orderInfo = info;   // 订单信息

Runnable payRunnable = new Runnable() {

@Override

public void run() {

     PayTask alipay = new PayTask(DemoActivity.this);

     String result = alipay.payV2(orderInfo,true);

     Message msg = new Message();

     msg.what = SDK_PAY_FLAG;

     msg.obj = result;

    mHandler.sendMessage(msg);

}

};

     // 必须异步调用

Thread payThread = new Thread(payRunnable);

payThread.start();

 

(7) 支付结果获取及通知

private Handler mHandler = new Handler() {

public void handleMessage(Message msg) {

Result result = new Result((String) msg.obj);

Toast.makeText(DemoActivity.this, result.getResult(),

Toast.LENGTH_LONG).show();

};

};

接入文档:https://docs.open.alipay.com/204/105297/

https://docs.open.alipay.com/204/105296/

 

 

 

 

详细介绍

 

一、产品流程

(1)用户已安装支付宝支付流程
步骤1:用户在商家App中选择商品下单、确认购买,进入支付环节,选择支付宝,用户点击确认支付,如图1;
步骤2:进入到支付宝页面,调起支付宝支付,出现确认支付界面,如图2;
步骤3:用户确认收款方和金额,点击立即支付后出现输入密码界面,如图3;

Android 支付宝App支付集成

步骤4:输入正确密码后,支付宝端显示支付结果,如图4;
步骤5:自动回跳到商家App中,商家根据付款结果个性化展示订单处理结果,如图5。

Android 支付宝App支付集成

 

 

(2)用户未安装支付宝支付流程
步骤1:用户在商家App中选择商品下单、确认购买,进入支付环节,选择支付宝,用户点击确认支付,如图1;
步骤2:用户未安装支付宝客户端,则调起支付宝网页支付收银台,用户登录支付宝账户,如图2;
步骤3:登录成功后,进入确认付款页面,如图3;

Android 支付宝App支付集成

步骤4:用户点击确认付款,进入支付密码页面,如下图4;
步骤5:用户输入密码,完成支付,展示支付结果,如图5。

 

Android 支付宝App支付集成

 

 

 

二、系统交互流程

Android 支付宝App支付集成

如图,以Android平台为例:
图中虚线标识商户链路,实线标识支付宝链路。
第4步:调用支付接口:此消息就是本接口所描述的支付宝客户端SDK提供的支付对象PayTask,将商户签名后的订单信息传进payv2方法唤起支付宝收银台,交易数据格式具体参见请求参数说明
第5步:支付请求:支付宝客户端SDK将会按照商户客户端提供的请求参数发送支付请求。
第8步:接口返回支付结果:商户客户端在第4步中调用的支付接口,会返回最终的支付结果(即同步通知),参见客户端同步返回
第13步:用户在支付宝APP或H5收银台完成支付后,会根据商户在手机网站支付API中传入的前台回跳地址return_url自动跳转回商户页面,同时在URL请求中附带上支付结果参数。同时,支付宝还会根据原始支付API中传入的异步通知地址notify_url,通过POST请求的形式将支付结果作为参数通知到商户系统,详情见支付结果异步通知

 

特别注意:

构造交易数据并签名必须在商户服务端完成,商户的应用私钥绝对不能保存在商户APP客户端中,也不能从服务端下发。

同步返回的数据,只是一个简单的结果通知,商户确定该笔交易付款是否成功需要依赖服务端收到支付宝异步通知的结果进行判断。

商户系统接收到通知以后,必须通过验签(验证通知中的sign参数)来确保支付通知是由支付宝发送的。建议使用支付宝提供的SDK来完成,详细验签规则参考异步通知验签

 

 

三、Android快速集成

导入开发资源

1.将alipaySdk-xxxxxxxx.jar包放入商户应用工程的libs目录下,如下图。

Android 支付宝App支付集成

2.右键 ---->Add to Favorites ---->当前APPMODULE

Android 支付宝App支付集成

或者在app module下的build.gradle下手动添加依赖,如下代码所示:

dependencies {

    ......

    compile files('libs/alipaySdk-20170725.jar')

    ......

}

修改Manifest

在商户应用工程的AndroidManifest.xml文件里面添加声明:

<activity

    android:name="com.alipay.sdk.app.H5PayActivity"

    android:configChanges="orientation|keyboardHidden|navigation|screenSize"

    android:exported="false"

    android:screenOrientation="behind"

    android:windowSoftInputMode="adjustResize|stateHidden" >

</activity>

 <activity

    android:name="com.alipay.sdk.app.H5AuthActivity"

    android:configChanges="orientation|keyboardHidden|navigation"

    android:exported="false"

    android:screenOrientation="behind"

    android:windowSoftInputMode="adjustResize|stateHidden" >

</activity>

和权限声明:

<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" />

添加混淆规则

在商户应用工程的proguard-project.txt里添加以下相关规则:

-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 *;}

-keep class com.alipay.sdk.app.H5PayCallback {

    <fields>;

    <methods>;

}

-keep class com.alipay.android.phone.mrpc.core.** { *; }

-keep class com.alipay.apmobilesecuritysdk.** { *; }

-keep class com.alipay.mobile.framework.service.annotation.** { *; }

-keep class com.alipay.mobilesecuritysdk.face.** { *; }

-keep class com.alipay.tscenter.biz.rpc.** { *; }

-keep class org.json.alipay.** { *; }

-keep class com.alipay.tscenter.** { *; }

-keep class com.ta.utdid2.** { *;}

-keep class com.ut.device.** { *;}

 

 

支付接口调用

需要在新线程中调用支付接口。(可参考alipay_demo实现)

PayTask对象主要为商户提供订单支付、查询功能,及获取当前开发包版本号。
获取PayTask支付对象调用支付(支付行为需要在独立的非ui线程中执行),代码示例:

 

public void payV2() {
  /**
     * 这里只是为了方便直接向商户展示支付宝的整个支付流程;所以Demo中加签过程直接放在客户端完成;
     * 真实App里,privateKey等数据严禁放在客户端,加签过程务必要放在服务端完成;
     * 防止商户私密数据泄露,造成不必要的资金损失,及面临各种安全风险;
     *
     * orderInfo的获取必须来自服务端;
     */
 boolean rsa2 = (Constant.RSA2_PRIVATE.length() > 0);
Map<String, String> params = OrderInfoUtil2_0.buildOrderParamMap(Constant.APPID, rsa2);
    String orderParam = OrderInfoUtil2_0.buildOrderParamString(params);

    String privateKey = rsa2 ? Constant.RSA2_PRIVATE : Constant.RSA_PRIVATE;
    String sign = OrderInfoUtil2_0.getSign(params, privateKey, rsa2);
    final String orderInfo = orderParam + "&" + sign;

    Log.e("orderInfo",orderInfo);

Runnable payRunnable = new Runnable() {

        @Override
        public void run() {
            PayTask alipay = new PayTask(OrderInfoActivity.this);
            Map<String, String> result = alipay.payV2(orderInfo, true);
            Log.i("msp", result.toString());

            Message msg = new Message();
            msg.what = SDK_PAY_FLAG;
            msg.obj = result;
            mHandler.sendMessage(msg);
        }
    };

Thread payThread = new Thread(payRunnable);
 payThread.start();

}

 

@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
    @SuppressWarnings("unused")
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case SDK_PAY_FLAG: {
     @SuppressWarnings("unchecked")
                PayResult payResult = new PayResult((Map<String, String>) msg.obj);
                /**
                 对于支付结果,请商户依赖服务端的异步通知结果。同步通知结果,仅作为支付结束的通知。
                 */
                String resultInfo = payResult.getResult();// 同步返回需要验证的信息
                String resultStatus = payResult.getResultStatus();
                // 判断resultStatus 为9000则代表支付成功
       if (TextUtils.equals(resultStatus, "9000")) {
                    // 该笔订单是否真实支付成功,需要依赖服务端的异步通知。
                    Toast.makeText(OrderInfoActivity.this, "支付成功",

Toast.LENGTH_SHORT).show();
                }else {
                    // 该笔订单真实的支付结果,需要依赖服务端的异步通知。
                    Toast.makeText(OrderInfoActivity.this, "支付失败",   Toast.LENGTH_SHORT).show();
                }

break;
            }

     default:
                break;
        }

   };
};

 

订单详情处理(应该在后台处理)

OrderInfoUtil2_0


package com.skt.itrip.alipay;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Random;

public class OrderInfoUtil2_0 {
   
   /**
    * 构造授权参数列表
    *
    * @param pid
    * @param app_id
    * @param target_id
    * @return
    */
   public static Map<String, String> buildAuthInfoMap(String pid, String app_id, String target_id, boolean rsa2) {
      Map<String, String> keyValues = new HashMap<String, String>();

      // 商户签约拿到的app_id,如:2013081700024223
      keyValues.put("app_id", app_id);

      // 商户签约拿到的pid,如:2088102123816631
      keyValues.put("pid", pid);

      // 服务接口名称, 固定值
      keyValues.put("apiname", "com.alipay.account.auth");

      // 商户类型标识, 固定值
      keyValues.put("app_name", "mc");

      // 业务类型, 固定值
      keyValues.put("biz_type", "openservice");

      // 产品码, 固定值
      keyValues.put("product_id", "APP_FAST_LOGIN");

      // 授权范围, 固定值
      keyValues.put("scope", "kuaijie");

      // 商户唯一标识,如:kkkkk091125
      keyValues.put("target_id", target_id);

      // 授权类型, 固定值
      keyValues.put("auth_type", "AUTHACCOUNT");

      // 签名类型
      keyValues.put("sign_type", rsa2 ? "RSA2" : "RSA");

      return keyValues;
   }

   /**
    * 构造支付订单参数列表
    * @param app_id
    * @return
    */
   public static Map<String, String> buildOrderParamMap(String app_id, boolean rsa2) {
      Map<String, String> keyValues = new HashMap<String, String>();

      keyValues.put("app_id", app_id);

      keyValues.put("biz_content", "{\"timeout_express\":\"30m\",\"product_code\":\"QUICK_MSECURITY_PAY\",\"total_amount\":\"0.01\",\"subject\":\"1\",\"body\":\"我是测试数据\",\"out_trade_no\":\"" + getOutTradeNo() +  "\"}");
      
      keyValues.put("charset", "utf-8");

      keyValues.put("method", "alipay.trade.app.pay");

      keyValues.put("sign_type", rsa2 ? "RSA2" : "RSA");

      keyValues.put("timestamp", "2016-07-29 16:55:53");

      keyValues.put("version", "1.0");
      
      return keyValues;
   }
   
   /**
    * 构造支付订单参数信息
    *
    * @param map
    * 支付订单参数
    * @return
    */
   public static String buildOrderParamString(Map<String, String> map) {
      List<String> keys = new ArrayList<String>(map.keySet());

      StringBuilder sb = new StringBuilder();
      for (int i = 0; i < keys.size() - 1; i++) {
         String key = keys.get(i);
         String value = map.get(key);
         sb.append(buildKeyValue(key, value, true));
         sb.append("&");
      }

      String tailKey = keys.get(keys.size() - 1);
      String tailValue = map.get(tailKey);
      sb.append(buildKeyValue(tailKey, tailValue, true));

      return sb.toString();
   }
   
   /**
    * 拼接键值对
    *
    * @param key
    * @param value
    * @param isEncode
    * @return
    */
   private static String buildKeyValue(String key, String value, boolean isEncode) {
      StringBuilder sb = new StringBuilder();
      sb.append(key);
      sb.append("=");
      if (isEncode) {
         try {
            sb.append(URLEncoder.encode(value, "UTF-8"));
         } catch (UnsupportedEncodingException e) {
            sb.append(value);
         }
      } else {
         sb.append(value);
      }
      return sb.toString();
   }
   
   /**
    * 对支付参数信息进行签名
    *
    * @param map
    *            待签名授权信息
    *
    * @return
    */
   public static String getSign(Map<String, String> map, String rsaKey, boolean rsa2) {
      List<String> keys = new ArrayList<String>(map.keySet());
      // key排序
      Collections.sort(keys);

      StringBuilder authInfo = new StringBuilder();
      for (int i = 0; i < keys.size() - 1; i++) {
         String key = keys.get(i);
         String value = map.get(key);
         authInfo.append(buildKeyValue(key, value, false));
         authInfo.append("&");
      }

      String tailKey = keys.get(keys.size() - 1);
      String tailValue = map.get(tailKey);
      authInfo.append(buildKeyValue(tailKey, tailValue, false));

      String oriSign = SignUtils.sign(authInfo.toString(), rsaKey, rsa2);
      String encodedSign = "";

      try {
         encodedSign = URLEncoder.encode(oriSign, "UTF-8");
      } catch (UnsupportedEncodingException e) {
         e.printStackTrace();
      }
      return "sign=" + encodedSign;
   }
   
   /**
    * 要求外部订单号必须唯一。
    * @return
    */
   private 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;
   }

}

 

四、App支付请求参数说明

文档很详细

https://docs.open.alipay.com/204/105465/