mirror of
https://github.com/bitwarden/server.git
synced 2025-02-18 02:11:22 +01:00
This reverts commit d9faa9a6df
.
This commit is contained in:
parent
de32524891
commit
03b9136623
@ -25,20 +25,6 @@ public static class Constants
|
|||||||
|
|
||||||
public const string CipherKeyEncryptionMinimumVersion = "2023.9.2";
|
public const string CipherKeyEncryptionMinimumVersion = "2023.9.2";
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// When you set the ProrationBehavior to create_prorations,
|
|
||||||
/// Stripe will automatically create prorations for any changes made to the subscription,
|
|
||||||
/// such as changing the plan, adding or removing quantities, or applying discounts.
|
|
||||||
/// </summary>
|
|
||||||
public const string CreateProrations = "create_prorations";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// When you set the ProrationBehavior to always_invoice,
|
|
||||||
/// Stripe will always generate an invoice when a subscription update occurs,
|
|
||||||
/// regardless of whether there is a proration or not.
|
|
||||||
/// </summary>
|
|
||||||
public const string AlwaysInvoice = "always_invoice";
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used by IdentityServer to identify our own provider.
|
/// Used by IdentityServer to identify our own provider.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
namespace Bit.Core.Models.Business;
|
|
||||||
|
|
||||||
public class InvoicePreviewResult
|
|
||||||
{
|
|
||||||
public bool IsInvoicedNow { get; set; }
|
|
||||||
public string PaymentIntentClientSecret { get; set; }
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
using Stripe;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models.Business;
|
|
||||||
|
|
||||||
public class PendingInoviceItems
|
|
||||||
{
|
|
||||||
public IEnumerable<InvoiceItem> PendingInvoiceItems { get; set; }
|
|
||||||
public IDictionary<string, InvoiceItem> PendingInvoiceItemsDict { get; set; }
|
|
||||||
}
|
|
@ -44,7 +44,7 @@ public class SecretsManagerSubscribeUpdate : SubscriptionUpdate
|
|||||||
{
|
{
|
||||||
updatedItems.Add(new SubscriptionItemOptions
|
updatedItems.Add(new SubscriptionItemOptions
|
||||||
{
|
{
|
||||||
Plan = _plan.SecretsManager.StripeSeatPlanId,
|
Price = _plan.SecretsManager.StripeSeatPlanId,
|
||||||
Quantity = _additionalSeats
|
Quantity = _additionalSeats
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -53,7 +53,7 @@ public class SecretsManagerSubscribeUpdate : SubscriptionUpdate
|
|||||||
{
|
{
|
||||||
updatedItems.Add(new SubscriptionItemOptions
|
updatedItems.Add(new SubscriptionItemOptions
|
||||||
{
|
{
|
||||||
Plan = _plan.SecretsManager.StripeServiceAccountPlanId,
|
Price = _plan.SecretsManager.StripeServiceAccountPlanId,
|
||||||
Quantity = _additionalServiceAccounts
|
Quantity = _additionalServiceAccounts
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -63,14 +63,14 @@ public class SecretsManagerSubscribeUpdate : SubscriptionUpdate
|
|||||||
{
|
{
|
||||||
updatedItems.Add(new SubscriptionItemOptions
|
updatedItems.Add(new SubscriptionItemOptions
|
||||||
{
|
{
|
||||||
Plan = _plan.SecretsManager.StripeSeatPlanId,
|
Price = _plan.SecretsManager.StripeSeatPlanId,
|
||||||
Quantity = _previousSeats,
|
Quantity = _previousSeats,
|
||||||
Deleted = _previousSeats == 0 ? true : (bool?)null,
|
Deleted = _previousSeats == 0 ? true : (bool?)null,
|
||||||
});
|
});
|
||||||
|
|
||||||
updatedItems.Add(new SubscriptionItemOptions
|
updatedItems.Add(new SubscriptionItemOptions
|
||||||
{
|
{
|
||||||
Plan = _plan.SecretsManager.StripeServiceAccountPlanId,
|
Price = _plan.SecretsManager.StripeServiceAccountPlanId,
|
||||||
Quantity = _previousServiceAccounts,
|
Quantity = _previousServiceAccounts,
|
||||||
Deleted = _previousServiceAccounts == 0 ? true : (bool?)null,
|
Deleted = _previousServiceAccounts == 0 ? true : (bool?)null,
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using Bit.Core.Models.BitStripe;
|
using Bit.Core.Models.BitStripe;
|
||||||
using Stripe;
|
|
||||||
|
|
||||||
namespace Bit.Core.Services;
|
namespace Bit.Core.Services;
|
||||||
|
|
||||||
@ -15,11 +14,8 @@ public interface IStripeAdapter
|
|||||||
Task<Stripe.Subscription> SubscriptionUpdateAsync(string id, Stripe.SubscriptionUpdateOptions options = null);
|
Task<Stripe.Subscription> SubscriptionUpdateAsync(string id, Stripe.SubscriptionUpdateOptions options = null);
|
||||||
Task<Stripe.Subscription> SubscriptionCancelAsync(string Id, Stripe.SubscriptionCancelOptions options = null);
|
Task<Stripe.Subscription> SubscriptionCancelAsync(string Id, Stripe.SubscriptionCancelOptions options = null);
|
||||||
Task<Stripe.Invoice> InvoiceUpcomingAsync(Stripe.UpcomingInvoiceOptions options);
|
Task<Stripe.Invoice> InvoiceUpcomingAsync(Stripe.UpcomingInvoiceOptions options);
|
||||||
Task<Stripe.Invoice> InvoiceCreateAsync(Stripe.InvoiceCreateOptions options);
|
|
||||||
Task<Stripe.InvoiceItem> InvoiceItemCreateAsync(Stripe.InvoiceItemCreateOptions options);
|
|
||||||
Task<Stripe.Invoice> InvoiceGetAsync(string id, Stripe.InvoiceGetOptions options);
|
Task<Stripe.Invoice> InvoiceGetAsync(string id, Stripe.InvoiceGetOptions options);
|
||||||
Task<List<Stripe.Invoice>> InvoiceListAsync(StripeInvoiceListOptions options);
|
Task<List<Stripe.Invoice>> InvoiceListAsync(StripeInvoiceListOptions options);
|
||||||
IEnumerable<InvoiceItem> InvoiceItemListAsync(InvoiceItemListOptions options);
|
|
||||||
Task<Stripe.Invoice> InvoiceUpdateAsync(string id, Stripe.InvoiceUpdateOptions options);
|
Task<Stripe.Invoice> InvoiceUpdateAsync(string id, Stripe.InvoiceUpdateOptions options);
|
||||||
Task<Stripe.Invoice> InvoiceFinalizeInvoiceAsync(string id, Stripe.InvoiceFinalizeOptions options);
|
Task<Stripe.Invoice> InvoiceFinalizeInvoiceAsync(string id, Stripe.InvoiceFinalizeOptions options);
|
||||||
Task<Stripe.Invoice> InvoiceSendInvoiceAsync(string id, Stripe.InvoiceSendOptions options);
|
Task<Stripe.Invoice> InvoiceSendInvoiceAsync(string id, Stripe.InvoiceSendOptions options);
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using Bit.Core.Models.BitStripe;
|
using Bit.Core.Models.BitStripe;
|
||||||
using Stripe;
|
|
||||||
|
|
||||||
namespace Bit.Core.Services;
|
namespace Bit.Core.Services;
|
||||||
|
|
||||||
@ -17,7 +16,6 @@ public class StripeAdapter : IStripeAdapter
|
|||||||
private readonly Stripe.BankAccountService _bankAccountService;
|
private readonly Stripe.BankAccountService _bankAccountService;
|
||||||
private readonly Stripe.PriceService _priceService;
|
private readonly Stripe.PriceService _priceService;
|
||||||
private readonly Stripe.TestHelpers.TestClockService _testClockService;
|
private readonly Stripe.TestHelpers.TestClockService _testClockService;
|
||||||
private readonly Stripe.InvoiceItemService _invoiceItemService;
|
|
||||||
|
|
||||||
public StripeAdapter()
|
public StripeAdapter()
|
||||||
{
|
{
|
||||||
@ -33,7 +31,6 @@ public class StripeAdapter : IStripeAdapter
|
|||||||
_bankAccountService = new Stripe.BankAccountService();
|
_bankAccountService = new Stripe.BankAccountService();
|
||||||
_priceService = new Stripe.PriceService();
|
_priceService = new Stripe.PriceService();
|
||||||
_testClockService = new Stripe.TestHelpers.TestClockService();
|
_testClockService = new Stripe.TestHelpers.TestClockService();
|
||||||
_invoiceItemService = new Stripe.InvoiceItemService();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<Stripe.Customer> CustomerCreateAsync(Stripe.CustomerCreateOptions options)
|
public Task<Stripe.Customer> CustomerCreateAsync(Stripe.CustomerCreateOptions options)
|
||||||
@ -82,16 +79,6 @@ public class StripeAdapter : IStripeAdapter
|
|||||||
return _invoiceService.UpcomingAsync(options);
|
return _invoiceService.UpcomingAsync(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<Stripe.Invoice> InvoiceCreateAsync(Stripe.InvoiceCreateOptions options)
|
|
||||||
{
|
|
||||||
return _invoiceService.CreateAsync(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<Stripe.InvoiceItem> InvoiceItemCreateAsync(Stripe.InvoiceItemCreateOptions options)
|
|
||||||
{
|
|
||||||
return _invoiceItemService.CreateAsync(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<Stripe.Invoice> InvoiceGetAsync(string id, Stripe.InvoiceGetOptions options)
|
public Task<Stripe.Invoice> InvoiceGetAsync(string id, Stripe.InvoiceGetOptions options)
|
||||||
{
|
{
|
||||||
return _invoiceService.GetAsync(id, options);
|
return _invoiceService.GetAsync(id, options);
|
||||||
@ -116,11 +103,6 @@ public class StripeAdapter : IStripeAdapter
|
|||||||
return invoices;
|
return invoices;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<InvoiceItem> InvoiceItemListAsync(InvoiceItemListOptions options)
|
|
||||||
{
|
|
||||||
return _invoiceItemService.ListAutoPaging(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<Stripe.Invoice> InvoiceUpdateAsync(string id, Stripe.InvoiceUpdateOptions options)
|
public Task<Stripe.Invoice> InvoiceUpdateAsync(string id, Stripe.InvoiceUpdateOptions options)
|
||||||
{
|
{
|
||||||
return _invoiceService.UpdateAsync(id, options);
|
return _invoiceService.UpdateAsync(id, options);
|
||||||
|
@ -7,7 +7,6 @@ using Bit.Core.Models.Business;
|
|||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Stripe;
|
|
||||||
using StaticStore = Bit.Core.Models.StaticStore;
|
using StaticStore = Bit.Core.Models.StaticStore;
|
||||||
using TaxRate = Bit.Core.Entities.TaxRate;
|
using TaxRate = Bit.Core.Entities.TaxRate;
|
||||||
|
|
||||||
@ -751,14 +750,16 @@ public class StripePaymentService : IPaymentService
|
|||||||
prorationDate ??= DateTime.UtcNow;
|
prorationDate ??= DateTime.UtcNow;
|
||||||
var collectionMethod = sub.CollectionMethod;
|
var collectionMethod = sub.CollectionMethod;
|
||||||
var daysUntilDue = sub.DaysUntilDue;
|
var daysUntilDue = sub.DaysUntilDue;
|
||||||
|
var chargeNow = collectionMethod == "charge_automatically";
|
||||||
var updatedItemOptions = subscriptionUpdate.UpgradeItemsOptions(sub);
|
var updatedItemOptions = subscriptionUpdate.UpgradeItemsOptions(sub);
|
||||||
|
|
||||||
var subUpdateOptions = new Stripe.SubscriptionUpdateOptions
|
var subUpdateOptions = new Stripe.SubscriptionUpdateOptions
|
||||||
{
|
{
|
||||||
Items = updatedItemOptions,
|
Items = updatedItemOptions,
|
||||||
ProrationBehavior = Constants.CreateProrations,
|
ProrationBehavior = "always_invoice",
|
||||||
DaysUntilDue = daysUntilDue ?? 1,
|
DaysUntilDue = daysUntilDue ?? 1,
|
||||||
CollectionMethod = "send_invoice"
|
CollectionMethod = "send_invoice",
|
||||||
|
ProrationDate = prorationDate,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!subscriptionUpdate.UpdateNeeded(sub))
|
if (!subscriptionUpdate.UpdateNeeded(sub))
|
||||||
@ -792,50 +793,66 @@ public class StripePaymentService : IPaymentService
|
|||||||
string paymentIntentClientSecret = null;
|
string paymentIntentClientSecret = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var subItemOptions = updatedItemOptions.Select(itemOption =>
|
|
||||||
new Stripe.InvoiceSubscriptionItemOptions
|
|
||||||
{
|
|
||||||
Id = itemOption.Id,
|
|
||||||
Plan = itemOption.Plan,
|
|
||||||
Quantity = itemOption.Quantity,
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
var reviewInvoiceResponse = await PreviewUpcomingInvoiceAndPayAsync(storableSubscriber, subItemOptions);
|
|
||||||
paymentIntentClientSecret = reviewInvoiceResponse.PaymentIntentClientSecret;
|
|
||||||
|
|
||||||
var subResponse = await _stripeAdapter.SubscriptionUpdateAsync(sub.Id, subUpdateOptions);
|
var subResponse = await _stripeAdapter.SubscriptionUpdateAsync(sub.Id, subUpdateOptions);
|
||||||
var invoice =
|
|
||||||
await _stripeAdapter.InvoiceGetAsync(subResponse?.LatestInvoiceId, new Stripe.InvoiceGetOptions());
|
var invoice = await _stripeAdapter.InvoiceGetAsync(subResponse?.LatestInvoiceId, new Stripe.InvoiceGetOptions());
|
||||||
if (invoice == null)
|
if (invoice == null)
|
||||||
{
|
{
|
||||||
throw new BadRequestException("Unable to locate draft invoice for subscription update.");
|
throw new BadRequestException("Unable to locate draft invoice for subscription update.");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (Exception e)
|
if (invoice.AmountDue > 0 && updatedItemOptions.Any(i => i.Quantity > 0))
|
||||||
{
|
|
||||||
// Need to revert the subscription
|
|
||||||
await _stripeAdapter.SubscriptionUpdateAsync(sub.Id, new Stripe.SubscriptionUpdateOptions
|
|
||||||
{
|
{
|
||||||
Items = subscriptionUpdate.RevertItemsOptions(sub),
|
try
|
||||||
// This proration behavior prevents a false "credit" from
|
{
|
||||||
// being applied forward to the next month's invoice
|
if (chargeNow)
|
||||||
ProrationBehavior = "none",
|
{
|
||||||
CollectionMethod = collectionMethod,
|
paymentIntentClientSecret = await PayInvoiceAfterSubscriptionChangeAsync(
|
||||||
DaysUntilDue = daysUntilDue,
|
storableSubscriber, invoice);
|
||||||
});
|
}
|
||||||
throw;
|
else
|
||||||
|
{
|
||||||
|
invoice = await _stripeAdapter.InvoiceFinalizeInvoiceAsync(subResponse.LatestInvoiceId, new Stripe.InvoiceFinalizeOptions
|
||||||
|
{
|
||||||
|
AutoAdvance = false,
|
||||||
|
});
|
||||||
|
await _stripeAdapter.InvoiceSendInvoiceAsync(invoice.Id, new Stripe.InvoiceSendOptions());
|
||||||
|
paymentIntentClientSecret = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Need to revert the subscription
|
||||||
|
await _stripeAdapter.SubscriptionUpdateAsync(sub.Id, new Stripe.SubscriptionUpdateOptions
|
||||||
|
{
|
||||||
|
Items = subscriptionUpdate.RevertItemsOptions(sub),
|
||||||
|
// This proration behavior prevents a false "credit" from
|
||||||
|
// being applied forward to the next month's invoice
|
||||||
|
ProrationBehavior = "none",
|
||||||
|
CollectionMethod = collectionMethod,
|
||||||
|
DaysUntilDue = daysUntilDue,
|
||||||
|
});
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!invoice.Paid)
|
||||||
|
{
|
||||||
|
// Pay invoice with no charge to customer this completes the invoice immediately without waiting the scheduled 1h
|
||||||
|
invoice = await _stripeAdapter.InvoicePayAsync(subResponse.LatestInvoiceId);
|
||||||
|
paymentIntentClientSecret = null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
// Change back the subscription collection method and/or days until due
|
// Change back the subscription collection method and/or days until due
|
||||||
if (collectionMethod != "send_invoice" || daysUntilDue == null)
|
if (collectionMethod != "send_invoice" || daysUntilDue == null)
|
||||||
{
|
{
|
||||||
await _stripeAdapter.SubscriptionUpdateAsync(sub.Id,
|
await _stripeAdapter.SubscriptionUpdateAsync(sub.Id, new Stripe.SubscriptionUpdateOptions
|
||||||
new Stripe.SubscriptionUpdateOptions
|
{
|
||||||
{
|
CollectionMethod = collectionMethod,
|
||||||
CollectionMethod = collectionMethod,
|
DaysUntilDue = daysUntilDue,
|
||||||
DaysUntilDue = daysUntilDue,
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -918,7 +935,6 @@ public class StripePaymentService : IPaymentService
|
|||||||
await _stripeAdapter.CustomerDeleteAsync(subscriber.GatewayCustomerId);
|
await _stripeAdapter.CustomerDeleteAsync(subscriber.GatewayCustomerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
//This method is no-longer is use because we return the dollar threshold feature on invoice will be generated. but we dont want to lose this implementation.
|
|
||||||
public async Task<string> PayInvoiceAfterSubscriptionChangeAsync(ISubscriber subscriber, Stripe.Invoice invoice)
|
public async Task<string> PayInvoiceAfterSubscriptionChangeAsync(ISubscriber subscriber, Stripe.Invoice invoice)
|
||||||
{
|
{
|
||||||
var customerOptions = new Stripe.CustomerGetOptions();
|
var customerOptions = new Stripe.CustomerGetOptions();
|
||||||
@ -1088,310 +1104,6 @@ public class StripePaymentService : IPaymentService
|
|||||||
return paymentIntentClientSecret;
|
return paymentIntentClientSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<InvoicePreviewResult> PreviewUpcomingInvoiceAndPayAsync(ISubscriber subscriber,
|
|
||||||
List<Stripe.InvoiceSubscriptionItemOptions> subItemOptions, int prorateThreshold = 50000)
|
|
||||||
{
|
|
||||||
var customer = await CheckInAppPurchaseMethod(subscriber);
|
|
||||||
|
|
||||||
string paymentIntentClientSecret = null;
|
|
||||||
|
|
||||||
var pendingInvoiceItems = GetPendingInvoiceItems(subscriber);
|
|
||||||
|
|
||||||
var upcomingPreview = await GetUpcomingInvoiceAsync(subscriber, subItemOptions);
|
|
||||||
|
|
||||||
var itemsForInvoice = GetItemsForInvoice(subItemOptions, upcomingPreview, pendingInvoiceItems);
|
|
||||||
var invoiceAmount = itemsForInvoice?.Sum(i => i.Amount) ?? 0;
|
|
||||||
var invoiceNow = invoiceAmount >= prorateThreshold;
|
|
||||||
if (invoiceNow)
|
|
||||||
{
|
|
||||||
await ProcessImmediateInvoiceAsync(subscriber, upcomingPreview, invoiceAmount, customer, itemsForInvoice, pendingInvoiceItems, paymentIntentClientSecret);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new InvoicePreviewResult { IsInvoicedNow = invoiceNow, PaymentIntentClientSecret = paymentIntentClientSecret };
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<InvoicePreviewResult> ProcessImmediateInvoiceAsync(ISubscriber subscriber, Invoice upcomingPreview, long invoiceAmount,
|
|
||||||
Customer customer, IEnumerable<InvoiceLineItem> itemsForInvoice, PendingInoviceItems pendingInvoiceItems,
|
|
||||||
string paymentIntentClientSecret)
|
|
||||||
{
|
|
||||||
// Owes more than prorateThreshold on the next invoice.
|
|
||||||
// Invoice them and pay now instead of waiting until the next billing cycle.
|
|
||||||
|
|
||||||
string cardPaymentMethodId = null;
|
|
||||||
var invoiceAmountDue = upcomingPreview.StartingBalance + invoiceAmount;
|
|
||||||
cardPaymentMethodId = GetCardPaymentMethodId(invoiceAmountDue, customer, cardPaymentMethodId);
|
|
||||||
|
|
||||||
Stripe.Invoice invoice = null;
|
|
||||||
var createdInvoiceItems = new List<Stripe.InvoiceItem>();
|
|
||||||
Braintree.Transaction braintreeTransaction = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await CreateInvoiceItemsAsync(subscriber, itemsForInvoice, pendingInvoiceItems, createdInvoiceItems);
|
|
||||||
|
|
||||||
invoice = await CreateInvoiceAsync(subscriber, cardPaymentMethodId);
|
|
||||||
|
|
||||||
var invoicePayOptions = new Stripe.InvoicePayOptions();
|
|
||||||
await CreateBrainTreeTransactionRequestAsync(subscriber, invoice, customer, invoicePayOptions,
|
|
||||||
cardPaymentMethodId, braintreeTransaction);
|
|
||||||
|
|
||||||
await InvoicePayAsync(invoicePayOptions, invoice, paymentIntentClientSecret);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
if (braintreeTransaction != null)
|
|
||||||
{
|
|
||||||
await _btGateway.Transaction.RefundAsync(braintreeTransaction.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (invoice != null)
|
|
||||||
{
|
|
||||||
if (invoice.Status == "paid")
|
|
||||||
{
|
|
||||||
// It's apparently paid, so we return without throwing an exception
|
|
||||||
return new InvoicePreviewResult
|
|
||||||
{
|
|
||||||
IsInvoicedNow = false,
|
|
||||||
PaymentIntentClientSecret = paymentIntentClientSecret
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
await RestoreInvoiceItemsAsync(invoice, customer, pendingInvoiceItems.PendingInvoiceItems);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (var ii in createdInvoiceItems)
|
|
||||||
{
|
|
||||||
await _stripeAdapter.InvoiceDeleteAsync(ii.Id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e is Stripe.StripeException strEx &&
|
|
||||||
(strEx.StripeError?.Message?.Contains("cannot be used because it is not verified") ?? false))
|
|
||||||
{
|
|
||||||
throw new GatewayException("Bank account is not yet verified.");
|
|
||||||
}
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new InvoicePreviewResult
|
|
||||||
{
|
|
||||||
IsInvoicedNow = false,
|
|
||||||
PaymentIntentClientSecret = paymentIntentClientSecret
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IEnumerable<InvoiceLineItem> GetItemsForInvoice(List<InvoiceSubscriptionItemOptions> subItemOptions, Invoice upcomingPreview,
|
|
||||||
PendingInoviceItems pendingInvoiceItems)
|
|
||||||
{
|
|
||||||
var itemsForInvoice = upcomingPreview.Lines?.Data?
|
|
||||||
.Where(i => pendingInvoiceItems.PendingInvoiceItemsDict.ContainsKey(i.Id) ||
|
|
||||||
(i.Plan.Id == subItemOptions[0]?.Plan && i.Proration));
|
|
||||||
return itemsForInvoice;
|
|
||||||
}
|
|
||||||
|
|
||||||
private PendingInoviceItems GetPendingInvoiceItems(ISubscriber subscriber)
|
|
||||||
{
|
|
||||||
var pendingInvoiceItems = new PendingInoviceItems();
|
|
||||||
var invoiceItems = _stripeAdapter.InvoiceItemListAsync(new Stripe.InvoiceItemListOptions
|
|
||||||
{
|
|
||||||
Customer = subscriber.GatewayCustomerId
|
|
||||||
}).ToList().Where(i => i.InvoiceId == null);
|
|
||||||
pendingInvoiceItems.PendingInvoiceItemsDict = invoiceItems.ToDictionary(pii => pii.Id);
|
|
||||||
return pendingInvoiceItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<Customer> CheckInAppPurchaseMethod(ISubscriber subscriber)
|
|
||||||
{
|
|
||||||
var customerOptions = GetCustomerPaymentOptions();
|
|
||||||
var customer = await _stripeAdapter.CustomerGetAsync(subscriber.GatewayCustomerId, customerOptions);
|
|
||||||
var usingInAppPaymentMethod = customer.Metadata.ContainsKey("appleReceipt");
|
|
||||||
if (usingInAppPaymentMethod)
|
|
||||||
{
|
|
||||||
throw new BadRequestException("Cannot perform this action with in-app purchase payment method. " +
|
|
||||||
"Contact support.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return customer;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetCardPaymentMethodId(long invoiceAmountDue, Customer customer, string cardPaymentMethodId)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (invoiceAmountDue <= 0 || customer.Metadata.ContainsKey("btCustomerId")) return cardPaymentMethodId;
|
|
||||||
var hasDefaultCardPaymentMethod = customer.InvoiceSettings?.DefaultPaymentMethod?.Type == "card";
|
|
||||||
var hasDefaultValidSource = customer.DefaultSource != null &&
|
|
||||||
(customer.DefaultSource is Stripe.Card ||
|
|
||||||
customer.DefaultSource is Stripe.BankAccount);
|
|
||||||
if (hasDefaultCardPaymentMethod || hasDefaultValidSource) return cardPaymentMethodId;
|
|
||||||
cardPaymentMethodId = GetLatestCardPaymentMethod(customer.Id)?.Id;
|
|
||||||
if (cardPaymentMethodId == null)
|
|
||||||
{
|
|
||||||
throw new BadRequestException("No payment method is available.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
throw new BadRequestException("No payment method is available.");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return cardPaymentMethodId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<Invoice> GetUpcomingInvoiceAsync(ISubscriber subscriber, List<InvoiceSubscriptionItemOptions> subItemOptions)
|
|
||||||
{
|
|
||||||
var upcomingPreview = await _stripeAdapter.InvoiceUpcomingAsync(new Stripe.UpcomingInvoiceOptions
|
|
||||||
{
|
|
||||||
Customer = subscriber.GatewayCustomerId,
|
|
||||||
Subscription = subscriber.GatewaySubscriptionId,
|
|
||||||
SubscriptionItems = subItemOptions
|
|
||||||
});
|
|
||||||
return upcomingPreview;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task RestoreInvoiceItemsAsync(Invoice invoice, Customer customer, IEnumerable<InvoiceItem> pendingInvoiceItems)
|
|
||||||
{
|
|
||||||
invoice = await _stripeAdapter.InvoiceVoidInvoiceAsync(invoice.Id, new Stripe.InvoiceVoidOptions());
|
|
||||||
if (invoice.StartingBalance != 0)
|
|
||||||
{
|
|
||||||
await _stripeAdapter.CustomerUpdateAsync(customer.Id,
|
|
||||||
new Stripe.CustomerUpdateOptions { Balance = customer.Balance });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore invoice items that were brought in
|
|
||||||
foreach (var item in pendingInvoiceItems)
|
|
||||||
{
|
|
||||||
var i = new Stripe.InvoiceItemCreateOptions
|
|
||||||
{
|
|
||||||
Currency = item.Currency,
|
|
||||||
Description = item.Description,
|
|
||||||
Customer = item.CustomerId,
|
|
||||||
Subscription = item.SubscriptionId,
|
|
||||||
Discountable = item.Discountable,
|
|
||||||
Metadata = item.Metadata,
|
|
||||||
Quantity = item.Proration ? 1 : item.Quantity,
|
|
||||||
UnitAmount = item.UnitAmount
|
|
||||||
};
|
|
||||||
await _stripeAdapter.InvoiceItemCreateAsync(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task InvoicePayAsync(InvoicePayOptions invoicePayOptions, Invoice invoice, string paymentIntentClientSecret)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _stripeAdapter.InvoicePayAsync(invoice.Id, invoicePayOptions);
|
|
||||||
}
|
|
||||||
catch (Stripe.StripeException e)
|
|
||||||
{
|
|
||||||
if (e.HttpStatusCode == System.Net.HttpStatusCode.PaymentRequired &&
|
|
||||||
e.StripeError?.Code == "invoice_payment_intent_requires_action")
|
|
||||||
{
|
|
||||||
// SCA required, get intent client secret
|
|
||||||
var invoiceGetOptions = new Stripe.InvoiceGetOptions();
|
|
||||||
invoiceGetOptions.AddExpand("payment_intent");
|
|
||||||
invoice = await _stripeAdapter.InvoiceGetAsync(invoice.Id, invoiceGetOptions);
|
|
||||||
paymentIntentClientSecret = invoice?.PaymentIntent?.ClientSecret;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new GatewayException("Unable to pay invoice.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task CreateBrainTreeTransactionRequestAsync(ISubscriber subscriber, Invoice invoice, Customer customer,
|
|
||||||
InvoicePayOptions invoicePayOptions, string cardPaymentMethodId, Braintree.Transaction braintreeTransaction)
|
|
||||||
{
|
|
||||||
if (invoice.AmountDue > 0)
|
|
||||||
{
|
|
||||||
if (customer?.Metadata?.ContainsKey("btCustomerId") ?? false)
|
|
||||||
{
|
|
||||||
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
|
|
||||||
{
|
|
||||||
SubmitForSettlement = true,
|
|
||||||
PayPal = new Braintree.TransactionOptionsPayPalRequest
|
|
||||||
{
|
|
||||||
CustomField = $"{subscriber.BraintreeIdField()}:{subscriber.Id}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
CustomFields = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
[subscriber.BraintreeIdField()] = subscriber.Id.ToString()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!transactionResult.IsSuccess())
|
|
||||||
{
|
|
||||||
throw new GatewayException("Failed to charge PayPal customer.");
|
|
||||||
}
|
|
||||||
|
|
||||||
braintreeTransaction = transactionResult.Target;
|
|
||||||
await _stripeAdapter.InvoiceUpdateAsync(invoice.Id, new Stripe.InvoiceUpdateOptions
|
|
||||||
{
|
|
||||||
Metadata = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
["btTransactionId"] = braintreeTransaction.Id,
|
|
||||||
["btPayPalTransactionId"] =
|
|
||||||
braintreeTransaction.PayPalDetails.AuthorizationId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
invoicePayOptions.OffSession = true;
|
|
||||||
invoicePayOptions.PaymentMethod = cardPaymentMethodId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<Invoice> CreateInvoiceAsync(ISubscriber subscriber, string cardPaymentMethodId)
|
|
||||||
{
|
|
||||||
Invoice invoice;
|
|
||||||
invoice = await _stripeAdapter.InvoiceCreateAsync(new Stripe.InvoiceCreateOptions
|
|
||||||
{
|
|
||||||
CollectionMethod = "send_invoice",
|
|
||||||
DaysUntilDue = 1,
|
|
||||||
Customer = subscriber.GatewayCustomerId,
|
|
||||||
Subscription = subscriber.GatewaySubscriptionId,
|
|
||||||
DefaultPaymentMethod = cardPaymentMethodId
|
|
||||||
});
|
|
||||||
return invoice;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task CreateInvoiceItemsAsync(ISubscriber subscriber, IEnumerable<InvoiceLineItem> itemsForInvoice,
|
|
||||||
PendingInoviceItems pendingInvoiceItems, List<InvoiceItem> createdInvoiceItems)
|
|
||||||
{
|
|
||||||
foreach (var invoiceLineItem in itemsForInvoice)
|
|
||||||
{
|
|
||||||
if (pendingInvoiceItems.PendingInvoiceItemsDict.ContainsKey(invoiceLineItem.Id))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var invoiceItem = await _stripeAdapter.InvoiceItemCreateAsync(new Stripe.InvoiceItemCreateOptions
|
|
||||||
{
|
|
||||||
Currency = invoiceLineItem.Currency,
|
|
||||||
Description = invoiceLineItem.Description,
|
|
||||||
Customer = subscriber.GatewayCustomerId,
|
|
||||||
Subscription = invoiceLineItem.Subscription,
|
|
||||||
Discountable = invoiceLineItem.Discountable,
|
|
||||||
Amount = invoiceLineItem.Amount
|
|
||||||
});
|
|
||||||
createdInvoiceItems.Add(invoiceItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task CancelSubscriptionAsync(ISubscriber subscriber, bool endOfPeriod = false,
|
public async Task CancelSubscriptionAsync(ISubscriber subscriber, bool endOfPeriod = false,
|
||||||
bool skipInAppPurchaseCheck = false)
|
bool skipInAppPurchaseCheck = false)
|
||||||
{
|
{
|
||||||
|
@ -739,300 +739,4 @@ public class StripePaymentServiceTests
|
|||||||
|
|
||||||
Assert.Null(result);
|
Assert.Null(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async Task PreviewUpcomingInvoiceAndPayAsync_WithInAppPaymentMethod_ThrowsBadRequestException(SutProvider<StripePaymentService> sutProvider,
|
|
||||||
Organization subscriber, List<Stripe.InvoiceSubscriptionItemOptions> subItemOptions)
|
|
||||||
{
|
|
||||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
|
||||||
stripeAdapter.CustomerGetAsync(Arg.Any<string>(), Arg.Any<Stripe.CustomerGetOptions>())
|
|
||||||
.Returns(new Stripe.Customer { Metadata = new Dictionary<string, string> { { "appleReceipt", "dummyData" } } });
|
|
||||||
|
|
||||||
var ex = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.PreviewUpcomingInvoiceAndPayAsync(subscriber, subItemOptions));
|
|
||||||
Assert.Equal("Cannot perform this action with in-app purchase payment method. Contact support.", ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async void PreviewUpcomingInvoiceAndPayAsync_UpcomingInvoiceBelowThreshold_DoesNotInvoiceNow(SutProvider<StripePaymentService> sutProvider,
|
|
||||||
Organization subscriber, List<Stripe.InvoiceSubscriptionItemOptions> subItemOptions)
|
|
||||||
{
|
|
||||||
var prorateThreshold = 50000;
|
|
||||||
var invoiceAmountBelowThreshold = prorateThreshold - 100;
|
|
||||||
var customer = MockStripeCustomer(subscriber);
|
|
||||||
sutProvider.GetDependency<IStripeAdapter>().CustomerGetAsync(default, default).ReturnsForAnyArgs(customer);
|
|
||||||
var invoiceItem = MockInoviceItemList(subscriber, "planId", invoiceAmountBelowThreshold, customer);
|
|
||||||
sutProvider.GetDependency<IStripeAdapter>().InvoiceItemListAsync(new Stripe.InvoiceItemListOptions
|
|
||||||
{
|
|
||||||
Customer = subscriber.GatewayCustomerId
|
|
||||||
}).ReturnsForAnyArgs(invoiceItem);
|
|
||||||
|
|
||||||
var invoiceLineItem = CreateInvoiceLineTime(subscriber, "planId", invoiceAmountBelowThreshold);
|
|
||||||
sutProvider.GetDependency<IStripeAdapter>().InvoiceUpcomingAsync(new Stripe.UpcomingInvoiceOptions
|
|
||||||
{
|
|
||||||
Customer = subscriber.GatewayCustomerId,
|
|
||||||
Subscription = subscriber.GatewaySubscriptionId,
|
|
||||||
SubscriptionItems = subItemOptions
|
|
||||||
}).ReturnsForAnyArgs(invoiceLineItem);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<IStripeAdapter>().InvoiceCreateAsync(Arg.Is<Stripe.InvoiceCreateOptions>(options =>
|
|
||||||
options.CollectionMethod == "send_invoice" &&
|
|
||||||
options.DaysUntilDue == 1 &&
|
|
||||||
options.Customer == subscriber.GatewayCustomerId &&
|
|
||||||
options.Subscription == subscriber.GatewaySubscriptionId &&
|
|
||||||
options.DefaultPaymentMethod == customer.InvoiceSettings.DefaultPaymentMethod.Id
|
|
||||||
)).ReturnsForAnyArgs(new Stripe.Invoice
|
|
||||||
{
|
|
||||||
Id = "mockInvoiceId",
|
|
||||||
CollectionMethod = "send_invoice",
|
|
||||||
DueDate = DateTime.Now.AddDays(1),
|
|
||||||
Customer = customer,
|
|
||||||
Subscription = new Stripe.Subscription
|
|
||||||
{
|
|
||||||
Id = "mockSubscriptionId",
|
|
||||||
Customer = customer,
|
|
||||||
Status = "active",
|
|
||||||
CurrentPeriodStart = DateTime.UtcNow,
|
|
||||||
CurrentPeriodEnd = DateTime.UtcNow.AddMonths(1),
|
|
||||||
CollectionMethod = "charge_automatically",
|
|
||||||
},
|
|
||||||
DefaultPaymentMethod = customer.InvoiceSettings.DefaultPaymentMethod,
|
|
||||||
AmountDue = invoiceAmountBelowThreshold,
|
|
||||||
Currency = "usd",
|
|
||||||
Status = "draft",
|
|
||||||
});
|
|
||||||
|
|
||||||
var result = await sutProvider.Sut.PreviewUpcomingInvoiceAndPayAsync(subscriber, new List<Stripe.InvoiceSubscriptionItemOptions>(), prorateThreshold);
|
|
||||||
|
|
||||||
Assert.False(result.IsInvoicedNow);
|
|
||||||
Assert.Null(result.PaymentIntentClientSecret);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async void PreviewUpcomingInvoiceAndPayAsync_NoPaymentMethod_ThrowsBadRequestException(SutProvider<StripePaymentService> sutProvider,
|
|
||||||
Organization subscriber, List<Stripe.InvoiceSubscriptionItemOptions> subItemOptions, string planId)
|
|
||||||
{
|
|
||||||
var prorateThreshold = 120000;
|
|
||||||
var invoiceAmountBelowThreshold = prorateThreshold;
|
|
||||||
var customer = new Stripe.Customer
|
|
||||||
{
|
|
||||||
Metadata = new Dictionary<string, string>(),
|
|
||||||
Id = subscriber.GatewayCustomerId,
|
|
||||||
DefaultSource = null,
|
|
||||||
InvoiceSettings = new Stripe.CustomerInvoiceSettings
|
|
||||||
{
|
|
||||||
DefaultPaymentMethod = null
|
|
||||||
}
|
|
||||||
};
|
|
||||||
sutProvider.GetDependency<IStripeAdapter>().CustomerGetAsync(default, default).ReturnsForAnyArgs(customer);
|
|
||||||
var invoiceItem = MockInoviceItemList(subscriber, planId, invoiceAmountBelowThreshold, customer);
|
|
||||||
sutProvider.GetDependency<IStripeAdapter>().InvoiceItemListAsync(new Stripe.InvoiceItemListOptions
|
|
||||||
{
|
|
||||||
Customer = subscriber.GatewayCustomerId
|
|
||||||
}).ReturnsForAnyArgs(invoiceItem);
|
|
||||||
|
|
||||||
var invoiceLineItem = CreateInvoiceLineTime(subscriber, planId, invoiceAmountBelowThreshold);
|
|
||||||
sutProvider.GetDependency<IStripeAdapter>().InvoiceUpcomingAsync(new Stripe.UpcomingInvoiceOptions
|
|
||||||
{
|
|
||||||
Customer = subscriber.GatewayCustomerId,
|
|
||||||
Subscription = subscriber.GatewaySubscriptionId,
|
|
||||||
SubscriptionItems = subItemOptions
|
|
||||||
}).ReturnsForAnyArgs(invoiceLineItem);
|
|
||||||
|
|
||||||
var ex = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.PreviewUpcomingInvoiceAndPayAsync(subscriber, subItemOptions));
|
|
||||||
Assert.Equal("No payment method is available.", ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async void PreviewUpcomingInvoiceAndPayAsync_UpcomingInvoiceAboveThreshold_DoesInvoiceNow(SutProvider<StripePaymentService> sutProvider,
|
|
||||||
Organization subscriber, List<Stripe.InvoiceSubscriptionItemOptions> subItemOptions, string planId)
|
|
||||||
{
|
|
||||||
var prorateThreshold = 50000;
|
|
||||||
var invoiceAmountBelowThreshold = 100000;
|
|
||||||
var customer = MockStripeCustomer(subscriber);
|
|
||||||
sutProvider.GetDependency<IStripeAdapter>().CustomerGetAsync(default, default).ReturnsForAnyArgs(customer);
|
|
||||||
var invoiceItem = MockInoviceItemList(subscriber, planId, invoiceAmountBelowThreshold, customer);
|
|
||||||
sutProvider.GetDependency<IStripeAdapter>().InvoiceItemListAsync(new Stripe.InvoiceItemListOptions
|
|
||||||
{
|
|
||||||
Customer = subscriber.GatewayCustomerId
|
|
||||||
}).ReturnsForAnyArgs(invoiceItem);
|
|
||||||
|
|
||||||
var invoiceLineItem = CreateInvoiceLineTime(subscriber, planId, invoiceAmountBelowThreshold);
|
|
||||||
sutProvider.GetDependency<IStripeAdapter>().InvoiceUpcomingAsync(new Stripe.UpcomingInvoiceOptions
|
|
||||||
{
|
|
||||||
Customer = subscriber.GatewayCustomerId,
|
|
||||||
Subscription = subscriber.GatewaySubscriptionId,
|
|
||||||
SubscriptionItems = subItemOptions
|
|
||||||
}).ReturnsForAnyArgs(invoiceLineItem);
|
|
||||||
|
|
||||||
var invoice = MockInVoice(customer, invoiceAmountBelowThreshold);
|
|
||||||
sutProvider.GetDependency<IStripeAdapter>().InvoiceCreateAsync(Arg.Is<Stripe.InvoiceCreateOptions>(options =>
|
|
||||||
options.CollectionMethod == "send_invoice" &&
|
|
||||||
options.DaysUntilDue == 1 &&
|
|
||||||
options.Customer == subscriber.GatewayCustomerId &&
|
|
||||||
options.Subscription == subscriber.GatewaySubscriptionId &&
|
|
||||||
options.DefaultPaymentMethod == customer.InvoiceSettings.DefaultPaymentMethod.Id
|
|
||||||
)).ReturnsForAnyArgs(invoice);
|
|
||||||
|
|
||||||
var result = await sutProvider.Sut.PreviewUpcomingInvoiceAndPayAsync(subscriber, new List<Stripe.InvoiceSubscriptionItemOptions>(), prorateThreshold);
|
|
||||||
|
|
||||||
await sutProvider.GetDependency<IStripeAdapter>().Received(1).InvoicePayAsync(invoice.Id,
|
|
||||||
Arg.Is<Stripe.InvoicePayOptions>((options =>
|
|
||||||
options.OffSession == true
|
|
||||||
)));
|
|
||||||
|
|
||||||
|
|
||||||
Assert.True(result.IsInvoicedNow);
|
|
||||||
Assert.Null(result.PaymentIntentClientSecret);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Stripe.Invoice MockInVoice(Stripe.Customer customer, int invoiceAmountBelowThreshold) =>
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Id = "mockInvoiceId",
|
|
||||||
CollectionMethod = "send_invoice",
|
|
||||||
DueDate = DateTime.Now.AddDays(1),
|
|
||||||
Customer = customer,
|
|
||||||
Subscription = new Stripe.Subscription
|
|
||||||
{
|
|
||||||
Id = "mockSubscriptionId",
|
|
||||||
Customer = customer,
|
|
||||||
Status = "active",
|
|
||||||
CurrentPeriodStart = DateTime.UtcNow,
|
|
||||||
CurrentPeriodEnd = DateTime.UtcNow.AddMonths(1),
|
|
||||||
CollectionMethod = "charge_automatically",
|
|
||||||
},
|
|
||||||
DefaultPaymentMethod = customer.InvoiceSettings.DefaultPaymentMethod,
|
|
||||||
AmountDue = invoiceAmountBelowThreshold,
|
|
||||||
Currency = "usd",
|
|
||||||
Status = "draft",
|
|
||||||
};
|
|
||||||
|
|
||||||
private static List<Stripe.InvoiceItem> MockInoviceItemList(Organization subscriber, string planId, int invoiceAmountBelowThreshold, Stripe.Customer customer) =>
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
new Stripe.InvoiceItem
|
|
||||||
{
|
|
||||||
Id = "ii_1234567890",
|
|
||||||
Amount = invoiceAmountBelowThreshold,
|
|
||||||
Currency = "usd",
|
|
||||||
CustomerId = subscriber.GatewayCustomerId,
|
|
||||||
Description = "Sample invoice item 1",
|
|
||||||
Date = DateTime.UtcNow,
|
|
||||||
Discountable = true,
|
|
||||||
InvoiceId = "548458365"
|
|
||||||
},
|
|
||||||
new Stripe.InvoiceItem
|
|
||||||
{
|
|
||||||
Id = "ii_0987654321",
|
|
||||||
Amount = invoiceAmountBelowThreshold,
|
|
||||||
Currency = "usd",
|
|
||||||
CustomerId = customer.Id,
|
|
||||||
Description = "Sample invoice item 2",
|
|
||||||
Date = DateTime.UtcNow.AddDays(-5),
|
|
||||||
Discountable = false,
|
|
||||||
InvoiceId = null,
|
|
||||||
Proration = true,
|
|
||||||
Plan = new Stripe.Plan
|
|
||||||
{
|
|
||||||
Id = planId,
|
|
||||||
Amount = invoiceAmountBelowThreshold,
|
|
||||||
Currency = "usd",
|
|
||||||
Interval = "month",
|
|
||||||
IntervalCount = 1,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private static Stripe.Customer MockStripeCustomer(Organization subscriber)
|
|
||||||
{
|
|
||||||
var customer = new Stripe.Customer
|
|
||||||
{
|
|
||||||
Metadata = new Dictionary<string, string>(),
|
|
||||||
Id = subscriber.GatewayCustomerId,
|
|
||||||
DefaultSource = new Stripe.Card
|
|
||||||
{
|
|
||||||
Id = "card_12345",
|
|
||||||
Last4 = "1234",
|
|
||||||
Brand = "Visa",
|
|
||||||
ExpYear = 2025,
|
|
||||||
ExpMonth = 12
|
|
||||||
},
|
|
||||||
InvoiceSettings = new Stripe.CustomerInvoiceSettings
|
|
||||||
{
|
|
||||||
DefaultPaymentMethod = new Stripe.PaymentMethod
|
|
||||||
{
|
|
||||||
Id = "pm_12345",
|
|
||||||
Type = "card",
|
|
||||||
Card = new Stripe.PaymentMethodCard
|
|
||||||
{
|
|
||||||
Last4 = "1234",
|
|
||||||
Brand = "Visa",
|
|
||||||
ExpYear = 2025,
|
|
||||||
ExpMonth = 12
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return customer;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Stripe.Invoice CreateInvoiceLineTime(Organization subscriber, string planId, int invoiceAmountBelowThreshold) =>
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
AmountDue = invoiceAmountBelowThreshold,
|
|
||||||
AmountPaid = 0,
|
|
||||||
AmountRemaining = invoiceAmountBelowThreshold,
|
|
||||||
CustomerId = subscriber.GatewayCustomerId,
|
|
||||||
SubscriptionId = subscriber.GatewaySubscriptionId,
|
|
||||||
ApplicationFeeAmount = 0,
|
|
||||||
Currency = "usd",
|
|
||||||
Description = "Upcoming Invoice",
|
|
||||||
Discount = null,
|
|
||||||
DueDate = DateTime.UtcNow.AddDays(1),
|
|
||||||
EndingBalance = 0,
|
|
||||||
Number = "INV12345",
|
|
||||||
Paid = false,
|
|
||||||
PeriodStart = DateTime.UtcNow,
|
|
||||||
PeriodEnd = DateTime.UtcNow.AddMonths(1),
|
|
||||||
ReceiptNumber = null,
|
|
||||||
StartingBalance = 0,
|
|
||||||
Status = "draft",
|
|
||||||
Id = "ii_0987654321",
|
|
||||||
Total = invoiceAmountBelowThreshold,
|
|
||||||
Lines = new Stripe.StripeList<Stripe.InvoiceLineItem>
|
|
||||||
{
|
|
||||||
Data = new List<Stripe.InvoiceLineItem>
|
|
||||||
{
|
|
||||||
new Stripe.InvoiceLineItem
|
|
||||||
{
|
|
||||||
Amount = invoiceAmountBelowThreshold,
|
|
||||||
Currency = "usd",
|
|
||||||
Description = "Sample line item",
|
|
||||||
Id = "ii_0987654321",
|
|
||||||
Livemode = false,
|
|
||||||
Object = "line_item",
|
|
||||||
Discountable = false,
|
|
||||||
Period = new Stripe.InvoiceLineItemPeriod()
|
|
||||||
{
|
|
||||||
Start = DateTime.UtcNow,
|
|
||||||
End = DateTime.UtcNow.AddMonths(1)
|
|
||||||
},
|
|
||||||
Plan = new Stripe.Plan
|
|
||||||
{
|
|
||||||
Id = planId,
|
|
||||||
Amount = invoiceAmountBelowThreshold,
|
|
||||||
Currency = "usd",
|
|
||||||
Interval = "month",
|
|
||||||
IntervalCount = 1,
|
|
||||||
},
|
|
||||||
Proration = true,
|
|
||||||
Quantity = 1,
|
|
||||||
Subscription = subscriber.GatewaySubscriptionId,
|
|
||||||
SubscriptionItem = "si_12345",
|
|
||||||
Type = "subscription",
|
|
||||||
UnitAmountExcludingTax = invoiceAmountBelowThreshold,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user