1
0
mirror of https://github.com/bitwarden/server.git synced 2025-02-16 01:51:21 +01:00

Early return zero or negative amount invoices (#1595)

Stripe handles these by immediately finalizing as paid and crediting
their account the appropriate amount.
This commit is contained in:
Matt Gibson 2021-09-27 10:20:47 -04:00 committed by GitHub
parent dac3b3e893
commit 3d74f514ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -747,6 +747,19 @@ namespace Bit.Core.Services
var subResponse = await subscriptionService.UpdateAsync(sub.Id, subUpdateOptions);
var invoiceService = new InvoiceService();
var invoice = await invoiceService.GetAsync(subResponse?.LatestInvoiceId, new InvoiceGetOptions());
if (invoice == null)
{
throw new BadRequestException("Unable to locate draft invoice for subscription update.");
}
// If no amount due, invoice is autofinalized, we're done
if (invoice.AmountDue <= 0)
{
return null;
}
string paymentIntentClientSecret = null;
if (updatedItemOptions.Quantity > 0)
{
@ -755,12 +768,12 @@ namespace Bit.Core.Services
if (chargeNow)
{
paymentIntentClientSecret = await PayInvoiceAfterSubscriptionChangeAsync(
storableSubscriber, subResponse?.LatestInvoiceId);
storableSubscriber, invoice);
}
else
{
var invoiceService = new InvoiceService();
var invoice = await invoiceService.FinalizeInvoiceAsync(subResponse.LatestInvoiceId, new InvoiceFinalizeOptions
invoice = await invoiceService.FinalizeInvoiceAsync(subResponse.LatestInvoiceId, new InvoiceFinalizeOptions
{
AutoAdvance = false,
});
@ -867,7 +880,7 @@ namespace Bit.Core.Services
await customerService.DeleteAsync(subscriber.GatewayCustomerId);
}
public async Task<string> PayInvoiceAfterSubscriptionChangeAsync(ISubscriber subscriber, string invoiceId)
public async Task<string> PayInvoiceAfterSubscriptionChangeAsync(ISubscriber subscriber, Invoice invoice)
{
var customerService = new CustomerService();
var customerOptions = new CustomerGetOptions();
@ -884,16 +897,10 @@ namespace Bit.Core.Services
var invoiceService = new InvoiceService();
string paymentIntentClientSecret = null;
var invoice = await invoiceService.GetAsync(invoiceId, new InvoiceGetOptions());
if (invoice == null)
{
throw new BadRequestException("Unable to locate draft invoice for subscription update.");
}
// Invoice them and pay now instead of waiting until Stripe does this automatically.
string cardPaymentMethodId = null;
if (invoice?.AmountDue > 0 && !customer.Metadata.ContainsKey("btCustomerId"))
if (!customer.Metadata.ContainsKey("btCustomerId"))
{
var hasDefaultCardPaymentMethod = customer.InvoiceSettings?.DefaultPaymentMethod?.Type == "card";
var hasDefaultValidSource = customer.DefaultSource != null &&
@ -934,48 +941,45 @@ namespace Bit.Core.Services
{
PaymentMethod = cardPaymentMethodId,
};
if (invoice.AmountDue > 0)
if (customer?.Metadata?.ContainsKey("btCustomerId") ?? false)
{
if (customer?.Metadata?.ContainsKey("btCustomerId") ?? false)
{
invoicePayOptions.PaidOutOfBand = true;
var btInvoiceAmount = (invoice.AmountDue / 100M);
var transactionResult = await _btGateway.Transaction.SaleAsync(
new Braintree.TransactionRequest
invoicePayOptions.PaidOutOfBand = true;
var btInvoiceAmount = (invoice.AmountDue / 100M);
var transactionResult = await _btGateway.Transaction.SaleAsync(
new Braintree.TransactionRequest
{
Amount = btInvoiceAmount,
CustomerId = customer.Metadata["btCustomerId"],
Options = new Braintree.TransactionOptionsRequest
{
Amount = btInvoiceAmount,
CustomerId = customer.Metadata["btCustomerId"],
Options = new Braintree.TransactionOptionsRequest
SubmitForSettlement = true,
PayPal = new Braintree.TransactionOptionsPayPalRequest
{
SubmitForSettlement = true,
PayPal = new Braintree.TransactionOptionsPayPalRequest
{
CustomField = $"{subscriber.BraintreeIdField()}:{subscriber.Id}"
}
},
CustomFields = new Dictionary<string, string>
{
[subscriber.BraintreeIdField()] = subscriber.Id.ToString()
CustomField = $"{subscriber.BraintreeIdField()}:{subscriber.Id}"
}
});
if (!transactionResult.IsSuccess())
{
throw new GatewayException("Failed to charge PayPal customer.");
}
braintreeTransaction = transactionResult.Target;
invoice = await invoiceService.UpdateAsync(invoice.Id, new InvoiceUpdateOptions
{
Metadata = new Dictionary<string, string>
{
["btTransactionId"] = braintreeTransaction.Id,
["btPayPalTransactionId"] =
braintreeTransaction.PayPalDetails.AuthorizationId
},
CustomFields = new Dictionary<string, string>
{
[subscriber.BraintreeIdField()] = subscriber.Id.ToString()
}
});
invoicePayOptions.PaidOutOfBand = true;
if (!transactionResult.IsSuccess())
{
throw new GatewayException("Failed to charge PayPal customer.");
}
braintreeTransaction = transactionResult.Target;
invoice = await invoiceService.UpdateAsync(invoice.Id, new InvoiceUpdateOptions
{
Metadata = new Dictionary<string, string>
{
["btTransactionId"] = braintreeTransaction.Id,
["btPayPalTransactionId"] =
braintreeTransaction.PayPalDetails.AuthorizationId
},
});
invoicePayOptions.PaidOutOfBand = true;
}
try