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); }