mirror of
https://github.com/bitwarden/server.git
synced 2024-11-22 12:15:36 +01:00
AttemptToPayInvoiceWithAppleReceipt
This commit is contained in:
parent
b72744eafc
commit
d04d92bb89
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user