每次接手一个新的任务后,就是各种查资料,各种出错,还好,最后终于完成了正常支付。
先理一下支付宝支付的流程:
这里我们从第二步说起:
2.调用支付接口,此消息就是本接口所描述的开发包提供的支付对象PayTask,将商户订单信息传进pay方法唤起支付宝收银台
第三步:
手机支付宝支付开发包将会按照商户App提供的参数发送支付请求
第五步:
异步发送支付通知:手机支付宝支付服务器端发送异步通知消息给商户服务器端
第七步:
接口返回支付结果:商户应用客户端通过当前调用支付的Activity的Handler对象,通过它的回调函数获取支付结果
当然在这之前,我们要获取支付宝公钥和私钥
windows用户在cmd窗口下进行以下操作:
C:\Users\Hammer>cd C:\OpenSSL-Win32\bin 进入OpenSSL安装目录
C:\OpenSSL-Win32\bin>openssl.exe 进入OpenSSL程序
OpenSSL> genrsa -out rsa_private_key.pem 1024 生成私钥
OpenSSL> pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt Java开发者需要将私钥转换成PKCS8格式
OpenSSL> rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem 生成公钥
OpenSSL> exit ## 退出OpenSSL程序
Linux用户(以Ubuntu为例)
$ openssl 进入OpenSSL程序
OpenSSL> genrsa -out rsa_private_key.pem 1024
生成私钥
OpenSSL> pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt
Java开发者需要将私钥转换成PKCS8格式
OpenSSL> rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
生成公钥
OpenSSL> exit ##
退出OpenSSL程序
注意:对于使用Java的开发者,将pkcs8在console中输出的私钥去除头尾、换行和空格,作为开发者私钥,对于.NET和PHP的开发者来说,无需进行pkcs8命令行操作。
经过以上步骤,开发者可以在当前文件夹中(Windows用户在C:\OpenSSL-Win32\bin)看到rsa_private_key.pem和rsa_public_key.pem两个文件,前者为私钥,后者为公钥。开发者将私钥保留,将公钥提交给支付宝网关,用于信息加密及解密。以下为使用OpenSSL生成的私钥文件和公钥文件示例。
接下来:
1、将alipaySDK-20150602.jar包放入商户应用工程的libs目录下,如下图。
2、进入商户应用工程的Java Build Path,将libs目录下的alipaySDK-20150602.jar导入,如下图。
3、选中Order and Export,勾选alipaySDK-20150602.jar,如下图
然后修改应用工程的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>
权限声明:
<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里添加以下相关混淆规则:
-libraryjars libs/alipaySDK-20150602.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 *;}
至此,所有开发资源导入完成。
下面,我就把一些关键的代码贴上来供大家看看。
public class PayDemoActivity extends FragmentActivity {
private static int m_userId;
private static int m_serverId;
private static int m_price;
private static String m_productName;
private static String m_productDesc;
public static Myd2Activity m_instance;
public static final String TAG = "SdkApi";
//public static SdkApi m_shareInstance;
// 商户PID
public static final String PARTNER = "208*************";
// 商户收款账号
public static final String SELLER = "208*************";
// 商户私钥,pkcs8格式
public static final String RSA_PRIVATE = "*****************";
// 支付宝公钥
public static final String RSA_PUBLIC = "******************************";
private static final int SDK_PAY_FLAG = 1;
private static final int SDK_CHECK_FLAG = 2;
private static Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
Log.d("Message数:"," Message = " + msg.toString());
switch (msg.what) {
case SDK_PAY_FLAG: {
PayResult payResult = new PayResult((String) msg.obj);
// 支付宝返回此次支付结果及加签,建议对支付宝签名信息拿签约时支付宝提供的公钥做验签
String resultInfo = payResult.getResult();
String resultStatus = payResult.getResultStatus();
String success = payResult.getSuccess();
Log.d("resultStatus:"," resultStatus = " + resultStatus);
Log.d("success:"," success = " + success);
// 判断resultStatus 为“9000”则代表支付成功,具体状态码代表含义可参考接口文档
if (TextUtils.equals(resultStatus, "9000")) {
Toast.makeText(m_instance, "支付成功",Toast.LENGTH_SHORT).show();
} else {
// 判断resultStatus 为非“9000”则代表可能支付失败
// “8000”代表支付结果因为支付渠道原因或者系统原因还在等待支付结果确认,最终交易是否成功以服务端异步通知为准(小概率状态)
if (TextUtils.equals(resultStatus, "8000")) {
Toast.makeText(m_instance, "支付结果确认中",
Toast.LENGTH_SHORT).show();
} else {
// 其他值就可以判断为支付失败,包括用户主动取消支付,或者系统返回的错误
Toast.makeText(m_instance, "支付失败",
Toast.LENGTH_SHORT).show();
}
}
break;
}
case SDK_CHECK_FLAG: {
Toast.makeText(m_instance, "检查结果为:" + msg.obj,
Toast.LENGTH_SHORT).show();
break;
}
default:
break;
}
};
};
public static void pay()
{
int ad = Myd2Activity.GetAdCode();
String mAppOrderId = m_serverId + "-" + m_userId + "-" + ad + "-" + System.currentTimeMillis();
Log.d("订单号:"," orderId = " + mAppOrderId);
String sprice = Float.toString( m_price );
// 订单
String orderInfo = getOrderInfo(m_productName, m_productDesc, sprice, mAppOrderId);
Log.d("orderInfo:"," orderInfo = " + orderInfo);
// 对订单做RSA 签名
String sign = sign(orderInfo);
try {
// 仅需对sign 做URL编码
sign = URLEncoder.encode(sign, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
// 完整的符合支付宝参数规范的订单信息
final String payInfo = orderInfo + "&sign=\"" + sign + "\"&" + getSignType();
Runnable payRunnable = new Runnable() {
@Override
public void run() {
// 构造PayTask 对象
PayTask alipay = new PayTask(m_instance);
// 调用支付接口,获取支付结果
Log.d("payInfo:"," payInfo = " + payInfo);
String result = alipay.pay(payInfo);
Log.d("result:"," result = " + result);
Message msg = new Message();
msg.what = SDK_PAY_FLAG;
msg.obj = result;
mHandler.sendMessage(msg);
}
};
Thread checkThread = new Thread(payRunnable);
checkThread.start();
}
/**
* call alipay sdk pay. 调用SDK支付
*
*/
public static void goAlipyPay(int rId,int serverId,int price, String name, String desc) {
if (TextUtils.isEmpty(PARTNER) || TextUtils.isEmpty(RSA_PRIVATE)
|| TextUtils.isEmpty(SELLER)) {
new AlertDialog.Builder(m_instance)
.setTitle("警告")
.setMessage("需要配置PARTNER | RSA_PRIVATE| SELLER")
.setPositiveButton("确定",
new DialogInterface.OnClickListener() {
public void onClick(
DialogInterface dialoginterface, int i) {
//
m_instance.finish();
}
}).show();
return;
}
m_userId = rId;
m_serverId = serverId;
m_price = price;
m_productName = name;
m_productDesc = desc;
//支付或者授权的行为需要在独立的非ui线程中执行 ******非常重要****
Message m = m_instance.GetHandler().obtainMessage(m_instance.MSG_VER_PAY);
m_instance.GetHandler().sendMessage(m);
}
/**
* check whether the device has authentication alipay account.
* 查询终端设备是否存在支付宝认证账户
*
*/
public void check(View v) {
Runnable checkRunnable = new Runnable() {
@Override
public void run() {
// 构造PayTask 对象
PayTask payTask = new PayTask(SdkApi.this);
// 调用查询接口,获取查询结果
boolean isExist = payTask.checkAccountIfExist();
Message msg = new Message();
msg.what = SDK_CHECK_FLAG;
msg.obj = isExist;
mHandler.sendMessage(msg);
}
};
Thread checkThread = new Thread(checkRunnable);
checkThread.start();
}
/**
* get the sdk version. 获取SDK版本号
*
*/
public void getSDKVersion() {
PayTask payTask = new PayTask(SdkApi.this);
String version = payTask.getVersion();
Toast.makeText(SdkApi.this, version, Toast.LENGTH_SHORT).show();
}
/**
* create the order info. 创建订单信息
*
*/
public static String getOrderInfo(String subject, String body, String price, String tradeNo) {
// 签约合作者身份ID
String orderInfo = "partner=" + "\"" + PARTNER + "\"";
// 签约卖家支付宝账号
orderInfo += "&seller_id=" + "\"" + SELLER + "\"";
// 商户网站唯一订单号
//orderInfo += "&out_trade_no=" + "\"" + getOutTradeNo() + "\"";
orderInfo += "&out_trade_no=" + "\"" + tradeNo + "\"";
// 商品名称
orderInfo += "&subject=" + "\"" + subject + "\"";
// 商品详情
orderInfo += "&body=" + "\"" + body + "\"";
// 商品金额
orderInfo += "&total_fee=" + "\"" + price + "\"";
// 服务器异步通知页面路径
orderInfo += "¬ify_url=" + "\"" + "http://124.202.141.19/pay/callback.php"
+ "\"";
// 服务接口名称, 固定值
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;
}
/**
* get the out_trade_no for an order. 生成商户订单号,该值在商户端应保持唯一(可自定义格式规范)
*
*/
public 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;
}
/**
* sign the order info. 对订单信息进行签名
*
* @param content
* 待签名订单信息
*/
public static String sign(String content) {
return SignUtils.sign(content, RSA_PRIVATE);
}
/**
* get the sign type we use. 获取签名方式
*
*/
public static String getSignType() {
return "sign_type=\"RSA\"";
}
工程目录如下:
关于支付请求的示例如下所示:
partner="2088101568358171"&seller_id="xxx@alipay.com"&out_trade_no="0819145412-6177"&subject="测试"&body="测试测试"&total_fee="0.01"¬ify_url="http://notify.msp.hk/notify.htm"&service="mobile.securitypay.pay"&payment_type="1"&_input_charset="utf-8"&it_b_pay="30m"&sign="lBBK%2F0w5LOajrMrji7DUgEqNjIhQbidR13GovA5r3TgIbNqv231yC1NksLdw%2Ba3JnfHXoXuet6XNNHtn7VE%2BeCoRO1O%2BR1KugLrQEZMtG5jmJIe2pbjm%2F3kb%2FuGkpG%2BwYQYI51%2BhA3YBbvZHVQBYveBqK%2Bh8mUyb7GM1HxWs9k4%3D"&sign_type="RSA"
返回值为:
resultStatus={9000};memo={};result={partner="2088101568358171"&seller_id="xxx@alipay.com"&out_trade_no="0819145412-6177"&subject="测试"&body="测试测试"&total_fee="0.01"¬ify_url="http://notify.msp.hk/notify.htm"&service="mobile.securitypay.pay"&payment_type="1"&_input_charset="utf-8"&it_b_pay="30m"&success="true"&sign_type="RSA"&sign="hkFZr+zE9499nuqDNLZEF7W75RFFPsly876QuRSeN8WMaUgcdR00IKy5ZyBJ4eldhoJ/2zghqrD4E2G2mNjs3aE+HCLiBXrPDNdLKCZgSOIqmv46TfPTEqopYfhs+o5fZzXxt34fwdrzN4mX6S13cr3UwmEV4L3Ffir/02RBVtU="}
注意:
返回结果需要通过resultStatus以及result字段的值来综合判断并确定支付结果。在resultStatus=9000,并且success=“true”以及sign=“xxx”校验通过的情 况下,证明支付成功,其它情况归为失败。较低安全级别的场合,也可以只通过检查resultStatus以及success=“true”来判定支付结果。
Android平台和iOS平台的返回结果串唯一不同之处是resultStatus这个key,在iOS的返回结果串中原始的数据是ResultStatus(为了兼容历史版本首字母大写),Android平台是resultStatus
补充
RSA密钥生成命令:注意:“>”符号后面的才是需要输入的命令。
1、openssl>genrsa -out rsa_private_key.pem 1024(生成RSA私钥)
2、openssl>rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem(生成RSA公钥)
3、openssl>pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt(将RSA私钥转换成PKCS8格式)
这里再说下如何获取支付宝公钥,前文已经说明,通过openssl.exe可以获取到rsa_private_key.pme、rsa_public_key.pme两文件,执行完第三条命令之后会在cmd窗口下看到生成的pkcs8格式的RSA私钥,至于获取支付宝公钥网上有两种做法,第一种是官网上的教程:https://doc.open.alipay.com/doc2/detail.htm?spm=0.0.0.0.fTBHMS&treeId=44&articleId=103243&docType=1,比较麻烦,要添加证书什么的,安装了证书之后还学要证书备份,蠢得一笔,果断放弃;另外一种是https://cshall.alipay.com/enterprise/help_detail.htm?help_id=473890&keyword=%C8%E7%BA%CE%C9%FA%B3%C9RSA%C3%DC%D4%BF&sToken=&from=search,个人推荐第二种,比较简单。