这两天,在移动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 += "¬ify_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.'"¬ify_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("支付未完成");
}
}
吐槽
向alipaysdk发送的订单数据一定要加“”,否则报错
签名、和签名处理 一定不能有误,否则报错
后台代码中,一定要注意文件的路径和权限。(主要是私钥的路径和log.txt的权限)
特别吐槽:rsa签名,在后台本来可以直接生成订单数据,但只能分别生成订单前部分数据+rsa签名数据,在客户端对rsa签名再进行URLEncoder,才能顺利通过。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。