当你确定只用一家厂商,且短期内不会扩到第二家,就走「单厂商消费」路线。它的特点是:装一个厂商包、注册一次、直接注入能力接口。比多厂商路由少了 Resolver 这层,代码更直接。
这篇指南用 Payment.Alipay(支付宝) 做完整可运行示例,同样的模式适用于任何域的单厂商消费(FileStorage.Oss、AI.OpenAI、eSign.Esign……)。
单厂商消费四步
四步固定,业务逻辑全部写在第 4 步之后。换厂商时只改前两步(换包、换配置),后两步的代码一行不动。
1. 安装厂商包
只装你要用的那一家,不用装聚合包。包的传递依赖会自动带齐抽象层、Core、Compatibility、RequestLogging。
dotnet new web -n MyShopcd MyShop
# ① 装支付宝支付包(传递依赖自动带齐其余)dotnet add package Bitzsoft.Integrations.Payment.Alipay2. 配置 Options
每个厂商包对应一个配置节,节名默认就是厂商名(如 Alipay)。密钥不要进 Git,用 user-secrets 或环境变量管理。
# ① 密钥进 user-secrets,不进代码库dotnet user-secrets initdotnet user-secrets set "Alipay:AppId" "2021000..."dotnet user-secrets set "Alipay:AppPrivateKey" "MIIEvQIBADANB..." # PEM 格式dotnet user-secrets set "Alipay:AlipayPublicKey" "MIIBIjANBg..."dotnet user-secrets set "Alipay:NotifyUrl" "https://your-domain.com/callback/alipay"等价的 appsettings.json(生产用环境变量覆盖敏感字段):
{ "Alipay": { "AppId": "2021000...", "AppPrivateKey": "MIIEvQIBADANB...", "AlipayPublicKey": "MIIBIjANBg...", "Gateway": "https://openapi.alipay.com/gateway.do", "NotifyUrl": "https://your-domain.com/callback/alipay", "SignType": "RSA2", "Timeout": "00:00:30" }}配置字段与 AlipayOptions 一一对应。Gateway、SignType、Timeout 有默认值,可不配;AppId / AppPrivateKey / AlipayPublicKey 必填,缺失会在首次请求时抛 PaymentException。
3. 注册服务
调用厂商包提供的 AddBitzsoft{厂商}{域}() 扩展方法。这会自动注册命名 HttpClient 并挂上请求审计日志。
using Bitzsoft.Integrations.Payment;using Bitzsoft.Integrations.Payment.Models;
var builder = WebApplication.CreateBuilder(args);
// ① 一行注册:自动挂载 HttpClient + RequestLogHandler 审计脱敏builder.Services.AddBitzsoftAlipayPayment(builder.Configuration.GetSection("Alipay"));
var app = builder.Build();app.Run();注册内部完成的事:绑定 AlipayOptions、注册命名 HttpClient(名为 AlipayPaymentProvider)并挂 AddRequestLogging、注册 IAlipayConfigProvider、把 AlipayPaymentProvider 作为 IPaymentProvider 能力提交到目录。
4. 注入能力接口并使用
直接注入 IPaymentProvider,不用关心后面是哪家厂商。所有方法返回 PaymentResult<T>,不抛业务异常给调用方。
// Program.cs —— 下单端点app.MapPost("/api/pay", async ( IPaymentProvider payment, // ① 直接注入能力接口 PaymentOrderRequest request) =>{ // ② 调用方法,换厂商时这段代码不动 var result = await payment.CreateOrderAsync(request);
// ③ 统一处理 Result,不写 try/catch return result.IsSuccess ? Results.Ok(result.Data) : Results.BadRequest(new { result.ErrorCode, result.ErrorMessage });});
// 查询订单状态app.MapGet("/api/pay/status/{outTradeNo}", async ( string outTradeNo, IPaymentProvider payment) =>{ var result = await payment.QueryStatusAsync(outTradeNo);
if (!result.IsSuccess) return Results.BadRequest(new { result.ErrorCode, result.ErrorMessage });
var data = result.Data!; return Results.Ok(new { data.OutTradeNo, data.TradeNo, Status = data.Status.ToString(), data.PaidAmount, });});
// 回调验签app.MapPost("/callback/alipay", async (HttpContext ctx, IPaymentProvider payment) =>{ var body = await new StreamReader(ctx.Request.Body).ReadToEndAsync(); var payload = new CallbackPayload { Body = body, QueryString = ctx.Request.QueryString.Value, };
var result = await payment.VerifyCallbackAsync(payload); if (!result.IsSuccess) return Results.Text("fail");
var data = result.Data!; Console.WriteLine($"订单 {data.OutTradeNo} 支付 {data.PaidAmount / 100m:F2} 元"); return Results.Text("success"); // ① 必须返回 "success",否则支付宝会重复通知});处理 Result 的标准模式
所有域的 Result<T> 结构一致:IsSuccess / Data / ErrorCode / ErrorMessage。消费侧不写 try/catch,统一用 if (!result.IsSuccess) 分支。
常见错误码:
| ErrorCode | 含义 | 建议 |
|---|---|---|
HTTP_ERROR | 网络层失败(超时、DNS、连接拒绝) | 重试或告警 |
SIGN_VERIFY_FAILED | 回调签名验证失败 | 检查公钥配置,拒绝处理 |
厂商业务码(如支付宝 ACQ.*) | 厂商返回的业务错误 | 按厂商文档处理 |
UNSUPPORTED_SCENE | 请求的支付场景不支持 | 检查 Scene 枚举值 |
完整可运行示例
下面是一个最小完整应用,装包、配置、注册、下单、查询全在一个 Program.cs 里。
dotnet new web -n MyShop && cd MyShopdotnet add package Bitzsoft.Integrations.Payment.Alipayusing Bitzsoft.Integrations.Payment;using Bitzsoft.Integrations.Payment.Models;
var builder = WebApplication.CreateBuilder(args);builder.Services.AddBitzsoftAlipayPayment(builder.Configuration.GetSection("Alipay")); // ① 注册var app = builder.Build();
app.MapPost("/api/pay", async (IPaymentProvider payment, PaymentOrderRequest req) => // ② 注入{ var result = await payment.CreateOrderAsync(req); // ③ 调用 return result.IsSuccess ? Results.Ok(result.Data) : Results.BadRequest(result.ErrorMessage); // ④ 处理});
app.Run();发起一笔电脑网站支付:
curl -X POST http://localhost:5000/api/pay \ -H "Content-Type: application/json" \ -d '{ "outTradeNo": "ORDER-20260801-001", "totalAmount": 100, "subject": "测试商品", "scene": "Web" }'响应里的 payUrl 就是支付宝收银台跳转地址。
{ "isSuccess": true, "data": { "outTradeNo": "ORDER-20260801-001", "payUrl": "https://openapi.alipay.com/gateway.do?..." }}何时升级到多厂商路由
单厂商模式在以下任一情况出现时,建议切到多厂商路由:
- 业务需要接入第二家厂商(如同时支持支付宝和微信)。
- 需要按租户、地区或灰度策略动态切换厂商。
- 想在测试环境用沙箱、生产用正式环境,且通过 Provider ID 切换。
升级很平滑:把直接注入 IPaymentProvider 换成注入 IIntegrationProviderResolver<IPaymentProvider>,调用前多一步 resolver.GetRequired(providerId)。业务方法(CreateOrderAsync 等)和 Result 处理代码原样保留。
下一步
- 多厂商路由消费指南:接入第二家时怎么改。
- 快速开始:更详细的支付宝接入实战。
- Provider 目录与解析:理解 Resolver 背后的设计。