public async Task CancelSubscriptionAsync(Guid organizationId, bool endOfPeriod = false) { var organization = await _organizationRepository.GetByIdAsync(organizationId); if (organization == null) { throw new NotFoundException(); } if (string.IsNullOrWhiteSpace(organization.StripeSubscriptionId)) { throw new BadRequestException("Organization has no subscription."); } var subscriptionService = new StripeSubscriptionService(); var sub = await subscriptionService.GetAsync(organization.StripeSubscriptionId); if (sub == null) { throw new BadRequestException("Organization subscription was not found."); } if (sub.CanceledAt.HasValue) { throw new BadRequestException("Organization subscription is already canceled."); } var canceledSub = await subscriptionService.CancelAsync(sub.Id, endOfPeriod); if (!canceledSub.CanceledAt.HasValue) { throw new BadRequestException("Unable to cancel subscription."); } }
public async Task ReinstateSubscriptionAsync(Guid organizationId) { var organization = await _organizationRepository.GetByIdAsync(organizationId); if (organization == null) { throw new NotFoundException(); } if (string.IsNullOrWhiteSpace(organization.StripeSubscriptionId)) { throw new BadRequestException("Organization has no subscription."); } var subscriptionService = new StripeSubscriptionService(); var sub = await subscriptionService.GetAsync(organization.StripeSubscriptionId); if (sub == null) { throw new BadRequestException("Organization subscription was not found."); } if (sub.Status != "active" || !sub.CanceledAt.HasValue) { throw new BadRequestException("Organization subscription is not marked for cancellation."); } // Just touch the subscription. var updatedSub = await subscriptionService.UpdateAsync(sub.Id, new StripeSubscriptionUpdateOptions { }); if (updatedSub.CanceledAt.HasValue) { throw new BadRequestException("Unable to reinstate subscription."); } }
public async Task <BillingInfo.BillingInvoice> GetUpcomingInvoiceAsync(ISubscriber subscriber) { if (!string.IsNullOrWhiteSpace(subscriber.GatewaySubscriptionId)) { var subscriptionService = new StripeSubscriptionService(); var invoiceService = new StripeInvoiceService(); var sub = await subscriptionService.GetAsync(subscriber.GatewaySubscriptionId); if (sub != null) { if (!sub.CanceledAt.HasValue && !string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId)) { try { var upcomingInvoice = await invoiceService.UpcomingAsync(subscriber.GatewayCustomerId); if (upcomingInvoice != null) { return(new BillingInfo.BillingInvoice(upcomingInvoice)); } } catch (StripeException) { } } } } return(null); }
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 subscriptionService = new StripeSubscriptionService(); var sub = await subscriptionService.GetAsync(subscriber.GatewaySubscriptionId); if (sub == null) { throw new GatewayException("Subscription was not found."); } if ((sub.Status != "active" && sub.Status != "trialing") || !sub.CanceledAt.HasValue) { throw new GatewayException("Subscription is not marked for cancellation."); } // Just touch the subscription. var updatedSub = await subscriptionService.UpdateAsync(sub.Id, new StripeSubscriptionUpdateOptions { }); if (updatedSub.CanceledAt.HasValue) { throw new GatewayException("Unable to reinstate subscription."); } }
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 subscriptionService = new StripeSubscriptionService(); var sub = await subscriptionService.GetAsync(subscriber.GatewaySubscriptionId); if (sub == null) { throw new GatewayException("Subscription was not found."); } if (sub.CanceledAt.HasValue || sub.Status == "canceled" || sub.Status == "unpaid") { // Already canceled return; } try { var canceledSub = endOfPeriod ? await subscriptionService.UpdateAsync(sub.Id, new StripeSubscriptionUpdateOptions { CancelAtPeriodEnd = true }) : await subscriptionService.CancelAsync(sub.Id, new StripeSubscriptionCancelOptions()); if (!canceledSub.CanceledAt.HasValue) { throw new GatewayException("Unable to cancel subscription."); } } catch (StripeException e) { if (e.Message != $"No such subscription: {subscriber.GatewaySubscriptionId}") { throw e; } } }
public async Task AdjustStorageAsync(IStorableSubscriber storableSubscriber, int additionalStorage, string storagePlanId) { var subscriptionItemService = new StripeSubscriptionItemService(); var subscriptionService = new StripeSubscriptionService(); var sub = await subscriptionService.GetAsync(storableSubscriber.GatewaySubscriptionId); if (sub == null) { throw new GatewayException("Subscription not found."); } var storageItem = sub.Items?.Data?.FirstOrDefault(i => i.Plan.Id == storagePlanId); if (additionalStorage > 0 && storageItem == null) { await subscriptionItemService.CreateAsync(new StripeSubscriptionItemCreateOptions { PlanId = storagePlanId, Quantity = additionalStorage, Prorate = true, SubscriptionId = sub.Id }); } else if (additionalStorage > 0 && storageItem != null) { await subscriptionItemService.UpdateAsync(storageItem.Id, new StripeSubscriptionItemUpdateOptions { PlanId = storagePlanId, Quantity = additionalStorage, Prorate = true }); } else if (additionalStorage == 0 && storageItem != null) { await subscriptionItemService.DeleteAsync(storageItem.Id); } if (additionalStorage > 0) { await PreviewUpcomingInvoiceAndPayAsync(storableSubscriber, storagePlanId, 400); } }
public async Task <OrganizationBilling> GetBillingAsync(Organization organization) { var orgBilling = new OrganizationBilling(); var customerService = new StripeCustomerService(); var subscriptionService = new StripeSubscriptionService(); var chargeService = new StripeChargeService(); var invoiceService = new StripeInvoiceService(); if (!string.IsNullOrWhiteSpace(organization.StripeCustomerId)) { var customer = await customerService.GetAsync(organization.StripeCustomerId); if (customer != null) { if (!string.IsNullOrWhiteSpace(customer.DefaultSourceId) && customer.Sources?.Data != null) { if (customer.DefaultSourceId.StartsWith("card_")) { orgBilling.PaymentSource = customer.Sources.Data.FirstOrDefault(s => s.Card?.Id == customer.DefaultSourceId); } else if (customer.DefaultSourceId.StartsWith("ba_")) { orgBilling.PaymentSource = customer.Sources.Data.FirstOrDefault(s => s.BankAccount?.Id == customer.DefaultSourceId); } } var charges = await chargeService.ListAsync(new StripeChargeListOptions { CustomerId = customer.Id, Limit = 20 }); orgBilling.Charges = charges?.Data?.OrderByDescending(c => c.Created); } } if (!string.IsNullOrWhiteSpace(organization.StripeSubscriptionId)) { var sub = await subscriptionService.GetAsync(organization.StripeSubscriptionId); if (sub != null) { orgBilling.Subscription = sub; } if (!sub.CanceledAt.HasValue && !string.IsNullOrWhiteSpace(organization.StripeCustomerId)) { try { var upcomingInvoice = await invoiceService.UpcomingAsync(organization.StripeCustomerId); if (upcomingInvoice != null) { orgBilling.UpcomingInvoice = upcomingInvoice; } } catch (StripeException) { } } } return(orgBilling); }
public async Task AdjustSeatsAsync(Guid organizationId, int seatAdjustment) { var organization = await _organizationRepository.GetByIdAsync(organizationId); if (organization == null) { throw new NotFoundException(); } if (string.IsNullOrWhiteSpace(organization.StripeCustomerId)) { throw new BadRequestException("No payment method found."); } if (string.IsNullOrWhiteSpace(organization.StripeSubscriptionId)) { throw new BadRequestException("No subscription found."); } var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == organization.PlanType); if (plan == null) { throw new BadRequestException("Existing plan not found."); } if (!plan.CanBuyAdditionalSeats) { throw new BadRequestException("Plan does not allow additional seats."); } var newSeatTotal = organization.Seats + seatAdjustment; if (plan.BaseSeats > newSeatTotal) { throw new BadRequestException($"Plan has a minimum of {plan.BaseSeats} seats."); } var additionalSeats = newSeatTotal - plan.BaseSeats; if (plan.MaxAdditionalSeats.HasValue && additionalSeats > plan.MaxAdditionalSeats.Value) { throw new BadRequestException($"Organization plan allows a maximum of " + $"{plan.MaxAdditionalSeats.Value} additional seats."); } if (!organization.Seats.HasValue || organization.Seats.Value > newSeatTotal) { var userCount = await _organizationUserRepository.GetCountByOrganizationIdAsync(organization.Id); if (userCount > newSeatTotal) { throw new BadRequestException($"Your organization currently has {userCount} seats filled. Your new plan " + $"only has ({newSeatTotal}) seats. Remove some users."); } } var invoiceService = new StripeInvoiceService(); var subscriptionItemService = new StripeSubscriptionItemService(); var subscriptionService = new StripeSubscriptionService(); var sub = await subscriptionService.GetAsync(organization.StripeSubscriptionId); if (sub == null) { throw new BadRequestException("Subscription not found."); } var seatItem = sub.Items?.Data?.FirstOrDefault(i => i.Plan.Id == plan.StripeSeatPlanId); if (seatItem == null) { await subscriptionItemService.CreateAsync(new StripeSubscriptionItemCreateOptions { PlanId = plan.StripeSeatPlanId, Quantity = additionalSeats, Prorate = true, SubscriptionId = sub.Id }); await PreviewUpcomingAndPayAsync(invoiceService, organization, plan); } else if (additionalSeats > 0) { await subscriptionItemService.UpdateAsync(seatItem.Id, new StripeSubscriptionItemUpdateOptions { PlanId = plan.StripeSeatPlanId, Quantity = additionalSeats, Prorate = true }); await PreviewUpcomingAndPayAsync(invoiceService, organization, plan); } else if (additionalSeats == 0) { await subscriptionItemService.DeleteAsync(seatItem.Id); } organization.Seats = (short?)newSeatTotal; await _organizationRepository.ReplaceAsync(organization); }
public async Task <IActionResult> PostWebhook([FromQuery] string key) { if (key != _billingSettings.StripeWebhookKey) { return(new BadRequestResult()); } StripeEvent parsedEvent; using (var sr = new StreamReader(HttpContext.Request.Body)) { var json = await sr.ReadToEndAsync(); parsedEvent = StripeEventUtility.ConstructEvent(json, Request.Headers["Stripe-Signature"], _billingSettings.StripeWebhookSecret); } if (string.IsNullOrWhiteSpace(parsedEvent?.Id)) { return(new BadRequestResult()); } if (_hostingEnvironment.IsProduction() && !parsedEvent.LiveMode) { return(new BadRequestResult()); } var invUpcoming = parsedEvent.Type.Equals("invoice.upcoming"); var subDeleted = parsedEvent.Type.Equals("customer.subscription.deleted"); var subUpdated = parsedEvent.Type.Equals("customer.subscription.updated"); if (subDeleted || subUpdated) { StripeSubscription subscription = Mapper <StripeSubscription> .MapFromJson( parsedEvent.Data.Object.ToString()); if (subscription == null) { throw new Exception("Subscription is null."); } var ids = GetIdsFromMetaData(subscription.Metadata); var subCanceled = subDeleted && subscription.Status == "canceled"; var subUnpaid = subUpdated && subscription.Status == "unpaid"; if (subCanceled || subUnpaid) { // org if (ids.Item1.HasValue) { await _organizationService.DisableAsync(ids.Item1.Value, subscription.CurrentPeriodEnd); } // user else if (ids.Item2.HasValue) { await _userService.DisablePremiumAsync(ids.Item2.Value, subscription.CurrentPeriodEnd); } } if (subUpdated) { // org if (ids.Item1.HasValue) { await _organizationService.UpdateExpirationDateAsync(ids.Item1.Value, subscription.CurrentPeriodEnd); } // user else if (ids.Item2.HasValue) { await _userService.UpdatePremiumExpirationAsync(ids.Item2.Value, subscription.CurrentPeriodEnd); } } } else if (false /* Disabled for now */ && invUpcoming) { StripeInvoice invoice = Mapper <StripeInvoice> .MapFromJson( parsedEvent.Data.Object.ToString()); if (invoice == null) { throw new Exception("Invoice is null."); } // TODO: maybe invoice subscription expandable is already here any we don't need to call API? var subscriptionService = new StripeSubscriptionService(); var subscription = await subscriptionService.GetAsync(invoice.SubscriptionId); if (subscription == null) { throw new Exception("Invoice subscription is null."); } var ids = GetIdsFromMetaData(subscription.Metadata); // To include in email: // invoice.AmountDue; // invoice.DueDate; // org if (ids.Item1.HasValue) { // TODO: email billing contact } // user else if (ids.Item2.HasValue) { // TODO: email user } } return(new OkResult()); }
public async Task <BillingInfo> GetBillingAsync(ISubscriber subscriber) { var billingInfo = new BillingInfo(); var customerService = new StripeCustomerService(); var subscriptionService = new StripeSubscriptionService(); var chargeService = new StripeChargeService(); var invoiceService = new StripeInvoiceService(); if (!string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId)) { var customer = await customerService.GetAsync(subscriber.GatewayCustomerId); if (customer != null) { if (!string.IsNullOrWhiteSpace(customer.DefaultSourceId) && customer.Sources?.Data != null) { if (customer.DefaultSourceId.StartsWith("card_")) { var source = customer.Sources.Data.FirstOrDefault(s => s.Card?.Id == customer.DefaultSourceId); if (source != null) { billingInfo.PaymentSource = new BillingInfo.BillingSource(source); } } else if (customer.DefaultSourceId.StartsWith("ba_")) { var source = customer.Sources.Data .FirstOrDefault(s => s.BankAccount?.Id == customer.DefaultSourceId); if (source != null) { billingInfo.PaymentSource = new BillingInfo.BillingSource(source); } } } var charges = await chargeService.ListAsync(new StripeChargeListOptions { CustomerId = customer.Id, Limit = 20 }); billingInfo.Charges = charges?.Data?.OrderByDescending(c => c.Created) .Select(c => new BillingInfo.BillingCharge(c)); } } if (!string.IsNullOrWhiteSpace(subscriber.GatewaySubscriptionId)) { var sub = await subscriptionService.GetAsync(subscriber.GatewaySubscriptionId); if (sub != null) { billingInfo.Subscription = new BillingInfo.BillingSubscription(sub); } if (!sub.CanceledAt.HasValue && !string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId)) { try { var upcomingInvoice = await invoiceService.UpcomingAsync(subscriber.GatewayCustomerId); if (upcomingInvoice != null) { billingInfo.UpcomingInvoice = new BillingInfo.BillingInvoice(upcomingInvoice); } } catch (StripeException) { } } } return(billingInfo); }
public async Task <IActionResult> PostWebhook([FromQuery] string key) { if (key != _billingSettings.StripeWebhookKey) { return(new BadRequestResult()); } StripeEvent parsedEvent; using (var sr = new StreamReader(HttpContext.Request.Body)) { var json = await sr.ReadToEndAsync(); parsedEvent = StripeEventUtility.ConstructEvent(json, Request.Headers["Stripe-Signature"], _billingSettings.StripeWebhookSecret); } if (string.IsNullOrWhiteSpace(parsedEvent?.Id)) { return(new BadRequestResult()); } if (_hostingEnvironment.IsProduction() && !parsedEvent.LiveMode) { return(new BadRequestResult()); } var invUpcoming = parsedEvent.Type.Equals("invoice.upcoming"); var subDeleted = parsedEvent.Type.Equals("customer.subscription.deleted"); var subUpdated = parsedEvent.Type.Equals("customer.subscription.updated"); if (subDeleted || subUpdated) { StripeSubscription subscription = Mapper <StripeSubscription> .MapFromJson( parsedEvent.Data.Object.ToString()); if (subscription == null) { throw new Exception("Subscription is null."); } var ids = GetIdsFromMetaData(subscription.Metadata); var subCanceled = subDeleted && subscription.Status == "canceled"; var subUnpaid = subUpdated && subscription.Status == "unpaid"; if (subCanceled || subUnpaid) { // org if (ids.Item1.HasValue) { await _organizationService.DisableAsync(ids.Item1.Value, subscription.CurrentPeriodEnd); } // user else if (ids.Item2.HasValue) { await _userService.DisablePremiumAsync(ids.Item2.Value, subscription.CurrentPeriodEnd); } } if (subUpdated) { // org if (ids.Item1.HasValue) { await _organizationService.UpdateExpirationDateAsync(ids.Item1.Value, subscription.CurrentPeriodEnd); } // user else if (ids.Item2.HasValue) { await _userService.UpdatePremiumExpirationAsync(ids.Item2.Value, subscription.CurrentPeriodEnd); } } } else if (invUpcoming) { StripeInvoice invoice = Mapper <StripeInvoice> .MapFromJson( parsedEvent.Data.Object.ToString()); if (invoice == null) { throw new Exception("Invoice is null."); } var subscriptionService = new StripeSubscriptionService(); var subscription = await subscriptionService.GetAsync(invoice.SubscriptionId); if (subscription == null) { throw new Exception("Invoice subscription is null."); } string email = null; var ids = GetIdsFromMetaData(subscription.Metadata); // org if (ids.Item1.HasValue) { var org = await _organizationRepository.GetByIdAsync(ids.Item1.Value); if (org != null && OrgPlanForInvoiceNotifications(org)) { email = org.BillingEmail; } } // user else if (ids.Item2.HasValue) { var user = await _userService.GetUserByIdAsync(ids.Item2.Value); if (user.Premium) { email = user.Email; } } if (!string.IsNullOrWhiteSpace(email) && invoice.NextPaymentAttempt.HasValue) { var items = invoice.StripeInvoiceLineItems.Select(i => i.Description).ToList(); await _mailService.SendInvoiceUpcomingAsync(email, invoice.AmountDue / 100M, invoice.NextPaymentAttempt.Value, items, ids.Item1.HasValue); } } return(new OkResult()); }