mirror of
https://github.com/bitwarden/server.git
synced 2024-12-30 18:17:36 +01:00
attempt to pay invoices via braintree
This commit is contained in:
parent
856e331ef3
commit
9e14af3223
@ -1,4 +1,5 @@
|
|||||||
using Bit.Core.Enums;
|
using Bit.Core;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Table;
|
using Bit.Core.Models.Table;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
@ -24,8 +25,10 @@ namespace Bit.Billing.Controllers
|
|||||||
private readonly ITransactionRepository _transactionRepository;
|
private readonly ITransactionRepository _transactionRepository;
|
||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
private readonly IMailService _mailService;
|
private readonly IMailService _mailService;
|
||||||
|
private readonly Braintree.BraintreeGateway _btGateway;
|
||||||
|
|
||||||
public StripeController(
|
public StripeController(
|
||||||
|
GlobalSettings globalSettings,
|
||||||
IOptions<BillingSettings> billingSettings,
|
IOptions<BillingSettings> billingSettings,
|
||||||
IHostingEnvironment hostingEnvironment,
|
IHostingEnvironment hostingEnvironment,
|
||||||
IOrganizationService organizationService,
|
IOrganizationService organizationService,
|
||||||
@ -41,6 +44,15 @@ namespace Bit.Billing.Controllers
|
|||||||
_transactionRepository = transactionRepository;
|
_transactionRepository = transactionRepository;
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
_mailService = mailService;
|
_mailService = mailService;
|
||||||
|
|
||||||
|
_btGateway = new Braintree.BraintreeGateway
|
||||||
|
{
|
||||||
|
Environment = globalSettings.Braintree.Production ?
|
||||||
|
Braintree.Environment.PRODUCTION : Braintree.Environment.SANDBOX,
|
||||||
|
MerchantId = globalSettings.Braintree.MerchantId,
|
||||||
|
PublicKey = globalSettings.Braintree.PublicKey,
|
||||||
|
PrivateKey = globalSettings.Braintree.PrivateKey
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("webhook")]
|
[HttpPost("webhook")]
|
||||||
@ -74,8 +86,7 @@ namespace Bit.Billing.Controllers
|
|||||||
|
|
||||||
if(subDeleted || subUpdated)
|
if(subDeleted || subUpdated)
|
||||||
{
|
{
|
||||||
var subscription = parsedEvent.Data.Object as Subscription;
|
if(!(parsedEvent.Data.Object is Subscription subscription))
|
||||||
if(subscription == null)
|
|
||||||
{
|
{
|
||||||
throw new Exception("Subscription is null.");
|
throw new Exception("Subscription is null.");
|
||||||
}
|
}
|
||||||
@ -117,8 +128,7 @@ namespace Bit.Billing.Controllers
|
|||||||
}
|
}
|
||||||
else if(parsedEvent.Type.Equals("invoice.upcoming"))
|
else if(parsedEvent.Type.Equals("invoice.upcoming"))
|
||||||
{
|
{
|
||||||
var invoice = parsedEvent.Data.Object as Invoice;
|
if(!(parsedEvent.Data.Object is Invoice invoice))
|
||||||
if(invoice == null)
|
|
||||||
{
|
{
|
||||||
throw new Exception("Invoice is null.");
|
throw new Exception("Invoice is null.");
|
||||||
}
|
}
|
||||||
@ -160,8 +170,7 @@ namespace Bit.Billing.Controllers
|
|||||||
}
|
}
|
||||||
else if(parsedEvent.Type.Equals("charge.succeeded"))
|
else if(parsedEvent.Type.Equals("charge.succeeded"))
|
||||||
{
|
{
|
||||||
var charge = parsedEvent.Data.Object as Charge;
|
if(!(parsedEvent.Data.Object is Charge charge))
|
||||||
if(charge == null)
|
|
||||||
{
|
{
|
||||||
throw new Exception("Charge is null.");
|
throw new Exception("Charge is null.");
|
||||||
}
|
}
|
||||||
@ -224,8 +233,7 @@ namespace Bit.Billing.Controllers
|
|||||||
}
|
}
|
||||||
else if(parsedEvent.Type.Equals("charge.refunded"))
|
else if(parsedEvent.Type.Equals("charge.refunded"))
|
||||||
{
|
{
|
||||||
var charge = parsedEvent.Data.Object as Charge;
|
if(!(parsedEvent.Data.Object is Charge charge))
|
||||||
if(charge == null)
|
|
||||||
{
|
{
|
||||||
throw new Exception("Charge is null.");
|
throw new Exception("Charge is null.");
|
||||||
}
|
}
|
||||||
@ -273,6 +281,30 @@ namespace Bit.Billing.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if(parsedEvent.Type.Equals("invoice.payment_failed"))
|
||||||
|
{
|
||||||
|
if(!(parsedEvent.Data.Object is Invoice invoice))
|
||||||
|
{
|
||||||
|
throw new Exception("Invoice is null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(invoice.AttemptCount > 1 && UnpaidAutoChargeInvoiceForSubscriptionCycle(invoice))
|
||||||
|
{
|
||||||
|
await AttemptToPayInvoiceWithBraintreeAsync(invoice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(parsedEvent.Type.Equals("invoice.created"))
|
||||||
|
{
|
||||||
|
if(!(parsedEvent.Data.Object is Invoice invoice))
|
||||||
|
{
|
||||||
|
throw new Exception("Invoice is null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(UnpaidAutoChargeInvoiceForSubscriptionCycle(invoice))
|
||||||
|
{
|
||||||
|
await AttemptToPayInvoiceWithBraintreeAsync(invoice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new OkResult();
|
return new OkResult();
|
||||||
}
|
}
|
||||||
@ -328,5 +360,70 @@ namespace Bit.Billing.Controllers
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<bool> AttemptToPayInvoiceWithBraintreeAsync(Invoice invoice)
|
||||||
|
{
|
||||||
|
var customerService = new CustomerService();
|
||||||
|
var customer = await customerService.GetAsync(invoice.CustomerId);
|
||||||
|
if(!customer?.Metadata?.ContainsKey("btCustomerId") ?? true)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var subscriptionService = new SubscriptionService();
|
||||||
|
var subscription = await subscriptionService.GetAsync(invoice.SubscriptionId);
|
||||||
|
var ids = GetIdsFromMetaData(subscription?.Metadata);
|
||||||
|
if(!ids.Item1.HasValue && !ids.Item2.HasValue)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var btObjIdField = ids.Item1.HasValue ? "organization_id" : "user_id";
|
||||||
|
var btObjId = ids.Item1 ?? ids.Item2.Value;
|
||||||
|
var btInvoiceAmount = (invoice.AmountDue / 100M);
|
||||||
|
|
||||||
|
var transactionResult = await _btGateway.Transaction.SaleAsync(
|
||||||
|
new Braintree.TransactionRequest
|
||||||
|
{
|
||||||
|
Amount = btInvoiceAmount,
|
||||||
|
CustomerId = customer.Metadata["btCustomerId"],
|
||||||
|
Options = new Braintree.TransactionOptionsRequest
|
||||||
|
{
|
||||||
|
SubmitForSettlement = true,
|
||||||
|
PayPal = new Braintree.TransactionOptionsPayPalRequest
|
||||||
|
{
|
||||||
|
CustomField = $"{btObjIdField}:{btObjId}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
CustomFields = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
[btObjIdField] = btObjId.ToString()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!transactionResult.IsSuccess())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var invoiceService = new InvoiceService();
|
||||||
|
await invoiceService.UpdateAsync(invoice.Id, new InvoiceUpdateOptions
|
||||||
|
{
|
||||||
|
Metadata = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["btTransactionId"] = transactionResult.Target.Id,
|
||||||
|
["btPayPalTransactionId"] =
|
||||||
|
transactionResult.Target.PayPalDetails?.AuthorizationId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await invoiceService.PayAsync(invoice.Id, new InvoicePayOptions { PaidOutOfBand = true });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool UnpaidAutoChargeInvoiceForSubscriptionCycle(Invoice invoice)
|
||||||
|
{
|
||||||
|
return invoice.AmountDue > 0 && !invoice.Paid && invoice.Billing == Stripe.Billing.ChargeAutomatically &&
|
||||||
|
invoice.BillingReason == "subscription_cycle" && invoice.SubscriptionId != null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user