Stripe webhook 400 error failed delivery with c# razor pages

156 Views Asked by At

I have created a web app that uses stripe to purchase something, and then I have a webhook that is to test the success of the purchase. I'm not using mvc so I don't have the [HTTP] tag that stripe docs use. I'm unsure if I'm doing this correctly in razor pages, or if it's even possible.

I tried ensuring there was no authentication, that wasn't the issue. I tried making sure the stripe key was configured and correctly being received in program.cs. I have used logs to see if the posted method is even being reached, it isn't.

program.cs stripe key:


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

Creating the checkout:

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

The backend of the /tokenSuccess razor page:

 [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}");
        }
    }

I have made sure the webhook configuration in the stripe dashboard was correct. It was previously a 404 error, but now it is 400 but the initial log of "IT WORKS" does not go through. Stripe Error

2

There are 2 best solutions below

1
On

The 400 error you're showing seems to indicate that your server is having trouble understanding the request. Based on what you've said so far, this is likely an issue with how your route is set up for /tokenSuccess.

One thing specifically that I noticed is that you're using /tokenSuccess for your success_url as well as your webhook endpoint. Usually you would have a separate URL for the success_url so that webhook requests aren't mixed up with Checkout Session redirects.

I would recommend creating an entirely separate endpoint for your webhooks and then following this guide to get the endpoint set up to receive Stripe events.

Additionally you should not post Webhook Secrets to public forums, so I would recommend redacting the whsec_ object ID from the endpointSecret variable in your code as soon as possible.

0
On

I think it was a cross site forgery issue: learnrazorpages.com/security/request-verification. I have added [IgnoreAntiforgeryToken(Order = 1001)] above public class tokenSuccessModel : PageModel and it went through, but there were other errors. At least I am getting logs now.