接入必读

商户接入规范标准化

接口调用规范

  • 针对唤起收银台的交易确保能够及时获得用户支付结果

    • 原因:对于等待支付的交易,商户如果无法及时确认顾客付款是否成功,容易引起资损和纠纷。

    • 建议方案:对所有唤起收银台的交易(支付宝返回状态10003)发起轮询,并且建议轮询总时间设为30秒,轮询平均间隔设为3秒。在让用户再次支付前,必须通过查询确认当前订单的状态。

  • 同一订单的部分退款确保退款请求号不重复

    • 原因:同一笔交易如果不改变退款请求号 out_request_no,会因为请求号冲突,导致部分退款失败。

    • 建议方案:同一笔交易若发起多次部分退款,每一次都需要改变 out_request_no。

异常处理

  • 避免不合法的传参

    • 原因:传参错误会导致支付异常。

    • 建议方案:对所有返回 INVALID_PARAMETER 的响应进行监控,一旦发现请停止调用并检查请求参数,修改后重新发起请求。

  • 接口调用发生异常时能够正确处理

    • 原因:接口的大量调用错误,商户带宽被无效调用占用,容易导致访问支付宝速度变慢,并且容易引发商户端处理错误,造成系统无法正常使用,严重影响系统稳定性及用户体验。

    • 建议方案:在调用支付宝接口时,可能会遇到网络超时或支付宝未知异常(接口返回 code=20000,sub_code=isp.unknow-error 或 aop.ACQ.SYSTEM_ERROR),此时业务处理结果是未知的,需要根据具体 API 采取一下措施。具体措施如下:

      • a. 查询接口 alipay.trade.query 和撤销接口 alipay.trade.cancel 调用异常:

        • 立即重试一分钟,如果仍然返回超时或未知异常,需要记录该异常交易并走人工处理流程。
      • b. 预下单接口 alipay.trade.precreate 调用异常:

        • 使用新的商户订单号 out_trade_no 重新调用预下单接口。
      • c. 退款接口 alipay.trade.refund 调用异常:

        • 使用相同的参数重试一分钟,如果仍然返回超时或未知异常,需要记录该异常交易并走人工处理流程,不能简单的推断为退款成功或失败。
      • d. 支付接口 alipay.trade.pay 调用异常,立即调用查询接口,如果:

        • 查询的交易不存在(错误码 ACQ.TRADE_NOT_EXIST),使用相同的参数重新调用支付接口。

        • 网络超时或未知异常,继续查询一分钟,如仍然超时或未知异常,需要记录该异常交易并走人工处理流程,不能简单的推断为付款失败。

支付体验

  • 合理的轮询间隔以及轮询总时间

    • 原因:如果轮询总时间太短,而用户输入密码时间较长,商户可能无法获得支付结果,导致资损和纠纷。如果轮询时间太长,会产生不必要的资源消耗,降低服务器性能。同样,轮询间隔如果过短,会给服务器带来压力,降低性能,反之,会无法及时得到支付结果,增加不必要的排队等待时间。

    • 建议方案:建议轮询总时间设置在30秒,轮询间隔设为3秒。

  • 提高订单支付成功率

    • 原因:支付失败现象增多会引起商户收银问题,增加顾客等待时间,引起单边账,降低顾客满意度。

    • 建议方案:对支付接口的返回进行监控,如果有较高比例的支付失败,需要及时排查原因。条码支付成功率建议保持在95%以上。

  • 新建的订单都需要改变订单号

    • 原因:外部订单号重复会导致交易失败,不予以控制将增大服务器开销,增加顾客等待时间。

    • 建议方案:对新建的订单进行跳号处理。

资金安全

  • 未支付订单请及时撤销

    • 原因:未支付订单如果不及时关闭,顾客可能会误支付,从而导致顾客资损,引起单边账

    • 建议方案:对于未支付的订单,请及时通过调用撤销接口关闭订单(注意:超过24小时的订单无法撤销);另外一种方法是为每笔订单设置超时时间,超过时间未支付的订单会自动关闭。

  • 不要将撤销接口用于退款业务

    • 原因:退款接口用于正常退款业务,撤销接口用于在订单出现问题时候将其撤销并关闭。两者的业务场景完全不同,错用会导致订单状态与业务状态不匹配,在监控下无法准确地发现系统异常。

    • 建议方案:所有退款业务都必须调用退款接口。

  • 单品总金额与订单总金额要一致

    • 原因:单品总金额与订单金额不一致会导致实收金额与应收金额不匹配,从而引起资损。

    • 建议方案:在传入单品的情况下,订单总金额需要基于单品的单价和数量计算出来。

  • 不要在没有获得交易结果的时候要求用户再次付款

    • 原因:商户端没有获得交易结果,很有可能是用户已经付款成功只是商户端单方面原因(网络问题)导致没有获得支付结果,再次支付会导致用户资损。

    • 建议方案:对于商户端没有获得交易结果的订单,请和用户确认支付结果,如果用户已经支付成功,请先退款然后让用户再次支付。

入参规范

  • auth_code 用户付款码建议做好设计预留工作

    • 原因:由于业务发展需要,支付宝将会在2017年9月底对支付宝的用户付款码做升级处理。付款码将由原来的28开头扩充到25-30开头,长度由原来的16-18位扩充到16-24位。未来随移动支付产业的发展,用户付款码可能会有所加长,建议开发者做好设计预留工作。

    • 建议方案:如果开发者在对接当面付条码支付接口时,有对支付宝条码做码段和长度的限制,请务必完成相关升级。

  • storeid 不要含有中文字符

    • 原因:中文有可能引起乱码,在有营销活动的情况下,会导致营销活动无法生效。

    • 建议方案:建议 storeid 全部用字母和数字的组合。

  • 支付请求需要传入 storeid

    • 原因:如果不传,会导致在商家自营销活动发放的券无法核销。

    • 建议方案:建议所有支付订单都加入 storeid 参数。

  • 机具接入请传入 terminalid

    • 原因:在机具支付异常的情况下无法跟踪,无法得到后续保障,也会导致营销活动无法生效。

    • 建议方案:所有机具接入的支付请求需要带入terminal_id参数。

  • 需要返佣的 ISV (独立软件开发商)请确保传入正确的返佣参数

    • 原因:系统商接入如果不传入正确的 sys_service_provider_id,会导致无法获得返佣。

    • 建议方案:建议系统商接入的所有订单都加入 sys_service_provider_id参数,并检查参数是否正确。

  • 确保带有折扣参数的订单可以正常享受优惠

    • 原因:订单里的不可打折金额(undiscountable_amount)如果设置成全额,该笔订单将无法享受折扣。

    • 建议方案:在传入打折参数的情况下,请确认 discountable_amount 不为0或者 undiscountable_amount 不是全额。

  • 参与单品活动的交易需要传入正确的 goodsdetail 信息

    • 原因:如果交易参数中的 goodsdetail 为空或者有误,将无法参与单品活动。

    • 建议方案:参与单品活动的商户请检查请求是否包含 goodsdetail,并确保 goodsID 和口碑后台所配置的相一致。

监控保障

  • 交易保障接口接入

    • 原因:如果没有接入交易保障接口,商户/ISV将无法通过支付宝提供的自助平台监控自身收银端问题,失去了支付宝提供的一项保障能力。

    • 建议方案:每30分钟(或小于30分钟)将收银终端的交易性能和异常等数据同步至支付宝。支付宝将该数据和支付宝内大数据有机整合为商户/ISV提供实时监控能力,为线下收银保障护航。交易性能和交易异常数据必须要从收银终端统计,不能在服务端统计。因为这样才可以监控到真实的性能和异常。

支付结果异步通知

  • 异步通知验签
    商户接受到异步通知后,一定要先做验签,来确保该通知确实是从支付宝发出的。验签的详细规则见异步通知验签,同时建议您参考以下代码使用支付宝提供的开发平台服务端SDK来辅助验签:
Map<String, String> paramsMap = ... //将异步通知中收到的所有参数都存放到map中
boolean signVerified = AlipaySignature.rsaCheckV1(paramsMap, ALIPAY_PUBLIC_KEY, CHARSET,SIGN_TYPE) //调用SDK验证签名
if(signVerfied){
    // TODO 验签成功则继续业务操作,最后在response中返回success
}else{
    // TODO 验签失败则记录异常日志,并在response中返回failure.
}

注意
出于安全考虑,验签完成后,请开发者务必检查通知中的 appid、外部订单号和订单金额与预下单时传入的是否一致。

避免单边账

场景介绍

单边账是指对于一笔交易,商户端和用户端的结果不一致,分为两种情况:

  • 商户资损单边账:用户实际未付款成功,但商户系统判定支付成功;或用户支付成功后,商户系统由于逻辑问题发起了撤销。

  • 用户资损单边账:用户付款成功,但商户系统未得到支付成功的结果,误认为付款失败,再次扫用户付款码发起支付,导致用户多支付了一笔。在用户手机网络不好的情况下,支付成功后用户手机不一定会显示支付成功页面,用户自己也不知道已经付成功了。这种情况在小额场景下尤其容易出现,且难以发现,需要商户和系统商特别注意。

为了避免单边账,建议商户和系统商采取以下措施:

  • 每一笔交易一定要闭环,即要么支付成功,要么撤销交易,一定不能有交易一直停留在等待用户付款的状态。

  • 轮询+撤销的流程中,如轮询的结果一直为未付款,撤销一定要紧接着最后一次查询,当中不能有时间间隔。

  • 门店收银系统应该具备独立的手动查询功能作为兜底,输入商户订单号(可从用户手机账单中获得)调用支付宝查询接口获得确切的支付状态。

  • 当遇到网络超时和未知异常时,参考异常处理流程正确处理,对于每一笔交易或退款,一定要得到确切的结果。

  • 撤销接口调用成功后,需要在收银台页面为收银员展示撤销成功的强提示文案,且按实际业务情况引导收银员进行手工订单查询。如果撤销接口没有明确返回处理结果,如遇到网络超时或支付宝未知异常等情况,则需要在收银台提示文案中表明,撤销正在处理中;若该笔订单已经支付则后续会有发生退款的可能,并和用户做好线下沟通。

  • 对于经过轮询和撤销仍然无法确认结果的(例如系统故障或门店断网),应上报总部IT或财务,由IT(从系统内)或财务(从支付宝后台),确认支付结果后,通过后台发起退款。或让顾客查询手机支付宝账单,如顾客手机没网络,可拨打支付宝客服热线95188确认支付结果。

  • 在上述基础上,业务流程培训时应强调支付结果必须以商户端为准,用户手机上的支付宝结果或账单只能做参考,不能作为最终识别标准。如果商户未正确处理业务逻辑和业务流程培训,存在潜在的风险,商户自行承担因此而产生的所有损失。

退款

场景介绍

商户因业务原因需要退款时,可通过成功交易的商户订单号或支付宝交易号进行退款 ,支持部分退款。

调用流程

商户系统调用 交易退款接口 alipay.trade.refund,传入商户订单号或支付宝交易号、退款请求号、退款金额等参数请求退款,并同步获得退款结果。具体流程如下图所示:

注意
退款接口会根据外部请求号 out_request_no 幂等返回,因此同一笔需要多次部分退款时,必须使用不同的 out_request_no。

使用SDK快速接入

交易退款接口 alipay.trade.refund

AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", APP_ID, APP_PRIVATE_KEY, "json", CHARSET, ALIPAY_PUBLIC_KEY, "RSA2"); //获得初始化的AlipayClient
AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();//创建API对应的request类
request.setBizContent("{" +
"    \"out_trade_no\":\"20150320010101001\"," +
"    \"trade_no\":\"2014112611001004680073956707\"," +
"    \"out_request_no\":\"1000001\"," +
"    \"refund_amount\":\"1.00\"}"); //设置业务参数
AlipayTradeRefundResponse response = alipayClient.execute(request);//通过alipayClient调用API,获得对应的response类
System.out.print(response.getBody());
//根据response中的结果继续业务逻辑处理

关键入参:

参数名称 参数说明
out_trade_no 支付时传入的商户订单号,与trade_no必填一个
trade_no 支付时返回的支付宝交易号,与out_trade_no必填一个
out_request_no 本次退款请求流水号,部分退款时必传
refund_amount 本次退款金额

关键出参:

参数名称 参数说明
refund_fee 该笔交易已退款的总金额

对账

场景介绍

商家/系统商可通过接口下载指定日期(当天除外)的业务明细账单文件,并结合自身业务系统实现自动对账。

调用流程

  1. 商户系统调用查询对账单下载地址接口 alipay.data.dataservice.bill.downloadurl.query,传入指定日期,获得该日期账单文件的下载地址。

  2. 商户系统通过HTTP方式后台访问账单下载链接,将账单csv文件下载到本地后自行处理。注意该下载链接仅30秒,在得到链接后系统需要立刻请求下载账单文件。

  3. 账单示例
    20887027411111110156_20180105.csv.zip
    具体流程如下图所示:


使用SDK快速接入

查询对账单下载地址接口 alipay.data.dataservice.bill.downloadurl.query

AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", APP_ID, APP_PRIVATE_KEY, "json", CHARSET, ALIPAY_PUBLIC_KEY, "RSA2");//获得初始化的AlipayClient
AlipayDataDataserviceBillDownloadurlQueryRequest request = new AlipayDataDataserviceBillDownloadurlQueryRequest();//创建API对应的request类
request.setBizContent("{" +
"    \"bill_type\":\"trade\"," +
"    \"bill_date\":\"2016-04-05\"}"); //设置业务参数
AlipayDataDataserviceBillDownloadurlQueryResponse response = alipayClient.execute(request);//通过alipayClient调用API,获得对应的response类
System.out.print(response.getBody());
//根据response中的结果继续业务逻辑处理

关键入参:

参数名称 参数说明
bill_type 固定传入trade
bill_date 需要下载的账单日期,最晚是当期日期的前一天

关键出参:

参数名称 参数说明
bill_download_url 账单文件下载地址,30秒有效

下载账单文件:

//将接口返回的对账单下载地址传入urlStr
String urlStr = "http://dwbillcenter.alipay.com/downloadBillFile.resource?bizType=X&userId=X&fileType=X&bizDates=X&downloadFileName=X&fileId=X";
//指定希望保存的文件路径
String filePath = "/Users/fund_bill_20160405.csv.zip";
URL url = null;
HttpURLConnection httpUrlConnection = null;
InputStream fis = null;
FileOutputStream fos = null;
try {
    url = new URL(urlStr);
    httpUrlConnection = (HttpURLConnection) url.openConnection();
    httpUrlConnection.setConnectTimeout(5 * 1000);
    httpUrlConnection.setDoInput(true);
    httpUrlConnection.setDoOutput(true);
    httpUrlConnection.setUseCaches(false);
    httpUrlConnection.setRequestMethod("GET");
    httpUrlConnection.setRequestProperty("CHARSET", "UTF-8");
    httpUrlConnection.connect();
    fis = httpUrlConnection.getInputStream();
    byte[] temp = new byte[1024];
    int b;
    fos = new FileOutputStream(new File(filePath));
    while ((b = fis.read(temp)) != -1) {
        fos.write(temp, 0, b);
        fos.flush();
    }
} catch (MalformedURLException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
} finally {
    try {
        if(fis!=null) fis.close();
        if(fos!=null) fos.close();
        if(httpUrlConnection!=null) httpUrlConnection.disconnect();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

自助工具

如果需要验证当前接入质量是否符合我们所罗列的标准规范,可以访问商户自助监控平台查看集成健康度。

onlineServer