skip to Main Content

I have a Razor page I am trying to implement with Stripe subscription. I have successfully created a Subscription Table that when Subscribe is clicked goes to the Create Subscription page with the productid passed and the Form for capturing the credit card visible. When I enter in the test credit card per their documentation 4242 4242 4242 4242, CVC and Zip code Validation works fine, when I press the wired up Subscribe Button I get a 400 error. Do not want to use their no-code version and be taken to their website for checkout, Stripe Elements preferred resolution.

Here is the code base with everything checking out as far as I can tell, the Keys are correct and tested already and I am getting info from strip to populate ProductID

My 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>
}
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);
            }
        }
    }
}

The Error I see in troubleshooting the code in the Network tab of Chrome this appears to be related to when i am making an AJAX call tot he server:
Request URL:
https://localhost:7141/Members/CreateSubscriptions
Request Method:
POST
Status Code:
400
Remote Address:
[::1]:7141
Referrer Policy:
strict-origin-when-cross-origin

2

Answers


  1. Not really helpful to your CORS error, but the way you collect and apply the Payment Method won’t work.

    You’re collecting the Payment Method from the card element with the createPaymentMethod function, then passing it to your Subscription as default_payment_method.

    -First of all, this won’t work, because you need the Payment Method attached the Customer first – which it isn’t here, if I’m not missing anything.

    -Even if you attach the Payment Method to the Customer, if the customer’s bank requires authentication for this payment, this won’t work either, because you’ll get a requires_authentication decline on the attach call:
    https://stripe.com/docs/api/payment_methods/attach

    What you should do is either save the card with a Setup Intent first:
    https://stripe.com/docs/payments/save-and-reuse
    Either create the Subscription, then use the intent it generates to collect the card details:
    https://stripe.com/docs/billing/subscriptions/build-subscriptions?ui=elements

    Note that both guides above use the payment element, however you can use the card element and confirmCardPayment function with the same flow:
    https://stripe.com/docs/js/payment_intents/confirm_card_payment

    Login or Signup to reply.
  2. Firstly, Razor Pages project will validate the AntiForgeryToken by default no matter you add the [ValidateAntiForgeryToken] or not . And this attribute can not be applied to the handler.

    Secondly, you need pass the token in the header to the backend like below:

    headers: {
    "RequestVerificationToken": $('[name=__RequestVerificationToken]').val()
    },
    

    Thirdly, your frontend posts the json type data to backend, but your backend contains two parameters, it means model binding system needs read the data from body for two times. But you need know that once the request stream is read by an input formatter, it’s no longer available to be read again for binding other from body parameters. One way is to create a model which contains the two parameters as the property. The second way is to post the data by form data.

    Two simple working demo you could follow:

    The first way

    Model design

    public class Test
    {
        public string planId { get; set; }
        public string paymentMethodId { get; set; }
    }
    

    JS code

    var planId="1";
    var paymentMethodId = "2";
    fetch('/Members/CreateSubscriptions', {
        method: 'POST',
        headers: {
        'Content-Type': 'application/json',
        "RequestVerificationToken": $('[name=__RequestVerificationToken]').val()
        },
    body: JSON.stringify({ planId, paymentMethodId }),
    })
    

    Backend

    public void OnPost([FromBody]Test test)
    {
    }
    

    The second way

    var planId="1";
    var paymentMethodId = "2";
    let formData = new FormData();
    formData.append('planId', planId);
    formData.append('paymentMethodId', paymentMethodId);
    fetch('/Members/CreateSubscriptions', {
        method: 'POST',
        headers: {
        //'Content-Type': 'application/json',
        "RequestVerificationToken": $('[name=__RequestVerificationToken]').val()
        },
    body: formData,
    })
    

    Backend:

    public void OnPost(string planId, string paymentMethodId)
    {
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search