asp.net core 3.1 getting current identity user within stripe HttpPost(“webhook”) returns NULL

旧街凉风 提交于 2021-02-10 16:15:48

问题


I've integrated stripe checkout payments in my website according to stripe's example

Everything works fine. I could verify webhooks are working with stripe CLI and also using ngrok, tunneling my localhost.

Now I've started implementing interaction with the identity database. I wanto to store there the stripe session.CustomerId after the webhook has fired checkout.session.completed.

For that I need to access my Identity database.

My code is:

[HttpPost("webhook")]
    public async Task<IActionResult> Webhook()
    {
        // GET logged current user
        var currentUser = await _userManager.GetUserAsync(User);

        var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();
        Event stripeEvent;
        try
        {
            stripeEvent = EventUtility.ConstructEvent(
                json,
                Request.Headers["Stripe-Signature"],
                this.options.Value.WebhookSecret
            );
            Console.WriteLine($"Webhook notification with type: {stripeEvent.Type} found for {stripeEvent.Id}");
        }
        catch (Exception e)
        {
            Console.WriteLine($"Something failed {e}");
            return BadRequest();
        }



        // Handle the event
        if (stripeEvent.Type == Events.PaymentIntentSucceeded) //Success
        {
            var paymentIntent = stripeEvent.Data.Object as PaymentIntent;
            var subscriptionItem = stripeEvent.Data.Object as Stripe.Subscription;
            var subscriptionInvoice = stripeEvent.Data.Object as Invoice;
            var paidTimestamp = paymentIntent.Created;
            var paidValidityTimestamp = subscriptionItem.CurrentPeriodEnd;
            Console.WriteLine("*********************");
            Console.WriteLine(".....................");
            Console.WriteLine("Payment Intent Succeeded");
            // Then define and call a method to handle the successful payment intent.
            // handlePaymentIntentSucceeded(paymentIntent);

            paymentIntent.ReceiptEmail = currentUser.Email;
            paymentIntent.Shipping.Address.City = currentUser.City;
            paymentIntent.Shipping.Address.PostalCode = currentUser.PostalCode.ToString();
            paymentIntent.Shipping.Address.Line1 = currentUser.Street + " " + currentUser.CivicNumber;

            currentUser.hasPaidQuote = true;
            currentUser.paidOnDate = paidTimestamp;
            currentUser.paidValidity = paidValidityTimestamp;
            currentUser.SubscriptionStatus = paymentIntent.Status;
            currentUser.Status = LoginUserStatus.Approved;
            await _userManager.UpdateAsync(currentUser);

            Console.WriteLine("has paid? " + currentUser.hasPaidQuote);
            Console.WriteLine("paid on date: " + currentUser.paidOnDate.Date.ToString());
            Console.WriteLine("payment validity: " + currentUser.paidValidity.Date.ToString());
            Console.WriteLine("subscription status: " + currentUser.SubscriptionStatus);
            Console.WriteLine("user status: " + currentUser.Status.ToString());
            Console.WriteLine("customer ID: " + paymentIntent.Customer.Id);
            Console.WriteLine("payment intent status: " + paymentIntent.Status);
            Console.WriteLine("invoice status from paymentIntent: " + paymentIntent.Invoice.Status);
            Console.WriteLine("invoice status from subscriptionItem: " + subscriptionItem.LatestInvoice.Status);
            Console.WriteLine("subscription status: " + subscriptionItem.Status);
            Console.WriteLine("invoice status: " + subscriptionInvoice.Paid.ToString());

            Console.WriteLine(".....................");
            Console.WriteLine("*********************");

        }
        else if (stripeEvent.Type == Events.PaymentIntentPaymentFailed) //Fails due to card error
        {
            var paymentIntent = stripeEvent.Data.Object as PaymentIntent;
            Console.WriteLine("*********************");
            Console.WriteLine(".....................");
            Console.WriteLine("Payment Intent requires payment method. Payment failed due to card error");
            // Then define and call a method to handle the successful attachment of a PaymentMethod.
            // handlePaymentMethodAttached(paymentMethod);

            Console.WriteLine("payment intent status: " + paymentIntent.Status);
            Console.WriteLine("invoice status: " + paymentIntent.Invoice.Status);
            Console.WriteLine(".....................");
            Console.WriteLine("*********************");
        }
        else if (stripeEvent.Type == Events.PaymentIntentRequiresAction) //Fails due to authentication
        {
            var paymentIntent = stripeEvent.Data.Object as PaymentIntent;
            Console.WriteLine("*********************");
            Console.WriteLine(".....................");
            Console.WriteLine("Payment Intent requires actions");
            // Then define and call a method to handle the successful attachment of a PaymentMethod.
            // handlePaymentMethodAttached(paymentMethod);

            Console.WriteLine("payment intent status: " + paymentIntent.Status);
            Console.WriteLine("invoice status: " + paymentIntent.Invoice.Status);
            Console.WriteLine(".....................");
            Console.WriteLine("*********************");
        }
        else if (stripeEvent.Type == Events.PaymentMethodAttached)
        {
            var paymentMethod = stripeEvent.Data.Object as PaymentMethod;
            Console.WriteLine("Payment Method Attached");
            // Then define and call a method to handle the successful attachment of a PaymentMethod.
            // handlePaymentMethodAttached(paymentMethod);
        }
        // ... handle other event types




        if (stripeEvent.Type == "checkout.session.completed")
        {
            var session = stripeEvent.Data.Object as Stripe.Checkout.Session;
            ViewBag.eventID = stripeEvent.Id;

            Console.WriteLine("*********************");
            Console.WriteLine(".....................");
            Console.WriteLine("checkout session completed");
            Console.WriteLine($"Session ID: {session.Id}");

            // Take some action based on session.
            currentUser.StripeCustomerId = session.CustomerId;
            await _userManager.UpdateAsync(currentUser);

            Console.WriteLine("Ssession.CustomerId has been retrieved from session and will be stored into database: " + session.CustomerId);
            Console.WriteLine("StripeCustomerId has been stored into database: " + currentUser.StripeCustomerId);
            Console.WriteLine(".....................");
            Console.WriteLine("*********************");
        }

        return Ok();
    }

My problem is that var currentUser = await _userManager.GetUserAsync(User); is returning NULL.

If I run the code in debugging mode a blue circle with an exclamation mark is showing up on my braking point. Hovering the mouse There I get the notification: "The process or thread has changes since last step". Using checkout API of stripe redirects me somewhere, losing the information of my session, hence losing also any link to my Identity database.

How can I obtain that my variable currentUser contains the logged-in user?

My PaymentsController is as follows:

public class PaymentsController : Controller
{
    public readonly IOptions<StripeOptions> options;
    private readonly IStripeClient client;
    private UserManager<VivaceApplicationUser> _userManager;

    private readonly IConfiguration Configuration;


    public IActionResult Index()
    {
        return View();
    }

    public IActionResult canceled()
    {
        return View();
    }

    public IActionResult success()
    {
        return View();
    }

    public PaymentsController(IOptions<StripeOptions> options,
        IConfiguration configuration,
        UserManager<VivaceApplicationUser> userManager)
    {
        this.options = options;
        this.client = new StripeClient(this.options.Value.SecretKey);
        _userManager = userManager;
        Configuration = configuration;
    } .......

}

My [HttpGet("checkout-session")] is

[HttpGet("checkout-session")]
    public async Task<IActionResult> CheckoutSession(string sessionId)
    {
        var currentUser = await _userManager.GetUserAsync(HttpContext.User);
        var service = new SessionService(this.client);
        var session = await service.GetAsync(sessionId);

        // Retrieve prices of product
        var product = new PriceService();
        var productBasic = product.Get(Configuration.GetSection("Stripe")["BASIC_PRICE_ID"]);
        var productMedium = product.Get(Configuration.GetSection("Stripe")["MEDIUM_PRICE_ID"]);
        var productPremium = product.Get(Configuration.GetSection("Stripe")["PREMIUM_PRICE_ID"]);

        int quoteBasic = (int)productBasic.UnitAmount;
        int quoteMedium = (int)productMedium.UnitAmount;
        int quotePremium = (int)productPremium.UnitAmount;

        int selectedPlan = (int)session.AmountTotal;
       
        if (!string.IsNullOrEmpty(session.Id))
        {
            // storing stripe customerId into the User database
            // this will be done if checkout.session.completed
            //currentUser.StripeCustomerId = session.CustomerId;
            
            if (selectedPlan == quoteBasic)
            {
                currentUser.Subscription = Models.VivaceUsers.Subscription.Basic;
            }
            else if (selectedPlan == quoteMedium)
            {
                currentUser.Subscription = Models.VivaceUsers.Subscription.Medium;
            }
            else if (selectedPlan == quotePremium)
            {
                currentUser.Subscription = Models.VivaceUsers.Subscription.Premium;
            }

            await _userManager.UpdateAsync(currentUser);
        }

        return Ok(session);
    }

In the "checkout-session" I can retrieve the currentUser without any problem using simply:

var currentUser = await _userManager.GetUserAsync(HttpContext.User);

How canI get it up and running in the webhook method?


回答1:


The issue is that there is no user session when you receive the POST request from Stripe. The authentication session is likely managed with cookies and persisted by your customer's browser. When receiving the POST request directly from Stripe, the request will not include any cookies related to your authenticated user.

Instead of using the following to lookup the current user, you'll need another way to associate the Checkout Session to a user in your database.

var currentUser = await _userManager.GetUserAsync(HttpContext.User);

There are two standard approaches to solving this.

  1. Store the ID of the user in the metadata for the Checkout Session.

  2. Store the ID of the Checkout Session in your database with some relation back to the current user.

Many users have success with option 1 and it might look something roughly like the following for creating the Checkout Session:

var currentUser = await _userManager.GetUserAsync(HttpContext.User);
var options = new SessionCreateOptions
{
    SuccessUrl = "https://example.com/success",
    Metadata = new Dictionary<string, string>
    {
        {"UserId", currentUser.ID},
    }
    //...
};

Then later when looking up the User from the webhook handler:

var currentUser = await _userManager.FindByIdAsync(checkoutSession.Metadata.UserId);



回答2:


Thank to CJAV I was able to find a way to get it up and running. I've also found very valuable information here Unable to retrieve a session's variable values in a Stripe webhook and also here Reconciling "New Checkout" Session ID with Webhook Event data.

In my [HttpPost("create-checkout-session")] I've added a reference to ClientReferenceId = currentUser.Id as follows:

var options = new SessionCreateOptions
{
    .....

    PaymentMethodTypes = new List<string>
    {
        "card",
    },
    Mode = "subscription",
    LineItems = new List<SessionLineItemOptions>
    {
        new SessionLineItemOptions
        {
            Price = req.PriceId,
            Quantity = 1,
        },

    },
    Metadata = new Dictionary<string, string>
    {
        { "UserId", currentUser.Id },
    },
    ClientReferenceId = currentUser.Id
};

My [HttpPost("webhook")] is now:

    .....
// Handle the event
if (stripeEvent.Type == Events.PaymentIntentSucceeded) //Success
{
    var paymentIntent = stripeEvent.Data.Object as PaymentIntent;
    var customerId = paymentIntent.CustomerId;
    var invoiceId = paymentIntent.InvoiceId;
    var fetchedEmail = paymentIntent.ReceiptEmail;

    var customerService = new CustomerService();
    var fetchedCustomer = customerService.Get(customerId);

    var invoiceService = new InvoiceService();
    var fetchedInvoice = invoiceService.Get(invoiceId);

    var currentUser = await _userManager.FindByEmailAsync(fetchedEmail);

    //var customerReferenceFromSession = session.Metadata.GetValueOrDefault("UserId");
    //var currentUser = await _userManager.FindByIdAsync(customerReferenceFromSession);

    var paidTimestamp = paymentIntent.Created;
    Console.WriteLine("*********************");
    Console.WriteLine(".....................");
    Console.WriteLine("Payment Intent Succeeded");
    // Then define and call a method to handle the successful payment intent.
    // handlePaymentIntentSucceeded(paymentIntent);

    var Line1 = currentUser.Street + " " + currentUser.CivicNumber;

    var paymentIntentOptions = new PaymentIntentUpdateOptions
    {
        Shipping = new ChargeShippingOptions()
        {
            Address = new AddressOptions
            {
                City = currentUser.City,
                PostalCode = currentUser.PostalCode.ToString(),
                Line1 = Line1
            },
            Name = currentUser.LastName + " " + currentUser.FirstName
        },                    
    };
    var paymentIntentService = new PaymentIntentService();
    paymentIntentService.Update(
    paymentIntent.Id,
    paymentIntentOptions
    );

    paymentIntent.ReceiptEmail = currentUser.Email;

    currentUser.hasPaidQuote = true;
    currentUser.paidOnDate = paidTimestamp;
    currentUser.SubscriptionStatus = paymentIntent.Status;
    currentUser.Status = LoginUserStatus.Approved;
    await _userManager.UpdateAsync(currentUser);

    Console.WriteLine("has paid? " + currentUser.hasPaidQuote);
    Console.WriteLine("paid on date: " + currentUser.paidOnDate.Date.ToString());
    Console.WriteLine("subscription status: " + currentUser.SubscriptionStatus);
    Console.WriteLine("user status: " + currentUser.Status.ToString());
    Console.WriteLine("customer ID: " + paymentIntent.CustomerId);
    Console.WriteLine("payment intent status: " + paymentIntent.Status);

    Console.WriteLine("invoice status from subscriptionItem: " + subscriptionItem.LatestInvoice.Status);
    Console.WriteLine("subscription status: " + subscriptionItem.Status);
    Console.WriteLine("invoice status: " + subscriptionInvoice.Paid.ToString());

    Console.WriteLine(".....................");
    Console.WriteLine("*********************");

}

And it works!! It would be great to make a short notice in the stripe docs.

Have fun in coding!



来源:https://stackoverflow.com/questions/65874833/asp-net-core-3-1-getting-current-identity-user-within-stripe-httppostwebhook

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!