Ejemplo n.º 1
0
        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.");
            }
        }
Ejemplo n.º 2
0
        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.");
            }
        }
Ejemplo n.º 3
0
        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);
        }
Ejemplo n.º 4
0
        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.");
            }
        }
Ejemplo n.º 5
0
        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;
                }
            }
        }
Ejemplo n.º 6
0
        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);
            }
        }
Ejemplo n.º 7
0
        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);
        }
Ejemplo n.º 8
0
        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);
        }
Ejemplo n.º 9
0
        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());
        }
Ejemplo n.º 10
0
        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);
        }
Ejemplo n.º 11
0
        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());
        }