现在的位置: 首页 > 综合 > 正文

电商课题VII:支付交易一般性准则

2011年01月05日 ⁄ 综合 ⁄ 共 3940字 ⁄ 字号 评论关闭

@郑昀汇总 创建于2012/11

发布版本号:v1.3
概念:
退款期限,交易,交易关闭,交易结束,掉单,幂等性,数据一致性
 
关键词:
历史记录不得直接篡改原则,
交易关闭通知处理,退款处理结束通知,
掉单被动处理,掉单主动处理,
多个渠道的重复支付处理,
支付成功时商品不可售卖的处理,
订单金额变化交易流水号变化规则,
推送订单不得包含违禁词,
支付通知并发到达的处理,
支付子系统的独立性和可靠性,
补录数据的时间准则

一. 通用规则
1.1. 历史记录不得直接篡改
电商核心服务基本都是分布式应用,分布式事务如处理不妥善,容易产生数据不一致。一旦出现数据不一致,一定要有旁证来修正。
所以数据库中以下关键资源的记录,郑昀提醒您注意,原则上不允许直接修改历史数据
  • 下单;
  • 支付购买;
  • 生码/验码/物流信息记录;
  • 退款(含部分退款);
  • 结算;
  • 用户注册;
  • 与第三方数据同步;
这里的“直接修改”特指,没有把变更行为记录到日志表里,而是直接在原始记录上 update 甚至 delete ,这种“篡改”和“毁尸灭迹”是明文禁止的,即使留下了文件类型日志也是不允许的。
第一,要修改这些记录的关键字段时,必须在相关日志表里保留变更日志,并记录操作人和发起人,一定要确保历史可回溯
第二,严禁对记录做物理删除,只能是软删除。
 
实例:
对于××团收到第三方支付的通知,我们有第三方交易流水记录表;
对于××团发起的到第三方支付的交易请求。我们有 jxxxe_pay_log 记录;
订单操作变更记录,我们有 jxxxe_order_action日志表记录。
 
Q:什么叫历史可回溯?
A:系统可能对关键记录做了一系列修改,甚至有程序在某个时间段内误写引入了脏数据,但郑昀提示您,我们依然要能从各种操作日志表中随时倒推回历史某一个时刻的快照,一是确保随时能安全地把数据还原回去,二是管理平台可以清晰地展示出由谁引发、怎么变化的历史,三是便于排查问题。
譬如,对于记录了订单信息的 order_info 表,会员如果点击使用账户余额支付了订单的应付金额,那么该订单操作日志表就会做如下记录,原订单记录的重要字段(what)在什么时候(when)从什么变为了什么(how),都会详细记录。
 
1.2. 对关键资源的操作,当接口保证不了幂等性时,必须能防并发
如果你的接口不具备幂等性,那么请保证整个(分布式)系统内对一个重要事物(订单,账户的资金变动等)的有效操作线程同一时间内有且只有一个
比如交易中心有N台服务器负载均衡,订单中心则有M台服务器,如何保证一个订单的同一笔支付处理,一个账户的同一笔资金变动操作是原子性的。
 
原因也很简单:
  • 第三方支付平台可能同一时刻给你的 pay.5xxxxn.com 交易中心集群服务推送过来两个一模一样的支付成功通知。
  • 用户浏览器可能安装了某种插件(如早期的迅雷插件),插件本身为了探测 一个URL 是否是BT资源,会同时模拟发起一个 HTTP GET 请求。
  • 上游服务不可控,不可预知地向发起下游服务发起并发请求。
 
此时可以基于 memcache 实现一个分布式锁,更多详情请阅读郑昀撰写的《电商课题:分布式锁》。
 
1.3. 支付子系统的独立性
电商业务容易出现以下问题:
  • 受到DDoS攻击,带宽被打满;
  • 某一个业务突然响应变慢,如从20ms激增为1s,业务请求被大量阻塞;
所以不同业务之间必须严格隔离,防止一个业务超负荷或宕机连累其他业务。
因此,我们至少要做到:
  • 支付子系统是一个独立工程,独立部署,有单独的二级域名。
最好能做到:
  • 独立带宽,
  • 独立的存储介质。
 
1.4. 支付子系统接收第三方支付通知的可靠性
电商的支付子系统提供一个 Web Service,来接收各家第三方支付的各种异步通知。
接收对方通知之后,你可能会遭遇各种异常,如:
  • 解析时发生异常;
  • 调用支付中心时发生异常,如网络故障,如支付中心宕机,如调用超时;
  • 写日志时发生异常;
即使如此,你也不应该丢弃该通知,并且没有返回“success”字符给第三方支付,
因为这样的话,相当于你的业务完全依赖于第三方支付下一次重试了。
所以,郑昀提醒您,你应该主动地、积极地先把对方的(支付成功、退款处理等)通知存入一个存储介质,如消息队列;
一旦同步处理失败,那么能以某种策略重播这个消息,直到业务系统恢复正常、处理完毕为止。
 
1.5. 补录数据的时间准则
电商结算和对账,由于帐期定义(如日清日结,如T+2结算,如销售佣金计算,如CPS联盟结算),非常依赖于数据记录的时间。
所以,当由于以下原因补录或同步数据时,请慎重考虑数据的时间字段到底如何采信
  • 掉单后主动处理(注:主动查询第三方支付网关,获得订单支付状态);
  • 不同系统之间同步失败后手动触发重新同步;
  • 不同公司的平台之间交换退款等数据。
 
下面举两个小例子:
例一:
××商城对供应商的结算标准是,仅仅以订单进入他们的ERP系统的时间为准,而不是以该订单的下单时间、支付时间等数据自身时间记录为准。
即,一个12月1日23:58支付成功的订单,数据一层一层传递到ERP时,同步时间是12月2日00:01,那么此订单就被判定在12月2日的应结算明细中,而不是12月1日的。
咋听上去好像不合理,仔细想想,供应商有很多,IT系统也就很多,彼此之间的服务器时间肯定不同步,更别提会有很多种类型的脏数据,所以××商城只有选择用ERP系统自身的时间作为唯一结算凭据,而不采信第三方系统的 add_time、update_time、pay_time 五花八门的时间,这样才不会重复结算或漏结算。
 
例二:
支付系统宕机,一段时间后才恢复,此时客服主动处理顾客投诉掉单的订单,从第三方支付查到已支付后,将订单置为已付款。那么,该订单的支付时间怎么记呢?
一是,采信第三方支付系统传递的真实支付时间。二是,记录为手动重置的当前时间。
郑昀的答案是,后者更安全。
因为,有可能补录数据已跨日或跨月,前一日的结算清单可能已计算完毕,如果按前者的逻辑,突然又补录一条记录,结果前一日(上一个月)也不结算它,后一日(下一个月)也不结算,那这个订单就漏结算了。
当然,真实支付时间也还是要记录到日志表的。
 

二. 易被忽略的逻辑处理
2.1. 交易关闭通知的处理
支付宝是这么定义“交易关闭”的:
枚举名称
枚举说明
TRADE_CLOSED
  • 指定时间段内未支付时关闭的交易;
  • 在交易完成全额退款成功时关闭的交易。
交易关闭通知默认是不发送的,如下表格所示:
触发条件名
触发条件描述
触发条件默认值
TRADE_CLOSED
交易关闭
false(不触发通知)
如果商户(也就是你的网站)向支付宝申请打开了该配置,那么请注意接收 TRADE_CLOSED 通知,它会对你的核心购买逻辑产生影响。
 
如何主动指定交易关闭时间呢?
即时到帐交易接口中有这么一个参数:
参数
参数名称
类型
参数说明
是否可为空
样例
it_b_pay
超时时间
String(3)
设置未付款交易的超时时间,一旦超时,该笔交易就会自动被关闭。
取值范围:1m~15d。
m:分钟、h:小时、d:天、1c:当天(无论交易何时创建,都在0点关闭)。
该功能需要联系技术支持来配置关闭时间。
可空
1h
支付宝收到这个参数后,界面会有如下展示:
http://images.cnblogs.com/cnblogs_com/zhengyun_ustc/255879/o_clip_image001%20-%20001%E5%89%AF%E6%9C%AC.jpg
 
此时提示几点:
1)如果交易已经关闭,但商户的网站上仍保留了订单的“付款”按钮,那么点击跳转到支付宝后,会看到如下警告信息:
http://images.cnblogs.com/cnblogs_com/zhengyun_ustc/255879/r_clip_image002%20-%20002%e5%89%af%e6%9c%ac.jpg  
http://images.cnblogs.com/cnblogs_com/zhengyun_ustc/255879/r_clip_image002%20-%20003%e5%89%af%e6%9c%ac.jpg
2)当交易状态为交易关闭时,就算用户能通过第三方网银对支付宝账单进行付款(用户可能已经跳转至银行交易页面,并且未关闭页面),第三方网银能将支付成功信息通知支付宝,支付宝也不会通知商户,而是会自动退还至支付宝余额中。
3)网银直连的订单是关闭不了的,因为它没有跟支付宝账户绑定。
4)商户如发现交易已关闭的订单被用户支付后,那么必须进入异常支付流程(能原路退返就退,如无法退返则返还至账户余额)。
 
2.2. 退款通知的处理
支付宝对此的定义是:
(1) 交易成功之后,商户(高级即时到账或机票平台商)可调用批量退款接口,系统会发送退款通知给商户。
(2) 当商户使用站内退款时,系统会发送包含 refund_status(退款状态)和 gmt_refund(退款时间)字段的通知给商户。
其中退款状态有两种:
枚举名称
枚举说明
REFUND_SUCCESS
退款成功:
  • 全额退款情况:trade_status= TRADE_CLOSED,而refund_status=REFUND_SUCCESS
  • 非全额退款情况:trade_status= TRADE_SUCCESS,而refund_status=REFUND_SUCCESS
REFUND_CLOSED 退款关闭
第三方支付在退款处理完毕后,会发送异步通知给商户,如下表格所示:
触发条件名
触发条件默认值
退款处理结束
true(触发通知)

这个所谓退款处理结束通知,实际上仍是一个“trade_status_sync(交易状态同步)”通知,特殊性在于携带的 refund_stauts =REFUND_SUCCESS 参数,实际例子如下所示:

http://images.cnblogs.com/cnblogs_com/zhengyun_ustc/255879/r_clipboard%20-%20004%e5%89%af%e6%9c%ac.png  
注意几个要点:
  1. 当交易状态为 TRADE_FINISHED(交易完成) ,那么不可退款
  • 此处有一个“退款期限”概念,交易关闭(TRADE_CLOSED)后3个月(或6个月)内可以退款,超过此期限后,该笔交易成功且结束,从此不可退款!对于业务逻辑,意味着此时只能退还金额到账户余额,无法原路退返。支付宝、快钱、财付通等均有此设定。
    • 手机支付退款如返回 D23190 错误码,含义是退款日期超过最大有效期(有效期是半年)。
  • 退款处理结束的通知到达时,支付宝会先发送一个支付成功通知,防止你的系统不知道有此交易。请正确处理这个支付成功通知,不要误认为这是“重复支付”(因为对应的订单可能已确认+已支付),以至于误判给原路退返了。
  •  

     

    抱歉!评论已关闭.