public async Task SendPaymentSucceededOrganizationMessage(ChargeBeeWebhookPayload payload)
        {
            var accountId = ChargeBeeUtilities.AccountIdFromCustomerId(payload.Content.Customer.Id);

            var planLineItem = payload.Content.Invoice.LineItems.Single(x => x.EntityType == "plan");

            var newInvoiceStartDate = DateTimeOffset.FromUnixTimeSeconds(planLineItem.DateFrom);
            var previousMonthStart  = DateTimeOffsetFloor(newInvoiceStartDate.AddMonths(-1));
            var previousMonthEnd    = DateTimeOffsetFloor(newInvoiceStartDate.AddDays(-1));

            int activeUsersCount;

            string[] activeUsersSample;
            using (var context = new ShipHubContext()) {
                activeUsersCount = await context.Usage
                                   .Where(x => (
                                              x.Date >= previousMonthStart &&
                                              x.Date <= previousMonthEnd &&
                                              context.OrganizationAccounts
                                              .Where(y => y.OrganizationId == accountId)
                                              .Select(y => y.UserId)
                                              .Contains(x.AccountId)))
                                   .Select(x => x.AccountId)
                                   .Distinct()
                                   .CountAsync();

                activeUsersSample = await context.Usage
                                    .Where(x => (
                                               x.Date >= previousMonthStart &&
                                               x.Date <= previousMonthEnd &&
                                               context.OrganizationAccounts
                                               .Where(y => y.OrganizationId == accountId)
                                               .Select(y => y.UserId)
                                               .Contains(x.AccountId)))
                                    .Select(x => x.Account.Login)
                                    .OrderBy(x => x)
                                    .Distinct()
                                    .Take(20)
                                    .ToArrayAsync();
            }

            var pdf = await PdfInfo(payload);

            await _mailer.PaymentSucceededOrganization(
                new Mail.Models.PaymentSucceededOrganizationMailMessage()
            {
                GitHubUserName                 = payload.Content.Customer.GitHubUserName,
                ToAddress                      = payload.Content.Customer.Email,
                CustomerName                   = payload.Content.Customer.FirstName + " " + payload.Content.Customer.LastName,
                InvoicePdfUrl                  = pdf.SignedUrl,
                AttachmentName                 = pdf.FileName,
                AttachmentUrl                  = pdf.DirectUrl,
                ServiceThroughDate             = DateTimeOffset.FromUnixTimeSeconds(planLineItem.DateTo),
                PreviousMonthActiveUsersCount  = activeUsersCount,
                PreviousMonthActiveUsersSample = activeUsersSample,
                PreviousMonthStart             = previousMonthStart,
                AmountPaid                     = payload.Content.Invoice.AmountPaid / 100.0,
                PaymentMethodSummary           = PaymentMethodSummary(payload.Content.Transaction),
            });
        }
        private async Task <string> GitHubUserNameFromWebhookPayload(ChargeBeeWebhookPayload payload)
        {
            // Most events include the customer portion which gives us the GitHub username.
            if (payload.Content.Customer?.GitHubUserName != null)
            {
                return(payload.Content.Customer.GitHubUserName);
            }
            else
            {
                // Invoice events (and maybe others, TBD) don't include the Customer portion so
                // we have to find the customer id in another section.
                var candidates = new[] {
                    payload.Content.Invoice?.CustomerId,
                    payload.Content.CreditNote?.CustomerId,
                };
                var customerId = candidates.SkipWhile(string.IsNullOrEmpty).FirstOrDefault();
                if (customerId != null)
                {
                    var accountId = ChargeBeeUtilities.AccountIdFromCustomerId(customerId);
                    using (var context = new ShipHubContext()) {
                        var login = await context.Accounts
                                    .AsNoTracking()
                                    .Where(x => x.Id == accountId)
                                    .Select(x => x.Login)
                                    .FirstOrDefaultAsync();

                        return(login);
                    }
                }
                else
                {
                    return(null);
                }
            }
        }
        private static string GetPaymentMethodUpdateUrl(IShipHubConfiguration configuration, string customerId)
        {
            var accountId = ChargeBeeUtilities.AccountIdFromCustomerId(customerId);

            var apiHostName = configuration.ApiHostName;

            var signature = BillingController.CreateSignature(accountId, accountId);
            var updateUrl = $"https://{apiHostName}/billing/update/{accountId}/{signature}";

            return(updateUrl);
        }
        public async Task SendPurchaseOrganizationMessage(ChargeBeeWebhookPayload payload)
        {
            var accountId = ChargeBeeUtilities.AccountIdFromCustomerId(payload.Content.Customer.Id);
            var pdf       = await PdfInfo(payload);

            await _mailer.PurchaseOrganization(
                new Mail.Models.PurchaseOrganizationMailMessage()
            {
                GitHubUserName = payload.Content.Customer.GitHubUserName,
                ToAddress      = payload.Content.Customer.Email,
                CustomerName   = payload.Content.Customer.FirstName + " " + payload.Content.Customer.LastName,
                InvoicePdfUrl  = pdf.SignedUrl,
                AttachmentName = pdf.FileName,
                AttachmentUrl  = pdf.DirectUrl,
            });
        }
        public async Task HandlePendingInvoiceCreated(ChargeBeeWebhookPayload payload)
        {
            var accountId    = ChargeBeeUtilities.AccountIdFromCustomerId(payload.Content.Invoice.CustomerId);
            var planLineItem = payload.Content.Invoice.LineItems.Single(x => x.EntityType == "plan");

            if (planLineItem.EntityId == "organization")
            {
                // Calculate the number of active users during the previous month, and then
                // attach extra charges to this month's invoice.  So, for organizations, the
                // base charge on every invoice is for the coming month, but the metered
                // component is always for the trailing month.
                var newInvoiceStartDate = DateTimeOffset.FromUnixTimeSeconds(planLineItem.DateFrom);
                var previousMonthStart  = DateTimeOffsetFloor(newInvoiceStartDate.AddMonths(-1));
                var previousMonthEnd    = DateTimeOffsetFloor(newInvoiceStartDate.AddDays(-1));

                int activeUsers;
                using (var context = new ShipHubContext()) {
                    activeUsers = await context.Usage
                                  .AsNoTracking()
                                  .Where(x => (
                                             x.Date >= previousMonthStart &&
                                             x.Date <= previousMonthEnd &&
                                             context.OrganizationAccounts
                                             .Where(y => y.OrganizationId == accountId)
                                             .Select(y => y.UserId)
                                             .Contains(x.AccountId)))
                                  .Select(x => x.AccountId)
                                  .Distinct()
                                  .CountAsync();
                }

                if (activeUsers > 1)
                {
                    await _chargeBee.Invoice.AddAddonCharge(payload.Content.Invoice.Id)
                    .AddonId("additional-seats")
                    .AddonQuantity(Math.Max(activeUsers - 1, 0))
                    .Request();
                }
            }

            await _chargeBee.Invoice.Close(payload.Content.Invoice.Id).Request();
        }
        public async Task HandleSubscriptionStateChange(ChargeBeeWebhookPayload payload)
        {
            var           accountId = ChargeBeeUtilities.AccountIdFromCustomerId(payload.Content.Customer.Id);
            ChangeSummary changes;
            var           tasks = new List <Task>();

            using (var context = new ShipHubContext()) {
                var sub = await context.Subscriptions
                          .AsNoTracking()
                          .SingleOrDefaultAsync(x => x.AccountId == accountId);

                if (sub == null)
                {
                    // We only care to update subscriptions we've already sync'ed.  This case often happens
                    // in development - e.g., you might be testing subscriptions on your local machine, and
                    // chargebee delivers webhook calls to shiphub-dev about subscriptions it doesn't know
                    // about yet.
                    return;
                }

                long incomingVersion;

                if (payload.EventType == "customer_deleted")
                {
                    incomingVersion = payload.Content.Customer.ResourceVersion;
                }
                else
                {
                    incomingVersion = payload.Content.Subscription.ResourceVersion;
                }

                if (incomingVersion <= sub.Version)
                {
                    // We're receiving webhook events out-of-order (which can happen due to re-delivery),
                    // so ignore.
                    return;
                }

                sub.Version = incomingVersion;
                var beforeState = sub.State;

                if (payload.EventType.Equals("subscription_deleted") ||
                    payload.EventType.Equals("customer_deleted"))
                {
                    sub.State        = SubscriptionState.NotSubscribed;
                    sub.TrialEndDate = null;
                }
                else
                {
                    switch (payload.Content.Subscription.Status)
                    {
                    case "in_trial":
                        sub.State        = SubscriptionState.InTrial;
                        sub.TrialEndDate = DateTimeOffset.FromUnixTimeSeconds((long)payload.Content.Subscription.TrialEnd);
                        break;

                    case "active":
                    case "non_renewing":
                    case "future":
                        sub.State        = SubscriptionState.Subscribed;
                        sub.TrialEndDate = null;
                        break;

                    case "cancelled":
                        sub.State        = SubscriptionState.NotSubscribed;
                        sub.TrialEndDate = null;
                        break;
                    }
                }

                changes = await context.BulkUpdateSubscriptions(new[] {
                    new SubscriptionTableType()
                    {
                        AccountId    = sub.AccountId,
                        State        = sub.StateName,
                        TrialEndDate = sub.TrialEndDate,
                        Version      = sub.Version,
                    }
                });
            }

            if (!changes.IsEmpty)
            {
                await _queueClient.NotifyChanges(changes);
            }

            await Task.WhenAll(tasks);
        }