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
相关产品推荐
相关产品推荐

