如何解决asp.net core 3.1 在条带 HttpPost("webhook") 中获取当前身份用户返回 NULL
我已根据 Stripe 的 example
在我的网站中集成了 Stripe checkout 付款一切正常。我可以验证 webhooks 是否使用 Stripe CLI 并使用 ngrok,对我的 localhost
进行隧道传输。
现在我已经开始实现与身份数据库的交互。
我想在 webhook 触发 session.CustomerId
后将条纹 checkout.session.completed
存储在那里。
为此,我需要访问我的身份数据库。
我的代码是:
[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();
}
我的问题是 var currentUser = await _userManager.GetUserAsync(User);
返回 NULL。
如果我在调试模式下运行代码,我的刹车点上会出现一个带有感叹号的蓝色圆圈。将鼠标悬停在那里我收到通知:“进程或线程自上一步以来发生了变化”。 使用条带的结帐 API 会将我重定向到某个地方,从而丢失了我的会话信息,因此也丢失了指向我的身份数据库的任何链接。
我的PaymentsController
如下:
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;
} .......
}
我的 [HttpGet("checkout-session")]
是
[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);
}
在“结帐会话”中,我可以简单地使用以下方法检索 currentUser:
var currentUser = await _userManager.GetUserAsync(HttpContext.User);
如何在 webhook
方法中启动并运行它?
解决方法
问题是当您收到来自 Stripe 的 POST 请求时,没有用户会话。身份验证会话可能由 cookie 管理并由您客户的浏览器保存。直接从 Stripe 接收 POST 请求时,该请求将不包含与您的已验证用户相关的任何 cookie。
不是使用以下方法查找当前用户,而是需要另一种方法将 Checkout 会话与数据库中的用户相关联。
var currentUser = await _userManager.GetUserAsync(HttpContext.User);
有两种标准方法可以解决这个问题。
-
将用户 ID 存储在结帐会话的元数据中。
-
将结帐会话的 ID 存储在您的数据库中,并与当前用户有某种关系。
许多用户在选项 1 上都取得了成功,创建结帐会话时它可能类似于以下内容:
var currentUser = await _userManager.GetUserAsync(HttpContext.User);
var options = new SessionCreateOptions
{
SuccessUrl = "https://example.com/success",Metadata = new Dictionary<string,string>
{
{"UserId",currentUser.ID},}
//...
};
然后在从 webhook 处理程序中查找用户时:
var currentUser = await _userManager.FindByIdAsync(checkoutSession.Metadata.UserId);
,
感谢 CJAV,我能够找到一种方法来启动和运行它。我还在 Unable to retrieve a session's variable values in a Stripe webhook 和 Reconciling "New Checkout" Session ID with Webhook Event data 处找到了非常有价值的信息。
在我的 [HttpPost("create-checkout-session")]
中,我添加了对 ClientReferenceId = currentUser.Id
的引用,如下所示:
var options = new SessionCreateOptions
{
.....
PaymentMethodTypes = new List<string>
{
"card",},Mode = "subscription",LineItems = new List<SessionLineItemOptions>
{
new SessionLineItemOptions
{
Price = req.PriceId,Quantity = 1,string>
{
{ "UserId",currentUser.Id },ClientReferenceId = currentUser.Id
};
我的 [HttpPost("webhook")]
现在是:
.....
// 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("*********************");
}
它有效!! 在条带文档中做一个简短的通知会很棒。
享受编码的乐趣!
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。