1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-22 12:15:36 +01:00

Merge pull request #782 from bitwarden/feature/tax-info-collection

Combined tax updates with other operations
This commit is contained in:
Chad Scharf 2020-06-18 11:26:58 -04:00 committed by GitHub
commit 61b15c55d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 85 additions and 22 deletions

View File

@ -468,13 +468,23 @@ namespace Bit.Api.Controllers
license = await ApiHelpers.ReadJsonFileFromBody<UserLicense>(HttpContext, model.License); license = await ApiHelpers.ReadJsonFileFromBody<UserLicense>(HttpContext, model.License);
} }
if (!valid && !_globalSettings.SelfHosted && string.IsNullOrWhiteSpace(model.Country))
{
throw new BadRequestException("Country is required.");
}
if (!valid || (_globalSettings.SelfHosted && license == null)) if (!valid || (_globalSettings.SelfHosted && license == null))
{ {
throw new BadRequestException("Invalid license."); throw new BadRequestException("Invalid license.");
} }
var result = await _userService.SignUpPremiumAsync(user, model.PaymentToken, var result = await _userService.SignUpPremiumAsync(user, model.PaymentToken,
model.PaymentMethodType.Value, model.AdditionalStorageGb.GetValueOrDefault(0), license); model.PaymentMethodType.Value, model.AdditionalStorageGb.GetValueOrDefault(0), license,
new TaxInfo
{
BillingAddressCountry = model.Country,
BillingAddressPostalCode = model.PostalCode,
});
var profile = new ProfileResponseModel(user, null, await _userService.TwoFactorIsEnabledAsync(user)); var profile = new ProfileResponseModel(user, null, await _userService.TwoFactorIsEnabledAsync(user));
return new PaymentResponseModel return new PaymentResponseModel
{ {
@ -534,7 +544,12 @@ namespace Bit.Api.Controllers
throw new UnauthorizedAccessException(); throw new UnauthorizedAccessException();
} }
await _userService.ReplacePaymentMethodAsync(user, model.PaymentToken, model.PaymentMethodType.Value); await _userService.ReplacePaymentMethodAsync(user, model.PaymentToken, model.PaymentMethodType.Value,
new TaxInfo
{
BillingAddressCountry = model.Country,
BillingAddressPostalCode = model.PostalCode,
});
} }
[HttpPost("storage")] [HttpPost("storage")]

View File

@ -210,7 +210,16 @@ namespace Bit.Api.Controllers
} }
await _organizationService.ReplacePaymentMethodAsync(orgIdGuid, model.PaymentToken, await _organizationService.ReplacePaymentMethodAsync(orgIdGuid, model.PaymentToken,
model.PaymentMethodType.Value); model.PaymentMethodType.Value, new TaxInfo
{
BillingAddressLine1 = model.Line1,
BillingAddressLine2 = model.Line2,
BillingAddressState = model.State,
BillingAddressCity = model.City,
BillingAddressPostalCode = model.PostalCode,
BillingAddressCountry = model.Country,
TaxIdNumber = model.TaxId,
});
} }
[HttpPost("{id}/upgrade")] [HttpPost("{id}/upgrade")]

View File

@ -13,11 +13,17 @@ namespace Bit.Core.Models.Api
[Range(0, 99)] [Range(0, 99)]
public short? AdditionalStorageGb { get; set; } public short? AdditionalStorageGb { get; set; }
public IFormFile License { get; set; } public IFormFile License { get; set; }
public string Country { get; set; }
public string PostalCode { get; set; }
public bool Validate(GlobalSettings globalSettings) public bool Validate(GlobalSettings globalSettings)
{ {
return (License == null && !globalSettings.SelfHosted) || if (!(License == null && !globalSettings.SelfHosted) ||
(License != null && globalSettings.SelfHosted); (License != null && globalSettings.SelfHosted))
{
return false;
}
return globalSettings.SelfHosted || !string.IsNullOrWhiteSpace(Country);
} }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
@ -27,6 +33,11 @@ namespace Bit.Core.Models.Api
{ {
yield return new ValidationResult("Payment token or license is required."); yield return new ValidationResult("Payment token or license is required.");
} }
if (Country == "US" && string.IsNullOrWhiteSpace(PostalCode))
{
yield return new ValidationResult("Zip / postal code is required.",
new string[] { nameof(PostalCode) });
}
} }
} }
} }

View File

@ -3,7 +3,7 @@ using Bit.Core.Enums;
namespace Bit.Core.Models.Api namespace Bit.Core.Models.Api
{ {
public class PaymentRequestModel public class PaymentRequestModel : OrganizationTaxInfoUpdateRequestModel
{ {
[Required] [Required]
public PaymentMethodType? PaymentMethodType { get; set; } public PaymentMethodType? PaymentMethodType { get; set; }

View File

@ -10,7 +10,8 @@ namespace Bit.Core.Services
{ {
public interface IOrganizationService public interface IOrganizationService
{ {
Task ReplacePaymentMethodAsync(Guid organizationId, string paymentToken, PaymentMethodType paymentMethodType); Task ReplacePaymentMethodAsync(Guid organizationId, string paymentToken, PaymentMethodType paymentMethodType,
TaxInfo taxInfo);
Task CancelSubscriptionAsync(Guid organizationId, bool? endOfPeriod = null); Task CancelSubscriptionAsync(Guid organizationId, bool? endOfPeriod = null);
Task ReinstateSubscriptionAsync(Guid organizationId); Task ReinstateSubscriptionAsync(Guid organizationId);
Task<Tuple<bool, string>> UpgradePlanAsync(Guid organizationId, OrganizationUpgrade upgrade); Task<Tuple<bool, string>> UpgradePlanAsync(Guid organizationId, OrganizationUpgrade upgrade);

View File

@ -14,13 +14,13 @@ namespace Bit.Core.Services
Task<string> UpgradeFreeOrganizationAsync(Organization org, Models.StaticStore.Plan plan, Task<string> UpgradeFreeOrganizationAsync(Organization org, Models.StaticStore.Plan plan,
short additionalStorageGb, short additionalSeats, bool premiumAccessAddon); short additionalStorageGb, short additionalSeats, bool premiumAccessAddon);
Task<string> PurchasePremiumAsync(User user, PaymentMethodType paymentMethodType, string paymentToken, Task<string> PurchasePremiumAsync(User user, PaymentMethodType paymentMethodType, string paymentToken,
short additionalStorageGb); short additionalStorageGb, TaxInfo taxInfo);
Task<string> AdjustStorageAsync(IStorableSubscriber storableSubscriber, int additionalStorage, string storagePlanId); Task<string> AdjustStorageAsync(IStorableSubscriber storableSubscriber, int additionalStorage, string storagePlanId);
Task CancelSubscriptionAsync(ISubscriber subscriber, bool endOfPeriod = false, Task CancelSubscriptionAsync(ISubscriber subscriber, bool endOfPeriod = false,
bool skipInAppPurchaseCheck = false); bool skipInAppPurchaseCheck = false);
Task ReinstateSubscriptionAsync(ISubscriber subscriber); Task ReinstateSubscriptionAsync(ISubscriber subscriber);
Task<bool> UpdatePaymentMethodAsync(ISubscriber subscriber, PaymentMethodType paymentMethodType, Task<bool> UpdatePaymentMethodAsync(ISubscriber subscriber, PaymentMethodType paymentMethodType,
string paymentToken, bool allowInAppPurchases = false); string paymentToken, bool allowInAppPurchases = false, TaxInfo taxInfo = null);
Task<bool> CreditAccountAsync(ISubscriber subscriber, decimal creditAmount); Task<bool> CreditAccountAsync(ISubscriber subscriber, decimal creditAmount);
Task<BillingInfo> GetBillingAsync(ISubscriber subscriber); Task<BillingInfo> GetBillingAsync(ISubscriber subscriber);
Task<SubscriptionInfo> GetSubscriptionAsync(ISubscriber subscriber); Task<SubscriptionInfo> GetSubscriptionAsync(ISubscriber subscriber);

View File

@ -46,11 +46,12 @@ namespace Bit.Core.Services
Task<IdentityResult> DeleteAsync(User user, string token); Task<IdentityResult> DeleteAsync(User user, string token);
Task SendDeleteConfirmationAsync(string email); Task SendDeleteConfirmationAsync(string email);
Task<Tuple<bool, string>> SignUpPremiumAsync(User user, string paymentToken, Task<Tuple<bool, string>> SignUpPremiumAsync(User user, string paymentToken,
PaymentMethodType paymentMethodType, short additionalStorageGb, UserLicense license); PaymentMethodType paymentMethodType, short additionalStorageGb, UserLicense license,
TaxInfo taxInfo);
Task IapCheckAsync(User user, PaymentMethodType paymentMethodType); Task IapCheckAsync(User user, PaymentMethodType paymentMethodType);
Task UpdateLicenseAsync(User user, UserLicense license); Task UpdateLicenseAsync(User user, UserLicense license);
Task<string> AdjustStorageAsync(User user, short storageAdjustmentGb); Task<string> AdjustStorageAsync(User user, short storageAdjustmentGb);
Task ReplacePaymentMethodAsync(User user, string paymentToken, PaymentMethodType paymentMethodType); Task ReplacePaymentMethodAsync(User user, string paymentToken, PaymentMethodType paymentMethodType, TaxInfo taxInfo);
Task CancelPremiumAsync(User user, bool? endOfPeriod = null, bool accountDelete = false); Task CancelPremiumAsync(User user, bool? endOfPeriod = null, bool accountDelete = false);
Task ReinstatePremiumAsync(User user); Task ReinstatePremiumAsync(User user);
Task EnablePremiumAsync(Guid userId, DateTime? expirationDate); Task EnablePremiumAsync(Guid userId, DateTime? expirationDate);

View File

@ -75,7 +75,7 @@ namespace Bit.Core.Services
} }
public async Task ReplacePaymentMethodAsync(Guid organizationId, string paymentToken, public async Task ReplacePaymentMethodAsync(Guid organizationId, string paymentToken,
PaymentMethodType paymentMethodType) PaymentMethodType paymentMethodType, TaxInfo taxInfo)
{ {
var organization = await GetOrgById(organizationId); var organization = await GetOrgById(organizationId);
if (organization == null) if (organization == null)
@ -83,6 +83,7 @@ namespace Bit.Core.Services
throw new NotFoundException(); throw new NotFoundException();
} }
await _paymentService.SaveTaxInfoAsync(organization, taxInfo);
var updated = await _paymentService.UpdatePaymentMethodAsync(organization, var updated = await _paymentService.UpdatePaymentMethodAsync(organization,
paymentMethodType, paymentToken); paymentMethodType, paymentToken);
if (updated) if (updated)

View File

@ -345,7 +345,7 @@ namespace Bit.Core.Services
} }
public async Task<string> PurchasePremiumAsync(User user, PaymentMethodType paymentMethodType, public async Task<string> PurchasePremiumAsync(User user, PaymentMethodType paymentMethodType,
string paymentToken, short additionalStorageGb) string paymentToken, short additionalStorageGb, TaxInfo taxInfo)
{ {
if (paymentMethodType != PaymentMethodType.Credit && string.IsNullOrWhiteSpace(paymentToken)) if (paymentMethodType != PaymentMethodType.Credit && string.IsNullOrWhiteSpace(paymentToken))
{ {
@ -393,7 +393,7 @@ namespace Bit.Core.Services
{ {
try try
{ {
await UpdatePaymentMethodAsync(user, paymentMethodType, paymentToken, true); await UpdatePaymentMethodAsync(user, paymentMethodType, paymentToken, true, taxInfo);
} }
catch (Exception e) catch (Exception e)
{ {
@ -463,7 +463,13 @@ namespace Bit.Core.Services
InvoiceSettings = new CustomerInvoiceSettingsOptions InvoiceSettings = new CustomerInvoiceSettingsOptions
{ {
DefaultPaymentMethod = stipeCustomerPaymentMethodId DefaultPaymentMethod = stipeCustomerPaymentMethodId
} },
Address = new AddressOptions
{
Line1 = string.Empty,
Country = taxInfo.BillingAddressCountry,
PostalCode = taxInfo.BillingAddressPostalCode,
},
}); });
createdStripeCustomer = true; createdStripeCustomer = true;
} }
@ -1098,7 +1104,7 @@ namespace Bit.Core.Services
} }
public async Task<bool> UpdatePaymentMethodAsync(ISubscriber subscriber, PaymentMethodType paymentMethodType, public async Task<bool> UpdatePaymentMethodAsync(ISubscriber subscriber, PaymentMethodType paymentMethodType,
string paymentToken, bool allowInAppPurchases = false) string paymentToken, bool allowInAppPurchases = false, TaxInfo taxInfo = null)
{ {
if (subscriber == null) if (subscriber == null)
{ {
@ -1286,7 +1292,16 @@ namespace Bit.Core.Services
InvoiceSettings = new CustomerInvoiceSettingsOptions InvoiceSettings = new CustomerInvoiceSettingsOptions
{ {
DefaultPaymentMethod = stipeCustomerPaymentMethodId DefaultPaymentMethod = stipeCustomerPaymentMethodId
} },
Address = taxInfo == null ? null : new AddressOptions
{
Country = taxInfo.BillingAddressCountry,
PostalCode = taxInfo.BillingAddressPostalCode,
Line1 = taxInfo.BillingAddressLine1 ?? string.Empty,
Line2 = taxInfo.BillingAddressLine2,
City = taxInfo.BillingAddressCity,
State = taxInfo.BillingAddressState,
},
}); });
subscriber.Gateway = GatewayType.Stripe; subscriber.Gateway = GatewayType.Stripe;
@ -1345,7 +1360,16 @@ namespace Bit.Core.Services
InvoiceSettings = new CustomerInvoiceSettingsOptions InvoiceSettings = new CustomerInvoiceSettingsOptions
{ {
DefaultPaymentMethod = defaultPaymentMethodId DefaultPaymentMethod = defaultPaymentMethodId
} },
Address = taxInfo == null ? null : new AddressOptions
{
Country = taxInfo.BillingAddressCountry,
PostalCode = taxInfo.BillingAddressPostalCode,
Line1 = taxInfo.BillingAddressLine1 ?? string.Empty,
Line2 = taxInfo.BillingAddressLine2,
City = taxInfo.BillingAddressCity,
State = taxInfo.BillingAddressState,
},
}); });
} }
} }

View File

@ -703,7 +703,8 @@ namespace Bit.Core.Services
} }
public async Task<Tuple<bool, string>> SignUpPremiumAsync(User user, string paymentToken, public async Task<Tuple<bool, string>> SignUpPremiumAsync(User user, string paymentToken,
PaymentMethodType paymentMethodType, short additionalStorageGb, UserLicense license) PaymentMethodType paymentMethodType, short additionalStorageGb, UserLicense license,
TaxInfo taxInfo)
{ {
if (user.Premium) if (user.Premium)
{ {
@ -742,7 +743,7 @@ namespace Bit.Core.Services
else else
{ {
paymentIntentClientSecret = await _paymentService.PurchasePremiumAsync(user, paymentMethodType, paymentIntentClientSecret = await _paymentService.PurchasePremiumAsync(user, paymentMethodType,
paymentToken, additionalStorageGb); paymentToken, additionalStorageGb, taxInfo);
} }
user.Premium = true; user.Premium = true;
@ -844,14 +845,14 @@ namespace Bit.Core.Services
return secret; return secret;
} }
public async Task ReplacePaymentMethodAsync(User user, string paymentToken, PaymentMethodType paymentMethodType) public async Task ReplacePaymentMethodAsync(User user, string paymentToken, PaymentMethodType paymentMethodType, TaxInfo taxInfo)
{ {
if (paymentToken.StartsWith("btok_")) if (paymentToken.StartsWith("btok_"))
{ {
throw new BadRequestException("Invalid token."); throw new BadRequestException("Invalid token.");
} }
var updated = await _paymentService.UpdatePaymentMethodAsync(user, paymentMethodType, paymentToken); var updated = await _paymentService.UpdatePaymentMethodAsync(user, paymentMethodType, paymentToken, taxInfo: taxInfo);
if (updated) if (updated)
{ {
await SaveUserAsync(user); await SaveUserAsync(user);