ASP.NET Core中集成第三方支付接口如支付宝微信支付插图

ASP.NET Core中集成第三方支付接口:以支付宝和微信支付为例

大家好,作为一名在.NET生态里摸爬滚打多年的开发者,我深知在电商或需要在线交易的系统中,支付功能是核心,也是最容易“踩坑”的环节。今天,我想和大家分享一下在ASP.NET Core项目中,如何相对优雅地集成支付宝和微信支付。这个过程不仅仅是调用一个API那么简单,它涉及到配置管理、签名验证、异步通知处理和状态机维护等一系列问题。我会结合自己的实战经验,把关键步骤和容易出错的点都捋清楚。

一、前期准备:理清思路与配置申请

在动手写代码之前,准备工作至关重要。这直接决定了后续开发的顺畅程度。

1. 申请商户平台账号: 你需要分别前往支付宝开放平台微信支付商户平台申请企业资质账号,并创建你的应用或小程序/公众号。这个过程需要营业执照等材料,可能需要几天时间。

2. 获取关键配置信息:

  • 支付宝: `APP_ID`(应用ID)、`应用私钥`、`支付宝公钥`。特别注意,这里使用的是RSA2签名,你需要用支付宝提供的工具生成一对密钥,将应用公钥上传到平台,然后获取支付宝公钥。
  • 微信支付: `APP_ID`(公众号或小程序ID)、`商户号(MCHID)`、`API V3密钥`、`API证书`(包含证书文件和私钥)。微信支付V3版主要使用API密钥进行签名,证书用于敏感信息加密和退款等操作。

3. 配置开发环境: 两个平台都支持沙箱环境(Sandbox)用于测试。强烈建议先在沙箱环境完成全部联调,再切换到生产环境。将上述敏感信息妥善保存,我们将在项目中使用IConfiguration或用户机密(User Secrets)来管理。

二、项目搭建与配置管理

我们创建一个ASP.NET Core Web API或MVC项目。我的习惯是为支付模块建立一个独立的服务层。

1. 管理配置:appsettings.jsonappsettings.Development.json中配置支付参数。切勿将真实密钥提交到源码仓库!

{
  "PaymentSettings": {
    "Alipay": {
      "AppId": "你的沙箱APP_ID",
      "GatewayUrl": "https://openapi.alipaydev.com/gateway.do", // 沙箱地址
      "PrivateKey": "你的应用私钥(去除-----BEGIN/END PRIVATE KEY-----和换行)",
      "AlipayPublicKey": "支付宝公钥",
      "NotifyUrl": "https://yourdomain.com/api/payment/alipay/notify",
      "ReturnUrl": "https://yourdomain.com/payment/success"
    },
    "WeChatPay": {
      "AppId": "公众号或小程序APPID",
      "MchId": "商户号",
      "ApiV3Key": "你的API V3密钥",
      "PrivateKey": "证书私钥内容或路径",
      "SerialNo": "证书序列号",
      "NotifyUrl": "https://yourdomain.com/api/payment/wechatpay/notify"
    }
  }
}

2. 创建配置模型与支付服务接口: 创建对应的C#配置类(如AlipayOptions, WeChatPayOptions),并通过services.Configure()注入。然后定义一个统一的支付服务接口IPaymentService,包含CreatePaymentAsync(创建订单)、HandleNotificationAsync(处理异步通知)等方法。

三、集成支付宝支付

支付宝的集成相对直观。我们可以使用官方SDK(AlipaySDKNet),也可以手动组装参数并签名。为了更透彻地理解流程,这里展示核心的手动签名和请求构造思路。

1. 创建支付订单: 在控制器中,接收前端传来的订单信息,调用支付服务生成支付参数。

// 示例:构建支付宝电脑网站支付请求参数
public async Task CreateAlipayPagePayAsync(Order order)
{
    var options = _config.GetSection("PaymentSettings:Alipay").Get();
    
    var bizContent = new {
        out_trade_no = order.OrderNo, // 商户订单号
        total_amount = order.TotalAmount.ToString("F2"), // 金额,两位小数
        subject = order.Subject, // 订单标题
        product_code = "FAST_INSTANT_TRADE_PAY"
    };

    var sortedParams = new SortedDictionary(StringComparer.Ordinal)
    {
        {"app_id", options.AppId},
        {"method", "alipay.trade.page.pay"},
        {"charset", "utf-8"},
        {"sign_type", "RSA2"},
        {"timestamp", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")},
        {"version", "1.0"},
        {"biz_content", JsonSerializer.Serialize(bizContent)},
        {"notify_url", options.NotifyUrl},
        {"return_url", options.ReturnUrl}
    };

    // 1. 拼接待签名字符串
    var signContent = BuildSignContent(sortedParams);
    // 2. 使用私钥进行RSA2签名
    var sign = RSAHelper.Sign(signContent, options.PrivateKey, "RSA2");
    sortedParams.Add("sign", sign);

    // 3. 将所有参数拼接成最终请求URL
    var requestUrl = options.GatewayUrl + "?" + BuildQueryString(sortedParams);
    return requestUrl; // 前端应重定向到此URL
}

踩坑提示: 金额单位是“元”,必须保留两位小数。`notify_url`必须是公网可访问的,用于接收支付结果异步通知,这是保证交易状态一致性的关键。

2. 处理异步通知(Notify): 这是支付集成的核心和难点。支付宝服务器会以POST形式将支付结果发送到你的NotifyUrl。你必须验证签名,并返回success(失败则返回fail,支付宝会重试)。

[HttpPost("alipay/notify")]
public async Task AlipayNotify()
{
    using var reader = new StreamReader(Request.Body);
    var notifyData = await reader.ReadToEndAsync();
    var parameters = ParseQueryString(notifyData); // 解析参数字典

    // 1. 验证签名(使用支付宝公钥)
    if (!VerifySignature(parameters, _alipayOptions.AlipayPublicKey))
    {
        _logger.LogWarning("支付宝通知签名验证失败");
        return Content("fail");
    }

    // 2. 验证商户APP_ID和订单号(out_trade_no)是否匹配
    // 3. 验证交易状态(trade_status)是否为 TRADE_SUCCESS 或 TRADE_FINISHED

    var orderNo = parameters["out_trade_no"];
    var tradeNo = parameters["trade_no"]; // 支付宝交易号

    // 4. 处理业务:更新订单状态为已支付,并检查金额是否匹配(重要!防止金额篡改)
    bool handleResult = await _orderService.ProcessPaidOrderAsync(orderNo, tradeNo, ...);
    
    return handleResult ? Content("success") : Content("fail");
}

四、集成微信支付(V3版)

微信支付V3版使用了基于API密钥的HMAC-SHA256签名,并且通知数据是JSON格式且经过AES-GCM加密,更安全但也更复杂。强烈建议使用社区优秀库,如 SKIT.FlurlHttpClient.Wechat.TenpayV3,它能极大简化流程。

1. 使用FlurlHttpClient库: 首先通过NuGet安装该库。

dotnet add package SKIT.FlurlHttpClient.Wechat.TenpayV3

2. 服务注册与下单:

// Startup.cs 或 Program.cs
services.AddSingleton(sp =>
{
    var options = sp.GetRequiredService<IOptions>().Value;
    var settings = new WechatTenpayClientSettings()
    {
        MerchantId = options.MchId,
        MerchantV3Secret = options.ApiV3Key,
        MerchantCertificateSerialNumber = options.SerialNo,
        MerchantCertificatePrivateKey = options.PrivateKey // 私钥文本
    };
    return new WechatTenpayClient(settings);
});

// 在支付服务中调用JSAPI下单(以公众号支付为例)
public async Task CreateWeChatPayJsApiAsync(Order order)
{
    var client = _serviceProvider.GetRequiredService();
    var request = new CreatePayTransactionJsapiRequest()
    {
        OutTradeNumber = order.OrderNo,
        AppId = _wechatOptions.AppId,
        Description = order.Subject,
        NotifyUrl = _wechatOptions.NotifyUrl,
        Amount = new CreatePayTransactionJsapiRequest.Types.Amount()
        {
            Total = (int)(order.TotalAmount * 100) // 微信支付金额单位是分!
        },
        Payer = new CreatePayTransactionJsapiRequest.Types.Payer()
        {
            OpenId = userOpenId // 从微信授权获取的OpenId
        }
    };
    var response = await client.ExecuteCreatePayTransactionJsapiAsync(request);
    if (response.IsSuccessful())
    {
        // response.PrepayId 用于前端调起支付
        return new WechatPayResponse { PrepayId = response.PrepayId };
    }
    throw new Exception($"微信支付下单失败:{response.ErrorMessage}");
}

3. 处理微信支付通知: 微信的通知是加密的,使用上述库可以轻松解密。

[HttpPost("wechatpay/notify")]
public async Task WeChatPayNotify(
    [FromHeader(Name = "Wechatpay-Signature")] string signature,
    [FromHeader(Name = "Wechatpay-Serial")] string serial,
    [FromHeader(Name = "Wechatpay-Nonce")] string nonce,
    [FromHeader(Name = "Wechatpay-Timestamp")] string timestamp)
{
    try
    {
        // 该库提供了中间件,这里展示手动处理逻辑
        var client = _serviceProvider.GetRequiredService();
        var notify = await client.DecryptEventResourceAsync(Request);
        
        if (notify.EventType == "TRANSACTION.SUCCESS")
        {
            var orderNo = notify.OutTradeNumber;
            var tradeNo = notify.TransactionId;
            // 更新订单状态...
            await _orderService.ProcessPaidOrderAsync(orderNo, tradeNo, ...);
            
            // 返回成功JSON
            return new JsonResult(new { code = "SUCCESS", message = "成功" });
        }
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "处理微信支付通知异常");
    }
    return new JsonResult(new { code = "FAIL", message = "失败" });
}

踩坑提示: 微信支付的金额单位是,千万别弄错。证书序列号(`SerialNo`)和API V3密钥是核心。通知处理必须严格按照微信要求的格式返回JSON。

五、总结与最佳实践

集成支付接口是一个系统工程,总结几点心得:

  1. 异步通知是唯一可信来源: 前端支付成功回调(`return_url`)仅用于页面跳转,绝不能作为更新订单状态的依据。所有状态更新必须基于服务端验证过的异步通知。
  2. 做好幂等与对账: 异步通知可能会重复发送,你的处理逻辑必须是幂等的。同时,定期(如每日)通过支付平台的对账单接口进行对账,确保系统状态与支付平台一致。
  3. 善用成熟库: 对于微信支付V3,使用像FlurlHttpClient.Wechat这样的成熟库能节省大量时间,避免在加密、签名等底层细节上犯错。
  4. 完善的日志与监控: 支付流程的每一步,尤其是接收和处理通知,都要记录详细日志。设置异常监控,确保问题能第一时间被发现。

希望这篇结合了实战经验和“踩坑”提示的教程,能帮助你在ASP.NET Core项目中更顺利、更稳健地集成支付功能。支付无小事,务必细心测试,祝大家编码愉快!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。