1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-05 21:18:13 -05:00

started charging sales tax on seat/storage upgrades and auto renewals (#1034)

* started charging sales tax on seat/storage upgrades and auto renewals

* Code review fixes for auto-renewing subscriptions charging sales tax
This commit is contained in:
Addison Beck 2020-12-09 14:04:46 -05:00 committed by GitHub
parent 7d3fb55b2d
commit fee5c932db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 83 additions and 8 deletions

View File

@ -36,6 +36,7 @@ namespace Bit.Billing.Controllers
private readonly ILogger<StripeController> _logger; private readonly ILogger<StripeController> _logger;
private readonly Braintree.BraintreeGateway _btGateway; private readonly Braintree.BraintreeGateway _btGateway;
private readonly IReferenceEventService _referenceEventService; private readonly IReferenceEventService _referenceEventService;
private readonly ITaxRateRepository _taxRateRepository;
public StripeController( public StripeController(
GlobalSettings globalSettings, GlobalSettings globalSettings,
@ -48,7 +49,8 @@ namespace Bit.Billing.Controllers
IAppleIapService appleIapService, IAppleIapService appleIapService,
IMailService mailService, IMailService mailService,
IReferenceEventService referenceEventService, IReferenceEventService referenceEventService,
ILogger<StripeController> logger) ILogger<StripeController> logger,
ITaxRateRepository taxRateRepository)
{ {
_billingSettings = billingSettings?.Value; _billingSettings = billingSettings?.Value;
_hostingEnvironment = hostingEnvironment; _hostingEnvironment = hostingEnvironment;
@ -59,6 +61,7 @@ namespace Bit.Billing.Controllers
_appleIapService = appleIapService; _appleIapService = appleIapService;
_mailService = mailService; _mailService = mailService;
_referenceEventService = referenceEventService; _referenceEventService = referenceEventService;
_taxRateRepository = taxRateRepository;
_logger = logger; _logger = logger;
_btGateway = new Braintree.BraintreeGateway _btGateway = new Braintree.BraintreeGateway
{ {
@ -150,6 +153,8 @@ namespace Bit.Billing.Controllers
throw new Exception("Invoice subscription is null. " + invoice.Id); throw new Exception("Invoice subscription is null. " + invoice.Id);
} }
subscription = await VerifyCorrectTaxRateForCharge(invoice, subscription);
string email = null; string email = null;
var ids = GetIdsFromMetaData(subscription.Metadata); var ids = GetIdsFromMetaData(subscription.Metadata);
// org // org
@ -741,5 +746,31 @@ namespace Bit.Billing.Controllers
} }
return subscription; return subscription;
} }
private async Task<Subscription> VerifyCorrectTaxRateForCharge(Invoice invoice, Subscription subscription)
{
if (!string.IsNullOrWhiteSpace(invoice?.CustomerAddress?.Country) && !string.IsNullOrWhiteSpace(invoice?.CustomerAddress?.PostalCode))
{
var localBitwardenTaxRates = await _taxRateRepository.GetByLocationAsync(
new Bit.Core.Models.Table.TaxRate()
{
Country = invoice.CustomerAddress.Country,
PostalCode = invoice.CustomerAddress.PostalCode
}
);
if (localBitwardenTaxRates.Any())
{
var stripeTaxRate = await new TaxRateService().GetAsync(localBitwardenTaxRates.First().Id);
if (stripeTaxRate != null && !subscription.DefaultTaxRates.Any(x => x == stripeTaxRate))
{
subscription.DefaultTaxRates = new List<Stripe.TaxRate> { stripeTaxRate };
var subscriptionOptions = new SubscriptionUpdateOptions() { DefaultTaxRates = new List<string>() { stripeTaxRate.Id } };
subscription = await new SubscriptionService().UpdateAsync(subscription.Id, subscriptionOptions);
}
}
}
return subscription;
}
} }
} }

View File

@ -38,6 +38,7 @@ namespace Bit.Core.Services
private readonly ISsoUserRepository _ssoUserRepository; private readonly ISsoUserRepository _ssoUserRepository;
private readonly IReferenceEventService _referenceEventService; private readonly IReferenceEventService _referenceEventService;
private readonly GlobalSettings _globalSettings; private readonly GlobalSettings _globalSettings;
private readonly ITaxRateRepository _taxRateRepository;
public OrganizationService( public OrganizationService(
IOrganizationRepository organizationRepository, IOrganizationRepository organizationRepository,
@ -59,7 +60,8 @@ namespace Bit.Core.Services
ISsoConfigRepository ssoConfigRepository, ISsoConfigRepository ssoConfigRepository,
ISsoUserRepository ssoUserRepository, ISsoUserRepository ssoUserRepository,
IReferenceEventService referenceEventService, IReferenceEventService referenceEventService,
GlobalSettings globalSettings) GlobalSettings globalSettings,
ITaxRateRepository taxRateRepository)
{ {
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository; _organizationUserRepository = organizationUserRepository;
@ -81,6 +83,7 @@ namespace Bit.Core.Services
_ssoUserRepository = ssoUserRepository; _ssoUserRepository = ssoUserRepository;
_referenceEventService = referenceEventService; _referenceEventService = referenceEventService;
_globalSettings = globalSettings; _globalSettings = globalSettings;
_taxRateRepository = taxRateRepository;
} }
public async Task ReplacePaymentMethodAsync(Guid organizationId, string paymentToken, public async Task ReplacePaymentMethodAsync(Guid organizationId, string paymentToken,
@ -392,7 +395,7 @@ namespace Bit.Core.Services
// Retain original collection method // Retain original collection method
var collectionMethod = sub.CollectionMethod; var collectionMethod = sub.CollectionMethod;
var subResponse = await subscriptionService.UpdateAsync(sub.Id, new SubscriptionUpdateOptions var subUpdateOptions = new SubscriptionUpdateOptions
{ {
Items = new List<SubscriptionItemOptions> Items = new List<SubscriptionItemOptions>
{ {
@ -408,7 +411,26 @@ namespace Bit.Core.Services
DaysUntilDue = 1, DaysUntilDue = 1,
CollectionMethod = "send_invoice", CollectionMethod = "send_invoice",
ProrationDate = prorationDate, ProrationDate = prorationDate,
}); };
var customer = await new CustomerService().GetAsync(sub.CustomerId);
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; string paymentIntentClientSecret = null;
if (additionalSeats > 0) if (additionalSeats > 0)

View File

@ -684,7 +684,7 @@ namespace Bit.Core.Services
// Retain original collection method // Retain original collection method
var collectionMethod = sub.CollectionMethod; var collectionMethod = sub.CollectionMethod;
var subResponse = await subscriptionService.UpdateAsync(sub.Id, new SubscriptionUpdateOptions var subUpdateOptions = new SubscriptionUpdateOptions
{ {
Items = new List<SubscriptionItemOptions> Items = new List<SubscriptionItemOptions>
{ {
@ -700,7 +700,26 @@ namespace Bit.Core.Services
DaysUntilDue = 1, DaysUntilDue = 1,
CollectionMethod = "send_invoice", CollectionMethod = "send_invoice",
ProrationDate = prorationDate, ProrationDate = prorationDate,
}); };
var customer = await new CustomerService().GetAsync(sub.CustomerId);
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; string paymentIntentClientSecret = null;
if (additionalStorage > 0) if (additionalStorage > 0)

View File

@ -36,11 +36,12 @@ namespace Bit.Core.Test.Services
var ssoUserRepo = Substitute.For<ISsoUserRepository>(); var ssoUserRepo = Substitute.For<ISsoUserRepository>();
var referenceEventService = Substitute.For<IReferenceEventService>(); var referenceEventService = Substitute.For<IReferenceEventService>();
var globalSettings = Substitute.For<GlobalSettings>(); var globalSettings = Substitute.For<GlobalSettings>();
var taxRateRepository = Substitute.For<ITaxRateRepository>();
var orgService = new OrganizationService(orgRepo, orgUserRepo, collectionRepo, userRepo, var orgService = new OrganizationService(orgRepo, orgUserRepo, collectionRepo, userRepo,
groupRepo, dataProtector, mailService, pushNotService, pushRegService, deviceRepo, groupRepo, dataProtector, mailService, pushNotService, pushRegService, deviceRepo,
licenseService, eventService, installationRepo, appCacheService, paymentService, policyRepo, licenseService, eventService, installationRepo, appCacheService, paymentService, policyRepo,
ssoConfigRepo, ssoUserRepo, referenceEventService, globalSettings); ssoConfigRepo, ssoUserRepo, referenceEventService, globalSettings, taxRateRepository);
var id = Guid.NewGuid(); var id = Guid.NewGuid();
var userId = Guid.NewGuid(); var userId = Guid.NewGuid();
@ -97,11 +98,12 @@ namespace Bit.Core.Test.Services
var ssoUserRepo = Substitute.For<ISsoUserRepository>(); var ssoUserRepo = Substitute.For<ISsoUserRepository>();
var referenceEventService = Substitute.For<IReferenceEventService>(); var referenceEventService = Substitute.For<IReferenceEventService>();
var globalSettings = Substitute.For<GlobalSettings>(); var globalSettings = Substitute.For<GlobalSettings>();
var taxRateRepo = Substitute.For<ITaxRateRepository>();
var orgService = new OrganizationService(orgRepo, orgUserRepo, collectionRepo, userRepo, var orgService = new OrganizationService(orgRepo, orgUserRepo, collectionRepo, userRepo,
groupRepo, dataProtector, mailService, pushNotService, pushRegService, deviceRepo, groupRepo, dataProtector, mailService, pushNotService, pushRegService, deviceRepo,
licenseService, eventService, installationRepo, appCacheService, paymentService, policyRepo, licenseService, eventService, installationRepo, appCacheService, paymentService, policyRepo,
ssoConfigRepo, ssoUserRepo, referenceEventService, globalSettings); ssoConfigRepo, ssoUserRepo, referenceEventService, globalSettings, taxRateRepo);
var id = Guid.NewGuid(); var id = Guid.NewGuid();
var userId = Guid.NewGuid(); var userId = Guid.NewGuid();

View File

@ -25,6 +25,7 @@ namespace Bit.Core.Test.Services
_appleIapService = Substitute.For<IAppleIapService>(); _appleIapService = Substitute.For<IAppleIapService>();
_globalSettings = new GlobalSettings(); _globalSettings = new GlobalSettings();
_logger = Substitute.For<ILogger<StripePaymentService>>(); _logger = Substitute.For<ILogger<StripePaymentService>>();
_taxRateRepository = Substitute.For<ITaxRateRepository>();
_sut = new StripePaymentService( _sut = new StripePaymentService(
_transactionRepository, _transactionRepository,