1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-22 12:15:36 +01:00

AttemptToPayInvoiceWithAppleReceipt

This commit is contained in:
Kyle Spearrin 2019-09-23 09:03:18 -04:00
parent b72744eafc
commit d04d92bb89

View File

@ -21,12 +21,15 @@ namespace Bit.Billing.Controllers
[Route("stripe")]
public class StripeController : Controller
{
private const decimal PremiumPlanAppleIapPrice = 14.99M;
private readonly BillingSettings _billingSettings;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IOrganizationService _organizationService;
private readonly IOrganizationRepository _organizationRepository;
private readonly ITransactionRepository _transactionRepository;
private readonly IUserService _userService;
private readonly IAppleIapService _appleIapService;
private readonly IMailService _mailService;
private readonly ILogger<StripeController> _logger;
private readonly Braintree.BraintreeGateway _btGateway;
@ -39,6 +42,7 @@ namespace Bit.Billing.Controllers
IOrganizationRepository organizationRepository,
ITransactionRepository transactionRepository,
IUserService userService,
IAppleIapService appleIapService,
IMailService mailService,
ILogger<StripeController> logger)
{
@ -48,6 +52,7 @@ namespace Bit.Billing.Controllers
_organizationRepository = organizationRepository;
_transactionRepository = transactionRepository;
_userService = userService;
_appleIapService = appleIapService;
_mailService = mailService;
_logger = logger;
_btGateway = new Braintree.BraintreeGateway
@ -384,7 +389,7 @@ namespace Bit.Billing.Controllers
var invoice = await GetInvoiceAsync(parsedEvent, true);
if(!invoice.Paid && invoice.AttemptCount > 1 && UnpaidAutoChargeInvoiceForSubscriptionCycle(invoice))
{
await AttemptToPayInvoiceWithBraintreeAsync(invoice);
await AttemptToPayInvoiceAsync(invoice);
}
}
else if(parsedEvent.Type.Equals("invoice.created"))
@ -392,7 +397,7 @@ namespace Bit.Billing.Controllers
var invoice = await GetInvoiceAsync(parsedEvent, true);
if(!invoice.Paid && UnpaidAutoChargeInvoiceForSubscriptionCycle(invoice))
{
await AttemptToPayInvoiceWithBraintreeAsync(invoice);
await AttemptToPayInvoiceAsync(invoice);
}
}
else
@ -455,10 +460,116 @@ namespace Bit.Billing.Controllers
}
}
private async Task<bool> AttemptToPayInvoiceWithBraintreeAsync(Invoice invoice)
private async Task<bool> AttemptToPayInvoiceAsync(Invoice invoice)
{
var customerService = new CustomerService();
var customer = await customerService.GetAsync(invoice.CustomerId);
if(customer?.Metadata?.ContainsKey("appleReceipt") ?? false)
{
return await AttemptToPayInvoiceWithAppleReceiptAsync(invoice, customer);
}
else if(customer?.Metadata?.ContainsKey("btCustomerId") ?? false)
{
return await AttemptToPayInvoiceWithBraintreeAsync(invoice, customer);
}
return false;
}
private async Task<bool> AttemptToPayInvoiceWithAppleReceiptAsync(Invoice invoice, Customer customer)
{
if(!customer?.Metadata?.ContainsKey("appleReceipt") ?? true)
{
return false;
}
var originalAppleReceiptTransactionId = customer.Metadata["appleReceipt"];
var appleReceiptRecord = await _appleIapService.GetReceiptAsync(originalAppleReceiptTransactionId);
if(string.IsNullOrWhiteSpace(appleReceiptRecord?.Item1) || !appleReceiptRecord.Item2.HasValue)
{
return false;
}
var subscriptionService = new SubscriptionService();
var subscription = await subscriptionService.GetAsync(invoice.SubscriptionId);
var ids = GetIdsFromMetaData(subscription?.Metadata);
if(!ids.Item2.HasValue)
{
// Apple receipt is only for user subscriptions
return false;
}
if(appleReceiptRecord.Item2.Value != ids.Item2.Value)
{
_logger.LogError("User Ids for Apple Receipt and subscription do not match: {0} != {1}.",
appleReceiptRecord.Item2.Value, ids.Item2.Value);
return false;
}
var appleReceiptStatus = await _appleIapService.GetVerifiedReceiptStatusAsync(appleReceiptRecord.Item1);
if(appleReceiptStatus == null)
{
// TODO: cancel sub if receipt is cancelled?
return false;
}
var receiptExpiration = appleReceiptStatus.GetLastExpiresDate().GetValueOrDefault(DateTime.MinValue);
var invoiceDue = invoice.DueDate.GetValueOrDefault(DateTime.MinValue);
if(receiptExpiration <= invoiceDue)
{
_logger.LogWarning("Apple receipt expiration is before invoice due date. {0} <= {1}",
receiptExpiration, invoiceDue);
return false;
}
var receiptLastTransactionId = appleReceiptStatus.GetLastTransactionId();
var existingTransaction = await _transactionRepository.GetByGatewayIdAsync(
GatewayType.AppStore, receiptLastTransactionId);
if(existingTransaction != null)
{
_logger.LogWarning("There is already an existing transaction for this Apple receipt.",
receiptLastTransactionId);
return false;
}
var appleTransaction = appleReceiptStatus.BuildTransactionFromLastTransaction(
PremiumPlanAppleIapPrice, ids.Item2.Value);
appleTransaction.Type = TransactionType.Charge;
var invoiceService = new InvoiceService();
try
{
await invoiceService.UpdateAsync(invoice.Id, new InvoiceUpdateOptions
{
Metadata = new Dictionary<string, string>
{
["appleReceipt"] = appleReceiptStatus.GetOriginalTransactionId(),
["appleReceiptTransactionId"] = receiptLastTransactionId
}
});
await _transactionRepository.CreateAsync(appleTransaction);
await invoiceService.PayAsync(invoice.Id, new InvoicePayOptions { PaidOutOfBand = true });
}
catch(Exception e)
{
if(e.Message.Contains("Invoice is already paid"))
{
await invoiceService.UpdateAsync(invoice.Id, new InvoiceUpdateOptions
{
Metadata = invoice.Metadata
});
}
else
{
throw e;
}
}
return true;
}
private async Task<bool> AttemptToPayInvoiceWithBraintreeAsync(Invoice invoice, Customer customer)
{
if(!customer?.Metadata?.ContainsKey("btCustomerId") ?? true)
{
return false;