mirror of
https://github.com/bitwarden/server.git
synced 2025-02-17 02:01:53 +01:00
AttemptToPayInvoiceWithAppleReceipt
This commit is contained in:
parent
b72744eafc
commit
d04d92bb89
@ -21,12 +21,15 @@ namespace Bit.Billing.Controllers
|
|||||||
[Route("stripe")]
|
[Route("stripe")]
|
||||||
public class StripeController : Controller
|
public class StripeController : Controller
|
||||||
{
|
{
|
||||||
|
private const decimal PremiumPlanAppleIapPrice = 14.99M;
|
||||||
|
|
||||||
private readonly BillingSettings _billingSettings;
|
private readonly BillingSettings _billingSettings;
|
||||||
private readonly IHostingEnvironment _hostingEnvironment;
|
private readonly IHostingEnvironment _hostingEnvironment;
|
||||||
private readonly IOrganizationService _organizationService;
|
private readonly IOrganizationService _organizationService;
|
||||||
private readonly IOrganizationRepository _organizationRepository;
|
private readonly IOrganizationRepository _organizationRepository;
|
||||||
private readonly ITransactionRepository _transactionRepository;
|
private readonly ITransactionRepository _transactionRepository;
|
||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
|
private readonly IAppleIapService _appleIapService;
|
||||||
private readonly IMailService _mailService;
|
private readonly IMailService _mailService;
|
||||||
private readonly ILogger<StripeController> _logger;
|
private readonly ILogger<StripeController> _logger;
|
||||||
private readonly Braintree.BraintreeGateway _btGateway;
|
private readonly Braintree.BraintreeGateway _btGateway;
|
||||||
@ -39,6 +42,7 @@ namespace Bit.Billing.Controllers
|
|||||||
IOrganizationRepository organizationRepository,
|
IOrganizationRepository organizationRepository,
|
||||||
ITransactionRepository transactionRepository,
|
ITransactionRepository transactionRepository,
|
||||||
IUserService userService,
|
IUserService userService,
|
||||||
|
IAppleIapService appleIapService,
|
||||||
IMailService mailService,
|
IMailService mailService,
|
||||||
ILogger<StripeController> logger)
|
ILogger<StripeController> logger)
|
||||||
{
|
{
|
||||||
@ -48,6 +52,7 @@ namespace Bit.Billing.Controllers
|
|||||||
_organizationRepository = organizationRepository;
|
_organizationRepository = organizationRepository;
|
||||||
_transactionRepository = transactionRepository;
|
_transactionRepository = transactionRepository;
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
|
_appleIapService = appleIapService;
|
||||||
_mailService = mailService;
|
_mailService = mailService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_btGateway = new Braintree.BraintreeGateway
|
_btGateway = new Braintree.BraintreeGateway
|
||||||
@ -384,7 +389,7 @@ namespace Bit.Billing.Controllers
|
|||||||
var invoice = await GetInvoiceAsync(parsedEvent, true);
|
var invoice = await GetInvoiceAsync(parsedEvent, true);
|
||||||
if(!invoice.Paid && invoice.AttemptCount > 1 && UnpaidAutoChargeInvoiceForSubscriptionCycle(invoice))
|
if(!invoice.Paid && invoice.AttemptCount > 1 && UnpaidAutoChargeInvoiceForSubscriptionCycle(invoice))
|
||||||
{
|
{
|
||||||
await AttemptToPayInvoiceWithBraintreeAsync(invoice);
|
await AttemptToPayInvoiceAsync(invoice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(parsedEvent.Type.Equals("invoice.created"))
|
else if(parsedEvent.Type.Equals("invoice.created"))
|
||||||
@ -392,7 +397,7 @@ namespace Bit.Billing.Controllers
|
|||||||
var invoice = await GetInvoiceAsync(parsedEvent, true);
|
var invoice = await GetInvoiceAsync(parsedEvent, true);
|
||||||
if(!invoice.Paid && UnpaidAutoChargeInvoiceForSubscriptionCycle(invoice))
|
if(!invoice.Paid && UnpaidAutoChargeInvoiceForSubscriptionCycle(invoice))
|
||||||
{
|
{
|
||||||
await AttemptToPayInvoiceWithBraintreeAsync(invoice);
|
await AttemptToPayInvoiceAsync(invoice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
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 customerService = new CustomerService();
|
||||||
var customer = await customerService.GetAsync(invoice.CustomerId);
|
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)
|
if(!customer?.Metadata?.ContainsKey("btCustomerId") ?? true)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
Loading…
Reference in New Issue
Block a user