最近做的项目的支付模块需要对接支付宝的当面付,刚做完,想把整个过程以及需要注意的一些点整理罗列下,于是便想着写一写。
接入的大体思路及过程
一、支付宝当面付官方文档
题外话:阿里的文档是真的很详细,因此避免了很多坑,所以有不懂的一般翻翻文档就大概知道了。
好了,不说废话了,根据支付宝的官方文档我们首先要了解的是整个业务流程:
以及用户的使用流程:
以上两个都是官方文档提供的图,文档中都有说,我就不复述了。
实际上对接到项目中,属于我们项目本身的处理逻辑主要集中是下图红框内的流程:
以我的项目为例:
系统通过用户订单向支付宝后台发起下单请求–》支付宝后台收到请求并返回订单数据以及二维码–》系统后台接受并处理支付宝的返回数据–》向用户展示二维码–》用户通过支付宝进行扫码操作–》支付宝回调支付数据–》系统后台进行处理
emm,感觉我这个例子太乱的同学可以直接无视=。 =
整个支付宝当面付中需要重点注意的有两点:
1. 密钥的配置
关于支付宝密钥的解释和流程请参考(扫码支付接入指引 )中的“配置应用 ”
支付宝的密钥有两种:“RSA2(SHA256)密钥”和“RSA(SHA1)密钥”,官方推荐的是“RSA2(SHA256)密钥”,而我使用的也是官方推荐的
下面我将叙述密钥的配置(RSA2(SHA256)密钥)过程:
以下操作均在沙箱环境操作,没有沙箱环境的请务必创建沙箱环境(支付宝沙箱环境)
因为需要生成RSA密钥,所以需要用支付宝提供一键生成工具生成一对RSA密钥(生成RSA密钥)
我的系统是windows的,所以用密钥生成工具的过程是这样的:
生成密钥后,将“商户应用公钥”复制到沙箱应用下的“RSA2(SHA256)密钥”设置中。
保存后系统会生成“支付宝公钥”,可直接点击“查看支付宝公钥”查看。另外,“商户应用私钥”请自行保存,因为后面会用到。
PS:密钥的配置是对交易数据进行双方校验,所以请务必配置正确。
2.避免单边账
以下均引用官方文档(避免单边账):
一,单边帐是指对于一笔交易,商户端和用户端的结果不一致,分为两种情况:
1.1 商户资损单边账:用户实际未付款成功,但商户系统判定支付成功;或用户支付成功后,商户系统由于逻辑问题发起了撤销。
1.2 用户资损单边账:用户付款成功,但商户系统未得到支付成功的结果,误认为付款失败,再次扫用户付款码发起支付,导致用户多支付了一笔。在用户手机网络不好的情况下,支付成功后用户手机不一定会显示支付成功页面,用户自己也不知道已经付成功了。这种情况在小额场景下尤其容易出现,且难以发现,需要商户和系统商特别注意。二,为了避免单边帐,建议商户和系统商采取以下措施:
2.1. 每一笔交易一定要闭环,即要么支付成功,要么撤销交易,一定不能有交易一直停留在等待用户付款的状态。
2.2 轮询+撤销的流程中,如轮询的结果一直为未付款,撤销一定要紧接着最后一次查询,当中不能有时间间隔。
2.3 门店收银系统应该具备独立的手动查询功能作为兜底,输入商户订单号(可从用户手机账单中获得)调用支付宝查询接口获得确切的支付状态。
2.4 当遇到网络超时和未知异常时,参考异常处理流程正确处理,对于每一笔交易或退款,一定要得到确切的结果。
2.5 撤销接口调用成功后,需要在收银台页面为收银员展示撤销成功的强提示文案,且按实际业务情况引导收银员进行手工订单查询。如果撤销接口没有明确返回处理结果,如遇到网络超时或支付宝未知异常等情况,则需要在收银台提示文案中表明,撤销正在处理中;若该笔订单已经支付则后续会有发生退款的可能,并和用户做好线下沟通。
2.6 对于经过轮询和撤销仍然无法确认结果的(例如系统故障或门店断网),应上报总部IT或财务,由IT(从系统内)或财务(从支付宝后台),确认支付结果后,通过后台发起退款。或让顾客查询手机支付宝账单,如顾客手机没网络,可拨打支付宝客服热线95188确认支付结果。
2.7 在上述基础上,业务流程培训时应强调支付结果必须以商户端为准,用户手机上的支付宝结果或账单只能做参考,不能作为最终识别标准。如果商户未正确处理业务逻辑和业务流程培训,存在潜在的风险,商户自行承担因此而产生的所有损失。
官方文档叙述的已经足够清楚了,我就不造轮子了,各位重点注意便可。
二、沙箱环境调试测试
支付宝沙箱是支付宝官方提供的沙箱测试环境,里面提供了支付宝的部分功能,但相对来说够用了。
支付宝沙箱环境
注册申请完沙箱环境后,系统会自动生成沙箱应用。在上面的密钥配置了,我们的公钥配置就是在沙箱应用中配置的。
首先我们先说下”沙箱应用“,因为开发操作也主要围绕”沙箱应用“,下图是沙箱应用的介绍图:
图中应用网关因为暂时没用到,所以没有设置,需要关注的主要是密钥配置,其次需要关注的是”授权回调地址”,因为支付宝回调地址要求是外网地址,所以需要进行”内网穿透”,当然你有其他的方法也可以,关于内网穿透,我使用的是”NATAPP”,其他的也有,你们自行按需求配置吧,不做累述。
沙箱版钱包需要用沙箱帐号登录
支付宝提供了一个买家和一个卖家的沙箱帐号供我们测试用。
为保证沙箱长期稳定,支付宝会在每周日中午12点至每周一中午12点沙箱环境进行维护,期间可能出现不可用。
三、当面付官方Demo调试
支付宝当面付官方Demo
通过下载上面链接内的官方Demo后,将项目引入你的IDE中,我用的是IDEA。
引入后可能会因为JDK版本已经jar包问题报错,一般重新配置下JDK和重新引入下jar包就可以了。
然后我们就要对Demo的配置文件进行修改了“zfbinfo.properties”
# 支付宝网关名、partnerId和appId
open_api_domain = 填写支付宝网关
mcloud_api_domain = http://mcloudmonitor.com/gateway.do
pid = 填写沙箱应用中的"商户UID"
appid = 填写沙箱应用中的"APPID"
# RSA私钥、公钥和支付宝公钥
private_key = 填写商户应用私钥
public_key = 填写商户应用公钥
#SHA1withRsa对应支付宝公钥
#alipay_public_key = MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDI6d306Q8fIfCOaTXyiUeJHkrIvYISRcc73s3vF1ZT7XN8RNPwJxo8pWaJMmvyTn9N4HQ632qJBVHf8sxHi/fEsraprwCtzvzQETrNRwVxLO5jVmRGi60j8Ue1efIlzPXV9je9mkjzOmdssymZkh2QhUrCmZYI/FCEa3/cNMW0QIDAQAB
#SHA256withRsa对应支付宝公钥
alipay_public_key = 填写支付宝公钥
# 签名类型: RSA->SHA1withRsa,RSA2->SHA256withRsa
sign_type = RSA2
# 当面付最大查询次数和查询间隔(毫秒)
max_query_retry = 5
query_duration = 5000
# 当面付最大撤销次数和撤销间隔(毫秒)
max_cancel_retry = 3
cancel_duration = 2000
# 交易保障线程第一次调度延迟和调度间隔(秒)
heartbeat_delay = 5
heartbeat_duration = 900
需要特别注意配置的是密钥的配置,密钥的生成请参考第一节的密钥配置,千万别配错了,代码中我用中文已经备注好了“填写XXX”,那些是我们要配置的,其他保持默认就可以了。
配置完后运行Main方法
如无异常,运行结果应该是这样子的:
十一月 19, 2017 8:30:36 下午 com.alipay.demo.trade.config.Configs init
信息: 配置文件名: zfbinfo.properties
十一月 19, 2017 8:30:36 下午 com.alipay.demo.trade.config.Configs init
信息: Configs{支付宝openapi网关: https://openapi.alipaydev.com/gateway.do
, 支付宝mcloudapi网关域名: http://mcloudmonitor.com/gateway.do
, pid: 2088102172412792
, appid: 2016082100304819
, 商户RSA私钥: MIIEvw******KZYA==
, 商户RSA公钥: MIIBIj******IDAQAB
, 支付宝RSA公钥: MIIBIj******IDAQAB
, 签名类型: RSA2
, 查询重试次数: 5
, 查询间隔(毫秒): 5000
, 撤销尝试次数: 3
, 撤销重试间隔(毫秒): 2000
, 交易保障调度延迟(秒): 5
, 交易保障调度间隔(秒): 900
}
十一月 19, 2017 8:30:36 下午 com.alipay.demo.trade.service.impl.AbsAlipayTradeService tradePrecreate
信息: trade.precreate bizContent:{"out_trade_no":"tradeprecreate15110946363841555882","seller_id":"","total_amount":"0.01","undiscountable_amount":"0","subject":"xxx品牌xxx门店当面付扫码消费","body":"购买商品3件共20.00元","goods_detail":[{"goods_id":"goods_id001","goods_name":"xxx小面包","quantity":1,"price":"10"},{"goods_id":"goods_id002","goods_name":"xxx牙刷","quantity":2,"price":"5"}],"operator_id":"test_operator_id","store_id":"test_store_id","extend_params":{"sys_service_provider_id":"2088100200300400500"},"timeout_express":"120m"}
十一月 19, 2017 8:30:41 下午 com.alipay.demo.trade.service.impl.AbsAlipayService getResponse
信息: {"alipay_trade_precreate_response":{"code":"10000","msg":"Success","out_trade_no":"tradeprecreate15110946363841555882","qr_code":"https:\/\/qr.alipay.com\/bax07752bxnnqtb3s9dn00ce"},"sign":"yS0X5NPxoqLesEVTQPxaXUumC48R1rY89GrWpqVbrByElA3uSkSkpyTQ+ggJPVw5Q7/ZxUTJplTMhCnTU2NvsnFyrUYbZriRhFytMVDl24ndj7T6iFvqCjiyfuLdsuVMPByn88Dy7cRjHK2kS025hlTF46fvTB0xLUq/dgSlEdL84XZuyi/l0XvrXqRpwHDNMNPTIx7o7WPwzYMgHAZQPuiHAtZ3+dMnZGceqd1LyXYGF7DQKnpwhI+s16LIwNKWX+BLAB3b6QYTNHX8mOSkH3ixxfnSYpMeq9twMK88fpdw9b7TXxF22ze8wvyXKPOSdMrgpU0iES3TJttYKfTRgA=="}
十一月 19, 2017 8:30:41 下午 com.alipay.demo.trade.Main test_trade_precreate
信息: 支付宝预下单成功: )
十一月 19, 2017 8:30:41 下午 com.alipay.demo.trade.Main dumpResponse
信息: code:10000, msg:Success
十一月 19, 2017 8:30:41 下午 com.alipay.demo.trade.Main dumpResponse
信息: body:{"alipay_trade_precreate_response":{"code":"10000","msg":"Success","out_trade_no":"tradeprecreate15110946363841555882","qr_code":"https:\/\/qr.alipay.com\/bax07752bxnnqtb3s9dn00ce"},"sign":"yS0X5NPxoqLesEVTQPxaXUumC48R1rY89GrWpqVbrByElA3uSkSkpyTQ+ggJPVw5Q7/ZxUTJplTMhCnTU2NvsnFyrUYbZriRhFytMVDl24ndj7T6iFvqCjiyfuLdsuVMPByn88Dy7cRjHK2kS025hlTF46fvTB0xLUq/dgSlEdL84XZuyi/l0XvrXqRpwHDNMNPTIx7o7WPwzYMgHAZQPuiHAtZ3+dMnZGceqd1LyXYGF7DQKnpwhI+s16LIwNKWX+BLAB3b6QYTNHX8mOSkH3ixxfnSYpMeq9twMK88fpdw9b7TXxF22ze8wvyXKPOSdMrgpU0iES3TJttYKfTRgA=="}
十一月 19, 2017 8:30:41 下午 com.alipay.demo.trade.Main test_trade_precreate
信息: filePath:/Users/sudo/Desktop/qr-tradeprecreate15110946363841555882.png
Process finished with exit code 0
状态码返回1000,表示正常。
四、将支付宝当面付集成到自己项目中
首先将Demo中的配置文件以及SDK拷贝到自己项目的相应目录中
另外还有些通用的jar包,我是通过配置Maven的pom.xml引入的,jar包版本和Demo保持一致,各位可以参考下:
<dependency>
<groupId>commons-codecgroupId>
<artifactId>commons-codecartifactId>
<version>1.10version>
dependency>
<dependency>
<groupId>commons-configurationgroupId>
<artifactId>commons-configurationartifactId>
<version>1.10version>
dependency>
<dependency>
<groupId>commons-langgroupId>
<artifactId>commons-langartifactId>
<version>2.6version>
dependency>
<dependency>
<groupId>commons-logginggroupId>
<artifactId>commons-loggingartifactId>
<version>1.1.1version>
dependency>
<dependency>
<groupId>com.google.zxinggroupId>
<artifactId>coreartifactId>
<version>2.1version>
dependency>
<dependency>
<groupId>com.google.code.gsongroupId>
<artifactId>gsonartifactId>
<version>2.3.1version>
dependency>
<dependency>
<groupId>org.hamcrestgroupId>
<artifactId>hamcrest-coreartifactId>
<version>1.3version>
dependency>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<configuration>
<source>1.7source>
<target>1.7target>
<encoding>UTF-8encoding>
<compilerArguments>
<extdirs>${project.basedir}/src/main/webapp/WEB-INF/libextdirs>
compilerArguments>
configuration>
plugin>
将Demo中alipay.demo.trade包以及其中的两个类拷贝到你的项目中,用来测试配置正不正确。在运行Main方法测试之前,先将支付宝的jar包引入下。
我项目中关于支付宝当面付的接口主要有三个“pay.do”,”query_order_pay_status.do”和“alipay_callback.do”,pay.do是用来根据项目订单,向支付宝发起下单请求,而alipay_callback.do是用来接收支付宝的回调信息,“query_order_pay_status.do”是用来查询订单的支付状态。
各位把这三个接口对应到第一张业务流程图就会很清楚这几个接口的明确作用了。
以上几个接口的实现几乎都是根据官方Demo和自己的项目需求进行修改出来的,所以各位只需根据自己的项目需求,在Demo中寻找自己需要的功能,并对Demo源码进行分析了解就可以了。
以查询订单为例:
// 测试当面付2.0查询订单
public void test_trade_query() {
// (必填) 商户订单号,通过此商户订单号查询当面付的交易状态
String outTradeNo = "tradepay14817938139942440181";
// 创建查询请求builder,设置请求参数
AlipayTradeQueryRequestBuilder builder = new AlipayTradeQueryRequestBuilder()
.setOutTradeNo(outTradeNo);
AlipayF2FQueryResult result = tradeService.queryTradeResult(builder);
switch (result.getTradeStatus()) {
case SUCCESS:
log.info("查询返回该订单支付成功: )");
AlipayTradeQueryResponse response = result.getResponse();
dumpResponse(response);
log.info(response.getTradeStatus());
if (Utils.isListNotEmpty(response.getFundBillList())) {
for (TradeFundBill bill : response.getFundBillList()) {
log.info(bill.getFundChannel() + ":" + bill.getAmount());
}
}
break;
case FAILED:
log.error("查询返回该订单支付失败或被关闭!!!");
break;
case UNKNOWN:
log.error("系统异常,订单支付状态未知!!!");
break;
default:
log.error("不支持的交易状态,交易返回异常!!!");
break;
}
}
上述代码是Demo中Main.java中的案例,如果我们项目中需要查询订单的接口,那么我们可以直接将它拿过来,将“outTradeNo ”改为我们传入的订单号,然后其他的日志以及异常按照自己的需求进行修改就可以了…
实际上整个对接过程中最花时间的是查看官方文档了解需要重点注意的一些点,然后配置调通Demo,就这两个便占了70%的时间,其余30%的时间便是你自己真正设计编码的时间。
并不是说这70%的时间无用,因为只有当你根据文档和Demo了解清楚后,你剩下30%的时间才能轻松完成你的设计以及编码。
emmmmmmm,后面第4节真正接入写的有点敷衍。每个人的项目需求不同,侧重点也不同。即使我把我项目的相关代码贴出来讲,可能也不是各位想要的,我只是希望给各位讲一下我个人对于接入支付宝当面付的思想过程。
如果想看我接入的详细源码,请直接点击查看