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

使用Stripe Elements实现Razor页面订阅时遇400错误求助

Razor页面使用Stripe Elements实现订阅时AJAX请求400错误排查

我在Razor页面中用Stripe Elements实现订阅功能:已完成订阅列表页面,点击订阅可跳转至携带productId的创建订阅页面,测试信用卡(4242 4242 4242 4242)的CVC、邮编验证正常,但点击订阅按钮触发AJAX请求时出现400错误。不想使用Stripe无代码方案跳转至其官网结账,希望通过Stripe Elements解决问题(已确认密钥有效)。


CreateSubscription.CSHTML代码

@page
@model myProject.Pages.Members.CreateSubscriptionsModel

@{
    Layout = "_member";
}

<div class="container mt-5">
    <h1>Create Subscription</h1>
    <form id="subscription-form">
        @Html.AntiForgeryToken()
        <div id="card-element">
            <!-- A Stripe Element will be inserted here. -->
        </div>

        <!-- Used to display Element errors. -->
        <div id="card-errors" role="alert"></div>

        <button id="submit-button">Subscribe</button>
    </form>
</div>

@section Scripts
    {
    <script>
     
        var stripe = Stripe('verified key');
        var elements = stripe.elements();

    
        var card = elements.create('card');
        card.mount('#card-element');

       
        card.on('change', function (event) {
            var displayError = document.getElementById('card-errors');
            if (event.error) {
                displayError.textContent = event.error.message;
            } else {
                displayError.textContent = '';
            }
        });

        // Handle form submission

        var form = document.getElementById('subscription-form');
        form.addEventListener('submit', async function (e) {
            e.preventDefault();

            const { paymentMethod, error } = await stripe.createPaymentMethod({
                type: 'card',
                card: card,
            });

            if (error) 
                var displayError = document.getElementById('card-errors');
                displayError.textContent = "Payment failed: " + error.message;
                console.log("Payment failed: " + error.message);
            } else {
             
                const urlParams = new URLSearchParams(window.location.search);
                const planId = urlParams.get('planId');
                console.log('planId:', planId); // Add this log

                if (!planId) {
             
                    var displayError = document.getElementById('card-errors');
                    displayError.textContent = "Invalid URL. Missing planId.";
                    console.log('planId:', planId);
                    return;
                }

                const paymentMethodId = paymentMethod.id;

                fetch('/Members/CreateSubscriptions', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({ planId, paymentMethodId }),
                })
                    .then(response => response.json())
                    .then(data => {
                        if (data.status === "failure") {
                            // Handle and display custom error
                            var displayError = document.getElementById('card-errors');
                            displayError.textContent = "Subscription failed: " + data.message;
                        } else {
                            console.log('Success:', data);
                            // handle successful subscription
                        }
                    })
                    .catch((error) => {
                        console.error('Error:', error);
                        // handle error
                    });
            }
        });

    </script>
}

后端C#代码

using myProject.Data;
using Microsoft.AspNetCore.Identity;
using Stripe;
using Microsoft.AspNetCore.Mvc.Filters;

namespace myProject.Pages.Members
{
    public class CreateSubscriptionsModel : PageModel
    {

        public override void OnPageHandlerExecuting(PageHandlerExecutingContext context)
        {
            context.HttpContext.Response.Headers.Add("Access-Control-Allow-Origin", "*");
            base.OnPageHandlerExecuting(context);
        }

        private readonly UserManager<ApplicationUser> _userManager;

        public CreateSubscriptionsModel(UserManager<ApplicationUser> userManager)
        {
            _userManager = userManager;
        }

        [ValidateAntiForgeryToken]
        public async Task<IActionResult> OnPostAsync(string planId, string paymentMethodId)
        {
            if (string.IsNullOrEmpty(paymentMethodId))
            {
                return new JsonResult(new { Status = "failure", Message = "Invalid payment method ID" });
            }

            StripeConfiguration.ApiKey = "Already Verfied Key";

            var planService = new PlanService();
            Plan stripePlan;
            try
            {
                stripePlan = await planService.GetAsync(planId);
            }
            catch (StripeException e)
            {
                return new JsonResult(new { Status = "failure", Message = "Invalid plan ID: " + e.Message });
            }

            var user = await _userManager.GetUserAsync(User);
            string customerId = await GetOrCreateStripeCustomerId(user);

            try
            {
                var options = new SubscriptionCreateOptions
                {
                    Customer = customerId,
                    Items = new List<SubscriptionItemOptions>
            {
                new SubscriptionItemOptions
                {
                    Plan = planId,
                },
            },
                    DefaultPaymentMethod = paymentMethodId
                };

                var service = new SubscriptionService();
                Subscription subscription = await service.CreateAsync(options);

                return new JsonResult(new { subscriptionId = subscription.Id });
            }
            catch (StripeException e)
            {
                return new JsonResult(new { Status = "failure", Message = e.Message });
            }
        }


        private async Task<string> GetOrCreateStripeCustomerId(ApplicationUser user)
        {
            if (!string.IsNullOrEmpty(user.StripeCustomerId))
            {
                return user.StripeCustomerId;
            }

            var customerOptions = new CustomerCreateOptions { Email = user.Email };
            var customerService = new CustomerService();

            try
            {
                Customer stripeCustomer = await customerService.CreateAsync(customerOptions);
               
                user.StripeCustomerId = stripeCustomer.Id;
                await _userManager.UpdateAsync(user);
                return user.StripeCustomerId;
            }
            catch (StripeException e)
            {
                throw new Exception("Failed to create Stripe customer: " + e.Message);
            }
        }
    }
}

错误详情

Chrome网络面板显示:请求URL为https://localhost:7141/Members/CreateSubscriptions,请求方法POST,状态码400,Referrer Policy为strict-origin-when-cross-origin。


问题排查与修复方案

1. 防伪造令牌缺失导致拦截

后端方法标注了[ValidateAntiForgeryToken],但前端AJAX请求未携带令牌,直接触发400。
修复:在fetch请求的headers中添加令牌:

fetch('/Members/CreateSubscriptions', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value
    },
    // 其余代码不变
})

2. 前端JS语法错误

if (error)代码块缺少大括号,导致逻辑执行异常:
修复:给if(error)添加大括号:

if (error) {
    var displayError = document.getElementById('card-errors');
    displayError.textContent = "Payment failed: " + error.message;
    console.log("Payment failed: " + error.message);
}

3. PaymentMethod未关联Customer

创建Subscription时,未将PaymentMethod附加到对应Customer会触发Stripe内部错误,表现为请求400。
修复:在后端创建Subscription前添加关联代码:

// 创建Subscription之前插入
var paymentMethodService = new PaymentMethodService();
await paymentMethodService.AttachAsync(paymentMethodId, new PaymentMethodAttachOptions
{
    Customer = customerId
});

4. JSON参数绑定失败

后端用普通参数接收JSON请求,无法正确解析。
修复:创建请求模型并使用[FromBody]绑定:

// 新增模型类
public class SubscriptionRequest
{
    public string PlanId { get; set; }
    public string PaymentMethodId { get; set; }
}

// 修改后端方法
[ValidateAntiForgeryToken]
public async Task<IActionResult> OnPostAsync([FromBody] SubscriptionRequest request)
{
    var planId = request.PlanId;
    var paymentMethodId = request.PaymentMethodId;
    // 后续逻辑不变
}

5. 冗余跨域头设置

本地localhost请求无需设置Access-Control-Allow-Origin: *,移除OnPageHandlerExecuting中的跨域头代码即可。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 14:00:54