From d568b86e1e23b9c935832c33bb59a64016d3c277 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Fri, 8 Feb 2019 23:53:09 -0500 Subject: [PATCH] inject stripepaymentservice --- src/Api/Controllers/AccountsController.cs | 6 +- .../Controllers/OrganizationsController.cs | 6 +- .../Jobs/PremiumRenewalRemindersJob.cs | 6 +- src/Core/Models/Table/ISubscriber.cs | 1 - src/Core/Models/Table/Organization.cs | 23 -- src/Core/Models/Table/User.cs | 23 -- src/Core/Services/IPaymentService.cs | 2 + .../BraintreePaymentService.cs | 386 ------------------ .../Implementations/OrganizationService.cs | 26 +- .../Implementations/StripePaymentService.cs | 20 + .../Services/Implementations/UserService.cs | 22 +- .../Utilities/ServiceCollectionExtensions.cs | 1 + 12 files changed, 58 insertions(+), 464 deletions(-) delete mode 100644 src/Core/Services/Implementations/BraintreePaymentService.cs diff --git a/src/Api/Controllers/AccountsController.cs b/src/Api/Controllers/AccountsController.cs index 703f7a8df..eae0760ed 100644 --- a/src/Api/Controllers/AccountsController.cs +++ b/src/Api/Controllers/AccountsController.cs @@ -28,6 +28,7 @@ namespace Bit.Api.Controllers private readonly ICipherService _cipherService; private readonly IOrganizationUserRepository _organizationUserRepository; private readonly ILicensingService _licenseService; + private readonly IPaymentService _paymentService; private readonly GlobalSettings _globalSettings; public AccountsController( @@ -38,6 +39,7 @@ namespace Bit.Api.Controllers ICipherService cipherService, IOrganizationUserRepository organizationUserRepository, ILicensingService licenseService, + IPaymentService paymentService, GlobalSettings globalSettings) { _userService = userService; @@ -47,6 +49,7 @@ namespace Bit.Api.Controllers _cipherService = cipherService; _organizationUserRepository = organizationUserRepository; _licenseService = licenseService; + _paymentService = paymentService; _globalSettings = globalSettings; } @@ -476,8 +479,7 @@ namespace Bit.Api.Controllers if(!_globalSettings.SelfHosted && user.Gateway != null) { - var paymentService = user.GetPaymentService(_globalSettings); - var billingInfo = await paymentService.GetBillingAsync(user); + var billingInfo = await _paymentService.GetBillingAsync(user); var license = await _userService.GenerateLicenseAsync(user, billingInfo); return new BillingResponseModel(user, billingInfo, license); } diff --git a/src/Api/Controllers/OrganizationsController.cs b/src/Api/Controllers/OrganizationsController.cs index cea2162d9..f9e8213e6 100644 --- a/src/Api/Controllers/OrganizationsController.cs +++ b/src/Api/Controllers/OrganizationsController.cs @@ -24,6 +24,7 @@ namespace Bit.Api.Controllers private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IOrganizationService _organizationService; private readonly IUserService _userService; + private readonly IPaymentService _paymentService; private readonly CurrentContext _currentContext; private readonly GlobalSettings _globalSettings; @@ -32,6 +33,7 @@ namespace Bit.Api.Controllers IOrganizationUserRepository organizationUserRepository, IOrganizationService organizationService, IUserService userService, + IPaymentService paymentService, CurrentContext currentContext, GlobalSettings globalSettings) { @@ -39,6 +41,7 @@ namespace Bit.Api.Controllers _organizationUserRepository = organizationUserRepository; _organizationService = organizationService; _userService = userService; + _paymentService = paymentService; _currentContext = currentContext; _globalSettings = globalSettings; } @@ -78,8 +81,7 @@ namespace Bit.Api.Controllers if(!_globalSettings.SelfHosted && organization.Gateway != null) { - var paymentService = new StripePaymentService(_globalSettings); - var billingInfo = await paymentService.GetBillingAsync(organization); + var billingInfo = await _paymentService.GetBillingAsync(organization); if(billingInfo == null) { throw new NotFoundException(); diff --git a/src/Billing/Jobs/PremiumRenewalRemindersJob.cs b/src/Billing/Jobs/PremiumRenewalRemindersJob.cs index 94dc35e87..b87703821 100644 --- a/src/Billing/Jobs/PremiumRenewalRemindersJob.cs +++ b/src/Billing/Jobs/PremiumRenewalRemindersJob.cs @@ -17,12 +17,14 @@ namespace Bit.Billing.Jobs private readonly GlobalSettings _globalSettings; private readonly IUserRepository _userRepository; private readonly IMailService _mailService; + private readonly IPaymentService _paymentService; public PremiumRenewalRemindersJob( IOptions billingSettings, GlobalSettings globalSettings, IUserRepository userRepository, IMailService mailService, + IPaymentService paymentService, ILogger logger) : base(logger) { @@ -30,6 +32,7 @@ namespace Bit.Billing.Jobs _globalSettings = globalSettings; _userRepository = userRepository; _mailService = mailService; + _paymentService = paymentService; } protected async override Task ExecuteJobAsync(IJobExecutionContext context) @@ -37,8 +40,7 @@ namespace Bit.Billing.Jobs var users = await _userRepository.GetManyByPremiumRenewalAsync(); foreach(var user in users) { - var paymentService = user.GetPaymentService(_globalSettings); - var upcomingInvoice = await paymentService.GetUpcomingInvoiceAsync(user); + var upcomingInvoice = await _paymentService.GetUpcomingInvoiceAsync(user); if(upcomingInvoice?.Date != null) { var items = new List { "1 × Premium Membership (Annually)" }; diff --git a/src/Core/Models/Table/ISubscriber.cs b/src/Core/Models/Table/ISubscriber.cs index 302025881..fd22d7d09 100644 --- a/src/Core/Models/Table/ISubscriber.cs +++ b/src/Core/Models/Table/ISubscriber.cs @@ -15,6 +15,5 @@ namespace Bit.Core.Models.Table string BraintreeCustomerIdPrefix(); string BraintreeIdField(); string GatewayIdField(); - IPaymentService GetPaymentService(GlobalSettings globalSettings); } } diff --git a/src/Core/Models/Table/Organization.cs b/src/Core/Models/Table/Organization.cs index ed9a0dad7..f9aa28229 100644 --- a/src/Core/Models/Table/Organization.cs +++ b/src/Core/Models/Table/Organization.cs @@ -99,29 +99,6 @@ namespace Bit.Core.Models.Table return maxStorageBytes - Storage.Value; } - public IPaymentService GetPaymentService(GlobalSettings globalSettings) - { - if(Gateway == null) - { - throw new BadRequestException("No gateway."); - } - - IPaymentService paymentService = null; - switch(Gateway) - { - case GatewayType.Stripe: - paymentService = new StripePaymentService(globalSettings); - break; - case GatewayType.Braintree: - paymentService = new BraintreePaymentService(globalSettings); - break; - default: - throw new NotSupportedException("Unsupported gateway."); - } - - return paymentService; - } - public Dictionary GetTwoFactorProviders() { if(string.IsNullOrWhiteSpace(TwoFactorProviders)) diff --git a/src/Core/Models/Table/User.cs b/src/Core/Models/Table/User.cs index 2c99e89a9..bd2b75455 100644 --- a/src/Core/Models/Table/User.cs +++ b/src/Core/Models/Table/User.cs @@ -148,29 +148,6 @@ namespace Bit.Core.Models.Table return maxStorageBytes - Storage.Value; } - public IPaymentService GetPaymentService(GlobalSettings globalSettings) - { - if(Gateway == null) - { - throw new BadRequestException("No gateway."); - } - - IPaymentService paymentService = null; - switch(Gateway) - { - case GatewayType.Stripe: - paymentService = new StripePaymentService(globalSettings); - break; - case GatewayType.Braintree: - paymentService = new BraintreePaymentService(globalSettings); - break; - default: - throw new NotSupportedException("Unsupported gateway."); - } - - return paymentService; - } - public IdentityUser ToIdentityUser(bool twoFactorEnabled) { return new IdentityUser diff --git a/src/Core/Services/IPaymentService.cs b/src/Core/Services/IPaymentService.cs index d45e378b3..633d6fadd 100644 --- a/src/Core/Services/IPaymentService.cs +++ b/src/Core/Services/IPaymentService.cs @@ -8,6 +8,8 @@ namespace Bit.Core.Services public interface IPaymentService { Task CancelAndRecoverChargesAsync(ISubscriber subscriber); + Task PurchaseOrganizationAsync(Organization org, PaymentMethodType paymentMethodType, string paymentToken, + Models.StaticStore.Plan plan, short additionalStorageGb, short additionalSeats, bool premiumAccessAddon); Task PurchasePremiumAsync(User user, PaymentMethodType paymentMethodType, string paymentToken, short additionalStorageGb); Task AdjustStorageAsync(IStorableSubscriber storableSubscriber, int additionalStorage, string storagePlanId); diff --git a/src/Core/Services/Implementations/BraintreePaymentService.cs b/src/Core/Services/Implementations/BraintreePaymentService.cs deleted file mode 100644 index c205235fd..000000000 --- a/src/Core/Services/Implementations/BraintreePaymentService.cs +++ /dev/null @@ -1,386 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Bit.Core.Models.Table; -using Braintree; -using Bit.Core.Exceptions; -using Bit.Core.Models.Business; -using Bit.Core.Enums; -using System.Collections.Generic; - -namespace Bit.Core.Services -{ - public class BraintreePaymentService : IPaymentService - { - private const string PremiumPlanId = "premium-annually"; - private const string StoragePlanId = "storage-gb-annually"; - private readonly BraintreeGateway _gateway; - - public BraintreePaymentService( - GlobalSettings globalSettings) - { - _gateway = new BraintreeGateway - { - Environment = globalSettings.Braintree.Production ? - Braintree.Environment.PRODUCTION : Braintree.Environment.SANDBOX, - MerchantId = globalSettings.Braintree.MerchantId, - PublicKey = globalSettings.Braintree.PublicKey, - PrivateKey = globalSettings.Braintree.PrivateKey - }; - } - - public async Task AdjustStorageAsync(IStorableSubscriber storableSubscriber, int additionalStorage, string storagePlanId) - { - var sub = await _gateway.Subscription.FindAsync(storableSubscriber.GatewaySubscriptionId); - if(sub == null) - { - throw new GatewayException("Subscription was not found."); - } - - var req = new SubscriptionRequest - { - AddOns = new AddOnsRequest(), - Options = new SubscriptionOptionsRequest - { - ProrateCharges = true, - RevertSubscriptionOnProrationFailure = true - } - }; - - var storageItem = sub.AddOns?.FirstOrDefault(a => a.Id == storagePlanId); - if(additionalStorage > 0 && storageItem == null) - { - req.AddOns.Add = new AddAddOnRequest[] - { - new AddAddOnRequest - { - InheritedFromId = storagePlanId, - Quantity = additionalStorage, - NeverExpires = true - } - }; - } - else if(additionalStorage > 0 && storageItem != null) - { - req.AddOns.Update = new UpdateAddOnRequest[] - { - new UpdateAddOnRequest - { - ExistingId = storageItem.Id, - Quantity = additionalStorage, - NeverExpires = true - } - }; - } - else if(additionalStorage == 0 && storageItem != null) - { - req.AddOns.Remove = new string[] { storageItem.Id }; - } - - var result = await _gateway.Subscription.UpdateAsync(sub.Id, req); - if(!result.IsSuccess()) - { - throw new GatewayException("Failed to adjust storage."); - } - } - - public async Task CancelAndRecoverChargesAsync(ISubscriber subscriber) - { - if(!string.IsNullOrWhiteSpace(subscriber.GatewaySubscriptionId)) - { - await _gateway.Subscription.CancelAsync(subscriber.GatewaySubscriptionId); - } - - if(string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId)) - { - return; - } - - var transactionRequest = new TransactionSearchRequest().CustomerId.Is(subscriber.GatewayCustomerId); - var transactions = _gateway.Transaction.Search(transactionRequest); - - if((transactions?.MaximumCount ?? 0) > 0) - { - var txs = transactions.Cast().Where(c => c.RefundedTransactionId == null); - foreach(var transaction in txs) - { - await _gateway.Transaction.RefundAsync(transaction.Id); - } - } - - await _gateway.Customer.DeleteAsync(subscriber.GatewayCustomerId); - } - - public async Task CancelSubscriptionAsync(ISubscriber subscriber, bool endOfPeriod = false) - { - if(subscriber == null) - { - throw new ArgumentNullException(nameof(subscriber)); - } - - if(string.IsNullOrWhiteSpace(subscriber.GatewaySubscriptionId)) - { - throw new GatewayException("No subscription."); - } - - var sub = await _gateway.Subscription.FindAsync(subscriber.GatewaySubscriptionId); - if(sub == null) - { - throw new GatewayException("Subscription was not found."); - } - - if(sub.Status == SubscriptionStatus.CANCELED || sub.Status == SubscriptionStatus.EXPIRED || - !sub.NeverExpires.GetValueOrDefault()) - { - throw new GatewayException("Subscription is already canceled."); - } - - if(endOfPeriod) - { - var req = new SubscriptionRequest - { - NeverExpires = false, - NumberOfBillingCycles = sub.CurrentBillingCycle - }; - - var result = await _gateway.Subscription.UpdateAsync(subscriber.GatewaySubscriptionId, req); - if(!result.IsSuccess()) - { - throw new GatewayException("Unable to cancel subscription."); - } - } - else - { - var result = await _gateway.Subscription.CancelAsync(subscriber.GatewaySubscriptionId); - if(!result.IsSuccess()) - { - throw new GatewayException("Unable to cancel subscription."); - } - } - } - - public async Task GetUpcomingInvoiceAsync(ISubscriber subscriber) - { - if(!string.IsNullOrWhiteSpace(subscriber.GatewaySubscriptionId)) - { - var sub = await _gateway.Subscription.FindAsync(subscriber.GatewaySubscriptionId); - if(sub != null) - { - var cancelAtEndDate = !sub.NeverExpires.GetValueOrDefault(); - var canceled = sub.Status == SubscriptionStatus.CANCELED; - if(!canceled && !cancelAtEndDate && sub.NextBillingDate.HasValue) - { - return new BillingInfo.BillingInvoice(sub); - } - } - } - return null; - } - - public async Task GetBillingAsync(ISubscriber subscriber) - { - var billingInfo = new BillingInfo(); - if(!string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId)) - { - var customer = await _gateway.Customer.FindAsync(subscriber.GatewayCustomerId); - if(customer != null) - { - if(customer.DefaultPaymentMethod != null) - { - billingInfo.PaymentSource = new BillingInfo.BillingSource(customer.DefaultPaymentMethod); - } - - var transactionRequest = new TransactionSearchRequest().CustomerId.Is(customer.Id); - var transactions = _gateway.Transaction.Search(transactionRequest); - billingInfo.Charges = transactions?.Cast() - .OrderByDescending(t => t.CreatedAt).Select(t => new BillingInfo.BillingCharge(t)); - } - } - - if(!string.IsNullOrWhiteSpace(subscriber.GatewaySubscriptionId)) - { - var sub = await _gateway.Subscription.FindAsync(subscriber.GatewaySubscriptionId); - if(sub != null) - { - var plans = await _gateway.Plan.AllAsync(); - var plan = plans?.FirstOrDefault(p => p.Id == sub.PlanId); - billingInfo.Subscription = new BillingInfo.BillingSubscription(sub, plan); - } - - if(!billingInfo.Subscription.Cancelled && !billingInfo.Subscription.CancelAtEndDate && - sub.NextBillingDate.HasValue) - { - billingInfo.UpcomingInvoice = new BillingInfo.BillingInvoice(sub); - } - } - - return billingInfo; - } - - public async Task PurchasePremiumAsync(User user, PaymentMethodType paymentMethodType, string paymentToken, - short additionalStorageGb) - { - var customerResult = await _gateway.Customer.CreateAsync(new CustomerRequest - { - PaymentMethodNonce = paymentToken, - Email = user.Email, - CustomFields = new Dictionary - { - [user.BraintreeIdField()] = user.Id.ToString() - } - }); - - if(!customerResult.IsSuccess() || customerResult.Target.PaymentMethods.Length == 0) - { - throw new GatewayException("Failed to create customer."); - } - - var subId = user.BraintreeCustomerIdPrefix() + user.Id.ToString("N").ToLower() + - Utilities.CoreHelpers.RandomString(3, upper: false, numeric: false); - - var subRequest = new SubscriptionRequest - { - Id = subId, - PaymentMethodToken = customerResult.Target.PaymentMethods[0].Token, - PlanId = PremiumPlanId - }; - - if(additionalStorageGb > 0) - { - subRequest.AddOns = new AddOnsRequest(); - subRequest.AddOns.Add = new AddAddOnRequest[] - { - new AddAddOnRequest - { - InheritedFromId = StoragePlanId, - Quantity = additionalStorageGb - } - }; - } - - var subResult = await _gateway.Subscription.CreateAsync(subRequest); - - if(!subResult.IsSuccess()) - { - await _gateway.Customer.DeleteAsync(customerResult.Target.Id); - throw new GatewayException("Failed to create subscription."); - } - - user.Gateway = Enums.GatewayType.Braintree; - user.GatewayCustomerId = customerResult.Target.Id; - user.GatewaySubscriptionId = subResult.Target.Id; - user.Premium = true; - user.PremiumExpirationDate = subResult.Target.BillingPeriodEndDate; - } - - public async Task ReinstateSubscriptionAsync(ISubscriber subscriber) - { - if(subscriber == null) - { - throw new ArgumentNullException(nameof(subscriber)); - } - - if(string.IsNullOrWhiteSpace(subscriber.GatewaySubscriptionId)) - { - throw new GatewayException("No subscription."); - } - - var sub = await _gateway.Subscription.FindAsync(subscriber.GatewaySubscriptionId); - if(sub == null) - { - throw new GatewayException("Subscription was not found."); - } - - if(sub.Status != SubscriptionStatus.ACTIVE || sub.NeverExpires.GetValueOrDefault()) - { - throw new GatewayException("Subscription is not marked for cancellation."); - } - - var req = new SubscriptionRequest - { - NeverExpires = true, - NumberOfBillingCycles = null - }; - - var result = await _gateway.Subscription.UpdateAsync(subscriber.GatewaySubscriptionId, req); - if(!result.IsSuccess()) - { - throw new GatewayException("Unable to reinstate subscription."); - } - } - - public async Task UpdatePaymentMethodAsync(ISubscriber subscriber, PaymentMethodType paymentMethodType, - string paymentToken) - { - if(subscriber == null) - { - throw new ArgumentNullException(nameof(subscriber)); - } - - if(paymentMethodType != PaymentMethodType.PayPal) - { - throw new GatewayException("Payment method not allowed"); - } - - if(subscriber.Gateway.HasValue && subscriber.Gateway.Value != GatewayType.Braintree) - { - throw new GatewayException("Switching from one payment type to another is not supported. " + - "Contact us for assistance."); - } - - var updatedSubscriber = false; - Customer customer = null; - - if(!string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId)) - { - customer = await _gateway.Customer.FindAsync(subscriber.GatewayCustomerId); - } - - if(customer == null) - { - var result = await _gateway.Customer.CreateAsync(new CustomerRequest - { - Email = subscriber.BillingEmailAddress(), - PaymentMethodNonce = paymentToken, - CustomFields = new Dictionary - { - [subscriber.BraintreeIdField()] = subscriber.Id.ToString() - } - }); - - if(!result.IsSuccess()) - { - throw new GatewayException("Cannot create customer."); - } - - customer = result.Target; - subscriber.Gateway = Enums.GatewayType.Braintree; - subscriber.GatewayCustomerId = customer.Id; - updatedSubscriber = true; - } - else - { - if(customer.DefaultPaymentMethod != null) - { - var deleteResult = await _gateway.PaymentMethod.DeleteAsync(customer.DefaultPaymentMethod.Token); - if(!deleteResult.IsSuccess()) - { - throw new GatewayException("Cannot delete old payment method."); - } - } - - var result = await _gateway.PaymentMethod.CreateAsync(new PaymentMethodRequest - { - PaymentMethodNonce = paymentToken, - CustomerId = customer.Id - }); - if(!result.IsSuccess()) - { - throw new GatewayException("Cannot add new payment method."); - } - } - - return updatedSubscriber; - } - } -} diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index a7308ffec..1321cb24c 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -32,7 +32,7 @@ namespace Bit.Core.Services private readonly IEventService _eventService; private readonly IInstallationRepository _installationRepository; private readonly IApplicationCacheService _applicationCacheService; - private readonly StripePaymentService _stripePaymentService; + private readonly IPaymentService _paymentService; private readonly GlobalSettings _globalSettings; public OrganizationService( @@ -50,6 +50,7 @@ namespace Bit.Core.Services IEventService eventService, IInstallationRepository installationRepository, IApplicationCacheService applicationCacheService, + IPaymentService paymentService, GlobalSettings globalSettings) { _organizationRepository = organizationRepository; @@ -66,7 +67,7 @@ namespace Bit.Core.Services _eventService = eventService; _installationRepository = installationRepository; _applicationCacheService = applicationCacheService; - _stripePaymentService = new StripePaymentService(globalSettings); + _paymentService = paymentService; _globalSettings = globalSettings; } @@ -92,7 +93,7 @@ namespace Bit.Core.Services paymentMethodType = PaymentMethodType.PayPal; } - var updated = await _stripePaymentService.UpdatePaymentMethodAsync(organization, + var updated = await _paymentService.UpdatePaymentMethodAsync(organization, paymentMethodType, paymentToken); if(updated) { @@ -115,7 +116,7 @@ namespace Bit.Core.Services eop = false; } - await _stripePaymentService.CancelSubscriptionAsync(organization, eop); + await _paymentService.CancelSubscriptionAsync(organization, eop); } public async Task ReinstateSubscriptionAsync(Guid organizationId) @@ -126,7 +127,7 @@ namespace Bit.Core.Services throw new NotFoundException(); } - await _stripePaymentService.ReinstateSubscriptionAsync(organization); + await _paymentService.ReinstateSubscriptionAsync(organization); } public async Task UpgradePlanAsync(Guid organizationId, PlanType plan, int additionalSeats) @@ -286,7 +287,7 @@ namespace Bit.Core.Services throw new BadRequestException("Plan does not allow additional storage."); } - await BillingHelpers.AdjustStorageAsync(_stripePaymentService, organization, storageAdjustmentGb, + await BillingHelpers.AdjustStorageAsync(_paymentService, organization, storageAdjustmentGb, plan.StripeStoragePlanId); await ReplaceAndUpdateCache(organization); } @@ -411,7 +412,7 @@ namespace Bit.Core.Services var invoicedNow = false; if(additionalSeats > 0) { - invoicedNow = await _stripePaymentService.PreviewUpcomingInvoiceAndPayAsync( + invoicedNow = await (_paymentService as StripePaymentService).PreviewUpcomingInvoiceAndPayAsync( organization, plan.StripeSeatPlanId, subItemOptions, 500); } @@ -561,7 +562,7 @@ namespace Bit.Core.Services paymentMethodType = PaymentMethodType.PayPal; } - await _stripePaymentService.PurchaseOrganizationAsync(organization, paymentMethodType, + await _paymentService.PurchaseOrganizationAsync(organization, paymentMethodType, signup.PaymentToken, plan, signup.AdditionalStorageGb, signup.AdditionalSeats, signup.PremiumAccessAddon); } @@ -676,7 +677,7 @@ namespace Bit.Core.Services { if(withPayment) { - await _stripePaymentService.CancelAndRecoverChargesAsync(organization); + await _paymentService.CancelAndRecoverChargesAsync(organization); } if(organization.Id != default(Guid)) @@ -785,7 +786,7 @@ namespace Bit.Core.Services { var eop = !organization.ExpirationDate.HasValue || organization.ExpirationDate.Value >= DateTime.UtcNow; - await _stripePaymentService.CancelSubscriptionAsync(organization, eop); + await _paymentService.CancelSubscriptionAsync(organization, eop); } catch(GatewayException) { } } @@ -1205,9 +1206,8 @@ namespace Bit.Core.Services { throw new BadRequestException("Invalid installation id"); } - - var paymentService = new StripePaymentService(_globalSettings); - var billingInfo = await paymentService.GetBillingAsync(organization); + + var billingInfo = await _paymentService.GetBillingAsync(organization); return new OrganizationLicense(organization, billingInfo, installationId, _licensingService); } diff --git a/src/Core/Services/Implementations/StripePaymentService.cs b/src/Core/Services/Implementations/StripePaymentService.cs index 64f9e1a2e..7ca6dc85b 100644 --- a/src/Core/Services/Implementations/StripePaymentService.cs +++ b/src/Core/Services/Implementations/StripePaymentService.cs @@ -7,6 +7,7 @@ using Bit.Core.Exceptions; using System.Linq; using Bit.Core.Models.Business; using Bit.Core.Enums; +using Bit.Core.Repositories; namespace Bit.Core.Services { @@ -15,9 +16,11 @@ namespace Bit.Core.Services private const string PremiumPlanId = "premium-annually"; private const string StoragePlanId = "storage-gb-annually"; + private readonly ITransactionRepository _transactionRepository; private readonly Braintree.BraintreeGateway _btGateway; public StripePaymentService( + ITransactionRepository transactionRepository, GlobalSettings globalSettings) { _btGateway = new Braintree.BraintreeGateway @@ -28,6 +31,7 @@ namespace Bit.Core.Services PublicKey = globalSettings.Braintree.PublicKey, PrivateKey = globalSettings.Braintree.PrivateKey }; + _transactionRepository = transactionRepository; } public async Task PurchaseOrganizationAsync(Organization org, PaymentMethodType paymentMethodType, @@ -912,6 +916,22 @@ namespace Bit.Core.Services public async Task GetBillingAsync(ISubscriber subscriber) { var billingInfo = new BillingInfo(); + + ICollection transactions = null; + if(subscriber is User) + { + transactions = await _transactionRepository.GetManyByUserIdAsync(subscriber.Id); + } + else if(subscriber is Organization) + { + transactions = await _transactionRepository.GetManyByOrganizationIdAsync(subscriber.Id); + } + if(transactions != null) + { + billingInfo.Transactions = transactions?.OrderByDescending(i => i.CreationDate) + .Select(t => new BillingInfo.BillingTransaction(t)); + } + var customerService = new CustomerService(); var subscriptionService = new SubscriptionService(); var chargeService = new ChargeService(); diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index 9201cf45b..4afb22049 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -42,6 +42,7 @@ namespace Bit.Core.Services private readonly ILicensingService _licenseService; private readonly IEventService _eventService; private readonly IApplicationCacheService _applicationCacheService; + private readonly IPaymentService _paymentService; private readonly IDataProtector _organizationServiceDataProtector; private readonly CurrentContext _currentContext; private readonly GlobalSettings _globalSettings; @@ -67,6 +68,7 @@ namespace Bit.Core.Services IEventService eventService, IApplicationCacheService applicationCacheService, IDataProtectionProvider dataProtectionProvider, + IPaymentService paymentService, CurrentContext currentContext, GlobalSettings globalSettings) : base( @@ -94,6 +96,7 @@ namespace Bit.Core.Services _licenseService = licenseService; _eventService = eventService; _applicationCacheService = applicationCacheService; + _paymentService = paymentService; _organizationServiceDataProtector = dataProtectionProvider.CreateProtector( "OrganizationServiceDataProtector"); _currentContext = currentContext; @@ -717,7 +720,7 @@ namespace Bit.Core.Services paymentMethodType = PaymentMethodType.PayPal; } - await new StripePaymentService(_globalSettings).PurchasePremiumAsync(user, paymentMethodType, + await _paymentService.PurchasePremiumAsync(user, paymentMethodType, paymentToken, additionalStorageGb); } else @@ -792,9 +795,8 @@ namespace Bit.Core.Services { throw new BadRequestException("Not a premium user."); } - - var paymentService = user.GetPaymentService(_globalSettings); - await BillingHelpers.AdjustStorageAsync(paymentService, user, storageAdjustmentGb, StoragePlanId); + + await BillingHelpers.AdjustStorageAsync(_paymentService, user, storageAdjustmentGb, StoragePlanId); await SaveUserAsync(user); } @@ -806,7 +808,6 @@ namespace Bit.Core.Services } PaymentMethodType paymentMethodType; - var paymentService = new StripePaymentService(_globalSettings); if(paymentToken.StartsWith("tok_")) { paymentMethodType = PaymentMethodType.Card; @@ -816,7 +817,7 @@ namespace Bit.Core.Services paymentMethodType = PaymentMethodType.PayPal; } - var updated = await paymentService.UpdatePaymentMethodAsync(user, paymentMethodType, paymentToken); + var updated = await _paymentService.UpdatePaymentMethodAsync(user, paymentMethodType, paymentToken); if(updated) { await SaveUserAsync(user); @@ -825,20 +826,18 @@ namespace Bit.Core.Services public async Task CancelPremiumAsync(User user, bool? endOfPeriod = null) { - var paymentService = user.GetPaymentService(_globalSettings); var eop = endOfPeriod.GetValueOrDefault(true); if(!endOfPeriod.HasValue && user.PremiumExpirationDate.HasValue && user.PremiumExpirationDate.Value < DateTime.UtcNow) { eop = false; } - await paymentService.CancelSubscriptionAsync(user, eop); + await _paymentService.CancelSubscriptionAsync(user, eop); } public async Task ReinstatePremiumAsync(User user) { - var paymentService = user.GetPaymentService(_globalSettings); - await paymentService.ReinstateSubscriptionAsync(user); + await _paymentService.ReinstateSubscriptionAsync(user); } public async Task DisablePremiumAsync(Guid userId, DateTime? expirationDate) @@ -878,8 +877,7 @@ namespace Bit.Core.Services if(billingInfo == null && user.Gateway != null) { - var paymentService = user.GetPaymentService(_globalSettings); - billingInfo = await paymentService.GetBillingAsync(user); + billingInfo = await _paymentService.GetBillingAsync(user); } return billingInfo == null ? new UserLicense(user, _licenseService) : diff --git a/src/Core/Utilities/ServiceCollectionExtensions.cs b/src/Core/Utilities/ServiceCollectionExtensions.cs index 2de1ba284..93f3b4fd9 100644 --- a/src/Core/Utilities/ServiceCollectionExtensions.cs +++ b/src/Core/Utilities/ServiceCollectionExtensions.cs @@ -78,6 +78,7 @@ namespace Bit.Core.Utilities public static void AddDefaultServices(this IServiceCollection services, GlobalSettings globalSettings) { + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton();