2

这两天,在移动APP上集成了支付宝支付功能,费了一些周折,除了其他博客上提到的一些问题,这里分享一下自己的经验

Android客户端代码集成

1、准备

a 注册支付宝商家账号
b 开通移动支付功能
c 生成RSA私钥和公钥,上传自己的公钥给支付宝 官网方法

2、类库下载

支付宝demo下载,将alipaysdk.jar放到自己的项目中。

3、代码分析

(我自己将支付集成在一个project,作为android library使用)

/** 支付宝类  */
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处理,然后反馈数据.关于反馈,我定义了一个类和一个接口,代码如下:

/** 支付反馈接口*/
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中添加支付功能

  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上集成支付宝就顺利完成了


PHP服务器端代码集成

进一步仔细研究,发现支付宝建议我们将RSA签名信息放到后台。这里做了进一步的集成,步骤如下:

  • -> APP提供参数

  • -> 服务器生成支付宝订单信息

  • -> APP将信息提供给alipaysdk

  • -> 支付宝处理支付订单

  • -> 支付宝请求notify_url

  • -> notify_url处理获取支付宝的订单反馈

我们后台使用的是PHP,同样在之前的demo下载中有服务端代码,复制php-uft8文件即可,结构如下:
图片描述

其中,需要将key文件夹的rsa_private_key.pem换成自己生成的RSA密钥即可。

1、配置文件

修改配置文件(该处按照自己习惯有改动,可自行考虑),代码如下:

/** 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'
);

最后需要修改两个重要的文件,一个是支付宝订单信息的生成文件,一个是异步通知文件。

3、订单生成文件

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

3、异步通知文件

 <?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,才能顺利通过。


edgardeng
83 声望3 粉丝