1
0
mirror of https://github.com/bitwarden/server synced 2025-12-13 06:43:45 +00:00

Use invoice to pay if subscription set to invoice (#1571)

* Use invoice to pay if subscription set to invoice

* Apply suggestions from code review

Co-authored-by: Addison Beck <abeck@bitwarden.com>

* PR review

Move to subscriber model for subscription updates.

Co-authored-by: Addison Beck <abeck@bitwarden.com>
This commit is contained in:
Matt Gibson
2021-09-14 09:18:06 -04:00
committed by GitHub
parent cc76d45aef
commit 97b27220dd
4 changed files with 157 additions and 145 deletions

View File

@@ -355,6 +355,11 @@ namespace Bit.Core.Services
throw new NotFoundException();
}
if (organization.Seats == null)
{
throw new BadRequestException("Organization has no seat limit, no need to adjust seats");
}
if (string.IsNullOrWhiteSpace(organization.GatewayCustomerId))
{
throw new BadRequestException("No payment method found.");
@@ -376,7 +381,7 @@ namespace Bit.Core.Services
throw new BadRequestException("Plan does not allow additional seats.");
}
var newSeatTotal = organization.Seats + seatAdjustment;
var newSeatTotal = organization.Seats.Value + seatAdjustment;
if (plan.BaseSeats > newSeatTotal)
{
throw new BadRequestException($"Plan has a minimum of {plan.BaseSeats} seats.");
@@ -404,104 +409,7 @@ namespace Bit.Core.Services
}
}
var subscriptionItemService = new SubscriptionItemService();
var subscriptionService = new SubscriptionService();
var sub = await subscriptionService.GetAsync(organization.GatewaySubscriptionId);
if (sub == null)
{
throw new BadRequestException("Subscription not found.");
}
var prorationDate = DateTime.UtcNow;
var seatItem = sub.Items?.Data?.FirstOrDefault(i => i.Plan.Id == plan.StripeSeatPlanId);
// Retain original collection method and days util due
var collectionMethod = sub.CollectionMethod;
var daysUntilDue = sub.DaysUntilDue;
var subUpdateOptions = new SubscriptionUpdateOptions
{
Items = new List<SubscriptionItemOptions>
{
new SubscriptionItemOptions
{
Id = seatItem?.Id,
Plan = plan.StripeSeatPlanId,
Quantity = additionalSeats,
Deleted = (seatItem?.Id != null && additionalSeats == 0) ? true : (bool?)null
}
},
ProrationBehavior = "always_invoice",
CollectionMethod = "send_invoice",
DaysUntilDue = daysUntilDue ?? 1,
ProrationDate = prorationDate,
};
var customer = await new CustomerService().GetAsync(sub.CustomerId);
if (!string.IsNullOrWhiteSpace(customer?.Address?.Country)
&& !string.IsNullOrWhiteSpace(customer?.Address?.PostalCode))
{
var taxRates = await _taxRateRepository.GetByLocationAsync(
new Bit.Core.Models.Table.TaxRate()
{
Country = customer.Address.Country,
PostalCode = customer.Address.PostalCode
}
);
var taxRate = taxRates.FirstOrDefault();
if (taxRate != null && !sub.DefaultTaxRates.Any(x => x.Equals(taxRate.Id)))
{
subUpdateOptions.DefaultTaxRates = new List<string>(1)
{
taxRate.Id
};
}
}
var subResponse = await subscriptionService.UpdateAsync(sub.Id, subUpdateOptions);
string paymentIntentClientSecret = null;
if (additionalSeats > 0)
{
try
{
paymentIntentClientSecret = await (_paymentService as StripePaymentService)
.PayInvoiceAfterSubscriptionChangeAsync(organization, subResponse.LatestInvoiceId);
}
catch
{
// Need to revert the subscription
await subscriptionService.UpdateAsync(sub.Id, new SubscriptionUpdateOptions
{
Items = new List<SubscriptionItemOptions>
{
new SubscriptionItemOptions
{
Id = seatItem?.Id,
Plan = plan.StripeSeatPlanId,
Quantity = organization.Seats,
Deleted = seatItem?.Id == null ? true : (bool?)null
}
},
// This proration behavior prevents a false "credit" from
// being applied forward to the next month's invoice
ProrationBehavior = "none",
CollectionMethod = collectionMethod,
DaysUntilDue = daysUntilDue,
});
throw;
}
}
// Change back the subscription collection method and/or days until due
if (collectionMethod != "send_invoice" || daysUntilDue == null)
{
await subscriptionService.UpdateAsync(sub.Id, new SubscriptionUpdateOptions
{
CollectionMethod = collectionMethod,
DaysUntilDue = daysUntilDue,
});
}
var paymentIntentClientSecret = await _paymentService.AdjustSeatsAsync(organization, plan, additionalSeats);
await _referenceEventService.RaiseEventAsync(
new ReferenceEvent(ReferenceEventType.AdjustSeats, organization)
{