Android学习系列(28)–App集成支付宝

标签: 设计 | 发表时间:2012-04-04 22:51 | 作者:谦虚的天下
出处:http://www.youfind.com.cn

手机的在线支付,被认为是2012年最看好的功能,我个人认为这也是移动互联网较传统互联网将会大放光彩的一个功能。
人人有手机,人人携带手机,花钱买东西,不再需要取钱付现,不再需要回家上网银,想买什么,扫描一下,或者搜索一下,然后下单,不找零,直接送到你家,这将是手机支付给我们带来的全新交易体验。
谷歌刚推出了谷歌钱包,这必是我们后面要使用的主要手段,但是鉴于当前国情,我觉得有必要介绍一下android手机集成支付宝功能。 

1.下载官方架包和说明文档
其实官方已经提供了安装指南,下载地址:
https://mobiless.alipay.com/product/product_down_load.htm?code=SECURITY_PAY
里面有有个pdf,详细说明了说用指南,写的比较详细,可以重点参考。


下载下来,我们主要是用到Android(20120104)目录下的alipay_plugin.jar和AppDemo/assets下的alipay_plugin223_0309.apk,这两个文件是我们不能修改的支付宝api和安装包。

2. 商户签约
现在的安全机制,都是这样,客户端需要先和服务端请求验证后才能进行进一步操作,oauth也是如此。
打开https://ms.alipay.com/,登陆支付宝,点击签约入口,选择"应用类产品",填写并等待审核,获取商户ID和账户ID。
签约的时候还要向需要提供实名认证和上传应用,所以我建议先把应用做好了,最后再集成支付宝。


我大概等了1-2天审核,审核是失败的,回复是应用类型啥的应该是"虚拟货币",我改成那个马上自动就审核通过了。

3.密钥配置
解压openssl-0.9.8k_WIN32(RSA密钥生成工具).zip,打开cmd,命令行进入openssl-0.9.8k_WIN32(RSA密钥生成工具)\bin目录下,
(1).执行

openssl genrsa  -out rsa_private_key.pem 1024

生成rsa_private_key.pem文件。
(2).再执行

openssl rsa  -in rsa_private_key.pem  -pubout -out rsa_public_key.pem

生成rsa_public_key.pem 文件。
(3).在执行

openssl pkcs8  -topk8  -inform PEM  -in rsa_private_key.pem  -outform PEM  -nocrypt

将RSA私钥转换成 PKCS8 格式,去掉begin和end那两行,把里面的内容拷贝出来,保存到某个txt中,如rsa_private_pkcs8_key.txt中(我好像没用到这个)。
打开rsa_public_key.pem,即商户的公钥,复制到一个新的TXT中,删除文件头”-----BEGIN PUBLIC KEY-----“与文件尾”-----END PUBLIC KEY-----“还有空格、换行,变成一行字符串并保存该 TXT 文件,然后在网站的“我的商家服务”切换卡下的右边点击“密钥管理”,然后有个"上传商户公钥(RSA)"项,选择上传刚才的TXT文件.
好了,服务器配置OK,因为这一段之前没有截图,现在弄好了又不好截图,如果有不明白的地方请大家参考官方文档。 

4.引用jar和包含安装包
(1).新建android工程;
(2).copy上面说的alipay_plugin.jar到工程的libs目录下,并在java build path中通过Add External JARs找到并引用该jar;
(3).copy上面说的alipay_plugin223_0309.apk安装包到assets目录下,后面配置路径用到。

如果libs和assets目录没有,手动建立者两个目录。

5.调用代码整理
这里我们要严重的参考文档中AppDemo,我们建一个包com.tianxia.lib.baseworld.alipay,把AppDemo的com.alipay.android.appDemo4包下的源码全部copy到刚才我们自己的包下,还有res目录下的资源文件也合并到我们工程res下。
其中AlixDemo.java,ProductListAdapter.java,Products.java是示例类,我们借鉴完后可以删除。
PartnerConfig.java是配置类,配置商户的一些配置参数。
其他的类是严重参考类,直接留下使用。
PartnerConfig.java代码如下:

public class PartnerConfig {
	//合作商户ID。用签约支付宝账号登录ms.alipay.com后,在账户信息页面获取。
	public static final String PARTNER = "xxx";
	//账户ID。用签约支付宝账号登录ms.alipay.com后,在账户信息页面获取。
	public static final String SELLER = "xxx";
	//商户(RSA)私钥 ,即rsa_private_key.pem中去掉首行,最后一行,空格和换行最后拼成一行的字符串
	public static final String RSA_PRIVATE = "xxx";
	//支付宝(RSA)公钥  用签约支付宝账号登录ms.alipay.com后,在密钥管理页面获取。
	public static final String RSA_ALIPAY_PUBLIC = "xxx";
	//下面的配置告诉应用去assets目录下找安装包
	public static final String ALIPAY_PLUGIN_NAME ="alipay_plugin223_0309.apk";
}

AlixDemo中代码是最终的调用代码在onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {}中,下面我们提取其中的核心代码。

6.提取核心调用代码
在AlixDemo.java同目录下新建AlixPay.java,来提取AlixDemo.java的核心代码:

package com.tianxia.lib.baseworld.alipay;

import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;

import com.tianxia.lib.baseworld.R;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.os.Handler;
import android.os.Message;
import android.view.KeyEvent;
import android.widget.Toast;

public class AlixPay {

    static String TAG = "AlixPay";

    private Activity mActivity;
    public AlixPay(Activity activity) {
        mActivity = activity;
    }

    private ProgressDialog mProgress = null;

    // the handler use to receive the pay result.
    private Handler mHandler = new Handler() {
        public void handleMessage(Message msg) {
            try {
                String strRet = (String) msg.obj;

                switch (msg.what) {
                case AlixId.RQF_PAY: {

                    closeProgress();

                    BaseHelper.log(TAG, strRet);

                    try {
                        String memo = "memo=";
                        int imemoStart = strRet.indexOf("memo=");
                        imemoStart += memo.length();
                        int imemoEnd = strRet.indexOf(";result=");
                        memo = strRet.substring(imemoStart, imemoEnd);

                        ResultChecker resultChecker = new ResultChecker(strRet);

                        int retVal = resultChecker.checkSign();
                        if (retVal == ResultChecker.RESULT_CHECK_SIGN_FAILED) {
                            BaseHelper.showDialog(
                                    mActivity,
                                    "提示",
                                    mActivity.getResources().getString(
                                            R.string.check_sign_failed),
                                    android.R.drawable.ic_dialog_alert);
                        } else {
                            BaseHelper.showDialog(mActivity, "提示", memo,
                                    R.drawable.infoicon);
                        }
                        
                    } catch (Exception e) {
                        e.printStackTrace();

                        BaseHelper.showDialog(mActivity, "提示", strRet,
                                R.drawable.infoicon);
                    }
                }
                    break;
                }

                super.handleMessage(msg);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };

    // close the progress bar
    void closeProgress() {
        try {
            if (mProgress != null) {
                mProgress.dismiss();
                mProgress = null;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void pay() {
        MobileSecurePayHelper mspHelper = new MobileSecurePayHelper(mActivity);
        boolean isMobile_spExist = mspHelper.detectMobile_sp();
        if (!isMobile_spExist)
            return;

        if (!checkInfo()) {
            BaseHelper.showDialog(mActivity, "提示",
                    "缺少partner或者seller,", R.drawable.infoicon);
            return;
        }

        try {
            // prepare the order info.
            String orderInfo = getOrderInfo();
            String signType = getSignType();
            String strsign = sign(signType, orderInfo);
            strsign = URLEncoder.encode(strsign);
            String info = orderInfo + "&sign=" + "\"" + strsign + "\"" + "&"
                    + getSignType();
            
            // start the pay.
            MobileSecurePayer msp = new MobileSecurePayer();
            boolean bRet = msp.pay(info, mHandler, AlixId.RQF_PAY, mActivity);
            
            if (bRet) {
                // show the progress bar to indicate that we have started paying.
                closeProgress();
                mProgress = BaseHelper.showProgress(mActivity, null, "正在支付", false,
                        true);
            } else
                ;
        } catch (Exception ex) {
            Toast.makeText(mActivity, R.string.remote_call_failed,
                    Toast.LENGTH_SHORT).show();
        }
        
    }

    private boolean checkInfo() {
        String partner = PartnerConfig.PARTNER;
        String seller = PartnerConfig.SELLER;
        if (partner == null || partner.length() <= 0 || seller == null
                || seller.length() <= 0)
            return false;

        return true;
    }


    // get the selected order info for pay.
    String getOrderInfo() {
        String strOrderInfo = "partner=" + "\"" + PartnerConfig.PARTNER + "\"";
        strOrderInfo += "&";
        strOrderInfo += "seller=" + "\"" + PartnerConfig.SELLER + "\"";
        strOrderInfo += "&";
        strOrderInfo += "out_trade_no=" + "\"" + getOutTradeNo() + "\"";
        strOrderInfo += "&";
        //这笔交易价钱
        strOrderInfo += "subject=" + "\"" + mActivity.getString(R.string.donate_subject) + "\"";
        strOrderInfo += "&";
        //这笔交易内容
        strOrderInfo += "body=" + "\"" + mActivity.getString(R.string.donate_body) + "\"";
        strOrderInfo += "&";
        //这笔交易价钱
        strOrderInfo += "total_fee=" + "\"" + "10.00" + "\"";
        strOrderInfo += "&";
        strOrderInfo += "notify_url=" + "\""
                + "http://notify.java.jpxx.org/index.jsp" + "\"";

        return strOrderInfo;
    }

    // get the out_trade_no for an order.
    String getOutTradeNo() {
        SimpleDateFormat format = new SimpleDateFormat("MMddHHmmss");
        Date date = new Date();
        String strKey = format.format(date);

        java.util.Random r = new java.util.Random();
        strKey = strKey + r.nextInt();
        strKey = strKey.substring(0, 15);
        return strKey;
    }

    // get the sign type we use.
    String getSignType() {
        String getSignType = "sign_type=" + "\"" + "RSA" + "\"";
        return getSignType;
    }

    // sign the order info.
    String sign(String signType, String content) {
        return Rsa.sign(content, PartnerConfig.RSA_PRIVATE);
    }

    // the OnCancelListener for lephone platform.
    static class AlixOnCancelListener implements
            DialogInterface.OnCancelListener {
        Activity mcontext;

        AlixOnCancelListener(Activity context) {
            mcontext = context;
        }

        public void onCancel(DialogInterface dialog) {
            mcontext.onKeyDown(KeyEvent.KEYCODE_BACK, null);
        }
    }
}

这个类的pay方法就是支付的方法,最简单的不设置的话,调用方法如下:

AlixPay alixPay = new AlixPay(SettingTabActivity.this);
alixPay.pay();

如果没有安装支付宝,它会提示你安装,如果已经安装,它直接让你选择付款:

这说明已经配置成功了。
然后可以删掉那些示例java文件了: AlixDemo.java,ProductListAdapter.java,Products.java。 
你也可以通过调整参数来修改订单信息,如主题,价格等。
另外在BaseHelper的94行:

dialog.setOnCancelListener( new AlixDemo.AlixOnCancelListener( (Activity)context ) );

需要修改为:

dialog.setOnCancelListener( new AlixPay.AlixOnCancelListener( (Activity)context ) );

7.注意
我在测试的时候,调用的activity是框在一个ActivityGroup里的(与tabhost类似,据说tabhost也有这个问题),导致MobileSecurePayer.java的pay方法中调用服务的两行代码:

mActivity.bindService(new Intent(IAlixPay.class.getName()), mAlixPayConnection, Context.BIND_AUTO_CREATE);
mActivity.unbindService(mAlixPayConnection);

需要修改为:

mActivity.getApplicationContext().bindService(new Intent(IAlixPay.class.getName()), mAlixPayConnection, Context.BIND_AUTO_CREATE);
mActivity.getApplicationContext().unbindService(mAlixPayConnection);

不然会报错java.lang.ClassCastException: android.os.BinderProxy cannot be cast to com.android.server.am.ActivityRecord$Token...

8.小结
支付宝的集成比我想象的要复杂一些,比较麻烦,首先需要审核,然后代码需要提取,所以写出来与大家分享。 
在做集成配置的时候,一定要仔细认真,一个地方出错,可能要导致后面查错查很长时间。
因为本人是先集成成功后才写的这篇文章,难免会漏掉一些重要的细节或者步骤,如有不对,请留言指正。 

本文链接

相关 [android 学习 系列] 推荐:

Android学习系列(28)–App集成支付宝

- - 有方网-一切如此简单
手机的在线支付,被认为是2012年最看好的功能,我个人认为这也是移动互联网较传统互联网将会大放光彩的一个功能. 人人有手机,人人携带手机,花钱买东西,不再需要取钱付现,不再需要回家上网银,想买什么,扫描一下,或者搜索一下,然后下单,不找零,直接送到你家,这将是手机支付给我们带来的全新交易体验. 谷歌刚推出了谷歌钱包,这必是我们后面要使用的主要手段,但是鉴于当前国情,我觉得有必要介绍一下android手机集成支付宝功能.

Android学习系列(35)--App应用之启动界面SplashActivity

- - 博客园_首页
当前比较成熟一点的应用基本上都会在进入应用之显示一个启动界面. 这个启动界面或简单,或复杂,或简陋,或华丽,用意不同,风格也不同. 下面来观摩几个流行的应用的启动界面.. 以腾讯qq,新浪weibo,UC浏览器,游戏神庙逃亡等7个应用为例,比比看:. (我认为最精美的界面应该是qq2012,虽然只有一张图,基本的应用名称,版本,图标这些信息都有,但是看着舒服,觉得美.).

android NDK的学习

- - CSDN博客推荐文章
NDK是基于系统原生的C/C++的开发,但是它不是一种主流,而是Android SDK开发的有益补充,因为NDK没有提供界面,也没有提供生命周期管理这一类环境. NDK是一系列的工具包,使用这些工具包能够让我们很方便的进行JNI的开发. Java native interface,JNI就是java和C/C++相互调用的接口.

android学习路线图

- Enlizh - Starming星光社最新更新
      首先表扬一下iteye,本来想把这些文章发表在CSDN(虽然都是一家的). 但发现CSDN博客功能用户体验做的不是那么友好,图片压缩的太厉害. 好好的图片上传之后已经面目全非. 只好又再iteye注册了个相同的博客,当然内容也相同. 做android开发有一段时间了,一直想挤出点时间,写点什么.

Android学习之路——7.Service

- - ITeye博客
这两天又学习了Android四大组件之一的Service. (1)Service不是一个单独的Process,除非特别指派了,也不是一个Thread,但也不是运行在Main Thread中. (3)Service的生命周期:. 调用的Context.startService()   oncreate() --> onStartCommand ()--> Service is running --> The service is stopped by its or a client--> onDestroy() --> Service is shut down .

Android学习笔记(六)SQLite

- - 博客园_首页
SQLite是一个极轻量型的数据库. 它在提供了和大型数据库相当的功能,还具有轻便、跨平台等优点,SQLite使用非常方便,并不需要我们像常规数据库(SQLServer,Mysql等)那样进行安装,在Android的JDK中,其实是已经包含了SQLite这个数据库的核心. 当然我们必须要强调一点,SQLite并不是只针对Android的,其实它还可以用在别的很多地方.

Android拓展系列(2)--Git使用

- Pei - 博客园-首页原创精华区
git是免费的开源的分布式的版本控制系统. 我说的直白点,要强调的点是每个git clone下来的版本库都是一个完整的版本库,包括所有的历史记录和版本信息,不需要依赖网络,这点在使用的过程中你一定会有感触,git不是盖的. git很快,但是这点我没有大的感觉;. git易于使用,相对svn而言,我不觉得git比之简单,但是总体来说git还是比较容易的,尤其是服务器端的部署非常简易.

"Dzyplastic 系列 2" Android 玩偶照片

- yat - 谷安——谷奥Android专题站
DyzPlastic 又在挑逗我们,他们发布了一些“Dzyplastic 系列 2”Android 玩偶的照片. 上面这个玩偶称为“Racer(赛车手)”,不用多解释一看就明白了. 粉色这个称为“Rupture(疝气)”,看起来比较有意思,可能女性朋友会觉得比较给力. 蓝色这个称为“Bluebot(蓝色机器人)”,这个更是不用解释了,也是相当标致的呢.

Android性能系列-内存篇

- - 操作系统 - ITeye博客
众所周知,与C/C++需要通过手动编码来申请以及释放内存有所不同,Java拥有GC的机制. Android系统里面有一个Generational Heap Memory的模型,系统会根据内存中不同的内存数据类型分别执行不同的GC操作. 例如,最近刚分配的对象会放在Young Generation区域,这个区域的对象通常都是会快速被创建并且很快被销毁回收的,同时这个区域的GC操作速度也是比Old Generation区域的GC操作速度更快的.

Android Native 代码开发学习笔记

- iDesperadO - WindStorm
本文提供排版更佳的PDF版本下载. JNI,全称Java Native Interface,是用于让运行在JVM中的Java代码和运行在JVM外的Native代码(主要是C或者C++)沟通的桥梁. 代码编写者即可以使用JNI从Java的程序中调用Native代码,又可以从Native程序中调用Java代码.