fix(stripe): correcting webhook
This commit is contained in:
@@ -7,7 +7,7 @@ using Stripe.Checkout;
|
||||
|
||||
namespace Hutopy.Infrastructure.Payments.Stripe.Services;
|
||||
|
||||
public class StripeTipProcessor(
|
||||
internal class StripeTipProcessor(
|
||||
IOptions<StripeOptions> stripeOptions)
|
||||
: ITipProcessor
|
||||
{
|
||||
@@ -17,21 +17,23 @@ public class StripeTipProcessor(
|
||||
decimal amount,
|
||||
string currency,
|
||||
string message,
|
||||
string successUrl,
|
||||
string cancelUrl,
|
||||
Uri successUrl,
|
||||
Uri cancelUrl,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
StripeConfiguration.ApiKey = stripeOptions.Value.SecretKey;
|
||||
|
||||
// Create Stripe customer for the user if not already created
|
||||
CustomerService customerService = new();
|
||||
Customer? customer = await customerService.CreateAsync(
|
||||
var customer = await customerService
|
||||
.CreateAsync(
|
||||
new CustomerCreateOptions(),
|
||||
cancellationToken: ct);
|
||||
cancellationToken: ct)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// Create paymentIntent for the user
|
||||
SessionService sessionService = new();
|
||||
Session? session = await sessionService.CreateAsync(
|
||||
var session = await sessionService.CreateAsync(
|
||||
new SessionCreateOptions
|
||||
{
|
||||
ClientReferenceId = tipId.ToString(),
|
||||
@@ -57,21 +59,21 @@ public class StripeTipProcessor(
|
||||
Mode = "payment",
|
||||
PaymentIntentData = new SessionPaymentIntentDataOptions
|
||||
{
|
||||
ApplicationFeeAmount =
|
||||
Convert.ToInt64(amount * stripeOptions.Value.HutopyRate), // Platform fee
|
||||
ApplicationFeeAmount = Convert.ToInt64(amount * stripeOptions.Value.HutopyRate), // Platform fee
|
||||
TransferData = new SessionPaymentIntentDataTransferDataOptions
|
||||
{
|
||||
Destination = creator.StripeAccountId // Creator's Stripe account ID
|
||||
}
|
||||
},
|
||||
SuccessUrl = successUrl, // Redirect after successful payment
|
||||
CancelUrl = cancelUrl, // Redirect after canceled payment
|
||||
SuccessUrl = successUrl.ToString(), // Redirect after successful payment
|
||||
CancelUrl = cancelUrl.ToString(), // Redirect after canceled payment
|
||||
Metadata = new Dictionary<string, string>
|
||||
{
|
||||
{ "creatorId", creator.Id.ToString() }, { "creatorName", creator.Name }, { "message", message }
|
||||
}
|
||||
},
|
||||
cancellationToken: ct);
|
||||
cancellationToken: ct)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return new TipCheckoutSession(session.Id, session.Url);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Hutopy.Modules.Memberships.Handlers;
|
||||
internal class StripeWebhookEndpoint(
|
||||
ITipPaymentNotifier tipPaymentNotifier,
|
||||
IMembershipNotifier membershipNotifier,
|
||||
IOptions<StripeOptions> options)
|
||||
IOptions<StripeOptions> stripeOptions)
|
||||
: EndpointWithoutRequest
|
||||
{
|
||||
public override void Configure()
|
||||
@@ -28,7 +28,7 @@ internal class StripeWebhookEndpoint(
|
||||
|
||||
var json = await streamReader.ReadToEndAsync(ct).ConfigureAwait(false);
|
||||
|
||||
var stripeEvent = EventUtility.ConstructEvent(json, signatureHeader, options.Value.WebhookSecret);
|
||||
var stripeEvent = EventUtility.ConstructEvent(json, signatureHeader, stripeOptions.Value.WebhookSecret);
|
||||
|
||||
var stripeSession = stripeEvent.Data.Object as Session;
|
||||
var stripeSubscription = stripeEvent.Data.Object as Subscription;
|
||||
@@ -46,13 +46,22 @@ internal class StripeWebhookEndpoint(
|
||||
stripeSession.Customer?.Email ??
|
||||
"";
|
||||
|
||||
// Get the receipt URL, preferring the one directly on the charge if available
|
||||
var receiptUrl = stripeSession.Invoice?.HostedInvoiceUrl ?? "";
|
||||
StripeConfiguration.ApiKey = stripeOptions.Value.SecretKey;
|
||||
var paymentIntentService = new PaymentIntentService();
|
||||
var paymentIntent = await paymentIntentService
|
||||
.GetAsync(
|
||||
stripeSession.PaymentIntentId,
|
||||
new PaymentIntentGetOptions { Expand = ["latest_charge"] },
|
||||
cancellationToken: ct)
|
||||
.ConfigureAwait(false);
|
||||
var receiptUrl = paymentIntent.LatestCharge.ReceiptUrl;
|
||||
var receiptUri = new Uri(receiptUrl);
|
||||
|
||||
// Get the receipt URL, preferring the one directly on the charge if available
|
||||
await tipPaymentNotifier
|
||||
.NotifyPaymentSucceedAsync(
|
||||
stripeSession.Id,
|
||||
receiptUrl,
|
||||
receiptUri,
|
||||
customerEmail,
|
||||
ct)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
namespace Hutopy.Modules.Tipping.Contracts;
|
||||
|
||||
public interface ITipPaymentNotifier
|
||||
internal interface ITipPaymentNotifier
|
||||
{
|
||||
Task NotifyPaymentSucceedAsync(
|
||||
string stripeId,
|
||||
string invoiceUrl,
|
||||
string sessionId,
|
||||
Uri receiptUrl,
|
||||
string customerEmail,
|
||||
CancellationToken ct);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ using Hutopy.Modules.Creators.Contracts;
|
||||
|
||||
namespace Hutopy.Modules.Tipping.Contracts;
|
||||
|
||||
public interface ITipProcessor
|
||||
internal interface ITipProcessor
|
||||
{
|
||||
Task<TipCheckoutSession> CreateCheckoutSessionAsync(
|
||||
Guid tipId,
|
||||
@@ -10,7 +10,7 @@ public interface ITipProcessor
|
||||
decimal amount,
|
||||
string currency,
|
||||
string message,
|
||||
string successUrl,
|
||||
string cancelUrl,
|
||||
Uri successUrl,
|
||||
Uri cancelUrl,
|
||||
CancellationToken ct = default);
|
||||
}
|
||||
|
||||
@@ -13,12 +13,12 @@ internal static class SendTip
|
||||
decimal Amount,
|
||||
string Currency,
|
||||
string Message,
|
||||
string CheckoutSuccessUrl,
|
||||
string CheckoutCancelledUrl);
|
||||
Uri CheckoutSuccessUrl,
|
||||
Uri CheckoutCancelledUrl);
|
||||
|
||||
internal record Response(
|
||||
string Id,
|
||||
string Url);
|
||||
Uri Url);
|
||||
|
||||
internal class Validator : Validator<Request>
|
||||
{
|
||||
@@ -116,7 +116,7 @@ internal static class SendTip
|
||||
await dbContext.SaveChangesAsync(ct).ConfigureAwait(false);
|
||||
|
||||
await SendAsync(
|
||||
new Response(checkout.Id, checkout.Url),
|
||||
new Response(checkout.Id, new Uri(checkout.Url)),
|
||||
cancellation: ct)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ using Hutopy.Modules.Tipping.Data;
|
||||
|
||||
namespace Hutopy.Modules.Tipping.Services;
|
||||
|
||||
public class TipPaymentNotifier(
|
||||
internal class TipPaymentNotifier(
|
||||
TippingDbContext dbContext,
|
||||
IEmailSender emailSender,
|
||||
ICreatorLookup creatorLookup,
|
||||
@@ -14,22 +14,30 @@ public class TipPaymentNotifier(
|
||||
{
|
||||
public async Task NotifyPaymentSucceedAsync(
|
||||
string sessionId,
|
||||
string receiptUrl,
|
||||
Uri receiptUrl,
|
||||
string customerEmail,
|
||||
CancellationToken ct)
|
||||
{
|
||||
Tip? tip = await dbContext.Tips.SingleOrDefaultAsync(
|
||||
var tip = await dbContext
|
||||
.Tips
|
||||
.SingleOrDefaultAsync(
|
||||
t => t.StripeSessionId == sessionId,
|
||||
ct);
|
||||
ct)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (tip is not null)
|
||||
{
|
||||
tip.Status = TipStatus.Paid;
|
||||
tip.StripeInvoiceUrl = receiptUrl; // Store the receipt URL
|
||||
await dbContext.SaveChangesAsync(ct);
|
||||
tip.StripeInvoiceUrl = receiptUrl.ToString(); // Store the receipt URL
|
||||
|
||||
await dbContext
|
||||
.SaveChangesAsync(ct)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// Look up creator information
|
||||
CreatorReference? creator = await creatorLookup.GetCreatorAsync(tip.CreatorId, ct);
|
||||
var creator = await creatorLookup
|
||||
.GetCreatorAsync(tip.CreatorId, ct)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (!string.IsNullOrEmpty(customerEmail))
|
||||
{
|
||||
@@ -38,7 +46,8 @@ public class TipPaymentNotifier(
|
||||
creator?.Name ?? "le créateur",
|
||||
tip.Amount,
|
||||
tip.Currency,
|
||||
receiptUrl); // Pass the receipt URL
|
||||
receiptUrl)
|
||||
.ConfigureAwait(false); // Pass the receipt URL
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -52,10 +61,10 @@ public class TipPaymentNotifier(
|
||||
string creatorUsername,
|
||||
decimal amount,
|
||||
string currency,
|
||||
string receiptUrl) // Add receipt URL parameter
|
||||
Uri receiptUrl) // Add receipt URL parameter
|
||||
{
|
||||
string subject = $"Merci pour votre soutien à {creatorUsername}";
|
||||
string message = $"""
|
||||
var subject = $"Merci pour votre soutien à {creatorUsername}";
|
||||
var message = $"""
|
||||
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; color: #333;">
|
||||
<h1 style="color: #2c3e50; margin-bottom: 20px;">{creatorUsername} vous remercie !</h1>
|
||||
|
||||
@@ -69,7 +78,6 @@ public class TipPaymentNotifier(
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{(string.IsNullOrEmpty(receiptUrl) ? "" : $"""
|
||||
<div style="text-align: center; margin: 30px 0;">
|
||||
<a href='{receiptUrl}'
|
||||
style="background-color: #3498db;
|
||||
@@ -83,7 +91,6 @@ public class TipPaymentNotifier(
|
||||
Voir le reçu
|
||||
</a>
|
||||
</div>
|
||||
""")}
|
||||
|
||||
<p style="font-size: 14px; color: #7f8c8d; margin-top: 30px;">
|
||||
Cet email sert de reçu pour votre transaction. Nous vous conseillons de le conserver pour vos archives.
|
||||
@@ -97,7 +104,10 @@ public class TipPaymentNotifier(
|
||||
|
||||
try
|
||||
{
|
||||
await emailSender.SendEmailAsync(email, subject, message);
|
||||
await emailSender
|
||||
.SendEmailAsync(email, subject, message)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
logger.LogInformation("Tip confirmation email sent to {Email} for tip to {Creator}", email,
|
||||
creatorUsername);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user