这两天,在移动APP上集成了支付宝支付功能,费了一些周折,除了其他博客上提到的一些问题,这里分享一下自己的经验
Android客户端代码集成
1、准备
a 注册支付宝商家账号
b 开通移动支付功能c 生成RSA私钥和公钥,上传自己的公钥给支付宝2、类库下载
,将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、订单生成文件
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、异步通知文件
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,才能顺利通过。