Skip to content
Bitzsoft.Integrationsbitzsoft.integrations

Concept

Payment 支付域手册

支付域完整能力边界、统一接口、DTO、枚举、Result 模式、多厂商路由、回调验签与测试模式。

Last updated

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_SUCCESSHTTP 200 但业务码非成功
供应商网关错误HTTP_{statusCode}非 2xx 响应,包装为 PaymentException 后捕获
网络异常HTTP_ERRORHttpRequestException,消息为异常原文

所有厂商实现统一不吞 OperationCanceledException——没有兜底 catch (Exception),取消令牌能正常传播。

金额约定

全库金额以为单位,long 类型。100 表示 1.00 元。这避免了浮点精度问题。

涉及分单位的所有字段:

  • PaymentOrderRequest.TotalAmount(下单金额)
  • PaymentStatusResult.PaidAmount(实付金额)
  • RefundRequest.RefundAmount(退款金额)
  • RefundRequest.TotalAmount(原订单总金额,部分供应商退款接口要求)
  • CallbackVerificationResult.PaidAmount(回调中的实付金额)

PaymentScene 路由

CreateOrderAsync 内部根据 request.Scene 路由到对应供应商接口。不同厂商对同一场景的映射不同:

Scene支付宝接口微信支付接口Stripe
Appalipay.trade.app.pay → 签名串/v3/pay/transactions/app → prepay_idCheckout Session
Webalipay.trade.page.pay → 跳转 URL/v3/pay/transactions/native → code_urlCheckout Session
H5alipay.trade.wap.pay → 跳转 URL/v3/pay/transactions/h5 → h5_urlCheckout Session
Codealipay.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
StripeStripe-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"));
// Stripe
builder.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 断言

测试应断言 IsSuccessErrorCode,而非捕获异常:

var result = await provider.QueryStatusAsync("ORDER_123");
Assert.False(result.IsSuccess);
Assert.Equal("ACQ.TRADE_NOT_EXIST", result.ErrorCode);

验签测试

回调验签的测试需要构造合法签名的 payload。对于支付宝,使用测试密钥对参数签名;对于微信支付,需构造加密的 resource.ciphertext。

厂商清单

厂商Code场景签名 / 验签稳定性
支付宝alipayApp / Web / H5 / CodeRSA2(SHA256WithRSA)Preview
微信支付wechatpayApp / Native / H5 / CodeRSA-SHA256 + AES-256-GCMPreview
StripestripeApp / Web / H5(Checkout)Bearer + HMAC-SHA256Preview

相关

100%

滚轮或按钮缩放 · 放大后拖动画面 · 双击切换 100% / 200%