From 9f876d9bff1432076b2f8fce6dfc4b71b148abd2 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Thu, 31 Jan 2019 14:25:46 -0500 Subject: [PATCH] purchase org with paypal support --- .../Implementations/OrganizationService.cs | 135 ++++++------------ .../Implementations/StripePaymentService.cs | 120 ++++++++++++++++ .../Services/Implementations/UserService.cs | 5 + 3 files changed, 168 insertions(+), 92 deletions(-) diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index 067810c22f..a7308ffec0 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -476,6 +476,11 @@ namespace Bit.Core.Services throw new BadRequestException("Plan does not allow additional storage."); } + if(signup.AdditionalStorageGb < 0) + { + throw new BadRequestException("You can't subtract storage!"); + } + if(!plan.CanBuyPremiumAccessAddon && signup.PremiumAccessAddon) { throw new BadRequestException("This plan does not allow you to buy the premium access addon."); @@ -486,6 +491,11 @@ namespace Bit.Core.Services throw new BadRequestException("You do not have any seats!"); } + if(signup.AdditionalSeats < 0) + { + throw new BadRequestException("You can't subtract seats!"); + } + if(!plan.CanBuyAdditionalSeats && signup.AdditionalSeats > 0) { throw new BadRequestException("Plan does not allow additional users."); @@ -498,96 +508,10 @@ namespace Bit.Core.Services $"{plan.MaxAdditionalSeats.GetValueOrDefault(0)} additional users."); } - var customerService = new CustomerService(); - var subscriptionService = new SubscriptionService(); - Customer customer = null; - Subscription subscription = null; - - // Pre-generate the org id so that we can save it with the Stripe subscription.. - var newOrgId = CoreHelpers.GenerateComb(); - - if(plan.Type == PlanType.Free) - { - var adminCount = - await _organizationUserRepository.GetCountByFreeOrganizationAdminUserAsync(signup.Owner.Id); - if(adminCount > 0) - { - throw new BadRequestException("You can only be an admin of one free organization."); - } - } - else - { - customer = await customerService.CreateAsync(new CustomerCreateOptions - { - Description = signup.BusinessName, - Email = signup.BillingEmail, - SourceToken = signup.PaymentToken - }); - - var subCreateOptions = new SubscriptionCreateOptions - { - CustomerId = customer.Id, - TrialPeriodDays = plan.TrialPeriodDays, - Items = new List(), - Metadata = new Dictionary { - { "organizationId", newOrgId.ToString() } - } - }; - - if(plan.StripePlanId != null) - { - subCreateOptions.Items.Add(new SubscriptionItemOption - { - PlanId = plan.StripePlanId, - Quantity = 1 - }); - } - - if(signup.AdditionalSeats > 0 && plan.StripeSeatPlanId != null) - { - subCreateOptions.Items.Add(new SubscriptionItemOption - { - PlanId = plan.StripeSeatPlanId, - Quantity = signup.AdditionalSeats - }); - } - - if(signup.AdditionalStorageGb > 0) - { - subCreateOptions.Items.Add(new SubscriptionItemOption - { - PlanId = plan.StripeStoragePlanId, - Quantity = signup.AdditionalStorageGb - }); - } - - if(signup.PremiumAccessAddon && plan.StripePremiumAccessPlanId != null) - { - subCreateOptions.Items.Add(new SubscriptionItemOption - { - PlanId = plan.StripePremiumAccessPlanId, - Quantity = 1 - }); - } - - try - { - subscription = await subscriptionService.CreateAsync(subCreateOptions); - } - catch(StripeException) - { - if(customer != null) - { - await customerService.DeleteAsync(customer.Id); - } - - throw; - } - } - var organization = new Organization { - Id = newOrgId, + // Pre-generate the org id so that we can save it with the Stripe subscription.. + Id = CoreHelpers.GenerateComb(), Name = signup.Name, BillingEmail = signup.BillingEmail, BusinessName = signup.BusinessName, @@ -605,16 +529,43 @@ namespace Bit.Core.Services SelfHost = plan.SelfHost, UsersGetPremium = plan.UsersGetPremium || signup.PremiumAccessAddon, Plan = plan.Name, - Gateway = plan.Type == PlanType.Free ? null : (GatewayType?)GatewayType.Stripe, - GatewayCustomerId = customer?.Id, - GatewaySubscriptionId = subscription?.Id, + Gateway = null, Enabled = true, - ExpirationDate = subscription?.CurrentPeriodEnd, LicenseKey = CoreHelpers.SecureRandomString(20), CreationDate = DateTime.UtcNow, RevisionDate = DateTime.UtcNow }; + if(plan.Type == PlanType.Free) + { + var adminCount = + await _organizationUserRepository.GetCountByFreeOrganizationAdminUserAsync(signup.Owner.Id); + if(adminCount > 0) + { + throw new BadRequestException("You can only be an admin of one free organization."); + } + } + else + { + PaymentMethodType paymentMethodType; + if(signup.PaymentToken.StartsWith("btok_")) + { + paymentMethodType = PaymentMethodType.BankAccount; + } + else if(signup.PaymentToken.StartsWith("tok_")) + { + paymentMethodType = PaymentMethodType.Card; + } + else + { + paymentMethodType = PaymentMethodType.PayPal; + } + + await _stripePaymentService.PurchaseOrganizationAsync(organization, paymentMethodType, + signup.PaymentToken, plan, signup.AdditionalStorageGb, signup.AdditionalSeats, + signup.PremiumAccessAddon); + } + return await SignUpAsync(organization, signup.Owner.Id, signup.OwnerKey, signup.CollectionName, true); } diff --git a/src/Core/Services/Implementations/StripePaymentService.cs b/src/Core/Services/Implementations/StripePaymentService.cs index 3a23e857df..18d4fb280c 100644 --- a/src/Core/Services/Implementations/StripePaymentService.cs +++ b/src/Core/Services/Implementations/StripePaymentService.cs @@ -30,6 +30,126 @@ namespace Bit.Core.Services }; } + public async Task PurchaseOrganizationAsync(Organization org, PaymentMethodType paymentMethodType, + string paymentToken, Models.StaticStore.Plan plan, short additionalStorageGb, + short additionalSeats, bool premiumAccessAddon) + { + var invoiceService = new InvoiceService(); + var customerService = new CustomerService(); + + Braintree.Customer braintreeCustomer = null; + string stipeCustomerSourceToken = null; + var stripeCustomerMetadata = new Dictionary(); + var stripePaymentMethod = paymentMethodType == PaymentMethodType.Card || + paymentMethodType == PaymentMethodType.BankAccount; + + if(stripePaymentMethod) + { + stipeCustomerSourceToken = paymentToken; + } + else if(paymentMethodType == PaymentMethodType.PayPal) + { + var randomSuffix = Utilities.CoreHelpers.RandomString(3, upper: false, numeric: false); + var customerResult = await _btGateway.Customer.CreateAsync(new Braintree.CustomerRequest + { + PaymentMethodNonce = paymentToken, + Email = org.BillingEmail, + Id = "o" + org.Id.ToString("N").ToLower() + randomSuffix + }); + + if(!customerResult.IsSuccess() || customerResult.Target.PaymentMethods.Length == 0) + { + throw new GatewayException("Failed to create PayPal customer record."); + } + + braintreeCustomer = customerResult.Target; + stripeCustomerMetadata.Add("btCustomerId", braintreeCustomer.Id); + } + else + { + throw new GatewayException("Payment method is not supported at this time."); + } + + var subCreateOptions = new SubscriptionCreateOptions + { + TrialPeriodDays = plan.TrialPeriodDays, + Items = new List(), + Metadata = new Dictionary + { + ["organizationId"] = org.Id.ToString() + } + }; + + if(plan.StripePlanId != null) + { + subCreateOptions.Items.Add(new SubscriptionItemOption + { + PlanId = plan.StripePlanId, + Quantity = 1 + }); + } + + if(additionalSeats > 0 && plan.StripeSeatPlanId != null) + { + subCreateOptions.Items.Add(new SubscriptionItemOption + { + PlanId = plan.StripeSeatPlanId, + Quantity = additionalSeats + }); + } + + if(additionalStorageGb > 0) + { + subCreateOptions.Items.Add(new SubscriptionItemOption + { + PlanId = plan.StripeStoragePlanId, + Quantity = additionalStorageGb + }); + } + + if(premiumAccessAddon && plan.StripePremiumAccessPlanId != null) + { + subCreateOptions.Items.Add(new SubscriptionItemOption + { + PlanId = plan.StripePremiumAccessPlanId, + Quantity = 1 + }); + } + + Customer customer = null; + Subscription subscription = null; + try + { + customer = await customerService.CreateAsync(new CustomerCreateOptions + { + Description = org.BusinessName, + Email = org.BillingEmail, + SourceToken = stipeCustomerSourceToken, + Metadata = stripeCustomerMetadata + }); + subCreateOptions.CustomerId = customer.Id; + var subscriptionService = new SubscriptionService(); + subscription = await subscriptionService.CreateAsync(subCreateOptions); + } + catch(Exception e) + { + if(customer != null) + { + await customerService.DeleteAsync(customer.Id); + } + if(braintreeCustomer != null) + { + await _btGateway.Customer.DeleteAsync(braintreeCustomer.Id); + } + throw e; + } + + org.Gateway = GatewayType.Stripe; + org.GatewayCustomerId = customer.Id; + org.GatewaySubscriptionId = subscription.Id; + org.ExpirationDate = subscription.CurrentPeriodEnd; + } + public async Task PurchasePremiumAsync(User user, PaymentMethodType paymentMethodType, string paymentToken, short additionalStorageGb) { diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index af15171f7f..9201cf45bf 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -682,6 +682,11 @@ namespace Bit.Core.Services throw new BadRequestException("Already a premium user."); } + if(additionalStorageGb < 0) + { + throw new BadRequestException("You can't subtract storage!"); + } + IPaymentService paymentService = null; if(_globalSettings.SelfHosted) {