首先说说笔者的集成经历,一开始集成时,像往常一样百度了一下集成的方法,然后出来一大堆结果,以为应该会很简单,然而事实却并非如此。网上的集成方法很多都是旧版本的集成,现在支付宝已经对sdk以及demo进行了更新,虽说和旧版差别不是很大,不过对于不了解整个流程的开发人员来说,确实一个极大的痛苦;当然在集成过程中遇到各种各样的问题,甚至对一些流程根本就不了解,当然这跟笔者的水平有一定关系。最后给大家的建议是,不要心急,一步一步跟着流程走,其实集成是很简单的。本文介绍的是沙箱环境下的集成,到时候只需要在签约后将代码中的各种ID改为签约后的即可(集成app支付需要和支付宝进行签约)。笔者将整个流程分为两大部分,第一个是前期配置,第二个是开始集成。
前期配置
首先,进入开放平台后,我们点击应用
接着我们点击沙箱环境下的沙箱应用
在这边我们能看到支付宝给我们进行测试的应用ID和测试账号等,我们点击设置RSA2密钥(这边我已经设置好了,RSA(SHA1)可以不用设置)
那么如何设置密钥呢->查看密钥生成方法 进入之后我们下载对应版本后,运行“RSA签名验签工具.bat”(WINDOWS)或“RSA签名验签工具.command”(MAC_OSX),然后如下图勾选,点击生成密钥(注意保存)
然后我们将这边生成的应用公钥复制并粘贴到刚刚的需要设置应用公钥的地方,设置完成后会生成一个对应的支付宝公钥
好了,这边需要特别注意: ”应用公钥、应用私钥、支付宝公钥”这三个不要混淆、不要混淆、不要混淆。好了,第一部分的配置也差不多到这边了。
开始集成
流程
我们先来看下集成的整个流程
简单介绍一下整个流程(图中虚线标识商户链路,实线标识支付宝链路。)
步骤1:用户点击进行付款
步骤2:客户端从商户的服务端获取签名后的订单消息
步骤3:(商户将订单信息加签)返回签名后的订单信息
步骤4、5、6、7、8:这几个步骤是我们点击确认支付后自动执行的,不需要我们执行(其中第8步返回最终的支付结果(即同步通知))
步骤9、10:商户客户端将同步通知的支付结果发送至支付宝服务端并返回最终结果(这两个步骤可以不执行,因为我们后面还有步骤12的异步通知)
步骤12、13:根据我们设置的异步通知地址(下面会介绍)获取支付结果
好了,流程差不多就是这样,接下来我们结合代码开始实战。
编码
我们下载支付宝提供的SDK和DEMO(更新时间:2017/05/11) 解压导入Android Studio后我们打开PayDemoActivity
如果我们只想进行支付功能的话,只要设置上图的APPID和RSA2_PRIVATE即可。当然,这是沙箱环境,我们需要在onCreate下添加这一句:
EnvUtils.setEnv(EnvUtils.EnvEnum.SANDBOX);
位置如下:
当然整个支付的精华莫过于此处(代码已给出注释)
boolean rsa2 = (RSA2_PRIVATE.length() > 0);
Map<String, String> params = OrderInfoUtil2_0.buildOrderParamMap(APPID, rsa2,"英语课程(20次)",20);//设置订单详情和价格
String orderParam = OrderInfoUtil2_0.buildOrderParam(params);
String privateKey = rsa2 ? RSA2_PRIVATE : RSA_PRIVATE;
String sign = OrderInfoUtil2_0.getSign(params, privateKey, rsa2);//本地加签
final String orderInfo = orderParam + "&" + sign;//加签后的订单信息
Runnable payRunnable = new Runnable() {
@Override
public void run() {
PayTask alipay = new PayTask(PayDemoActivity.this);//调用支付接口
Map<String, String> result = alipay.payV2(orderInfo, true);//支付结果
Log.i("msp", result.toString());
Message msg = new Message();
msg.what = SDK_PAY_FLAG;
msg.obj = result;
mHandler.sendMessage(msg);
}
};
Thread payThread = new Thread(payRunnable);
payThread.start();
好了,完成如上操作即可通过客户端发起支付了。是不是发现这跟笔者刚刚介绍的服务端没有半点关系?其实,我们只是将加签验签放在客户端进行,这是非常不安全的。所以,下面开始介绍服务端的任务。
现在客户端中大部分代码都可以去掉,因为将这些过程都被放在了放在了服务端,比如下面的这些参数
我们所需要的代码,只剩下一下两部分,即调用支付接口的代码和Handle部分(case INFO为新增的代码,下面会用到)
Runnable payRunnable = new Runnable() {
@Override
public void run() {
PayTask alipay = new PayTask(PayDetailActivity.this);
Map<String, String> result = alipay.payV2(orderInfo, true);
Log.i("msp", result.toString());
Message msg = new Message();
msg.what = SDK_PAY_FLAG;
msg.obj = result;
mHandler.sendMessage(msg);
}
};
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
@SuppressWarnings("unused")
public void handleMessage(Message msg) {
switch (msg.what) {
case SDK_PAY_FLAG: {
@SuppressWarnings("unchecked")
PayResult payResult = new PayResult((Map<String, String>) msg.obj);
/**
对于支付结果,请商户依赖服务端的异步通知结果。同步通知结果,仅作为支付结束的通知。
*/
String resultInfo = payResult.getResult();// 同步返回需要验证的信息
String resultStatus = payResult.getResultStatus();
// 判断resultStatus 为9000则代表支付成功
if (TextUtils.equals(resultStatus, "9000")) {
// 该笔订单是否真实支付成功,需要依赖服务端的异步通知。
Toast.makeText(PayDetailActivity.this, "支付成功", Toast.LENGTH_SHORT).show();
flag=true;
btn_pay.setVisibility(View.GONE);
detail_state.setText("已支付");
} else {
// 该笔订单真实的支付结果,需要依赖服务端的异步通知。
Toast.makeText(PayDetailActivity.this, "支付失败", Toast.LENGTH_SHORT).show();
}
break;
}
case INFO: {
orderInfo = (String) msg.obj;
EnvUtils.setEnv(EnvUtils.EnvEnum.SANDBOX);
PayThread p = new PayThread();
p.start();
break;
}
default:
break;
}
};
};
将Runnable修改如下:
class PayThread extends Thread{
public void run(){
//EnvUtils.setEnv(EnvUtils.EnvEnum.SANDBOX);
PayTask alipay = new PayTask(PayDetailActivity.this);
Map<String, String> result = alipay.payV2(orderInfo, true);
Log.i("msp", result.toString());
Message msg = new Message();
msg.what = SDK_PAY_FLAG;
msg.obj = result;
mHandler.sendMessage(msg);
}
}
然后在客户端新建一个线程(用于从服务器获取加签的订单详情)
class accessThread2 extends Thread{
@Override
public void run() {
Message msg1=mHandler.obtainMessage();
msg1.what=INFO;
msg1.obj= GetPostUtil.sendPost("http://10.143.224.18:8080/HttpServer/Myserver","name=qwe123");
mHandler.sendMessage(msg1);
// super.run();
}
}
总结一下客户端的工作,先启动线程从服务器获取到加签后的订单详情,赋给msg1.obj;然后在Handle执行case INFO 部分的代码,接着调用PayTask 接口,并将支付结果赋给msg.obj,最后执行case SDK_PAY_FLAG这部分的代码。好了,客户端相比之前就整洁清爽多了。
接下来是服务端,我们在工程中导入服务端的SDK然后新建一个类用于获取加签后的订单详情(代码已给出注释):
public class GetSign {
/** 支付宝支付业务:入参app_id */
public static final String APPID = ""
/** 支付宝网关*/
public static final String GATE = "https://openapi.alipay.com/gateway.do";
/** 支付宝私钥*/
public static final String APP_PRIVATE_KEY = "";
/** 支付宝公钥 */
public static final String ALIPAY_PUBLIC_KEY = "";
/** 编码方式 */
public static final String CHARSET = "utf-8";
public static AlipayTradeAppPayResponse response;
public static String getsign(){
AlipayClient alipayClient = new DefaultAlipayClient(GATE,
APPID,
APP_PRIVATE_KEY,
"json",
CHARSET,
ALIPAY_PUBLIC_KEY,
"RSA2");
//实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
//SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setBody("我是测试数据");
model.setSubject("App支付测试Java");
model.setOutTradeNo("100342312764512");
model.setTimeoutExpress("30m");
model.setTotalAmount("0.01");
model.setProductCode("QUICK_MSECURITY_PAY");
request.setBizModel(model);
request.setNotifyUrl("商户外网可以访问的异步地址");
try {
//这里和普通的接口调用不同,使用的是sdkExecute
response = alipayClient.sdkExecute(request);
System.out.println(response.getBody());//就是orderString 可以直接给客户端请求,无需再做处理。
} catch (AlipayApiException e) {
e.printStackTrace();
}
return response.getBody();
}
以上代码就是加签的过程。其中response.getBody()即为加签过的订单详情。这边需要注意的是,订单数据格式不能随意乱写,比如OutTradeNo只能为数字、英文或下划线;此外,OutTradeNo不可以重复,若重复则会出现系统繁忙等错误。代码中各请求参数说明点这
再接下来,就是我们的异步通知了。在上一步的请求参数中,request.setNotifyUrl就是支付宝通知我们的地址。什么时候会触发呢?交易成功、交易创建、交易关闭、支付成功。由支付宝主动发起。来看一下回调部分的代码:
public String aliPay_notify(Map requestParams){
System.out.println("支付宝支付结果通知"+requestParams.toString());
//获取支付宝POST过来反馈信息
Map<String,String> params = new HashMap<String,String>();
for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) {
String name = (String) iter.next();
String[] values = (String[]) requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i]
: valueStr + values[i] + ",";
}
//乱码解决,这段代码在出现乱码时使用。
//valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
params.put(name, valueStr);
}
//切记alipaypublickey是支付宝的公钥,请去open.alipay.com对应应用下查看。
//boolean AlipaySignature.rsaCheckV1(Map<String, String> params, String publicKey, String charset, String sign_type)
try {
boolean flag = AlipaySignature.rsaCheckV1(params, alipay_public_key, charset, "RSA2");
if(flag){
if("TRADE_SUCCESS".equals(params.get("trade_status"))){
//付款金额
String amount = params.get("buyer_pay_amount");
//商户订单号
String out_trade_no = params.get("out_trade_no");
//支付宝交易号
String trade_no = params.get("trade_no");
//附加数据
String passback_params = URLDecoder.decode(params.get("passback_params"));
}
}
} catch (AlipayApiException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "success";
}
在if(flag)中,我们需要做如下判断:
**1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号
2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额)
3、校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email)
4、验证app_id是否为该商户本身。**
在上述验证通过后商户必须根据支付宝不同类型的业务通知,正确的进行不同的业务处理,并且过滤重复的通知结果数据。在支付宝的业务通知中,只有交易通知状态为TRADE_SUCCESS或TRADE_FINISHED时,支付宝才会认定为买家付款成功。
好了,整个开发流程大概就这样。如果要换为正式环境下的话只需要将参数修改为签约后支付宝提供的参数,切记去掉onCreate下的这句代码:
EnvUtils.setEnv(EnvUtils.EnvEnum.SANDBOX);
作者:陈曲兴
链接:一步一步带你完成支付宝支付功能的集成(超详细)