导读:本期聚焦于小伙伴创作的《如何用C#搭建稳定可靠的在线支付平台?实战经验全解析》,敬请观看详情。支付链路最怕的就是钱扣了订单却没更新,这类数据不一致问题往往源于没有用好分布式事务。在C#技术栈里,借助EF Core配合本地消息表,可以把订单、账户、流水三个模块的写操作收敛到同一个数据库事务中,再经由后台 worker 补偿推送至第三方支付网关。相比直接调用外部 API 并依赖回调,这种方案能把掉单率压到千分之一以下。本文结合一个真实项目的演进过程,说明如何用 ASP.NET Core 设计支付网关抽象层、如何处理异步通知验签、以及怎样用 Polly 做重试与熔断,帮助团队少走弯路。

在线支付平台对资金安全与系统稳定性要求极高,使用C#构建此类系统时需要从架构分层、数据一致性和外部渠道对接三个维度综合考虑。下面以一个实际落地的ASP.NET Core项目为例,梳理关键设计点。

如何用C#搭建稳定可靠的在线支付平台?实战经验全解析

一、整体架构与网关抽象

在项目初期,我们直接将支付宝、微信支付的SDK调用散落在订单服务中,导致后续接入银联时改动了大量业务代码。后来引入了支付网关抽象层,把所有渠道统一为IPaymentGateway接口,业务侧只依赖抽象,不关心具体实现。

这种设计符合依赖倒置原则,也方便做单元测试。例如使用Mock网关即可在本地跑通下单、退款流程,而不用频繁调用真实渠道。同时,我们将签名、验签、报文组装等通用逻辑下沉到基类,各渠道只需实现独有的请求映射。

public interface IPaymentGateway
{
    Task<PaymentResult> PayAsync(PaymentOrder order);
    Task<RefundResult> RefundAsync(RefundRequest request);
    bool VerifyNotify(string rawBody, string signature);
}

public abstract class BaseGateway : IPaymentGateway
{
    protected string MerchantId { get; set; }
    public abstract Task<PaymentResult> PayAsync(PaymentOrder order);
    public abstract Task<RefundResult> RefundAsync(RefundRequest request);
    public abstract bool VerifyNotify(string rawBody, string signature);
}

二、基于本地消息表的事务一致性

支付核心难题是“扣款成功但本地订单状态丢失”。我们采用本地消息表方案:在写入订单状态的同时,在同一数据库事务内插入一条待发送消息,由独立后台服务轮询并调用第三方API,成功后标记消息已发送。

借助EF Core的SaveChanges事务边界,可以保证业务数据与消息要么都提交、要么都回滚。相比引入重量级分布式事务框架,该方式在单一SQL Server实例上足够可靠,且排查问题只需看消息表状态。

using var tx = await dbContext.Database.BeginTransactionAsync();
var order = new Order { Status = OrderStatus.Paid };
dbContext.Orders.Add(order);
dbContext.OutboxMessages.Add(new OutboxMessage
{
    Topic = "payment.success",
    Body = JsonSerializer.Serialize(order),
    Sent = false
});
await dbContext.SaveChangesAsync();
await tx.CommitAsync();

三、异步通知与验签处理

第三方支付通常通过异步回调通知结果,这里最容易被忽略的是验签和幂等。我们为每个网关实现VerifyNotify方法,用渠道公钥校验签名,并将通知号作为幂等键,避免重复更新。

在ASP.NET Core里,专门建立NotifyController接收不同渠道的回调,内部路由到对应Gateway。如果验签失败直接返回失败状态码,防止伪造请求篡改订单。对于处理成功的通知,立即返回渠道要求的success文本。

[HttpPost("notify/{channel}")]
public async Task<IActionResult> Notify(string channel, [FromBody] string body)
{
    var gateway = gatewayFactory.Get(channel);
    if (!gateway.VerifyNotify(body, Request.Headers["sign"]))
        return BadRequest("invalid sign");
    await mediator.Publish(new PaymentNotifyEvent(body));
    return Ok("success");
}

四、用Polly增强调用韧性

调用外部支付接口时网络抖动难以避免。我们在网关HttpClient上集成Polly,配置重试与熔断策略:对于5xx或超时做有限次重试,连续失败则熔断一段时间,保护下游也保护自己。

具体做法是通过AddPolicyHandler注册策略,并将重试间隔设为指数退避。这样在大促期间某渠道不稳定时,系统能自动降级到备用渠道或排队,而不是雪崩。

var retry = Policy
    .Handle<HttpRequestException>()
    .OrResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
    .WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(Math.Pow(2, i)));

services.AddHttpClient("pay")
    .AddPolicyHandler(retry);

五、总结与避坑提醒

回顾整个项目,最大的收益来自清晰的网关抽象与本地消息表。切忌在业务代码里直接写死渠道SDK,也不要用单纯回调更新订单而不做幂等。资金类系统慢一点没关系,但必须准。

另外建议所有金额使用long型分单位存储,避免decimal浮点误差;日志中严禁打印卡号与密码,只记脱敏后的交易号。把这些基础规范固化到代码模板,团队扩展新支付渠道就能以天为单位交付。

C#在线支付事务一致性修改时间:2026-08-09 23:30:29

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。