1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-01 16:12:49 -05:00

billing fixes and added gateway to subscriber

This commit is contained in:
Kyle Spearrin
2017-07-28 14:24:07 -04:00
parent 082b53e133
commit cfc80f8d1e
19 changed files with 269 additions and 123 deletions

View File

@ -29,7 +29,7 @@ namespace Bit.Core.Services
public async Task AdjustStorageAsync(IStorableSubscriber storableSubscriber, int additionalStorage, string storagePlanId)
{
var sub = await _gateway.Subscription.FindAsync(storableSubscriber.StripeSubscriptionId);
var sub = await _gateway.Subscription.FindAsync(storableSubscriber.GatewaySubscriptionId);
if(sub == null)
{
throw new GatewayException("Subscription was not found.");
@ -82,17 +82,17 @@ namespace Bit.Core.Services
public async Task CancelAndRecoverChargesAsync(ISubscriber subscriber)
{
if(!string.IsNullOrWhiteSpace(subscriber.StripeSubscriptionId))
if(!string.IsNullOrWhiteSpace(subscriber.GatewaySubscriptionId))
{
await _gateway.Subscription.CancelAsync(subscriber.StripeSubscriptionId);
await _gateway.Subscription.CancelAsync(subscriber.GatewaySubscriptionId);
}
if(string.IsNullOrWhiteSpace(subscriber.StripeCustomerId))
if(string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId))
{
return;
}
var transactionRequest = new TransactionSearchRequest().CustomerId.Is(subscriber.StripeCustomerId);
var transactionRequest = new TransactionSearchRequest().CustomerId.Is(subscriber.GatewayCustomerId);
var transactions = _gateway.Transaction.Search(transactionRequest);
if((transactions?.MaximumCount ?? 0) > 0)
@ -103,7 +103,7 @@ namespace Bit.Core.Services
}
}
await _gateway.Customer.DeleteAsync(subscriber.StripeCustomerId);
await _gateway.Customer.DeleteAsync(subscriber.GatewayCustomerId);
}
public async Task CancelSubscriptionAsync(ISubscriber subscriber, bool endOfPeriod = false)
@ -113,12 +113,12 @@ namespace Bit.Core.Services
throw new ArgumentNullException(nameof(subscriber));
}
if(string.IsNullOrWhiteSpace(subscriber.StripeSubscriptionId))
if(string.IsNullOrWhiteSpace(subscriber.GatewaySubscriptionId))
{
throw new GatewayException("No subscription.");
}
var sub = await _gateway.Subscription.FindAsync(subscriber.StripeSubscriptionId);
var sub = await _gateway.Subscription.FindAsync(subscriber.GatewaySubscriptionId);
if(sub == null)
{
throw new GatewayException("Subscription was not found.");
@ -138,7 +138,7 @@ namespace Bit.Core.Services
NumberOfBillingCycles = sub.CurrentBillingCycle
};
var result = await _gateway.Subscription.UpdateAsync(subscriber.StripeSubscriptionId, req);
var result = await _gateway.Subscription.UpdateAsync(subscriber.GatewaySubscriptionId, req);
if(!result.IsSuccess())
{
throw new GatewayException("Unable to cancel subscription.");
@ -146,7 +146,7 @@ namespace Bit.Core.Services
}
else
{
var result = await _gateway.Subscription.CancelAsync(subscriber.StripeSubscriptionId);
var result = await _gateway.Subscription.CancelAsync(subscriber.GatewaySubscriptionId);
if(!result.IsSuccess())
{
throw new GatewayException("Unable to cancel subscription.");
@ -157,9 +157,9 @@ namespace Bit.Core.Services
public async Task<BillingInfo> GetBillingAsync(ISubscriber subscriber)
{
var billingInfo = new BillingInfo();
if(!string.IsNullOrWhiteSpace(subscriber.StripeCustomerId))
if(!string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId))
{
var customer = await _gateway.Customer.FindAsync(subscriber.StripeCustomerId);
var customer = await _gateway.Customer.FindAsync(subscriber.GatewayCustomerId);
if(customer != null)
{
if(customer.DefaultPaymentMethod != null)
@ -174,9 +174,9 @@ namespace Bit.Core.Services
}
}
if(!string.IsNullOrWhiteSpace(subscriber.StripeSubscriptionId))
if(!string.IsNullOrWhiteSpace(subscriber.GatewaySubscriptionId))
{
var sub = await _gateway.Subscription.FindAsync(subscriber.StripeSubscriptionId);
var sub = await _gateway.Subscription.FindAsync(subscriber.GatewaySubscriptionId);
if(sub != null)
{
var plans = await _gateway.Plan.AllAsync();
@ -184,7 +184,8 @@ namespace Bit.Core.Services
billingInfo.Subscription = new BillingInfo.BillingSubscription(sub, plan);
}
if(sub.NextBillingDate.HasValue)
if(!billingInfo.Subscription.Cancelled && !billingInfo.Subscription.CancelAtEndDate &&
sub.NextBillingDate.HasValue)
{
billingInfo.UpcomingInvoice = new BillingInfo.BillingInvoice(sub);
}
@ -237,8 +238,9 @@ namespace Bit.Core.Services
throw new GatewayException("Failed to create subscription.");
}
user.StripeCustomerId = customerResult.Target.Id;
user.StripeSubscriptionId = subResult.Target.Id;
user.Gateway = Enums.GatewayType.Braintree;
user.GatewayCustomerId = customerResult.Target.Id;
user.GatewaySubscriptionId = subResult.Target.Id;
}
public async Task ReinstateSubscriptionAsync(ISubscriber subscriber)
@ -248,12 +250,12 @@ namespace Bit.Core.Services
throw new ArgumentNullException(nameof(subscriber));
}
if(string.IsNullOrWhiteSpace(subscriber.StripeSubscriptionId))
if(string.IsNullOrWhiteSpace(subscriber.GatewaySubscriptionId))
{
throw new GatewayException("No subscription.");
}
var sub = await _gateway.Subscription.FindAsync(subscriber.StripeSubscriptionId);
var sub = await _gateway.Subscription.FindAsync(subscriber.GatewaySubscriptionId);
if(sub == null)
{
throw new GatewayException("Subscription was not found.");
@ -270,7 +272,7 @@ namespace Bit.Core.Services
NumberOfBillingCycles = null
};
var result = await _gateway.Subscription.UpdateAsync(subscriber.StripeSubscriptionId, req);
var result = await _gateway.Subscription.UpdateAsync(subscriber.GatewaySubscriptionId, req);
if(!result.IsSuccess())
{
throw new GatewayException("Unable to reinstate subscription.");
@ -284,12 +286,18 @@ namespace Bit.Core.Services
throw new ArgumentNullException(nameof(subscriber));
}
if(subscriber.Gateway.HasValue && subscriber.Gateway.Value != Enums.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.StripeCustomerId))
if(!string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId))
{
customer = await _gateway.Customer.FindAsync(subscriber.StripeCustomerId);
customer = await _gateway.Customer.FindAsync(subscriber.GatewayCustomerId);
}
if(customer == null)
@ -306,7 +314,8 @@ namespace Bit.Core.Services
}
customer = result.Target;
subscriber.StripeCustomerId = customer.Id;
subscriber.Gateway = Enums.GatewayType.Braintree;
subscriber.GatewayCustomerId = customer.Id;
updatedSubscriber = true;
}
else

View File

@ -95,7 +95,7 @@ namespace Bit.Core.Services
throw new NotFoundException();
}
if(string.IsNullOrWhiteSpace(organization.StripeCustomerId))
if(string.IsNullOrWhiteSpace(organization.GatewayCustomerId))
{
throw new BadRequestException("No payment method found.");
}
@ -160,7 +160,7 @@ namespace Bit.Core.Services
// TODO: Groups?
var subscriptionService = new StripeSubscriptionService();
if(string.IsNullOrWhiteSpace(organization.StripeSubscriptionId))
if(string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId))
{
// They must have been on a free plan. Create new sub.
var subCreateOptions = new StripeSubscriptionCreateOptions
@ -190,7 +190,7 @@ namespace Bit.Core.Services
});
}
await subscriptionService.CreateAsync(organization.StripeCustomerId, subCreateOptions);
await subscriptionService.CreateAsync(organization.GatewayCustomerId, subCreateOptions);
}
else
{
@ -218,7 +218,7 @@ namespace Bit.Core.Services
});
}
await subscriptionService.UpdateAsync(organization.StripeSubscriptionId, subUpdateOptions);
await subscriptionService.UpdateAsync(organization.GatewaySubscriptionId, subUpdateOptions);
}
// TODO: Update organization
@ -256,12 +256,12 @@ namespace Bit.Core.Services
throw new NotFoundException();
}
if(string.IsNullOrWhiteSpace(organization.StripeCustomerId))
if(string.IsNullOrWhiteSpace(organization.GatewayCustomerId))
{
throw new BadRequestException("No payment method found.");
}
if(string.IsNullOrWhiteSpace(organization.StripeSubscriptionId))
if(string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId))
{
throw new BadRequestException("No subscription found.");
}
@ -307,7 +307,7 @@ namespace Bit.Core.Services
var subscriptionItemService = new StripeSubscriptionItemService();
var subscriptionService = new StripeSubscriptionService();
var sub = await subscriptionService.GetAsync(organization.StripeSubscriptionId);
var sub = await subscriptionService.GetAsync(organization.GatewaySubscriptionId);
if(sub == null)
{
throw new BadRequestException("Subscription not found.");
@ -469,8 +469,9 @@ namespace Bit.Core.Services
UseDirectory = plan.UseDirectory,
UseTotp = plan.UseTotp,
Plan = plan.Name,
StripeCustomerId = customer?.Id,
StripeSubscriptionId = subscription?.Id,
Gateway = GatewayType.Stripe,
GatewayCustomerId = customer?.Id,
GatewaySubscriptionId = subscription?.Id,
Enabled = true,
CreationDate = DateTime.UtcNow,
RevisionDate = DateTime.UtcNow
@ -515,10 +516,10 @@ namespace Bit.Core.Services
public async Task DeleteAsync(Organization organization)
{
if(!string.IsNullOrWhiteSpace(organization.StripeSubscriptionId))
if(!string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId))
{
var subscriptionService = new StripeSubscriptionService();
var canceledSub = await subscriptionService.CancelAsync(organization.StripeSubscriptionId, false);
var canceledSub = await subscriptionService.CancelAsync(organization.GatewaySubscriptionId, false);
if(!canceledSub.CanceledAt.HasValue)
{
throw new BadRequestException("Unable to cancel subscription.");
@ -559,10 +560,10 @@ namespace Bit.Core.Services
await _organizationRepository.ReplaceAsync(organization);
if(updateBilling && !string.IsNullOrWhiteSpace(organization.StripeCustomerId))
if(updateBilling && !string.IsNullOrWhiteSpace(organization.GatewayCustomerId))
{
var customerService = new StripeCustomerService();
await customerService.UpdateAsync(organization.StripeCustomerId, new StripeCustomerUpdateOptions
await customerService.UpdateAsync(organization.GatewayCustomerId, new StripeCustomerUpdateOptions
{
Email = organization.BillingEmail,
Description = organization.BusinessName

View File

@ -60,8 +60,9 @@ namespace Bit.Core.Services
throw;
}
user.StripeCustomerId = customer.Id;
user.StripeSubscriptionId = subscription.Id;
user.Gateway = Enums.GatewayType.Stripe;
user.GatewayCustomerId = customer.Id;
user.GatewaySubscriptionId = subscription.Id;
}
public async Task AdjustStorageAsync(IStorableSubscriber storableSubscriber, int additionalStorage,
@ -69,7 +70,7 @@ namespace Bit.Core.Services
{
var subscriptionItemService = new StripeSubscriptionItemService();
var subscriptionService = new StripeSubscriptionService();
var sub = await subscriptionService.GetAsync(storableSubscriber.StripeSubscriptionId);
var sub = await subscriptionService.GetAsync(storableSubscriber.GatewaySubscriptionId);
if(sub == null)
{
throw new GatewayException("Subscription not found.");
@ -108,13 +109,13 @@ namespace Bit.Core.Services
public async Task CancelAndRecoverChargesAsync(ISubscriber subscriber)
{
if(!string.IsNullOrWhiteSpace(subscriber.StripeSubscriptionId))
if(!string.IsNullOrWhiteSpace(subscriber.GatewaySubscriptionId))
{
var subscriptionService = new StripeSubscriptionService();
await subscriptionService.CancelAsync(subscriber.StripeSubscriptionId, false);
await subscriptionService.CancelAsync(subscriber.GatewaySubscriptionId, false);
}
if(string.IsNullOrWhiteSpace(subscriber.StripeCustomerId))
if(string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId))
{
return;
}
@ -122,7 +123,7 @@ namespace Bit.Core.Services
var chargeService = new StripeChargeService();
var charges = await chargeService.ListAsync(new StripeChargeListOptions
{
CustomerId = subscriber.StripeCustomerId
CustomerId = subscriber.GatewayCustomerId
});
if(charges?.Data != null)
@ -135,17 +136,17 @@ namespace Bit.Core.Services
}
var customerService = new StripeCustomerService();
await customerService.DeleteAsync(subscriber.StripeCustomerId);
await customerService.DeleteAsync(subscriber.GatewayCustomerId);
}
public async Task PreviewUpcomingInvoiceAndPayAsync(ISubscriber subscriber, string planId,
int prorateThreshold = 500)
{
var invoiceService = new StripeInvoiceService();
var upcomingPreview = await invoiceService.UpcomingAsync(subscriber.StripeCustomerId,
var upcomingPreview = await invoiceService.UpcomingAsync(subscriber.GatewayCustomerId,
new StripeUpcomingInvoiceOptions
{
SubscriptionId = subscriber.StripeSubscriptionId
SubscriptionId = subscriber.GatewaySubscriptionId
});
var prorationAmount = upcomingPreview.StripeInvoiceLineItems?.Data?
@ -156,10 +157,10 @@ namespace Bit.Core.Services
{
// Owes more than prorateThreshold on next invoice.
// Invoice them and pay now instead of waiting until next month.
var invoice = await invoiceService.CreateAsync(subscriber.StripeCustomerId,
var invoice = await invoiceService.CreateAsync(subscriber.GatewayCustomerId,
new StripeInvoiceCreateOptions
{
SubscriptionId = subscriber.StripeSubscriptionId
SubscriptionId = subscriber.GatewaySubscriptionId
});
if(invoice.AmountDue > 0)
@ -178,13 +179,13 @@ namespace Bit.Core.Services
throw new ArgumentNullException(nameof(subscriber));
}
if(string.IsNullOrWhiteSpace(subscriber.StripeSubscriptionId))
if(string.IsNullOrWhiteSpace(subscriber.GatewaySubscriptionId))
{
throw new GatewayException("No subscription.");
}
var subscriptionService = new StripeSubscriptionService();
var sub = await subscriptionService.GetAsync(subscriber.StripeSubscriptionId);
var sub = await subscriptionService.GetAsync(subscriber.GatewaySubscriptionId);
if(sub == null)
{
throw new GatewayException("Subscription was not found.");
@ -209,13 +210,13 @@ namespace Bit.Core.Services
throw new ArgumentNullException(nameof(subscriber));
}
if(string.IsNullOrWhiteSpace(subscriber.StripeSubscriptionId))
if(string.IsNullOrWhiteSpace(subscriber.GatewaySubscriptionId))
{
throw new GatewayException("No subscription.");
}
var subscriptionService = new StripeSubscriptionService();
var sub = await subscriptionService.GetAsync(subscriber.StripeSubscriptionId);
var sub = await subscriptionService.GetAsync(subscriber.GatewaySubscriptionId);
if(sub == null)
{
throw new GatewayException("Subscription was not found.");
@ -241,15 +242,21 @@ namespace Bit.Core.Services
throw new ArgumentNullException(nameof(subscriber));
}
if(subscriber.Gateway.HasValue && subscriber.Gateway.Value != Enums.GatewayType.Stripe)
{
throw new GatewayException("Switching from one payment type to another is not supported. " +
"Contact us for assistance.");
}
var updatedSubscriber = false;
var cardService = new StripeCardService();
var customerService = new StripeCustomerService();
StripeCustomer customer = null;
if(!string.IsNullOrWhiteSpace(subscriber.StripeCustomerId))
if(!string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId))
{
customer = await customerService.GetAsync(subscriber.StripeCustomerId);
customer = await customerService.GetAsync(subscriber.GatewayCustomerId);
}
if(customer == null)
@ -261,7 +268,8 @@ namespace Bit.Core.Services
SourceToken = paymentToken
});
subscriber.StripeCustomerId = customer.Id;
subscriber.Gateway = Enums.GatewayType.Stripe;
subscriber.GatewayCustomerId = customer.Id;
updatedSubscriber = true;
}
else
@ -288,9 +296,9 @@ namespace Bit.Core.Services
var chargeService = new StripeChargeService();
var invoiceService = new StripeInvoiceService();
if(!string.IsNullOrWhiteSpace(subscriber.StripeCustomerId))
if(!string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId))
{
var customer = await customerService.GetAsync(subscriber.StripeCustomerId);
var customer = await customerService.GetAsync(subscriber.GatewayCustomerId);
if(customer != null)
{
if(!string.IsNullOrWhiteSpace(customer.DefaultSourceId) && customer.Sources?.Data != null)
@ -324,19 +332,19 @@ namespace Bit.Core.Services
}
}
if(!string.IsNullOrWhiteSpace(subscriber.StripeSubscriptionId))
if(!string.IsNullOrWhiteSpace(subscriber.GatewaySubscriptionId))
{
var sub = await subscriptionService.GetAsync(subscriber.StripeSubscriptionId);
var sub = await subscriptionService.GetAsync(subscriber.GatewaySubscriptionId);
if(sub != null)
{
billingInfo.Subscription = new BillingInfo.BillingSubscription(sub);
}
if(!sub.CanceledAt.HasValue && !string.IsNullOrWhiteSpace(subscriber.StripeCustomerId))
if(!sub.CanceledAt.HasValue && !string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId))
{
try
{
var upcomingInvoice = await invoiceService.UpcomingAsync(subscriber.StripeCustomerId);
var upcomingInvoice = await invoiceService.UpcomingAsync(subscriber.GatewayCustomerId);
if(upcomingInvoice != null)
{
billingInfo.UpcomingInvoice = new BillingInfo.BillingInvoice(upcomingInvoice);

View File

@ -164,10 +164,10 @@ namespace Bit.Core.Services
});
}
if(!string.IsNullOrWhiteSpace(user.StripeSubscriptionId))
if(!string.IsNullOrWhiteSpace(user.GatewaySubscriptionId))
{
var subscriptionService = new StripeSubscriptionService();
var canceledSub = await subscriptionService.CancelAsync(user.StripeSubscriptionId, false);
var canceledSub = await subscriptionService.CancelAsync(user.GatewaySubscriptionId, false);
if(!canceledSub.CanceledAt.HasValue)
{
throw new BadRequestException("Unable to cancel subscription.");
@ -560,7 +560,16 @@ namespace Bit.Core.Services
public async Task ReplacePaymentMethodAsync(User user, string paymentToken)
{
var paymentService = user.GetPaymentService(_globalSettings);
IPaymentService paymentService = null;
if(paymentToken.StartsWith("tok_"))
{
paymentService = new StripePaymentService();
}
else
{
paymentService = new BraintreePaymentService(_globalSettings);
}
var updated = await paymentService.UpdatePaymentMethodAsync(user, paymentToken);
if(updated)
{