Files
server/src/Billing/Controllers/StripeController.cs

215 lines
7.6 KiB
C#
Raw Normal View History

2018-05-11 08:29:23 -04:00
using Bit.Core.Models.Table;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Stripe;
using System;
2017-08-12 22:16:42 -04:00
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
2017-03-18 18:52:44 -04:00
namespace Bit.Billing.Controllers
{
[Route("stripe")]
public class StripeController : Controller
{
private readonly BillingSettings _billingSettings;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IOrganizationService _organizationService;
2018-05-11 08:29:23 -04:00
private readonly IOrganizationRepository _organizationRepository;
2017-07-25 09:04:22 -04:00
private readonly IUserService _userService;
2018-05-11 08:29:23 -04:00
private readonly IMailService _mailService;
public StripeController(
IOptions<BillingSettings> billingSettings,
IHostingEnvironment hostingEnvironment,
2017-07-25 09:04:22 -04:00
IOrganizationService organizationService,
2018-05-11 08:29:23 -04:00
IOrganizationRepository organizationRepository,
IUserService userService,
IMailService mailService)
{
_billingSettings = billingSettings?.Value;
_hostingEnvironment = hostingEnvironment;
_organizationService = organizationService;
2018-05-11 08:29:23 -04:00
_organizationRepository = organizationRepository;
2017-07-25 09:04:22 -04:00
_userService = userService;
2018-05-11 08:29:23 -04:00
_mailService = mailService;
}
2017-03-18 18:52:44 -04:00
[HttpPost("webhook")]
public async Task<IActionResult> PostWebhook([FromQuery] string key)
2017-03-18 18:52:44 -04:00
{
if(key != _billingSettings.StripeWebhookKey)
{
return new BadRequestResult();
}
2017-03-18 18:52:44 -04:00
Stripe.Event parsedEvent;
using(var sr = new StreamReader(HttpContext.Request.Body))
{
var json = await sr.ReadToEndAsync();
parsedEvent = EventUtility.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)
{
var subscription = parsedEvent.Data.Object as Subscription;
if(subscription == null)
{
throw new Exception("Subscription is null.");
}
2017-08-12 22:16:42 -04:00
var ids = GetIdsFromMetaData(subscription.Metadata);
var subCanceled = subDeleted && subscription.Status == "canceled";
var subUnpaid = subUpdated && subscription.Status == "unpaid";
if(subCanceled || subUnpaid)
2017-08-12 22:16:42 -04:00
{
// 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)
{
2017-08-12 22:16:42 -04:00
// org
if(ids.Item1.HasValue)
2017-07-25 09:04:22 -04:00
{
await _organizationService.UpdateExpirationDateAsync(ids.Item1.Value,
subscription.CurrentPeriodEnd);
2017-07-25 09:04:22 -04:00
}
2017-08-12 22:16:42 -04:00
// user
else if(ids.Item2.HasValue)
2017-07-25 09:04:22 -04:00
{
await _userService.UpdatePremiumExpirationAsync(ids.Item2.Value,
subscription.CurrentPeriodEnd);
2017-07-25 09:04:22 -04:00
}
}
}
2018-05-11 08:29:23 -04:00
else if(invUpcoming)
{
var invoice = parsedEvent.Data.Object as Invoice;
if(invoice == null)
{
throw new Exception("Invoice is null.");
}
var subscriptionService = new SubscriptionService();
var subscription = await subscriptionService.GetAsync(invoice.SubscriptionId);
if(subscription == null)
{
throw new Exception("Invoice subscription is null.");
}
2018-05-11 08:29:23 -04:00
string email = null;
var ids = GetIdsFromMetaData(subscription.Metadata);
// org
if(ids.Item1.HasValue)
{
2018-05-11 08:29:23 -04:00
var org = await _organizationRepository.GetByIdAsync(ids.Item1.Value);
if(org != null && OrgPlanForInvoiceNotifications(org))
{
email = org.BillingEmail;
}
}
// user
else if(ids.Item2.HasValue)
{
2018-05-11 08:29:23 -04:00
var user = await _userService.GetUserByIdAsync(ids.Item2.Value);
if(user.Premium)
{
email = user.Email;
}
}
if(!string.IsNullOrWhiteSpace(email) && invoice.NextPaymentAttempt.HasValue)
{
var items = invoice.Lines.Select(i => i.Description).ToList();
2018-05-11 08:29:23 -04:00
await _mailService.SendInvoiceUpcomingAsync(email, invoice.AmountDue / 100M,
invoice.NextPaymentAttempt.Value, items, ids.Item1.HasValue);
}
}
2017-08-12 22:16:42 -04:00
return new OkResult();
}
private Tuple<Guid?, Guid?> GetIdsFromMetaData(IDictionary<string, string> metaData)
{
if(metaData == null)
{
return new Tuple<Guid?, Guid?>(null, null);
}
2017-08-12 22:16:42 -04:00
Guid? orgId = null;
Guid? userId = null;
if(metaData.ContainsKey("organizationId"))
{
2017-08-12 22:16:42 -04:00
orgId = new Guid(metaData["organizationId"]);
}
else if(metaData.ContainsKey("userId"))
2017-08-12 22:16:42 -04:00
{
userId = new Guid(metaData["userId"]);
}
if(userId == null && orgId == null)
{
var orgIdKey = metaData.Keys.FirstOrDefault(k => k.ToLowerInvariant() == "organizationid");
if(!string.IsNullOrWhiteSpace(orgIdKey))
{
orgId = new Guid(metaData[orgIdKey]);
}
else
{
var userIdKey = metaData.Keys.FirstOrDefault(k => k.ToLowerInvariant() == "userid");
if(!string.IsNullOrWhiteSpace(userIdKey))
{
userId = new Guid(metaData[userIdKey]);
}
}
}
2017-08-12 22:16:42 -04:00
return new Tuple<Guid?, Guid?>(orgId, userId);
2017-03-18 18:52:44 -04:00
}
2018-05-11 08:29:23 -04:00
private bool OrgPlanForInvoiceNotifications(Organization org)
{
switch(org.PlanType)
{
case Core.Enums.PlanType.FamiliesAnnually:
case Core.Enums.PlanType.TeamsAnnually:
case Core.Enums.PlanType.EnterpriseAnnually:
return true;
default:
return false;
}
}
2017-03-18 18:52:44 -04:00
}
}