mirror of
https://github.com/bitwarden/server.git
synced 2025-07-03 00:52:49 -05:00
[PM-12420] Stripe events recovery (#4793)
* Billing: Add event recovery endpoints * Core: Add InternalBilling to BaseServiceUriSettings * Admin: Scaffold billing section * Admin: Scaffold ProcessStripeEvents section * Admin: Implement event processing * Run dotnet format
This commit is contained in:
68
src/Billing/Controllers/RecoveryController.cs
Normal file
68
src/Billing/Controllers/RecoveryController.cs
Normal file
@ -0,0 +1,68 @@
|
||||
using Bit.Billing.Models.Recovery;
|
||||
using Bit.Billing.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Billing.Controllers;
|
||||
|
||||
[Route("stripe/recovery")]
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
public class RecoveryController(
|
||||
IStripeEventProcessor stripeEventProcessor,
|
||||
IStripeFacade stripeFacade,
|
||||
IWebHostEnvironment webHostEnvironment) : Controller
|
||||
{
|
||||
private readonly string _stripeURL = webHostEnvironment.IsDevelopment() || webHostEnvironment.IsEnvironment("QA")
|
||||
? "https://dashboard.stripe.com/test"
|
||||
: "https://dashboard.stripe.com";
|
||||
|
||||
// ReSharper disable once RouteTemplates.ActionRoutePrefixCanBeExtractedToControllerRoute
|
||||
[HttpPost("events/inspect")]
|
||||
public async Task<Ok<EventsResponseBody>> InspectEventsAsync([FromBody] EventsRequestBody requestBody)
|
||||
{
|
||||
var inspected = await Task.WhenAll(requestBody.EventIds.Select(async eventId =>
|
||||
{
|
||||
var @event = await stripeFacade.GetEvent(eventId);
|
||||
return Map(@event);
|
||||
}));
|
||||
|
||||
var response = new EventsResponseBody { Events = inspected.ToList() };
|
||||
|
||||
return TypedResults.Ok(response);
|
||||
}
|
||||
|
||||
// ReSharper disable once RouteTemplates.ActionRoutePrefixCanBeExtractedToControllerRoute
|
||||
[HttpPost("events/process")]
|
||||
public async Task<Ok<EventsResponseBody>> ProcessEventsAsync([FromBody] EventsRequestBody requestBody)
|
||||
{
|
||||
var processed = await Task.WhenAll(requestBody.EventIds.Select(async eventId =>
|
||||
{
|
||||
var @event = await stripeFacade.GetEvent(eventId);
|
||||
try
|
||||
{
|
||||
await stripeEventProcessor.ProcessEventAsync(@event);
|
||||
return Map(@event);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
return Map(@event, exception.Message);
|
||||
}
|
||||
}));
|
||||
|
||||
var response = new EventsResponseBody { Events = processed.ToList() };
|
||||
|
||||
return TypedResults.Ok(response);
|
||||
}
|
||||
|
||||
private EventResponseBody Map(Event @event, string processingError = null) => new()
|
||||
{
|
||||
Id = @event.Id,
|
||||
URL = $"{_stripeURL}/workbench/events/{@event.Id}",
|
||||
APIVersion = @event.ApiVersion,
|
||||
Type = @event.Type,
|
||||
CreatedUTC = @event.Created,
|
||||
ProcessingError = processingError
|
||||
};
|
||||
}
|
9
src/Billing/Models/Recovery/EventsRequestBody.cs
Normal file
9
src/Billing/Models/Recovery/EventsRequestBody.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Bit.Billing.Models.Recovery;
|
||||
|
||||
public class EventsRequestBody
|
||||
{
|
||||
[JsonPropertyName("eventIds")]
|
||||
public List<string> EventIds { get; set; }
|
||||
}
|
31
src/Billing/Models/Recovery/EventsResponseBody.cs
Normal file
31
src/Billing/Models/Recovery/EventsResponseBody.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Bit.Billing.Models.Recovery;
|
||||
|
||||
public class EventsResponseBody
|
||||
{
|
||||
[JsonPropertyName("events")]
|
||||
public List<EventResponseBody> Events { get; set; }
|
||||
}
|
||||
|
||||
public class EventResponseBody
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
[JsonPropertyName("url")]
|
||||
public string URL { get; set; }
|
||||
|
||||
[JsonPropertyName("apiVersion")]
|
||||
public string APIVersion { get; set; }
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; }
|
||||
|
||||
[JsonPropertyName("createdUTC")]
|
||||
public DateTime CreatedUTC { get; set; }
|
||||
|
||||
[JsonPropertyName("processingError")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string ProcessingError { get; set; }
|
||||
}
|
@ -16,6 +16,12 @@ public interface IStripeFacade
|
||||
RequestOptions requestOptions = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<Event> GetEvent(
|
||||
string eventId,
|
||||
EventGetOptions eventGetOptions = null,
|
||||
RequestOptions requestOptions = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<Invoice> GetInvoice(
|
||||
string invoiceId,
|
||||
InvoiceGetOptions invoiceGetOptions = null,
|
||||
|
@ -6,6 +6,7 @@ public class StripeFacade : IStripeFacade
|
||||
{
|
||||
private readonly ChargeService _chargeService = new();
|
||||
private readonly CustomerService _customerService = new();
|
||||
private readonly EventService _eventService = new();
|
||||
private readonly InvoiceService _invoiceService = new();
|
||||
private readonly PaymentMethodService _paymentMethodService = new();
|
||||
private readonly SubscriptionService _subscriptionService = new();
|
||||
@ -19,6 +20,13 @@ public class StripeFacade : IStripeFacade
|
||||
CancellationToken cancellationToken = default) =>
|
||||
await _chargeService.GetAsync(chargeId, chargeGetOptions, requestOptions, cancellationToken);
|
||||
|
||||
public async Task<Event> GetEvent(
|
||||
string eventId,
|
||||
EventGetOptions eventGetOptions = null,
|
||||
RequestOptions requestOptions = null,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
await _eventService.GetAsync(eventId, eventGetOptions, requestOptions, cancellationToken);
|
||||
|
||||
public async Task<Customer> GetCustomer(
|
||||
string customerId,
|
||||
CustomerGetOptions customerGetOptions = null,
|
||||
|
Reference in New Issue
Block a user