PAYATHON 2026

C# 接入支付宝沙盒二维码支付时的问题如何解决?

支付小周

结论

支付宝沙盒二维码支付需要调用 alipay.trade.precreate 接口。服务端先创建预下单交易,再把响应中的 qr_code 转成二维码,供用户扫码。

支付失败时,问题通常不在二维码图片的生成过程,而在以下配置:

  • 网关没有指向支付宝沙盒地址;
  • app_id、应用私钥和支付宝公钥不属于同一个沙盒应用;
  • 签名方式或字符集配置有误;
  • 请求参数格式不符合接口要求;
  • 扫码时使用了正式支付宝账号;
  • 异步通知地址无法从公网访问;
  • 验签时误用了应用公钥。

目前的问题描述中没有错误码和接口响应,因此无法判断具体原因。排查时应先记录支付宝接口的原始响应,再逐项核对配置。

正确的接入流程

二维码支付通常按以下流程处理:

  1. C# 服务端调用 alipay.trade.precreate。
  2. 支付宝返回 qr_code。
  3. 服务端或前端把 qr_code 生成二维码图片。
  4. 用户通过支付宝沙盒客户端扫码付款。
  5. 支付宝向 notify_url 发送异步通知。
  6. 商户验签成功后,根据 out_trade_no 更新订单状态。
  7. 必要时调用 alipay.trade.query 主动查询支付结果。

二维码成功生成或前端完成跳转,都不能证明订单已经支付。订单的最终支付状态应以通过验签的异步通知为准,也可以通过主动查询确认。

检查沙盒配置

沙盒环境必须使用沙盒网关:

https://openapi-sandbox.dl.alipaydev.com/gateway.do

以下配置必须对应同一个沙盒应用:

var appId = "你的沙盒应用APPID";
var privateKey = "你的应用私钥";
var alipayPublicKey = "支付宝公钥";
var gateway = "https://openapi-sandbox.dl.alipaydev.com/gateway.do";
var charset = "UTF-8";
var signType = "RSA2";

接入时经常混淆以下几种密钥:

  • 应用私钥:由商户自行保管,用来为请求签名。
  • 应用公钥:上传到支付宝开放平台。
  • 支付宝公钥:从开放平台获取,用来验证支付宝返回的数据。
  • 支付宝私钥:不会提供给商户。

使用”公钥模式”时,验签配置应填写支付宝公钥,不能填写应用公钥。如果应用启用了证书模式,就要按照 SDK 的证书模式配置,不能与普通公钥模式混用。

C# 预下单示例

下面使用常见的 Alipay AOP SDK 调用方式。不同 SDK 包或版本的命名空间和构造函数可能略有差异,请以项目实际安装的官方 SDK 接口为准。

using System;
using Alipay.AopSdk.Core;
using Alipay.AopSdk.Core.Domain;
using Alipay.AopSdk.Core.Request;

public class AlipayQrPayService
{
    private readonly IAopClient _client;

    public AlipayQrPayService(
        string appId,
        string privateKey,
        string alipayPublicKey)
    {
        _client = new DefaultAopClient(
            "https://openapi-sandbox.dl.alipaydev.com/gateway.do",
            appId,
            privateKey,
            "json",
            "1.0",
            "RSA2",
            alipayPublicKey,
            "UTF-8",
            false
        );
    }

    public string CreateQrCode(
        string outTradeNo,
        decimal amount,
        string subject,
        string notifyUrl)
    {
        var request = new AlipayTradePrecreateRequest();
        request.SetNotifyUrl(notifyUrl);

        request.SetBizModel(new AlipayTradePrecreateModel
        {
            OutTradeNo = outTradeNo,
            TotalAmount = amount.ToString("0.00",
                System.Globalization.CultureInfo.InvariantCulture),
            Subject = subject
        });

        var response = _client.Execute(request);

        if (response == null)
        {
            throw new InvalidOperationException("支付宝接口未返回响应。");
        }

        if (response.Code != "10000")
        {
            throw new InvalidOperationException(
                $"支付宝预下单失败:Code={response.Code}," +
                $"SubCode={response.SubCode}," +
                $"Msg={response.Msg}," +
                $"SubMsg={response.SubMsg}"
            );
        }

        if (string.IsNullOrWhiteSpace(response.QrCode))
        {
            throw new InvalidOperationException("预下单成功,但未返回 qr_code。");
        }

        return response.QrCode;
    }
}

调用示例:

var service = new AlipayQrPayService(
    appId: "你的沙盒APPID",
    privateKey: "你的应用私钥",
    alipayPublicKey: "你的支付宝公钥"
);

string qrCodeContent = service.CreateQrCode(
    outTradeNo: "ORDER_" + DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
    amount: 0.01m,
    subject: "沙盒支付测试订单",
    notifyUrl: "https://example.com/api/alipay/notify"
);

qrCodeContent 保存的是二维码内容,不一定是图片地址。需要用二维码组件把这个字符串编码成图片。不要修改、截断或额外拼接字符串。

生成二维码图片

可以使用支持二维码生成的 .NET 库处理支付宝返回的 qr_code:

using QRCoder;

public static byte[] GenerateQrCode(string content)
{
    using var generator = new QRCodeGenerator();
    using var data = generator.CreateQrCode(
        content,
        QRCodeGenerator.ECCLevel.Q
    );

    var qrCode = new PngByteQRCode(data);
    return qrCode.GetGraphic(10);
}

在 ASP.NET Core 控制器中,可以直接返回 PNG 图片:

[HttpGet("pay/qrcode")]
public IActionResult GetPayQrCode()
{
    string content = _alipayService.CreateQrCode(
        "ORDER_" + DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
        0.01m,
        "沙盒测试订单",
        "https://example.com/api/alipay/notify"
    );

    byte[] image = GenerateQrCode(content);
    return File(image, "image/png");
}

二维码库只负责展示内容。即使图片能够正常生成,也不能说明支付宝预下单配置和订单参数一定正确。

正确处理异步通知

支付完成后,支付宝会以表单形式向 notify_url 发送通知。服务端需要读取全部参数并完成验签。

using Alipay.AopSdk.Core.Util;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

[ApiController]
public class AlipayNotifyController : ControllerBase
{
    private readonly string _alipayPublicKey = "你的支付宝公钥";

    [HttpPost("/api/alipay/notify")]
    public async Task<IActionResult> Notify()
    {
        var form = await Request.ReadFormAsync();

        var parameters = form.ToDictionary(
            item => item.Key,
            item => item.Value.ToString()
        );

        bool signVerified = AlipaySignature.RSACheckV1(
            parameters,
            _alipayPublicKey,
            "UTF-8",
            "RSA2",
            false
        );

        if (!signVerified)
        {
            return Content("failure");
        }

        parameters.TryGetValue("trade_status", out var tradeStatus);
        parameters.TryGetValue("out_trade_no", out var outTradeNo);
        parameters.TryGetValue("total_amount", out var totalAmount);
        parameters.TryGetValue("app_id", out var appId);

        // 还应核对 app_id、out_trade_no、total_amount 和订单当前状态。
        // 核对无误后,以幂等方式更新本地订单。
        if (tradeStatus == "TRADE_SUCCESS" ||
            tradeStatus == "TRADE_FINISHED")
        {
            // UpdateOrderAsPaid(outTradeNo);
            return Content("success");
        }

        return Content("success");
    }
}

这段代码只演示验签和基本处理结构。生产环境至少还要检查:

  • app_id 是否等于自己的应用 ID;
  • out_trade_no 是否存在;
  • total_amount 是否与本地订单金额一致;
  • 订单是否已经处理;
  • 通知中的商户身份信息是否匹配;
  • 当前交易状态是否允许更新订单。

通知处理成功后,接口必须返回以下纯文本:

success

如果接口返回其他内容、处理超时或响应状态不成功,支付宝可能再次发送通知。因此,订单更新逻辑必须支持幂等处理。

常见错误及处理方法

isv.invalid-signature

这个错误表示签名验证失败,应重点检查:

  • 是否把应用公钥当成了支付宝公钥;
  • 应用私钥与上传的应用公钥是否配对;
  • signType 是否一致,通常应为 RSA2;
  • 私钥格式是否符合 SDK 要求;
  • 配置内容是否包含多余的空格、换行或错误的头尾标记;
  • 公钥模式和证书模式是否混用;
  • 是否使用了其他应用或正式环境的密钥。

不要通过关闭签名验证来绕过这个错误。

isv.app-id-not-exist

这个错误通常表示 app_id 有误,或者应用 ID 与当前网关环境不匹配。请确认使用的是沙盒应用 ID,并且请求确实发送到了沙盒网关。

ACQ.INVALID_PARAMETER

这个错误表示业务参数不合法。需要记录并检查 SubCode 和 SubMsg,常见原因有:

  • out_trade_no 为空、过长或包含不合适的字符;
  • total_amount 格式错误;
  • subject 为空;
  • 请求模型序列化失败;
  • 必填参数没有传入。

金额建议使用 decimal,并按固定的小数格式输出:

amount.ToString(
    "0.00",
    System.Globalization.CultureInfo.InvariantCulture
);

不要直接采用受系统区域设置影响的格式,否则某些环境可能生成 0,01 这样的金额字符串。

二维码能显示,但无法付款

可以依次检查:

  • 是否使用支付宝沙盒客户端和沙盒买家账号;
  • 二维码内容是否完整;
  • 预下单响应是否确实返回 Code=10000;
  • 二维码是否已经过期;
  • 同一个 out_trade_no 是否曾用不同金额提交;
  • 沙盒应用配置是否仍然有效。

正式支付宝客户端通常不能与沙盒环境混用,应按照开放平台提供的沙盒测试方式操作。

支付成功,但收不到通知

常见原因有:

  • notify_url 使用了 localhost、局域网地址或其他无法从公网访问的地址;
  • 通知地址需要登录、验证码或额外鉴权;
  • HTTPS 证书无效;
  • 防火墙或反向代理拦截了 POST 请求;
  • 接口按 JSON 读取请求,但支付宝发送的是表单数据;
  • 通知处理时间过长;
  • 路由重定向导致请求失败。

本地开发时,可以使用安全的公网映射地址,或者调用主动查询接口确认订单状态。公网映射工具只适合调试,不应直接用作生产环境的通知方案。

主动查询订单状态

如果前端需要轮询支付状态,可以调用 alipay.trade.query,但需要控制轮询频率。

using Alipay.AopSdk.Core.Domain;
using Alipay.AopSdk.Core.Request;

public string QueryTrade(IAopClient client, string outTradeNo)
{
    var request = new AlipayTradeQueryRequest();

    request.SetBizModel(new AlipayTradeQueryModel
    {
        OutTradeNo = outTradeNo
    });

    var response = client.Execute(request);

    if (response == null)
    {
        throw new InvalidOperationException("查询接口未返回响应。");
    }

    if (response.Code != "10000")
    {
        return $"查询失败:{response.SubCode} - {response.SubMsg}";
    }

    return response.TradeStatus;
}

常见的交易状态包括:

WAIT_BUYER_PAY
TRADE_SUCCESS
TRADE_FINISHED
TRADE_CLOSED

各状态的具体含义和对应操作,应以支付宝当前接口文档为准。

建议的排查顺序

先记录以下字段,不要在捕获异常后只返回”支付失败”:

Code
Msg
SubCode
SubMsg
Body
OutTradeNo

然后按顺序检查:

  1. 请求是否发往沙盒网关;
  2. app_id 是否属于当前沙盒应用;
  3. 私钥和支付宝公钥是否配置正确;
  4. 签名方式是否统一为 RSA2;
  5. alipay.trade.precreate 是否返回 Code=10000;
  6. qr_code 是否未经修改就被生成二维码;
  7. 是否使用沙盒账号和沙盒客户端扫码;
  8. 通知接口能否从公网访问并正确验签;
  9. 本地订单金额、订单号和通知参数是否一致。

如果按照以上步骤仍无法定位问题,需要提供完整的 Code、SubCode、Msg 和 SubMsg。提交信息前应隐藏私钥、公钥、令牌和个人信息,但不要省略支付宝返回的错误码。仅凭”二维码支付报错”无法判断故障发生在哪个环节。

备注:内容仅供参考。