Payment 是全库最规范的三层实现参照。它统一了支付宝、微信支付和 Stripe 的下单、查询、关单、退款与回调验签能力。本页是域级手册——若需要方法逐参数的合同定义,参见 支付抽象参考;若需要特定厂商的配置与场景细节,参见各厂商页。
能力边界
| 能力 | 是否支持 | 说明 |
|---|---|---|
| 创建订单 | 支持 | App / Web / H5 / Code 四场景统一入口 |
| 查询状态 | 支持 | 按商户订单号查询交易状态 |
| 关闭订单 | 支持 | 关闭未支付订单 |
| 申请退款 | 支持 | 全额与部分退款,幂等号透传 |
| 回调验签 | 支持 | 异步通知验签 + 解析统一结果 |
| 分账 | 不支持 | 不在统一接口范围 |
| 转账到零钱 | 不支持 | 不在统一接口范围 |
| 对账单下载 | 不支持 | 不在统一接口范围 |
| 代扣 / 签约 | 不支持 | 不在统一接口范围 |
| 电子发票 | 不支持 | 不在统一接口范围 |
统一接口仅覆盖最通用的基础场景。复杂业务请使用各供应商的 partial class 扩展方法。
域结构
Bitzsoft.Integrations.Payment ← 抽象层(接口 + DTO + Result + Exception)├── Bitzsoft.Integrations.Payment.Alipay ← 支付宝(RSA2 签名)├── Bitzsoft.Integrations.Payment.WeChatPay ← 微信支付(APIv3,RSA-SHA256 + AES-256-GCM)├── Bitzsoft.Integrations.Payment.Stripe ← Stripe(Bearer + Webhook HMAC)└── Bitzsoft.Integrations.Payment.All ← 聚合包(按配置节自动注册)每个厂商包独立引用抽象层,可单独安装。聚合包按配置自动注册存在配置节的供应商。
统一接口
public interface IPaymentProvider{ string Code { get; }
Task<PaymentResult<PaymentOrderResult>> CreateOrderAsync( PaymentOrderRequest request, CancellationToken ct = default);
Task<PaymentResult<PaymentStatusResult>> QueryStatusAsync( string outTradeNo, CancellationToken ct = default);
Task<PaymentResult> CloseOrderAsync( string outTradeNo, CancellationToken ct = default);
Task<PaymentResult<RefundResult>> RefundAsync( RefundRequest request, CancellationToken ct = default);
Task<PaymentResult<CallbackVerificationResult>> VerifyCallbackAsync( CallbackPayload payload, CancellationToken ct = default);}Code 属性返回 PaymentProviderCode 常量(如 "alipay"),用于多厂商路由与日志关联。
Result 模式
Payment 域采用显式 Result 模式,业务操作不抛业务异常。所有操作返回 PaymentResult<T>,调用方通过 IsSuccess 判断。
public sealed class PaymentResult<T>{ public bool IsSuccess { get; init; } public T? Data { get; init; } // 成功时有值 public string? ErrorCode { get; init; } // 供应商原始错误码 public string? ErrorMessage { get; init; } // 错误消息
public static PaymentResult<T> Success(T data); public static PaymentResult<T> Fail(string errorCode, string errorMessage);}无数据操作(如 CloseOrderAsync)返回非泛型 PaymentResult,结构与上相同,仅省去 Data 字段,工厂方法为 Success() / Fail(errorCode, errorMessage)。
错误来源分三类,均被捕获并转换为 Fail:
| 错误来源 | 典型错误码 | 说明 |
|---|---|---|
| 供应商业务错误 | 供应商原始码(如 ACQ.TRADE_HAS_SUCCESS) | HTTP 200 但业务码非成功 |
| 供应商网关错误 | HTTP_{statusCode} | 非 2xx 响应,包装为 PaymentException 后捕获 |
| 网络异常 | HTTP_ERROR | HttpRequestException,消息为异常原文 |
所有厂商实现统一不吞 OperationCanceledException——没有兜底 catch (Exception),取消令牌能正常传播。
金额约定
全库金额以分为单位,long 类型。100 表示 1.00 元。这避免了浮点精度问题。
涉及分单位的所有字段:
PaymentOrderRequest.TotalAmount(下单金额)PaymentStatusResult.PaidAmount(实付金额)RefundRequest.RefundAmount(退款金额)RefundRequest.TotalAmount(原订单总金额,部分供应商退款接口要求)CallbackVerificationResult.PaidAmount(回调中的实付金额)
PaymentScene 路由
CreateOrderAsync 内部根据 request.Scene 路由到对应供应商接口。不同厂商对同一场景的映射不同:
| Scene | 支付宝接口 | 微信支付接口 | Stripe |
|---|---|---|---|
App | alipay.trade.app.pay → 签名串 | /v3/pay/transactions/app → prepay_id | Checkout Session |
Web | alipay.trade.page.pay → 跳转 URL | /v3/pay/transactions/native → code_url | Checkout Session |
H5 | alipay.trade.wap.pay → 跳转 URL | /v3/pay/transactions/h5 → h5_url | Checkout Session |
Code | alipay.trade.precreate → qr_code | /v3/pay/transactions/codepayment | 不支持 |
不支持的场景返回 Fail("UNSUPPORTED_SCENE", ...)。Stripe 额外要求 ReturnUrl(Checkout 的 success/cancel URL)。
枚举总览
public enum PaymentScene { App = 0, Web = 1, H5 = 2, Code = 3 }public enum PaymentStatus { Pending = 0, Success = 1, Closed = 2, Refunded = 3, Failed = 4 }public enum RefundStatus { Pending = 0, Success = 1, Failed = 2, Abnormal = 3 }PaymentProviderCode 为静态常量类:
| 常量 | 值 |
|---|---|
PaymentProviderCode.Alipay | "alipay" |
PaymentProviderCode.WeChatPay | "wechatpay" |
PaymentProviderCode.Stripe | "stripe" |
ConfigProvider 模式
Payment 域引入了 IPaymentConfigProvider<TOptions>,把”配置从哪来”与厂商实现解耦:
public interface IPaymentConfigProvider<TOptions> where TOptions : class{ TOptions Current { get; } string HttpClientName { get; }}默认实现从 IOptions<T> 读取配置。消费者可实现该接口从数据库或配置中心动态取配置(如多租户场景)。HttpClientName 用于 IHttpClientFactory 创建命名客户端,使审计日志 Handler 能正确关联。
多厂商路由
单供应商注册时,IPaymentProvider 直接注入厂商实现。多供应商场景使用解析器:
public class CheckoutRouter(IIntegrationProviderResolver<IPaymentProvider> providers){ public async Task PayAsync(string providerId, PaymentOrderRequest req) { var payment = providers.GetRequired(providerId); var result = await payment.CreateOrderAsync(req); if (!result.IsSuccess) throw new InvalidOperationException(result.ErrorMessage); }}providerId 对应 PaymentProviderCode 常量值。IIntegrationProviderResolver 由抽象层的 AddIntegrationProviderCapability 注册机制提供。
回调验签流程
VerifyCallbackAsync 统一接收 CallbackPayload,内部完成验签 + 解析:
CallbackPayload { Body, Headers, QueryString } │ ├─ Alipay: 从 Body / QueryString 解析参数 → RSA2 验签 → 直接读取明文字段 │ ├─ WeChatPay: 从 Headers 取签名 → RSA-SHA256 验签 → AES-256-GCM 解密 resource.ciphertext │ └─ Stripe: 从 Headers 取 Stripe-Signature → HMAC-SHA256 验签 → 直接读取明文 JSON │ ▼ CallbackVerificationResult { OutTradeNo, TradeNo?, PaidAmount, PaidAt?, Status, RawData }验签失败返回 Fail("SIGN_VERIFY_FAILED", ...)。不同厂商的 Headers 要求:
| 厂商 | 必需请求头 |
|---|---|
| 支付宝 | 无(签名在 Body / QueryString 参数中) |
| 微信支付 | Wechatpay-Timestamp / Wechatpay-Nonce / Wechatpay-Signature |
| Stripe | Stripe-Signature |
PaymentException 结构
虽然业务操作不抛异常,但 HTTP 层失败时会构造 PaymentException 再捕获转为 Result:
public class PaymentException : IntegrationException{ public string ProviderName { get; } // 如 "alipay" public string? ErrorCode { get; } // 供应商原始错误码 public string? ProviderMessage { get; } // 供应商原始错误消息}Message 格式为 "[{providerName}] {errorCode}: {providerMessage}",便于日志关联。
注册
单厂商
// 支付宝builder.Services.AddBitzsoftAlipayPayment(builder.Configuration.GetSection("Alipay"));
// 微信支付builder.Services.AddBitzsoftWeChatPayPayment(builder.Configuration.GetSection("WeChatPay"));
// Stripebuilder.Services.AddBitzsoftStripePayment(builder.Configuration.GetSection("Stripe"));多厂商聚合
builder.Services.AddBitzsoftPaymentAll(builder.Configuration, "Payment");聚合注册扫描配置节,仅注册存在配置的供应商:
{ "Payment": { "Alipay": { "AppId": "...", "AppPrivateKey": "...", "AlipayPublicKey": "..." }, "WeChatPay": { "AppId": "...", "MchId": "...", "MchSerialNo": "...", "APIv3Key": "...", "PrivateKey": "...", "WeChatPayPublicKey": "..." }, "Stripe": { "SecretKey": "sk_live_xxx", "WebhookSecret": "whsec_xxx" } }}每个 DI 方法命名遵循 AddBitzsoft{厂商}Payment() 约定,命名空间统一为 Microsoft.Extensions.DependencyInjection。
完整消费示例
public class CheckoutService(IPaymentProvider payment){ public async Task<string> CreateOrderAsync(string orderNo, long cents, string subject) { var result = await payment.CreateOrderAsync(new PaymentOrderRequest { OutTradeNo = orderNo, TotalAmount = cents, // 分 Subject = subject, Scene = PaymentScene.Web, ReturnUrl = "https://shop.example.com/return", });
if (!result.IsSuccess) throw new InvalidOperationException( $"[{result.ErrorCode}] {result.ErrorMessage}");
return result.Data!.PayUrl!; // Web 场景返回跳转 URL }}
public class RefundService(IPaymentProvider payment){ public async Task RefundAsync(string orderNo, string refundNo, long cents) { var result = await payment.RefundAsync(new RefundRequest { OutTradeNo = orderNo, RefundNo = refundNo, RefundAmount = cents, Reason = "用户申请退款", });
if (!result.IsSuccess || result.Data!.Status != RefundStatus.Success) throw new InvalidOperationException(result.ErrorMessage); }}测试模式
命名 HttpClient 测试
Payment 域的 HttpClient 通过 IHttpClientFactory 创建,命名由 Options.HttpClientName 控制(默认 nameof(Provider))。测试时注册 mock handler 并绑定同名客户端:
var handler = new MockHttpMessageHandler();services.AddHttpClient(nameof(AlipayPaymentProvider)) .ConfigurePrimaryHttpMessageHandler(() => handler);Result 断言
测试应断言 IsSuccess 与 ErrorCode,而非捕获异常:
var result = await provider.QueryStatusAsync("ORDER_123");
Assert.False(result.IsSuccess);Assert.Equal("ACQ.TRADE_NOT_EXIST", result.ErrorCode);验签测试
回调验签的测试需要构造合法签名的 payload。对于支付宝,使用测试密钥对参数签名;对于微信支付,需构造加密的 resource.ciphertext。
厂商清单
| 厂商 | Code | 场景 | 签名 / 验签 | 稳定性 |
|---|---|---|---|---|
| 支付宝 | alipay | App / Web / H5 / Code | RSA2(SHA256WithRSA) | Preview |
| 微信支付 | wechatpay | App / Native / H5 / Code | RSA-SHA256 + AES-256-GCM | Preview |
| Stripe | stripe | App / Web / H5(Checkout) | Bearer + HMAC-SHA256 | Preview |