You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# Razor Pages中Stripe Webhook 400交付失败问题求助

Stripe Webhook在Razor Pages中触发失败(400错误)排查与解决方案

问题描述

我开发了一款集成Stripe的Web应用,实现商品购买功能并配置Webhook验证购买是否成功。因未使用MVC框架,无法采用Stripe文档中的[HttpPost]标签,不确定Razor Pages中的实现是否正确可行。已排查认证无问题、Stripe密钥配置正确,但Webhook的Post方法未被触发,甚至初始日志「IT WORKS」都未生成。此前为404错误,现变为400错误,Stripe仪表盘的Webhook配置已确认无误。

相关代码

1. Program.cs中的Stripe密钥配置

// Retrieve the Stripe API key from Azure Key Vault
var stripeApiKeySecret = await secretClient2.GetSecretAsync("StripeSecretTest");
var stripeApiKey = stripeApiKeySecret.Value?.Value;

2. 创建结账会话代码

public async Task<ActionResult> OnPostAsync()
{
    var domain = "https://gainesopusinstitute.com";
    var pOption = Request.Form["PriceOption"];
    var priceOption = pOption.First();

    // Get current user
    var user = await _userManager.GetUserAsync(User);
    if (user == null)
    {
        return RedirectToPage("/Account/Login");
    }

    var options = new SessionCreateOptions
    {
        PaymentMethodTypes = new List<string>
        {
            "card",
        },
        LineItems = new List<SessionLineItemOptions>
        {
            new SessionLineItemOptions
            {
                Price = priceOption,
                Quantity = 1,
            },
        },
        Mode = "payment",
        SuccessUrl = domain + "/PagesLoggedIn/tokenSuccess",
        CancelUrl = domain + "/cancel/tokenCancel",
        Customer = user.StripeCustomerId,
        AutomaticTax = new SessionAutomaticTaxOptions { Enabled = true },
    };


    var service = new SessionService();
    Session session = service.Create(options);

    Response.Headers.Add("Location", session.Url);

    return new StatusCodeResult(303);
}

3. /tokenSuccess Razor页面后端代码

[AllowAnonymous]
public class tokenSuccessModel : PageModel
{
    private readonly UserManager<User> _userManager;
    private readonly ILogger<tokenSuccessModel> _logger;
    private readonly IConfiguration _configuration;

    public tokenSuccessModel(UserManager<User> userManager, ILogger<tokenSuccessModel> logger, IConfiguration configuration)
    {
        _userManager = userManager;
        _logger = logger;
        _configuration = configuration;
    }
    
    public async Task<IActionResult> OnPostAsync()
    {
        _logger.LogInformation("IT WORKS, IT WENT THROUGH!");
        var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();

        const string endpointSecret = "USneekyBoiTyrnnaStealMySecret";

        try
        {
            var stripeEvent = EventUtility.ConstructEvent(json, Request.Headers["Stripe-Signature"], endpointSecret);

            if (stripeEvent.Type == Events.PaymentIntentSucceeded)
            {
                var paymentIntent = stripeEvent.Data.Object as PaymentIntent;
                if (paymentIntent == null)
                {
                    _logger.LogInformation("Failed to retrieve PaymentIntent object.");
                    return BadRequest();
                }
                var connectedAccountId = stripeEvent.Account;
                handleSuccessfulPaymentIntent(connectedAccountId, paymentIntent);
                var customerId = paymentIntent.CustomerId;
                var user = await _userManager.Users.SingleOrDefaultAsync(u => u.StripeCustomerId == customerId);

                if (user == null)
                {
                    _logger.LogInformation($"User with StripeCustomerId {customerId} not found.");
                    return NotFound("User not found.");
                }

                _logger.LogInformation($"User {user.Id} found.");
                user.tokens += 1;
                _logger.LogInformation($"User {user.Id} tokens increased to {user.tokens}.");
                await _userManager.UpdateAsync(user);
                _logger.LogInformation($"User {user.Id} updated successfully.");
                return StatusCode(StatusCodes.Status200OK);
            }
            return StatusCode(StatusCodes.Status400BadRequest);
        }
        catch (Exception e)
        {
            _logger.LogInformation(e.ToString());
            return BadRequest();
        }
    }

    private void handleSuccessfulPaymentIntent(string connectedAccountId, PaymentIntent paymentIntent)
    {
        // Fulfill the purchase.
        _logger.LogInformation($"Connected account ID: {connectedAccountId}");
        _logger.LogInformation($"{paymentIntent}");
    }
}

Stripe报错信息

当前Stripe仪表盘显示400 Bad Request错误,服务器无法处理Webhook请求。

解决方案建议

1. 禁用CSRF保护

Razor Pages默认启用CSRF验证,而Stripe的Webhook请求不会携带CSRF令牌,会被拦截。在tokenSuccessModel上添加[IgnoreAntiforgeryToken]属性:

[AllowAnonymous]
[IgnoreAntiforgeryToken]
public class tokenSuccessModel : PageModel
{
    // 现有代码
}

2. 修正事件监听类型

结账会话完成后,Stripe默认发送的是checkout.session.completed事件(而非PaymentIntentSucceeded),需调整事件监听逻辑:

if (stripeEvent.Type == Events.CheckoutSessionCompleted)
{
    var session = stripeEvent.Data.Object as Session;
    if (session == null)
    {
        _logger.LogInformation("Failed to retrieve Checkout Session object.");
        return BadRequest();
    }
    var customerId = session.CustomerId;
    // 后续用户查询与token更新逻辑不变
}

3. 确保请求体读取完整

若中间件提前读取过请求体,会导致StreamReader无法获取内容,需在读取前重置流位置:

public async Task<IActionResult> OnPostAsync()
{
    _logger.LogInformation("IT WORKS, IT WENT THROUGH!");
    HttpContext.Request.Body.Position = 0; // 添加此行
    var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();
    // 现有代码
}

4. 验证Webhook配置细节

  • 确认Stripe仪表盘的Webhook端点URL为https://gainesopusinstitute.com/PagesLoggedIn/tokenSuccess(匹配Razor Pages路由)
  • 确保已勾选checkout.session.completed或payment_intent.succeeded事件
  • 核对代码中的endpointSecret与Stripe仪表盘的Webhook签名密钥完全一致(注意不是API密钥)

5. 检查请求头获取方式

将Request.Headers["Stripe-Signature"]改为字符串形式,避免StringValues类型导致的解析问题:

var stripeSignature = Request.Headers["Stripe-Signature"].ToString();
var stripeEvent = EventUtility.ConstructEvent(json, stripeSignature, endpointSecret);

内容的提问来源于stack exchange,提问作者Kaden Gaines

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 19:08:07