mirror of
https://github.com/bitwarden/server.git
synced 2025-07-13 21:57:30 -05:00
Merge branch 'master' into feature/flexible-collections
This commit is contained in:
@ -114,7 +114,7 @@ public class OrganizationSubscriptionResponseModel : OrganizationResponseModel
|
||||
{
|
||||
Subscription = subscription.Subscription != null ? new BillingSubscription(subscription.Subscription) : null;
|
||||
UpcomingInvoice = subscription.UpcomingInvoice != null ? new BillingSubscriptionUpcomingInvoice(subscription.UpcomingInvoice) : null;
|
||||
Discount = subscription.Discount != null ? new BillingCustomerDiscount(subscription.Discount) : null;
|
||||
CustomerDiscount = subscription.CustomerDiscount != null ? new BillingCustomerDiscount(subscription.CustomerDiscount) : null;
|
||||
Expiration = DateTime.UtcNow.AddYears(1); // Not used, so just give it a value.
|
||||
|
||||
if (hideSensitiveData)
|
||||
@ -145,7 +145,7 @@ public class OrganizationSubscriptionResponseModel : OrganizationResponseModel
|
||||
|
||||
public string StorageName { get; set; }
|
||||
public double? StorageGb { get; set; }
|
||||
public BillingCustomerDiscount Discount { get; set; }
|
||||
public BillingCustomerDiscount CustomerDiscount { get; set; }
|
||||
public BillingSubscription Subscription { get; set; }
|
||||
public BillingSubscriptionUpcomingInvoice UpcomingInvoice { get; set; }
|
||||
|
||||
|
@ -13,7 +13,6 @@ public class SubscriptionResponseModel : ResponseModel
|
||||
Subscription = subscription.Subscription != null ? new BillingSubscription(subscription.Subscription) : null;
|
||||
UpcomingInvoice = subscription.UpcomingInvoice != null ?
|
||||
new BillingSubscriptionUpcomingInvoice(subscription.UpcomingInvoice) : null;
|
||||
Discount = subscription.Discount != null ? new BillingCustomerDiscount(subscription.Discount) : null;
|
||||
StorageName = user.Storage.HasValue ? CoreHelpers.ReadableBytesSize(user.Storage.Value) : null;
|
||||
StorageGb = user.Storage.HasValue ? Math.Round(user.Storage.Value / 1073741824D, 2) : 0; // 1 GB
|
||||
MaxStorageGb = user.MaxStorageGb;
|
||||
@ -41,7 +40,6 @@ public class SubscriptionResponseModel : ResponseModel
|
||||
public short? MaxStorageGb { get; set; }
|
||||
public BillingSubscriptionUpcomingInvoice UpcomingInvoice { get; set; }
|
||||
public BillingSubscription Subscription { get; set; }
|
||||
public BillingCustomerDiscount Discount { get; set; }
|
||||
public UserLicense License { get; set; }
|
||||
public DateTime? Expiration { get; set; }
|
||||
public bool UsingInAppPurchase { get; set; }
|
||||
@ -53,10 +51,12 @@ public class BillingCustomerDiscount
|
||||
{
|
||||
Id = discount.Id;
|
||||
Active = discount.Active;
|
||||
PercentOff = discount.PercentOff;
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
public bool Active { get; set; }
|
||||
public string Id { get; }
|
||||
public bool Active { get; }
|
||||
public decimal? PercentOff { get; }
|
||||
}
|
||||
|
||||
public class BillingSubscription
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Vault.Entities;
|
||||
@ -15,7 +14,7 @@ public class AttachmentResponseModel : ResponseModel
|
||||
Url = data.Url;
|
||||
FileName = data.Data.FileName;
|
||||
Key = data.Data.Key;
|
||||
Size = data.Data.Size;
|
||||
Size = data.Data.Size.ToString();
|
||||
SizeName = CoreHelpers.ReadableBytesSize(data.Data.Size);
|
||||
}
|
||||
|
||||
@ -27,7 +26,7 @@ public class AttachmentResponseModel : ResponseModel
|
||||
Url = $"{globalSettings.Attachment.BaseUrl}/{cipher.Id}/{id}";
|
||||
FileName = data.FileName;
|
||||
Key = data.Key;
|
||||
Size = data.Size;
|
||||
Size = data.Size.ToString();
|
||||
SizeName = CoreHelpers.ReadableBytesSize(data.Size);
|
||||
}
|
||||
|
||||
@ -35,8 +34,7 @@ public class AttachmentResponseModel : ResponseModel
|
||||
public string Url { get; set; }
|
||||
public string FileName { get; set; }
|
||||
public string Key { get; set; }
|
||||
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
|
||||
public long Size { get; set; }
|
||||
public string Size { get; set; }
|
||||
public string SizeName { get; set; }
|
||||
|
||||
public static IEnumerable<AttachmentResponseModel> FromCipher(Cipher cipher, IGlobalSettings globalSettings)
|
||||
|
@ -52,6 +52,7 @@ public class StripeController : Controller
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IStripeEventService _stripeEventService;
|
||||
private readonly IStripeFacade _stripeFacade;
|
||||
|
||||
public StripeController(
|
||||
GlobalSettings globalSettings,
|
||||
@ -70,7 +71,8 @@ public class StripeController : Controller
|
||||
ITaxRateRepository taxRateRepository,
|
||||
IUserRepository userRepository,
|
||||
ICurrentContext currentContext,
|
||||
IStripeEventService stripeEventService)
|
||||
IStripeEventService stripeEventService,
|
||||
IStripeFacade stripeFacade)
|
||||
{
|
||||
_billingSettings = billingSettings?.Value;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
@ -97,6 +99,7 @@ public class StripeController : Controller
|
||||
_currentContext = currentContext;
|
||||
_globalSettings = globalSettings;
|
||||
_stripeEventService = stripeEventService;
|
||||
_stripeFacade = stripeFacade;
|
||||
}
|
||||
|
||||
[HttpPost("webhook")]
|
||||
@ -209,48 +212,71 @@ public class StripeController : Controller
|
||||
else if (parsedEvent.Type.Equals(HandledStripeWebhook.UpcomingInvoice))
|
||||
{
|
||||
var invoice = await _stripeEventService.GetInvoice(parsedEvent);
|
||||
var subscriptionService = new SubscriptionService();
|
||||
var subscription = await subscriptionService.GetAsync(invoice.SubscriptionId);
|
||||
|
||||
if (string.IsNullOrEmpty(invoice.SubscriptionId))
|
||||
{
|
||||
_logger.LogWarning("Received 'invoice.upcoming' Event with ID '{eventId}' that did not include a Subscription ID", parsedEvent.Id);
|
||||
return new OkResult();
|
||||
}
|
||||
|
||||
var subscription = await _stripeFacade.GetSubscription(invoice.SubscriptionId);
|
||||
|
||||
if (subscription == null)
|
||||
{
|
||||
throw new Exception("Invoice subscription is null. " + invoice.Id);
|
||||
throw new Exception(
|
||||
$"Received null Subscription from Stripe for ID '{invoice.SubscriptionId}' while processing Event with ID '{parsedEvent.Id}'");
|
||||
}
|
||||
|
||||
subscription = await VerifyCorrectTaxRateForCharge(invoice, subscription);
|
||||
var updatedSubscription = await VerifyCorrectTaxRateForCharge(invoice, subscription);
|
||||
|
||||
string email = null;
|
||||
var ids = GetIdsFromMetaData(subscription.Metadata);
|
||||
// org
|
||||
if (ids.Item1.HasValue)
|
||||
var (organizationId, userId) = GetIdsFromMetaData(updatedSubscription.Metadata);
|
||||
|
||||
var invoiceLineItemDescriptions = invoice.Lines.Select(i => i.Description).ToList();
|
||||
|
||||
async Task SendEmails(IEnumerable<string> emails)
|
||||
{
|
||||
// sponsored org
|
||||
if (IsSponsoredSubscription(subscription))
|
||||
{
|
||||
await _validateSponsorshipCommand.ValidateSponsorshipAsync(ids.Item1.Value);
|
||||
}
|
||||
var validEmails = emails.Where(e => !string.IsNullOrEmpty(e));
|
||||
|
||||
var org = await _organizationRepository.GetByIdAsync(ids.Item1.Value);
|
||||
if (org != null && OrgPlanForInvoiceNotifications(org))
|
||||
if (invoice.NextPaymentAttempt.HasValue)
|
||||
{
|
||||
email = org.BillingEmail;
|
||||
await _mailService.SendInvoiceUpcoming(
|
||||
validEmails,
|
||||
invoice.AmountDue / 100M,
|
||||
invoice.NextPaymentAttempt.Value,
|
||||
invoiceLineItemDescriptions,
|
||||
true);
|
||||
}
|
||||
}
|
||||
// user
|
||||
else if (ids.Item2.HasValue)
|
||||
|
||||
if (organizationId.HasValue)
|
||||
{
|
||||
var user = await _userService.GetUserByIdAsync(ids.Item2.Value);
|
||||
if (IsSponsoredSubscription(updatedSubscription))
|
||||
{
|
||||
await _validateSponsorshipCommand.ValidateSponsorshipAsync(organizationId.Value);
|
||||
}
|
||||
|
||||
var organization = await _organizationRepository.GetByIdAsync(organizationId.Value);
|
||||
|
||||
if (organization == null || !OrgPlanForInvoiceNotifications(organization))
|
||||
{
|
||||
return new OkResult();
|
||||
}
|
||||
|
||||
await SendEmails(new List<string> { organization.BillingEmail });
|
||||
|
||||
var ownerEmails = await _organizationRepository.GetOwnerEmailAddressesById(organization.Id);
|
||||
|
||||
await SendEmails(ownerEmails);
|
||||
}
|
||||
else if (userId.HasValue)
|
||||
{
|
||||
var user = await _userService.GetUserByIdAsync(userId.Value);
|
||||
|
||||
if (user.Premium)
|
||||
{
|
||||
email = user.Email;
|
||||
await SendEmails(new List<string> { user.Email });
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(email) && invoice.NextPaymentAttempt.HasValue)
|
||||
{
|
||||
var items = invoice.Lines.Select(i => i.Description).ToList();
|
||||
await _mailService.SendInvoiceUpcomingAsync(email, invoice.AmountDue / 100M,
|
||||
invoice.NextPaymentAttempt.Value, items, true);
|
||||
}
|
||||
}
|
||||
else if (parsedEvent.Type.Equals(HandledStripeWebhook.ChargeSucceeded))
|
||||
{
|
||||
|
@ -7,13 +7,16 @@ namespace Bit.Billing.Services.Implementations;
|
||||
public class StripeEventService : IStripeEventService
|
||||
{
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly ILogger<StripeEventService> _logger;
|
||||
private readonly IStripeFacade _stripeFacade;
|
||||
|
||||
public StripeEventService(
|
||||
GlobalSettings globalSettings,
|
||||
ILogger<StripeEventService> logger,
|
||||
IStripeFacade stripeFacade)
|
||||
{
|
||||
_globalSettings = globalSettings;
|
||||
_logger = logger;
|
||||
_stripeFacade = stripeFacade;
|
||||
}
|
||||
|
||||
@ -26,6 +29,12 @@ public class StripeEventService : IStripeEventService
|
||||
return eventCharge;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(eventCharge.Id))
|
||||
{
|
||||
_logger.LogWarning("Cannot retrieve up-to-date Charge for Event with ID '{eventId}' because no Charge ID was included in the Event.", stripeEvent.Id);
|
||||
return eventCharge;
|
||||
}
|
||||
|
||||
var charge = await _stripeFacade.GetCharge(eventCharge.Id, new ChargeGetOptions { Expand = expand });
|
||||
|
||||
if (charge == null)
|
||||
@ -46,6 +55,12 @@ public class StripeEventService : IStripeEventService
|
||||
return eventCustomer;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(eventCustomer.Id))
|
||||
{
|
||||
_logger.LogWarning("Cannot retrieve up-to-date Customer for Event with ID '{eventId}' because no Customer ID was included in the Event.", stripeEvent.Id);
|
||||
return eventCustomer;
|
||||
}
|
||||
|
||||
var customer = await _stripeFacade.GetCustomer(eventCustomer.Id, new CustomerGetOptions { Expand = expand });
|
||||
|
||||
if (customer == null)
|
||||
@ -66,6 +81,12 @@ public class StripeEventService : IStripeEventService
|
||||
return eventInvoice;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(eventInvoice.Id))
|
||||
{
|
||||
_logger.LogWarning("Cannot retrieve up-to-date Invoice for Event with ID '{eventId}' because no Invoice ID was included in the Event.", stripeEvent.Id);
|
||||
return eventInvoice;
|
||||
}
|
||||
|
||||
var invoice = await _stripeFacade.GetInvoice(eventInvoice.Id, new InvoiceGetOptions { Expand = expand });
|
||||
|
||||
if (invoice == null)
|
||||
@ -86,6 +107,12 @@ public class StripeEventService : IStripeEventService
|
||||
return eventPaymentMethod;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(eventPaymentMethod.Id))
|
||||
{
|
||||
_logger.LogWarning("Cannot retrieve up-to-date Payment Method for Event with ID '{eventId}' because no Payment Method ID was included in the Event.", stripeEvent.Id);
|
||||
return eventPaymentMethod;
|
||||
}
|
||||
|
||||
var paymentMethod = await _stripeFacade.GetPaymentMethod(eventPaymentMethod.Id, new PaymentMethodGetOptions { Expand = expand });
|
||||
|
||||
if (paymentMethod == null)
|
||||
@ -106,6 +133,12 @@ public class StripeEventService : IStripeEventService
|
||||
return eventSubscription;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(eventSubscription.Id))
|
||||
{
|
||||
_logger.LogWarning("Cannot retrieve up-to-date Subscription for Event with ID '{eventId}' because no Subscription ID was included in the Event.", stripeEvent.Id);
|
||||
return eventSubscription;
|
||||
}
|
||||
|
||||
var subscription = await _stripeFacade.GetSubscription(eventSubscription.Id, new SubscriptionGetOptions { Expand = expand });
|
||||
|
||||
if (subscription == null)
|
||||
@ -132,7 +165,7 @@ public class StripeEventService : IStripeEventService
|
||||
(await GetCharge(stripeEvent, true, customerExpansion))?.Customer?.Metadata,
|
||||
|
||||
HandledStripeWebhook.UpcomingInvoice =>
|
||||
(await GetInvoice(stripeEvent, true, customerExpansion))?.Customer?.Metadata,
|
||||
await GetCustomerMetadataFromUpcomingInvoiceEvent(stripeEvent),
|
||||
|
||||
HandledStripeWebhook.PaymentSucceeded or HandledStripeWebhook.PaymentFailed or HandledStripeWebhook.InvoiceCreated =>
|
||||
(await GetInvoice(stripeEvent, true, customerExpansion))?.Customer?.Metadata,
|
||||
@ -154,6 +187,20 @@ public class StripeEventService : IStripeEventService
|
||||
var customerRegion = GetCustomerRegion(customerMetadata);
|
||||
|
||||
return customerRegion == serverRegion;
|
||||
|
||||
/* In Stripe, when we receive an invoice.upcoming event, the event does not include an Invoice ID because
|
||||
the invoice hasn't been created yet. Therefore, rather than retrieving the fresh Invoice with a 'customer'
|
||||
expansion, we need to use the Customer ID on the event to retrieve the metadata. */
|
||||
async Task<Dictionary<string, string>> GetCustomerMetadataFromUpcomingInvoiceEvent(Event localStripeEvent)
|
||||
{
|
||||
var invoice = await GetInvoice(localStripeEvent);
|
||||
|
||||
var customer = !string.IsNullOrEmpty(invoice.CustomerId)
|
||||
? await _stripeFacade.GetCustomer(invoice.CustomerId)
|
||||
: null;
|
||||
|
||||
return customer?.Metadata;
|
||||
}
|
||||
}
|
||||
|
||||
private static T Extract<T>(Event stripeEvent)
|
||||
|
49
src/Core/Models/Business/SeatSubscriptionUpdate.cs
Normal file
49
src/Core/Models/Business/SeatSubscriptionUpdate.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using Bit.Core.Entities;
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Core.Models.Business;
|
||||
|
||||
public class SeatSubscriptionUpdate : SubscriptionUpdate
|
||||
{
|
||||
private readonly int _previousSeats;
|
||||
private readonly StaticStore.Plan _plan;
|
||||
private readonly long? _additionalSeats;
|
||||
protected override List<string> PlanIds => new() { _plan.PasswordManager.StripeSeatPlanId };
|
||||
public SeatSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats)
|
||||
{
|
||||
_plan = plan;
|
||||
_additionalSeats = additionalSeats;
|
||||
_previousSeats = organization.Seats.GetValueOrDefault();
|
||||
}
|
||||
|
||||
public override List<SubscriptionItemOptions> UpgradeItemsOptions(Subscription subscription)
|
||||
{
|
||||
var item = SubscriptionItem(subscription, PlanIds.Single());
|
||||
return new()
|
||||
{
|
||||
new SubscriptionItemOptions
|
||||
{
|
||||
Id = item?.Id,
|
||||
Plan = PlanIds.Single(),
|
||||
Quantity = _additionalSeats,
|
||||
Deleted = (item?.Id != null && _additionalSeats == 0) ? true : (bool?)null,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override List<SubscriptionItemOptions> RevertItemsOptions(Subscription subscription)
|
||||
{
|
||||
|
||||
var item = SubscriptionItem(subscription, PlanIds.Single());
|
||||
return new()
|
||||
{
|
||||
new SubscriptionItemOptions
|
||||
{
|
||||
Id = item?.Id,
|
||||
Plan = PlanIds.Single(),
|
||||
Quantity = _previousSeats,
|
||||
Deleted = _previousSeats == 0 ? true : (bool?)null,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
78
src/Core/Models/Business/SecretsManagerSubscribeUpdate.cs
Normal file
78
src/Core/Models/Business/SecretsManagerSubscribeUpdate.cs
Normal file
@ -0,0 +1,78 @@
|
||||
using Bit.Core.Entities;
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Core.Models.Business;
|
||||
|
||||
public class SecretsManagerSubscribeUpdate : SubscriptionUpdate
|
||||
{
|
||||
private readonly StaticStore.Plan _plan;
|
||||
private readonly long? _additionalSeats;
|
||||
private readonly long? _additionalServiceAccounts;
|
||||
private readonly int _previousSeats;
|
||||
private readonly int _previousServiceAccounts;
|
||||
protected override List<string> PlanIds => new() { _plan.SecretsManager.StripeSeatPlanId, _plan.SecretsManager.StripeServiceAccountPlanId };
|
||||
public SecretsManagerSubscribeUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats, long? additionalServiceAccounts)
|
||||
{
|
||||
_plan = plan;
|
||||
_additionalSeats = additionalSeats;
|
||||
_additionalServiceAccounts = additionalServiceAccounts;
|
||||
_previousSeats = organization.SmSeats.GetValueOrDefault();
|
||||
_previousServiceAccounts = organization.SmServiceAccounts.GetValueOrDefault();
|
||||
}
|
||||
|
||||
public override List<SubscriptionItemOptions> RevertItemsOptions(Subscription subscription)
|
||||
{
|
||||
var updatedItems = new List<SubscriptionItemOptions>();
|
||||
|
||||
RemovePreviousSecretsManagerItems(updatedItems);
|
||||
|
||||
return updatedItems;
|
||||
}
|
||||
|
||||
public override List<SubscriptionItemOptions> UpgradeItemsOptions(Subscription subscription)
|
||||
{
|
||||
var updatedItems = new List<SubscriptionItemOptions>();
|
||||
|
||||
AddNewSecretsManagerItems(updatedItems);
|
||||
|
||||
return updatedItems;
|
||||
}
|
||||
|
||||
private void AddNewSecretsManagerItems(List<SubscriptionItemOptions> updatedItems)
|
||||
{
|
||||
if (_additionalSeats > 0)
|
||||
{
|
||||
updatedItems.Add(new SubscriptionItemOptions
|
||||
{
|
||||
Price = _plan.SecretsManager.StripeSeatPlanId,
|
||||
Quantity = _additionalSeats
|
||||
});
|
||||
}
|
||||
|
||||
if (_additionalServiceAccounts > 0)
|
||||
{
|
||||
updatedItems.Add(new SubscriptionItemOptions
|
||||
{
|
||||
Price = _plan.SecretsManager.StripeServiceAccountPlanId,
|
||||
Quantity = _additionalServiceAccounts
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void RemovePreviousSecretsManagerItems(List<SubscriptionItemOptions> updatedItems)
|
||||
{
|
||||
updatedItems.Add(new SubscriptionItemOptions
|
||||
{
|
||||
Price = _plan.SecretsManager.StripeSeatPlanId,
|
||||
Quantity = _previousSeats,
|
||||
Deleted = _previousSeats == 0 ? true : (bool?)null,
|
||||
});
|
||||
|
||||
updatedItems.Add(new SubscriptionItemOptions
|
||||
{
|
||||
Price = _plan.SecretsManager.StripeServiceAccountPlanId,
|
||||
Quantity = _previousServiceAccounts,
|
||||
Deleted = _previousServiceAccounts == 0 ? true : (bool?)null,
|
||||
});
|
||||
}
|
||||
}
|
50
src/Core/Models/Business/ServiceAccountSubscriptionUpdate.cs
Normal file
50
src/Core/Models/Business/ServiceAccountSubscriptionUpdate.cs
Normal file
@ -0,0 +1,50 @@
|
||||
using Bit.Core.Entities;
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Core.Models.Business;
|
||||
|
||||
public class ServiceAccountSubscriptionUpdate : SubscriptionUpdate
|
||||
{
|
||||
private long? _prevServiceAccounts;
|
||||
private readonly StaticStore.Plan _plan;
|
||||
private readonly long? _additionalServiceAccounts;
|
||||
protected override List<string> PlanIds => new() { _plan.SecretsManager.StripeServiceAccountPlanId };
|
||||
|
||||
public ServiceAccountSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalServiceAccounts)
|
||||
{
|
||||
_plan = plan;
|
||||
_additionalServiceAccounts = additionalServiceAccounts;
|
||||
_prevServiceAccounts = organization.SmServiceAccounts ?? 0;
|
||||
}
|
||||
|
||||
public override List<SubscriptionItemOptions> UpgradeItemsOptions(Subscription subscription)
|
||||
{
|
||||
var item = SubscriptionItem(subscription, PlanIds.Single());
|
||||
_prevServiceAccounts = item?.Quantity ?? 0;
|
||||
return new()
|
||||
{
|
||||
new SubscriptionItemOptions
|
||||
{
|
||||
Id = item?.Id,
|
||||
Plan = PlanIds.Single(),
|
||||
Quantity = _additionalServiceAccounts,
|
||||
Deleted = (item?.Id != null && _additionalServiceAccounts == 0) ? true : (bool?)null,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override List<SubscriptionItemOptions> RevertItemsOptions(Subscription subscription)
|
||||
{
|
||||
var item = SubscriptionItem(subscription, PlanIds.Single());
|
||||
return new()
|
||||
{
|
||||
new SubscriptionItemOptions
|
||||
{
|
||||
Id = item?.Id,
|
||||
Plan = PlanIds.Single(),
|
||||
Quantity = _prevServiceAccounts,
|
||||
Deleted = _prevServiceAccounts == 0 ? true : (bool?)null,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
50
src/Core/Models/Business/SmSeatSubscriptionUpdate.cs
Normal file
50
src/Core/Models/Business/SmSeatSubscriptionUpdate.cs
Normal file
@ -0,0 +1,50 @@
|
||||
using Bit.Core.Entities;
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Core.Models.Business;
|
||||
|
||||
public class SmSeatSubscriptionUpdate : SubscriptionUpdate
|
||||
{
|
||||
private readonly int _previousSeats;
|
||||
private readonly StaticStore.Plan _plan;
|
||||
private readonly long? _additionalSeats;
|
||||
protected override List<string> PlanIds => new() { _plan.SecretsManager.StripeSeatPlanId };
|
||||
|
||||
public SmSeatSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats)
|
||||
{
|
||||
_plan = plan;
|
||||
_additionalSeats = additionalSeats;
|
||||
_previousSeats = organization.SmSeats.GetValueOrDefault();
|
||||
}
|
||||
|
||||
public override List<SubscriptionItemOptions> UpgradeItemsOptions(Subscription subscription)
|
||||
{
|
||||
var item = SubscriptionItem(subscription, PlanIds.Single());
|
||||
return new()
|
||||
{
|
||||
new SubscriptionItemOptions
|
||||
{
|
||||
Id = item?.Id,
|
||||
Plan = PlanIds.Single(),
|
||||
Quantity = _additionalSeats,
|
||||
Deleted = (item?.Id != null && _additionalSeats == 0) ? true : (bool?)null,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override List<SubscriptionItemOptions> RevertItemsOptions(Subscription subscription)
|
||||
{
|
||||
|
||||
var item = SubscriptionItem(subscription, PlanIds.Single());
|
||||
return new()
|
||||
{
|
||||
new SubscriptionItemOptions
|
||||
{
|
||||
Id = item?.Id,
|
||||
Plan = PlanIds.Single(),
|
||||
Quantity = _previousSeats,
|
||||
Deleted = _previousSeats == 0 ? true : (bool?)null,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Core.Models.Business;
|
||||
|
||||
public class SponsorOrganizationSubscriptionUpdate : SubscriptionUpdate
|
||||
{
|
||||
private readonly string _existingPlanStripeId;
|
||||
private readonly string _sponsoredPlanStripeId;
|
||||
private readonly bool _applySponsorship;
|
||||
protected override List<string> PlanIds => new() { _existingPlanStripeId, _sponsoredPlanStripeId };
|
||||
|
||||
public SponsorOrganizationSubscriptionUpdate(StaticStore.Plan existingPlan, StaticStore.SponsoredPlan sponsoredPlan, bool applySponsorship)
|
||||
{
|
||||
_existingPlanStripeId = existingPlan.PasswordManager.StripePlanId;
|
||||
_sponsoredPlanStripeId = sponsoredPlan?.StripePlanId;
|
||||
_applySponsorship = applySponsorship;
|
||||
}
|
||||
|
||||
public override List<SubscriptionItemOptions> RevertItemsOptions(Subscription subscription)
|
||||
{
|
||||
var result = new List<SubscriptionItemOptions>();
|
||||
if (!string.IsNullOrWhiteSpace(AddStripePlanId))
|
||||
{
|
||||
result.Add(new SubscriptionItemOptions
|
||||
{
|
||||
Id = AddStripeItem(subscription)?.Id,
|
||||
Plan = AddStripePlanId,
|
||||
Quantity = 0,
|
||||
Deleted = true,
|
||||
});
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(RemoveStripePlanId))
|
||||
{
|
||||
result.Add(new SubscriptionItemOptions
|
||||
{
|
||||
Id = RemoveStripeItem(subscription)?.Id,
|
||||
Plan = RemoveStripePlanId,
|
||||
Quantity = 1,
|
||||
Deleted = false,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override List<SubscriptionItemOptions> UpgradeItemsOptions(Subscription subscription)
|
||||
{
|
||||
var result = new List<SubscriptionItemOptions>();
|
||||
if (RemoveStripeItem(subscription) != null)
|
||||
{
|
||||
result.Add(new SubscriptionItemOptions
|
||||
{
|
||||
Id = RemoveStripeItem(subscription)?.Id,
|
||||
Plan = RemoveStripePlanId,
|
||||
Quantity = 0,
|
||||
Deleted = true,
|
||||
});
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(AddStripePlanId))
|
||||
{
|
||||
result.Add(new SubscriptionItemOptions
|
||||
{
|
||||
Id = AddStripeItem(subscription)?.Id,
|
||||
Plan = AddStripePlanId,
|
||||
Quantity = 1,
|
||||
Deleted = false,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private string RemoveStripePlanId => _applySponsorship ? _existingPlanStripeId : _sponsoredPlanStripeId;
|
||||
private string AddStripePlanId => _applySponsorship ? _sponsoredPlanStripeId : _existingPlanStripeId;
|
||||
private Stripe.SubscriptionItem RemoveStripeItem(Subscription subscription) =>
|
||||
_applySponsorship ?
|
||||
SubscriptionItem(subscription, _existingPlanStripeId) :
|
||||
SubscriptionItem(subscription, _sponsoredPlanStripeId);
|
||||
private Stripe.SubscriptionItem AddStripeItem(Subscription subscription) =>
|
||||
_applySponsorship ?
|
||||
SubscriptionItem(subscription, _sponsoredPlanStripeId) :
|
||||
SubscriptionItem(subscription, _existingPlanStripeId);
|
||||
}
|
53
src/Core/Models/Business/StorageSubscriptionUpdate.cs
Normal file
53
src/Core/Models/Business/StorageSubscriptionUpdate.cs
Normal file
@ -0,0 +1,53 @@
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Core.Models.Business;
|
||||
|
||||
public class StorageSubscriptionUpdate : SubscriptionUpdate
|
||||
{
|
||||
private long? _prevStorage;
|
||||
private readonly string _plan;
|
||||
private readonly long? _additionalStorage;
|
||||
protected override List<string> PlanIds => new() { _plan };
|
||||
|
||||
public StorageSubscriptionUpdate(string plan, long? additionalStorage)
|
||||
{
|
||||
_plan = plan;
|
||||
_additionalStorage = additionalStorage;
|
||||
}
|
||||
|
||||
public override List<SubscriptionItemOptions> UpgradeItemsOptions(Subscription subscription)
|
||||
{
|
||||
var item = SubscriptionItem(subscription, PlanIds.Single());
|
||||
_prevStorage = item?.Quantity ?? 0;
|
||||
return new()
|
||||
{
|
||||
new SubscriptionItemOptions
|
||||
{
|
||||
Id = item?.Id,
|
||||
Plan = _plan,
|
||||
Quantity = _additionalStorage,
|
||||
Deleted = (item?.Id != null && _additionalStorage == 0) ? true : (bool?)null,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override List<SubscriptionItemOptions> RevertItemsOptions(Subscription subscription)
|
||||
{
|
||||
if (!_prevStorage.HasValue)
|
||||
{
|
||||
throw new Exception("Unknown previous value, must first call UpgradeItemsOptions");
|
||||
}
|
||||
|
||||
var item = SubscriptionItem(subscription, PlanIds.Single());
|
||||
return new()
|
||||
{
|
||||
new SubscriptionItemOptions
|
||||
{
|
||||
Id = item?.Id,
|
||||
Plan = _plan,
|
||||
Quantity = _prevStorage.Value,
|
||||
Deleted = _prevStorage.Value == 0 ? true : (bool?)null,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ namespace Bit.Core.Models.Business;
|
||||
|
||||
public class SubscriptionInfo
|
||||
{
|
||||
public BillingCustomerDiscount Discount { get; set; }
|
||||
public BillingCustomerDiscount CustomerDiscount { get; set; }
|
||||
public BillingSubscription Subscription { get; set; }
|
||||
public BillingUpcomingInvoice UpcomingInvoice { get; set; }
|
||||
public bool UsingInAppPurchase { get; set; }
|
||||
@ -17,10 +17,12 @@ public class SubscriptionInfo
|
||||
{
|
||||
Id = discount.Id;
|
||||
Active = discount.Start != null && discount.End == null;
|
||||
PercentOff = discount.Coupon?.PercentOff;
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
public bool Active { get; }
|
||||
public decimal? PercentOff { get; }
|
||||
}
|
||||
|
||||
public class BillingSubscription
|
||||
|
@ -1,5 +1,4 @@
|
||||
using Bit.Core.Entities;
|
||||
using Stripe;
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Core.Models.Business;
|
||||
|
||||
@ -28,321 +27,3 @@ public abstract class SubscriptionUpdate
|
||||
protected static SubscriptionItem SubscriptionItem(Subscription subscription, string planId) =>
|
||||
planId == null ? null : subscription.Items?.Data?.FirstOrDefault(i => i.Plan.Id == planId);
|
||||
}
|
||||
|
||||
public abstract class BaseSeatSubscriptionUpdate : SubscriptionUpdate
|
||||
{
|
||||
private readonly int _previousSeats;
|
||||
protected readonly StaticStore.Plan Plan;
|
||||
private readonly long? _additionalSeats;
|
||||
|
||||
protected BaseSeatSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats, int previousSeats)
|
||||
{
|
||||
Plan = plan;
|
||||
_additionalSeats = additionalSeats;
|
||||
_previousSeats = previousSeats;
|
||||
}
|
||||
|
||||
protected abstract string GetPlanId();
|
||||
|
||||
protected override List<string> PlanIds => new() { GetPlanId() };
|
||||
|
||||
public override List<SubscriptionItemOptions> UpgradeItemsOptions(Subscription subscription)
|
||||
{
|
||||
var item = SubscriptionItem(subscription, PlanIds.Single());
|
||||
return new()
|
||||
{
|
||||
new SubscriptionItemOptions
|
||||
{
|
||||
Id = item?.Id,
|
||||
Plan = PlanIds.Single(),
|
||||
Quantity = _additionalSeats,
|
||||
Deleted = (item?.Id != null && _additionalSeats == 0) ? true : (bool?)null,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override List<SubscriptionItemOptions> RevertItemsOptions(Subscription subscription)
|
||||
{
|
||||
|
||||
var item = SubscriptionItem(subscription, PlanIds.Single());
|
||||
return new()
|
||||
{
|
||||
new SubscriptionItemOptions
|
||||
{
|
||||
Id = item?.Id,
|
||||
Plan = PlanIds.Single(),
|
||||
Quantity = _previousSeats,
|
||||
Deleted = _previousSeats == 0 ? true : (bool?)null,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class SeatSubscriptionUpdate : BaseSeatSubscriptionUpdate
|
||||
{
|
||||
public SeatSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats)
|
||||
: base(organization, plan, additionalSeats, organization.Seats.GetValueOrDefault())
|
||||
{ }
|
||||
|
||||
protected override string GetPlanId() => Plan.PasswordManager.StripeSeatPlanId;
|
||||
}
|
||||
|
||||
public class SmSeatSubscriptionUpdate : BaseSeatSubscriptionUpdate
|
||||
{
|
||||
public SmSeatSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats)
|
||||
: base(organization, plan, additionalSeats, organization.SmSeats.GetValueOrDefault())
|
||||
{ }
|
||||
|
||||
protected override string GetPlanId() => Plan.SecretsManager.StripeSeatPlanId;
|
||||
}
|
||||
|
||||
public class ServiceAccountSubscriptionUpdate : SubscriptionUpdate
|
||||
{
|
||||
private long? _prevServiceAccounts;
|
||||
private readonly StaticStore.Plan _plan;
|
||||
private readonly long? _additionalServiceAccounts;
|
||||
protected override List<string> PlanIds => new() { _plan.SecretsManager.StripeServiceAccountPlanId };
|
||||
|
||||
public ServiceAccountSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalServiceAccounts)
|
||||
{
|
||||
_plan = plan;
|
||||
_additionalServiceAccounts = additionalServiceAccounts;
|
||||
_prevServiceAccounts = organization.SmServiceAccounts ?? 0;
|
||||
}
|
||||
|
||||
public override List<SubscriptionItemOptions> UpgradeItemsOptions(Subscription subscription)
|
||||
{
|
||||
var item = SubscriptionItem(subscription, PlanIds.Single());
|
||||
_prevServiceAccounts = item?.Quantity ?? 0;
|
||||
return new()
|
||||
{
|
||||
new SubscriptionItemOptions
|
||||
{
|
||||
Id = item?.Id,
|
||||
Plan = PlanIds.Single(),
|
||||
Quantity = _additionalServiceAccounts,
|
||||
Deleted = (item?.Id != null && _additionalServiceAccounts == 0) ? true : (bool?)null,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override List<SubscriptionItemOptions> RevertItemsOptions(Subscription subscription)
|
||||
{
|
||||
var item = SubscriptionItem(subscription, PlanIds.Single());
|
||||
return new()
|
||||
{
|
||||
new SubscriptionItemOptions
|
||||
{
|
||||
Id = item?.Id,
|
||||
Plan = PlanIds.Single(),
|
||||
Quantity = _prevServiceAccounts,
|
||||
Deleted = _prevServiceAccounts == 0 ? true : (bool?)null,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class StorageSubscriptionUpdate : SubscriptionUpdate
|
||||
{
|
||||
private long? _prevStorage;
|
||||
private readonly string _plan;
|
||||
private readonly long? _additionalStorage;
|
||||
protected override List<string> PlanIds => new() { _plan };
|
||||
|
||||
public StorageSubscriptionUpdate(string plan, long? additionalStorage)
|
||||
{
|
||||
_plan = plan;
|
||||
_additionalStorage = additionalStorage;
|
||||
}
|
||||
|
||||
public override List<SubscriptionItemOptions> UpgradeItemsOptions(Subscription subscription)
|
||||
{
|
||||
var item = SubscriptionItem(subscription, PlanIds.Single());
|
||||
_prevStorage = item?.Quantity ?? 0;
|
||||
return new()
|
||||
{
|
||||
new SubscriptionItemOptions
|
||||
{
|
||||
Id = item?.Id,
|
||||
Plan = _plan,
|
||||
Quantity = _additionalStorage,
|
||||
Deleted = (item?.Id != null && _additionalStorage == 0) ? true : (bool?)null,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override List<SubscriptionItemOptions> RevertItemsOptions(Subscription subscription)
|
||||
{
|
||||
if (!_prevStorage.HasValue)
|
||||
{
|
||||
throw new Exception("Unknown previous value, must first call UpgradeItemsOptions");
|
||||
}
|
||||
|
||||
var item = SubscriptionItem(subscription, PlanIds.Single());
|
||||
return new()
|
||||
{
|
||||
new SubscriptionItemOptions
|
||||
{
|
||||
Id = item?.Id,
|
||||
Plan = _plan,
|
||||
Quantity = _prevStorage.Value,
|
||||
Deleted = _prevStorage.Value == 0 ? true : (bool?)null,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class SponsorOrganizationSubscriptionUpdate : SubscriptionUpdate
|
||||
{
|
||||
private readonly string _existingPlanStripeId;
|
||||
private readonly string _sponsoredPlanStripeId;
|
||||
private readonly bool _applySponsorship;
|
||||
protected override List<string> PlanIds => new() { _existingPlanStripeId, _sponsoredPlanStripeId };
|
||||
|
||||
public SponsorOrganizationSubscriptionUpdate(StaticStore.Plan existingPlan, StaticStore.SponsoredPlan sponsoredPlan, bool applySponsorship)
|
||||
{
|
||||
_existingPlanStripeId = existingPlan.PasswordManager.StripePlanId;
|
||||
_sponsoredPlanStripeId = sponsoredPlan?.StripePlanId;
|
||||
_applySponsorship = applySponsorship;
|
||||
}
|
||||
|
||||
public override List<SubscriptionItemOptions> RevertItemsOptions(Subscription subscription)
|
||||
{
|
||||
var result = new List<SubscriptionItemOptions>();
|
||||
if (!string.IsNullOrWhiteSpace(AddStripePlanId))
|
||||
{
|
||||
result.Add(new SubscriptionItemOptions
|
||||
{
|
||||
Id = AddStripeItem(subscription)?.Id,
|
||||
Plan = AddStripePlanId,
|
||||
Quantity = 0,
|
||||
Deleted = true,
|
||||
});
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(RemoveStripePlanId))
|
||||
{
|
||||
result.Add(new SubscriptionItemOptions
|
||||
{
|
||||
Id = RemoveStripeItem(subscription)?.Id,
|
||||
Plan = RemoveStripePlanId,
|
||||
Quantity = 1,
|
||||
Deleted = false,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override List<SubscriptionItemOptions> UpgradeItemsOptions(Subscription subscription)
|
||||
{
|
||||
var result = new List<SubscriptionItemOptions>();
|
||||
if (RemoveStripeItem(subscription) != null)
|
||||
{
|
||||
result.Add(new SubscriptionItemOptions
|
||||
{
|
||||
Id = RemoveStripeItem(subscription)?.Id,
|
||||
Plan = RemoveStripePlanId,
|
||||
Quantity = 0,
|
||||
Deleted = true,
|
||||
});
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(AddStripePlanId))
|
||||
{
|
||||
result.Add(new SubscriptionItemOptions
|
||||
{
|
||||
Id = AddStripeItem(subscription)?.Id,
|
||||
Plan = AddStripePlanId,
|
||||
Quantity = 1,
|
||||
Deleted = false,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private string RemoveStripePlanId => _applySponsorship ? _existingPlanStripeId : _sponsoredPlanStripeId;
|
||||
private string AddStripePlanId => _applySponsorship ? _sponsoredPlanStripeId : _existingPlanStripeId;
|
||||
private Stripe.SubscriptionItem RemoveStripeItem(Subscription subscription) =>
|
||||
_applySponsorship ?
|
||||
SubscriptionItem(subscription, _existingPlanStripeId) :
|
||||
SubscriptionItem(subscription, _sponsoredPlanStripeId);
|
||||
private Stripe.SubscriptionItem AddStripeItem(Subscription subscription) =>
|
||||
_applySponsorship ?
|
||||
SubscriptionItem(subscription, _sponsoredPlanStripeId) :
|
||||
SubscriptionItem(subscription, _existingPlanStripeId);
|
||||
|
||||
}
|
||||
|
||||
public class SecretsManagerSubscribeUpdate : SubscriptionUpdate
|
||||
{
|
||||
private readonly StaticStore.Plan _plan;
|
||||
private readonly long? _additionalSeats;
|
||||
private readonly long? _additionalServiceAccounts;
|
||||
private readonly int _previousSeats;
|
||||
private readonly int _previousServiceAccounts;
|
||||
protected override List<string> PlanIds => new() { _plan.SecretsManager.StripeSeatPlanId, _plan.SecretsManager.StripeServiceAccountPlanId };
|
||||
public SecretsManagerSubscribeUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats, long? additionalServiceAccounts)
|
||||
{
|
||||
_plan = plan;
|
||||
_additionalSeats = additionalSeats;
|
||||
_additionalServiceAccounts = additionalServiceAccounts;
|
||||
_previousSeats = organization.SmSeats.GetValueOrDefault();
|
||||
_previousServiceAccounts = organization.SmServiceAccounts.GetValueOrDefault();
|
||||
}
|
||||
|
||||
public override List<SubscriptionItemOptions> RevertItemsOptions(Subscription subscription)
|
||||
{
|
||||
var updatedItems = new List<SubscriptionItemOptions>();
|
||||
|
||||
RemovePreviousSecretsManagerItems(updatedItems);
|
||||
|
||||
return updatedItems;
|
||||
}
|
||||
|
||||
public override List<SubscriptionItemOptions> UpgradeItemsOptions(Subscription subscription)
|
||||
{
|
||||
var updatedItems = new List<SubscriptionItemOptions>();
|
||||
|
||||
AddNewSecretsManagerItems(updatedItems);
|
||||
|
||||
return updatedItems;
|
||||
}
|
||||
|
||||
private void AddNewSecretsManagerItems(List<SubscriptionItemOptions> updatedItems)
|
||||
{
|
||||
if (_additionalSeats > 0)
|
||||
{
|
||||
updatedItems.Add(new SubscriptionItemOptions
|
||||
{
|
||||
Price = _plan.SecretsManager.StripeSeatPlanId,
|
||||
Quantity = _additionalSeats
|
||||
});
|
||||
}
|
||||
|
||||
if (_additionalServiceAccounts > 0)
|
||||
{
|
||||
updatedItems.Add(new SubscriptionItemOptions
|
||||
{
|
||||
Price = _plan.SecretsManager.StripeServiceAccountPlanId,
|
||||
Quantity = _additionalServiceAccounts
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void RemovePreviousSecretsManagerItems(List<SubscriptionItemOptions> updatedItems)
|
||||
{
|
||||
updatedItems.Add(new SubscriptionItemOptions
|
||||
{
|
||||
Price = _plan.SecretsManager.StripeSeatPlanId,
|
||||
Quantity = _previousSeats,
|
||||
Deleted = _previousSeats == 0 ? true : (bool?)null,
|
||||
});
|
||||
|
||||
updatedItems.Add(new SubscriptionItemOptions
|
||||
{
|
||||
Price = _plan.SecretsManager.StripeServiceAccountPlanId,
|
||||
Quantity = _previousServiceAccounts,
|
||||
Deleted = _previousServiceAccounts == 0 ? true : (bool?)null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ public class UpdateSecretsManagerSubscriptionCommand : IUpdateSecretsManagerSubs
|
||||
{
|
||||
if (update.SmSeatsChanged)
|
||||
{
|
||||
await _paymentService.AdjustSeatsAsync(update.Organization, update.Plan, update.SmSeatsExcludingBase, update.ProrationDate);
|
||||
await _paymentService.AdjustSmSeatsAsync(update.Organization, update.Plan, update.SmSeatsExcludingBase, update.ProrationDate);
|
||||
|
||||
// TODO: call ReferenceEventService - see AC-1481
|
||||
}
|
||||
|
@ -14,4 +14,5 @@ public interface IOrganizationRepository : IRepository<Organization, Guid>
|
||||
Task<Organization> GetByLicenseKeyAsync(string licenseKey);
|
||||
Task<SelfHostedOrganizationDetails> GetSelfHostedOrganizationDetailsById(Guid id);
|
||||
Task<ICollection<Organization>> SearchUnassignedToProviderAsync(string name, string ownerEmail, int skip, int take);
|
||||
Task<IEnumerable<string>> GetOwnerEmailAddressesById(Guid organizationId);
|
||||
}
|
||||
|
@ -24,7 +24,17 @@ public interface IMailService
|
||||
Task SendOrganizationConfirmedEmailAsync(string organizationName, string email);
|
||||
Task SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(string organizationName, string email);
|
||||
Task SendPasswordlessSignInAsync(string returnUrl, string token, string email);
|
||||
Task SendInvoiceUpcomingAsync(string email, decimal amount, DateTime dueDate, List<string> items,
|
||||
Task SendInvoiceUpcoming(
|
||||
string email,
|
||||
decimal amount,
|
||||
DateTime dueDate,
|
||||
List<string> items,
|
||||
bool mentionInvoices);
|
||||
Task SendInvoiceUpcoming(
|
||||
IEnumerable<string> email,
|
||||
decimal amount,
|
||||
DateTime dueDate,
|
||||
List<string> items,
|
||||
bool mentionInvoices);
|
||||
Task SendPaymentFailedAsync(string email, decimal amount, bool mentionInvoices);
|
||||
Task SendAddedCreditAsync(string email, decimal amount);
|
||||
|
@ -18,6 +18,7 @@ public interface IPaymentService
|
||||
Task<string> PurchasePremiumAsync(User user, PaymentMethodType paymentMethodType, string paymentToken,
|
||||
short additionalStorageGb, TaxInfo taxInfo);
|
||||
Task<string> AdjustSeatsAsync(Organization organization, Plan plan, int additionalSeats, DateTime? prorationDate = null);
|
||||
Task<string> AdjustSmSeatsAsync(Organization organization, Plan plan, int additionalSeats, DateTime? prorationDate = null);
|
||||
Task<string> AdjustStorageAsync(IStorableSubscriber storableSubscriber, int additionalStorage, string storagePlanId, DateTime? prorationDate = null);
|
||||
|
||||
Task<string> AdjustServiceAccountsAsync(Organization organization, Plan plan, int additionalServiceAccounts,
|
||||
|
@ -285,10 +285,21 @@ public class HandlebarsMailService : IMailService
|
||||
await _mailDeliveryService.SendEmailAsync(message);
|
||||
}
|
||||
|
||||
public async Task SendInvoiceUpcomingAsync(string email, decimal amount, DateTime dueDate,
|
||||
List<string> items, bool mentionInvoices)
|
||||
public async Task SendInvoiceUpcoming(
|
||||
string email,
|
||||
decimal amount,
|
||||
DateTime dueDate,
|
||||
List<string> items,
|
||||
bool mentionInvoices) => await SendInvoiceUpcoming(new List<string> { email }, amount, dueDate, items, mentionInvoices);
|
||||
|
||||
public async Task SendInvoiceUpcoming(
|
||||
IEnumerable<string> emails,
|
||||
decimal amount,
|
||||
DateTime dueDate,
|
||||
List<string> items,
|
||||
bool mentionInvoices)
|
||||
{
|
||||
var message = CreateDefaultMessage("Your Subscription Will Renew Soon", email);
|
||||
var message = CreateDefaultMessage("Your Subscription Will Renew Soon", emails);
|
||||
var model = new InvoiceUpcomingViewModel
|
||||
{
|
||||
WebVaultUrl = _globalSettings.BaseServiceUri.VaultWithHash,
|
||||
|
@ -863,6 +863,11 @@ public class StripePaymentService : IPaymentService
|
||||
return FinalizeSubscriptionChangeAsync(organization, new SeatSubscriptionUpdate(organization, plan, additionalSeats), prorationDate);
|
||||
}
|
||||
|
||||
public Task<string> AdjustSmSeatsAsync(Organization organization, StaticStore.Plan plan, int additionalSeats, DateTime? prorationDate = null)
|
||||
{
|
||||
return FinalizeSubscriptionChangeAsync(organization, new SmSeatSubscriptionUpdate(organization, plan, additionalSeats), prorationDate);
|
||||
}
|
||||
|
||||
public Task<string> AdjustServiceAccountsAsync(Organization organization, StaticStore.Plan plan, int additionalServiceAccounts, DateTime? prorationDate = null)
|
||||
{
|
||||
return FinalizeSubscriptionChangeAsync(organization, new ServiceAccountSubscriptionUpdate(organization, plan, additionalServiceAccounts), prorationDate);
|
||||
@ -1563,7 +1568,7 @@ public class StripePaymentService : IPaymentService
|
||||
|
||||
if (customer.Discount != null)
|
||||
{
|
||||
subscriptionInfo.Discount = new SubscriptionInfo.BillingCustomerDiscount(customer.Discount);
|
||||
subscriptionInfo.CustomerDiscount = new SubscriptionInfo.BillingCustomerDiscount(customer.Discount);
|
||||
}
|
||||
|
||||
if (subscriber.IsUser())
|
||||
|
@ -88,11 +88,19 @@ public class NoopMailService : IMailService
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendInvoiceUpcomingAsync(string email, decimal amount, DateTime dueDate,
|
||||
List<string> items, bool mentionInvoices)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
public Task SendInvoiceUpcoming(
|
||||
string email,
|
||||
decimal amount,
|
||||
DateTime dueDate,
|
||||
List<string> items,
|
||||
bool mentionInvoices) => Task.FromResult(0);
|
||||
|
||||
public Task SendInvoiceUpcoming(
|
||||
IEnumerable<string> emails,
|
||||
decimal amount,
|
||||
DateTime dueDate,
|
||||
List<string> items,
|
||||
bool mentionInvoices) => Task.FromResult(0);
|
||||
|
||||
public Task SendPaymentFailedAsync(string email, decimal amount, bool mentionInvoices)
|
||||
{
|
||||
|
@ -149,4 +149,14 @@ public class OrganizationRepository : Repository<Organization, Guid>, IOrganizat
|
||||
return results.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<string>> GetOwnerEmailAddressesById(Guid organizationId)
|
||||
{
|
||||
await using var connection = new SqlConnection(ConnectionString);
|
||||
|
||||
return await connection.QueryAsync<string>(
|
||||
$"[{Schema}].[{Table}_ReadOwnerEmailAddressesById]",
|
||||
new { OrganizationId = organizationId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
}
|
||||
|
@ -224,4 +224,24 @@ public class OrganizationRepository : Repository<Core.Entities.Organization, Org
|
||||
return selfHostedOrganization;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<string>> GetOwnerEmailAddressesById(Guid organizationId)
|
||||
{
|
||||
using var scope = ServiceScopeFactory.CreateScope();
|
||||
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
|
||||
var query =
|
||||
from u in dbContext.Users
|
||||
join ou in dbContext.OrganizationUsers on u.Id equals ou.UserId
|
||||
where
|
||||
ou.OrganizationId == organizationId &&
|
||||
ou.Type == OrganizationUserType.Owner &&
|
||||
ou.Status == OrganizationUserStatusType.Confirmed
|
||||
group u by u.Email
|
||||
into grouped
|
||||
select grouped.Key;
|
||||
|
||||
return await query.ToListAsync();
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user