支付业务是电商系统中技术复杂度最高、稳定性要求最严的模块之一。支付的成功与否,不仅影响用户购物体验,更直接关系到平台的资金安全和收入稳定性。支付系统需要与订单系统、账户体系、风控系统、对账中心等多个模块紧密协作,同时还要对接多个外部支付渠道,技术挑战极大。
在电商业务中,支付模块的稳定性和用户体验至关重要。一个设计良好的支付系统能够有效提升转化率,降低用户流失,而一个有缺陷的支付系统则可能导致资金损失和用户投诉。
支付系统的核心在于稳定性、安全性和扩展性。一个完整的支付体系包含以下核心组件:

支付流程从用户提交订单开始,到最终支付成功结束,涉及多个状态转换和系统交互:

关键节点说明
支付订单生成:基于业务订单创建支付订单,记录支付金额、支付方式等信息
支付渠道调用:根据用户选择的支付方式,调用对应的支付渠道接口
同步结果处理:立即返回支付结果给用户端
异步通知确认:支付渠道通过回调通知最终支付结果,确保数据一致性
所谓产品架构图,简单的理解,就是站在产品角度,提供什么样的服务能力。下面是一个典型的支付系统的产品架构图。这个图画得比较简单,但是已经涵养一个支付系统最核心的产品能力。

实际实现时差异会很大,尤其是上面的产品或应用层,有很多机构为特殊的行业提供一些特殊的能力,比如携程的支付就会有航空方面的B2B业务。但基础的能力基本也就这些。
上面部分是会员或商户感知的产品能力,包括门户、收银台,收单产品,资金产品等。下面部分是支付系统最核心的服务,用于支撑对外的产品能力。如果多跳几家公司,就会发现基础的部分大家都差不太多。
这个图很精简,但是基本已经够用,应付本对本交易这种简单的业务是完全没有问题的。

一些复杂的支付系统可能还有外汇、额度中心、产品中心、卡中心等,甚至一个子系统可能会拆分为多个应用独立部署,比如收单结算就可以拆成收单和结算两个独立的应用。
跳过几个支付公司,这些基础的概念在几家公司都差不太多,区别是底层技术实现。比如RPC框架,数据库,业务流程,部署架构等。

这是一比较完整的系统架构图,属于逻辑划分。在单体应用中,就是一些模块,在分布式应用中,就是一些子域、子应用或子系统。

开放网关
主要对接商户,比如下单、支付等接口入口。通常要求有比较高的安全性。部分公司可能会把移动端网关、PC门户网关、商户通知等能力集成在开放网关,也可能会单独拆出部署。
收单结算
负责把商户的单收下来,并给商户发起结算。承担的收单产品包括有:线上收单,线下收单,担保交易、即时到账等,每个公司的商业策略不同,开出的收单产品会有差异。
有些公司把结算划到出款中心,对接银企直连的渠道。
资金产品
承担无买卖标的的纯资金转移能力。典型的有:充值、转账、提现、代发。和支付的区分在于支付是有买卖标的,而资金产品没有。也就是在系统中没有买卖记录发生,但在线下可能有。
资金产品一般需要独立的牌照。
收银核心
渲染可用支付方式。包括查询账户是否有余额,查询营销是否有营销券,查询渠道网关是否有可用的外部渠道,最后组合成可用支付方式,供前端渲染。
收银核心就像一个大内总管,收到请求后,找商户平台核实身份,找合约平台核实权限,找会员平台核实用户身份,找收单看一下这笔单是否可以继续支付,找账务中心获取余额信息,营销看看有没有可用的券,找渠道网关看看没有可用的渠道,找额度中心看看是否超限额了,找风控问一下当前支付是否安全,找会员平台校验支付密码 ... ...
支付引擎
负责真正的扣款或转账。有些公司叫支付核心。
如果是余额就调账务扣减余额,如果是红包就调营销做核销,如果是外部银行通道就调渠道网关。
渠道网关
负责去外部渠道扣款。通常还会提供渠道路由、渠道咨询等能力,做得细的公司可能会把渠道核心和报文/文件网关单独拆出来。其中渠道核心就提供渠道路由、渠道咨询、渠道开关等服务,报文/文件网关负责报文转换、签名验签等。
会员平台
管理会员的生命周期,比如注册、注销、登录等。同时还提供核身服务(比如登录密码,支付密码,短信验证码等)、实名认证服务等。
商户平台
管理商户的入驻签约、KYB、交易管理等。
产品中心
管理对外提供的产品能力,比如快捷支付,代扣等。一般大的支付系统才会独立成一个子系统。
账务中心
负责账户开立,记账等。
会计中心
会计科目管理、分录管理、日切管理等。
监管报表有时候也放在这里,有些公司也会独立出去。
很多集团公司往往有一套独立的专业财务系统,这个时候往往需要会计中心做完日切后,要把记账信息合并到集团公司的财务系统中去,简称并账。
对账中心
负责明细对账和资金对账。
营销平台
提供满减、红包等营销工具。
风控平台
针对账户和交易,提供实时、离线风控,控制交易的风险。反洗钱、反欺诈是基本要求。
通常各公司对风控规则看成是机密,研发也可能看不到运营配置的规则。经常看到有网友问:“有xx公司的人在吗?我有xxx场景下的支付总是提示风控不过,是否知道是什么原因,怎么才能通过?”,完全是浪费口舌,谁会对外公布自己的风控规则,让人去钻空子呢?
运营平台
订单管理、渠道管理、产品管理等综合运营工具。
数据平台
主要用于数据汇总和分析。当前各支付公司基本都是分布式部署N多个应用,数据都在散落在各子系统中,需要汇总到数据平台用于经营分析。
卡中心
负责管理用户的绑卡信息。需要经过PCI认证。
额度中心
累计用户、商户的额度,通常有日、月、年,单卡等各种分类。
外汇平台
负责外汇报价和兑换。
流动性与调拨中心
一些跨境支付公司,在多个国家多个银行有头寸,各头寸之间经常需要做流动性管理,提高资金利用率。
毕竟在国外不需要把备付金强制存到央行还不给利息。当资金量大的时候,这笔收益可不少。
差错中心
负责差错处理。比如渠道退款失败(银行账号销户,过了银行的退款有效期等),需要通过其它的方式退给用户。
拒付中心
处理用户的拒付和举证。在跨境支付场景下,信用卡用户联系发卡行说卡被盗刷或商品没有收到,或商品有问题等,拒绝支付给商户。国内基本没有看到拒付场景。
一般来说,技术风险主要包含稳定性和资损两个方面。其中稳定性风险就是大家经常说的几个9,比如99.999%可用,就是5个9。资损风险就是平台或用户的资金损失。
虽然资损也是技术风险的一种,但是因为对于专业的持牌支付公司来,资损是一种非常严重的事故,容易引发客诉、网络事件、甚至监管介入,所以又较一般的风险更为严重,常常把资损防控单独拿出来说。
技术风险体系过于庞大,这里只谈几点通用知识。
我们通常先需要知道风险来自哪里,才知道如何去防控。而风险往往来自变化。举几个例子,抛砖引玉:
流量变化:大促场景下,流量会暴增。
代码变化:引入了新的代码。
业务变化:修改了业务流程,或引入了新的业务。
外部变化:外部新的攻击手段。
根据变化去应对风险。比如大促引入了流量变化,那就做压测、扩容、限流、降级非核心业务等应对。比如原来只有支付,这些有了用户提现,针对用户提现,内部多个子域可能状态/金额不一致,和银行渠道的状态/金额也可能不一致,那就加入各种对账手段,以及对应的应急预案。
对账是资损防控中最效的手段之一。一般的支付平台会有内部系统之间的两两核对,这种核对主要是信息流层面的核对,主要核对状态、金额的一致性。

说明:
支付系统涉及多张核心表,包括支付订单表、支付流水表、退款表等:
支付订单表(payment_order)
sqlCREATE TABLE `payment_order` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`payment_order_no` varchar(32) NOT NULL COMMENT '支付订单号',
`business_order_no` varchar(32) NOT NULL COMMENT '业务订单号',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`amount` bigint(20) NOT NULL COMMENT '支付金额(分)',
`pay_amount` bigint(20) NOT NULL COMMENT '实付金额(分)',
`discount_amount` bigint(20) DEFAULT '0' COMMENT '优惠金额(分)',
`payment_channel` varchar(16) NOT NULL COMMENT '支付渠道:ALIPAY、WECHAT、UNIONPAY',
`payment_method` varchar(16) NOT NULL COMMENT '支付方式:APP、H5、PC、JSAPI',
`payment_status` varchar(16) NOT NULL COMMENT '支付状态',
`expire_time` datetime NOT NULL COMMENT '支付过期时间',
`pay_time` datetime DEFAULT NULL COMMENT '支付时间',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_payment_order_no` (`payment_order_no`),
KEY `idx_business_order_no` (`business_order_no`),
KEY `idx_user_id` (`user_id`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付订单表';
支付流水表(payment_flow)
sqlCREATE TABLE `payment_flow` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`payment_order_no` varchar(32) NOT NULL COMMENT '支付订单号',
`flow_no` varchar(32) NOT NULL COMMENT '支付流水号',
`channel_flow_no` varchar(64) DEFAULT NULL COMMENT '渠道流水号',
`request_params` text COMMENT '请求参数',
`response_result` text COMMENT '响应结果',
`flow_status` varchar(16) NOT NULL COMMENT '流水状态:SUCCESS、FAILED',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_flow_no` (`flow_no`),
KEY `idx_payment_order_no` (`payment_order_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付流水表';
分库分表
当数据量大的时间,分库分表是再所难免的。
一个经典的面试题是:如果分了100张表,按商户来分表,还是按商户订单号来分表?如果按商户分表怎么解决各表流水数据量平衡问题?如果是按商户订单号来分表,商户想按时间段查询怎么办?
解法有很多种。一种典型的解法,就是线上数据库按商户订单号分表,同时有一个离线库冗余一份按商户号分表的数据,甚至直接使用离线数据平台的能力,把商户的按时间段查询需求从在线库剥离出来。
状态机,也称为有限状态机(FSM, Finite State Machine),是一种行为模型,由一组定义良好的状态、状态之间的转换规则和一个初始状态组成。它根据当前的状态和输入的事件,从一个状态转移到另一个状态。

支付状态机管理支付订单的完整生命周期,确保状态流转的正确性:
javapublic enum PaymentStatus {
// 待支付
PENDING,
// 支付中
PROCESSING,
// 支付成功
SUCCESS,
// 支付失败
FAILED,
// 支付关闭
CLOSED,
// 退款中
REFUNDING,
// 部分退款
PARTIAL_REFUND,
// 全额退款
FULL_REFUND
}
状态流转规则:
PENDING → PROCESSING:用户发起支付请求
PROCESSING → SUCCESS:支付成功
PROCESSING → FAILED:支付失败
PENDING → CLOSED:订单超时关闭
SUCCESS → REFUNDING:发起退款
REFUNDING → PARTIAL_REFUND/FULL_REFUND:退款完成
常见代码实现误区
经常看到工作几年的同事实现状态机时,仍然使用if else或switch case来写。这是不对的,会让实现变得复杂,且容易出现问题。
甚至直接在订单的领域模型里面使用String来定义,而不是把状态模式封装单独的类。
还有就是直接调用领域模型更新状态,而不是通过事件来驱动。
支付网关作为统一入口,负责路由决策、参数组装和协议转换:
java@Service
public class PaymentGatewayService {
@Autowired
private Map<String, PaymentChannel> paymentChannelMap;
/**
* 统一支付接口
*/
public PaymentResponse pay(PaymentRequest request) {
// 1. 参数校验
validatePaymentRequest(request);
// 2. 风控检查
riskControlCheck(request);
// 3. 选择支付渠道
PaymentChannel channel = selectPaymentChannel(request);
// 4. 生成支付订单
PaymentOrder paymentOrder = createPaymentOrder(request);
// 5. 调用支付渠道
PaymentResponse response = channel.pay(buildChannelRequest(paymentOrder));
// 6. 记录支付流水
savePaymentFlow(paymentOrder, response);
return buildPaymentResponse(response);
}
/**
* 支付渠道路由
*/
private PaymentChannel selectPaymentChannel(PaymentRequest request) {
String channelKey = request.getPaymentChannel() + "_" + request.getPaymentMethod();
PaymentChannel channel = paymentChannelMap.get(channelKey);
if (channel == null) {
throw new PaymentException("不支持的支付方式");
}
return channel;
}
}
通过策略模式实现多支付渠道的适配,便于扩展和维护:
javapublic interface PaymentChannel {
/**
* 支付
*/
PaymentResponse pay(PaymentChannelRequest request);
/**
* 退款
*/
RefundResponse refund(RefundRequest request);
/**
* 查询支付结果
*/
QueryResponse query(QueryRequest request);
/**
* 处理异步通知
*/
NotifyResponse handleNotify(NotifyRequest request);
}
@Service("ALIPAY_APP")
public class AlipayAppChannel implements PaymentChannel {
@Override
public PaymentResponse pay(PaymentChannelRequest request) {
// 构造支付宝APP支付参数
AlipayTradeAppPayRequest alipayRequest = new AlipayTradeAppPayRequest();
// 设置业务参数
// 调用支付宝SDK
// 返回支付参数
return buildPaymentResponse(alipayResponse);
}
@Override
public NotifyResponse handleNotify(NotifyRequest request) {
// 验证签名
if (!verifySignature(request)) {
return NotifyResponse.fail("签名验证失败");
}
// 处理支付结果
processPaymentResult(request);
return NotifyResponse.success();
}
}
幂等是针对重复请求的,支付系统一般会面临以下几个重复请求的场景:
幂等解决方案

所谓业务幂等,就是由各域自己把唯一性的交易ID作为数据库唯一索引,这样可以保证不会重复处理。
在数据库前面可以加一层缓存来提高性能,但是缓存只用于查询,查到数据认为就返回幂等成功,但是但不到,需要尝试插入数据库,插入成功后再刷新数据到缓存。
为什么要使用数据库的唯一索引做为兜底?
是因为缓存是可能失效的。
在面临时经常有同学只回答到“使用redis分布式锁来实现幂等”,这是不对的。因为缓存有可能失效,分布式锁只是用于防并发操作的一种手段,无法根本性解决幂等问题,幂等一定是依赖数据库的唯一索引解决。
支付接口必须保证幂等性,防止重复支付:
java@Service
public class PaymentIdempotentService {
@Autowired
private RedisTemplate redisTemplate;
/**
* 支付幂等性校验
*/
public boolean checkIdempotent(String paymentOrderNo, String idempotentKey) {
String redisKey = "payment:idempotent:" + paymentOrderNo;
// 使用Lua脚本保证原子性
String luaScript =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return 1 " +
"else " +
" redis.call('set', KEYS[1], ARGV[1], 'EX', 3600) " +
" return 0 " +
"end";
Long result = redisTemplate.execute(
new DefaultRedisScript<>(luaScript, Long.class),
Collections.singletonList(redisKey),
idempotentKey
);
return result == 1;
}
}
大部分简单的支付系统只要有业务幂等基本也够用了。
对于研发经验不足的团队而言,经常会犯以下几种错误:
带来的后果,通常就是资金损失,再细化一下,最常见的情况有下面3种:
手动做单位换算导致金额被放大或缩小100倍。
比如大家规定传的是元,但是其中有位同学忘记了,以为传的是分,外部渠道要求传元,就手动乘以100。或者反过来。
还有一种情况,部分币种比如日元最小单元就是元,假如系统约定传的是分,外部渠道要求传元,就可能在网关处理时手动乘以100。
1分钱归属问题。比如结算给商家,或计算手续费时,碰到除不尽时,使用四舍五入,还是向零舍入,还是银行家舍入?这取决于财务策略。
精度丢失。在大金额时,double有可能会有精度丢失问题。
最佳实践:

DECIMAL 类型保存,保存单位为元。数据库一般都会设计一个自增ID作为主键,同时还会设计一个能唯一标识一笔业务的ID,这就是所谓的业务ID(也称业务键)。比如收单域的收单单号。
也有人采用所谓雪花算法,但其实不适用于支付场景。
下面以32位的支付系统业务ID生成为例说明。实际应用时可灵活调整。

第1-8位:日期。通过单号一眼能看出是哪天的交易。
第9位:数据版本。用于单据号的升级。
第10位:系统版本。用于内部系统版本升级,尤其是不兼容升级的时候,老业务使用老的系统处理,新业务使用新系统处理。
第11-13位:系统标识码。支付系统内部每个域分配一段,由各域自行再分配给内部系统。比如010是收单核心,012是结算核心。
第14-15位:业务标识位。由各域内部定,比如00-15代表支付类业务,01支付,02预授权,03请款等。
第16-17位:机房位。用于全球化部署。
第18-19位:用户分库位。支持百库。
第20-21位:用户分表位。支持百表。
第22位:预发生产标识位。比如0代表预发环境,1代表生产环境。
第23-24位:预留。各域根据实际情况扩展使用。
第24-32位:序列号空间。一亿规模,循环使用。一个机房一天一亿笔是很大的规模了。如果不够用,可以扩展到第24位,到十亿规模。
只要在公司写过代码,就一定打印过日志,但经常发现一些工作多年的工程师打印的日志也是乱七八糟的。我曾经在一家头部互联网公司接手过一个上线一年多的业务,相关日志一开始就没有设计好,导致很多监控无法实现,出了线上问题也不知道,最后只能安排工程师返工改造相关的日志。
我们要明白日志是用来做什么的。只是先弄明白做事的目的,我们才能更好把事情做对。在我看来,日志有两个核心的作用:
对于监控而言,我们需要知道几个核心的数据:业务/接口的请求量、成功量、成功率、耗时,系统返回码、业务返回码,异常信息等。对于排查问题而言,我们需要有出入参、中间处理数据的上下文、报错的上下文等。

接下来,基于上面的分析,我们就清楚我们应该有几种日志:
支付渠道的异步通知是确认支付结果的最终依据:
java@Service
public class PaymentNotifyService {
/**
* 处理支付通知
*/
@Transactional(rollbackFor = Exception.class)
public void processNotify(PaymentNotifyDTO notifyDTO) {
// 1. 验证通知合法性
if (!verifyNotify(notifyDTO)) {
throw new PaymentException("通知验证失败");
}
// 2. 查询支付订单
PaymentOrder paymentOrder = paymentOrderDAO.selectByOrderNo(notifyDTO.getPaymentOrderNo());
if (paymentOrder == null) {
throw new PaymentException("支付订单不存在");
}
// 3. 检查订单状态,避免重复处理
if (paymentOrder.getPaymentStatus() == PaymentStatus.SUCCESS) {
return;
}
// 4. 更新支付订单状态
updatePaymentOrderStatus(paymentOrder, notifyDTO);
// 5. 更新业务订单状态
updateBusinessOrderStatus(paymentOrder.getBusinessOrderNo());
// 6. 记录通知日志
saveNotifyLog(notifyDTO);
}
}
支付系统涉及多个服务的数据一致性,需要采用合适的分布式事务方案:
基于本地消息表的最终一致性:
java@Service
public class PaymentTransactionService {
/**
* 支付成功后的业务处理
*/
@Transactional(rollbackFor = Exception.class)
public void handlePaymentSuccess(String paymentOrderNo) {
// 1. 更新支付订单状态
updatePaymentOrder(paymentOrderNo, PaymentStatus.SUCCESS);
// 2. 插入本地消息表
EventMessage message = buildOrderPaidMessage(paymentOrderNo);
eventMessageDAO.insert(message);
// 3. 更新积分、库存等其他业务状态
updateRelatedBusiness(paymentOrderNo);
}
/**
* 消息补偿任务
*/
@Scheduled(fixedDelay = 30000)
public void compensateMessages() {
List<EventMessage> pendingMessages = eventMessageDAO.selectPendingMessages();
for (EventMessage message : pendingMessages) {
try {
// 重新投递消息
eventPublisher.publishEvent(message);
// 更新消息状态为已发送
eventMessageDAO.updateStatus(message.getId(), MessageStatus.SENT);
} catch (Exception e) {
log.error("消息补偿失败: {}", message.getId(), e);
}
}
}
}
为什么有些企业不用分布式事务?
分布式事务是个好东西,但是复杂度也高,还经常出现所谓的事务悬挂问题,且虽然各家都号称简单易用,对业务代码侵入少,但事实并非如此。
所以我个人更倾向于避免使用分布式事务解决方案,而是采用最终一致性来解决。对大部分中小公司来说,最终一致性已经够用。
每日对账是保障资金安全的重要手段:
java@Service
public class ReconciliationService {
/**
* 执行对账
*/
public void executeReconciliation(Date checkDate) {
// 1. 下载渠道对账单
List<ChannelBill> channelBills = downloadChannelBills(checkDate);
// 2. 查询系统支付记录
List<PaymentOrder> systemOrders = getSystemPaymentOrders(checkDate);
// 3. 对账匹配
ReconciliationResult result = matchBills(channelBills, systemOrders);
// 4. 处理差异订单
handleDifferences(result.getDifferences());
// 5. 生成对账报告
generateReport(result);
}
/**
* 账单匹配
*/
private ReconciliationResult matchBills(List<ChannelBill> channelBills,
List<PaymentOrder> systemOrders) {
ReconciliationResult result = new ReconciliationResult();
// 以渠道账单为准进行匹配
for (ChannelBill channelBill : channelBills) {
PaymentOrder systemOrder = findSystemOrder(systemOrders, channelBill);
if (systemOrder == null) {
// 渠道有系统无 - 长款
result.addDifference(ReconciliationDiff.extra(channelBill));
} else if (!amountEquals(channelBill, systemOrder)) {
// 金额不一致
result.addDifference(ReconciliationDiff.amountMismatch(channelBill, systemOrder));
} else {
// 匹配成功
result.addMatch(channelBill, systemOrder);
}
}
// 检查系统有渠道无 - 短款
for (PaymentOrder systemOrder : systemOrders) {
if (!isMatched(systemOrder)) {
result.addDifference(ReconciliationDiff.missing(systemOrder));
}
}
return result;
}
}
服务高可用设计,当某个支付渠道出现故障时,自动切换到备用渠道:
java@Service
public class PaymentCircuitBreaker {
private Map<String, CircuitBreakerStats> statsMap = new ConcurrentHashMap<>();
/**
* 获取可用的支付渠道
*/
public List<String> getAvailableChannels(PaymentRequest request) {
return statsMap.entrySet().stream()
.filter(entry -> isChannelAvailable(entry.getKey(), entry.getValue()))
.map(Map.Entry::getKey)
.sorted(Comparator.comparing(this::getChannelPriority))
.collect(Collectors.toList());
}
/**
* 判断渠道是否可用
*/
private boolean isChannelAvailable(String channel, CircuitBreakerStats stats) {
// 基于失败率、超时率等指标判断
return stats.getFailureRate() < 0.5 // 失败率低于50%
&& stats.getTimeoutRate() < 0.3 // 超时率低于30%
&& !stats.isCircuitOpen(); // 断路器未打开
}
/**
* 记录调用结果
*/
public void recordCallResult(String channel, boolean success, long cost) {
CircuitBreakerStats stats = statsMap.computeIfAbsent(
channel, k -> new CircuitBreakerStats()
);
stats.recordCall(success, cost);
}
}
通过多种机制保障支付数据的一致性:
java@Component
public class PaymentConsistencyChecker {
/**
* 定时检查支付状态不一致的订单
*/
@Scheduled(cron = "0 */5 * * * ?")
public void checkInconsistentOrders() {
// 查询支付中状态超过一定时间的订单
List<PaymentOrder> processingOrders =
paymentOrderDAO.selectLongTimeProcessingOrders();
for (PaymentOrder order : processingOrders) {
try {
// 主动查询支付渠道确认状态
QueryResponse response = paymentChannel.query(
new QueryRequest(order.getPaymentOrderNo())
);
if (response.isSuccess()) {
// 更新为成功状态
updatePaymentSuccess(order, response);
} else if (response.isFailed()) {
// 更新为失败状态
updatePaymentFailed(order, response);
}
// 仍然处理中则忽略
} catch (Exception e) {
log.error("支付状态检查失败: {}", order.getPaymentOrderNo(), e);
}
}
}
}
支付系统需要完善的监控体系来保障稳定性:
关键监控指标:
支付成功率:各渠道支付成功比率
支付耗时:支付各环节耗时分布
失败原因分布:各类失败原因的统计
渠道可用性:各支付渠道的健康状态
资金一致性:系统与渠道资金差异告警
java@Component
public class PaymentMetrics {
private final MeterRegistry meterRegistry;
public void recordPaymentResult(String channel, boolean success, long cost) {
// 记录支付结果
Counter.builder("payment.result")
.tag("channel", channel)
.tag("success", String.valueOf(success))
.register(meterRegistry)
.increment();
// 记录支付耗时
Timer.builder("payment.cost")
.tag("channel", channel)
.register(meterRegistry)
.record(cost, TimeUnit.MILLISECONDS);
}
}

支付安全是一个很大的范畴,但我们一般只需要重点关注以下几个核心点就够:
1. 敏感信息安全存储。
对个人和商户/渠道的敏感信息进行安全存储。
个人敏感信息包括身份证信息、支付卡明文数据和密码等,而商户/渠道的敏感信息则涉及商户登录/操作密码、渠道证书密钥等。
2. 交易信息安全传输。
确保客户端与支付系统服务器之间、商户系统与支付系统之间、支付系统内部服务器与服务器之间、支付系统与银行之间的数据传输安全。这包括采用加密技术等措施来保障数据传输过程中的安全性。
3. 交易信息的防篡改与防抵赖。 确保交易信息的完整性和真实性,防止交易信息被篡改或者被抵赖。一笔典型的交易,通常涉及到用户、商户、支付机构、银行四方,确保各方发出的信息没有被篡改也无法被抵赖。
4. 欺诈交易防范。 识别并防止欺诈交易,包括套现、洗钱等违规操作,以及通过识别用户信息泄露和可疑交易来保护用户资产的安全。这一方面通常由支付风控系统负责。
5. 服务可用性。 防范DDoS攻击,确保支付系统的稳定运行和服务可用性。通过部署防火墙、入侵检测系统等技术手段,及时发现并应对可能的DDoS攻击,保障支付服务的正常进行。
支付安全是一个综合性的系统工程,除了技术手段外,还需要建立健全的安全制度和合规制度,而后两者通常被大部分人所忽略。
下图是一个极简版的支付安全大图,包含了支付安全需要考虑的核心要点。

制度是基础。
哪种场景下需要加密存储,加密需要使用什么算法,密钥长度最少需要多少位,哪些场景下需要做签名验签,这些都是制度就明确了的。制度通常分为行业制度和内部安全制度。行业制度通常是国家层面制定的法律法规,比如《网络安全法》、《支付业务管理办法》等。内部安全制度通常是公司根据自身的业务和能力建立的制度,小公司可能就没有。
技术手段主要围绕四个目标:
1)敏感数据安全存储。
2)交易安全传输。
3)交易的完整性和真实性。
4)交易的合法性(无欺诈)。
对应的技术手段有:
支付系统作为电商平台的核心组件,其设计需要重点关注以下几个方面:
高可用性:通过多渠道冗余、断路器降级、异步化处理等手段保障系统可用性
数据一致性:采用分布式事务、最终一致性、对账补偿等机制确保资金安全
安全性:加强身份验证、参数签名、风险控制等安全措施
扩展性:通过模块化设计、策略模式等支持新支付渠道的快速接入
可观测性:建立完善的监控、日志、告警体系,快速发现和定位问题
一个健壮的支付系统不仅需要技术上的严谨设计,还需要业务流程上的周密考虑。只有在技术和业务的共同保障下,才能为用户提供安全、便捷、稳定的支付体验,为平台创造更大的商业价值。
本文作者:柳始恭
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!