mirror of
https://github.com/bitwarden/server
synced 2025-12-06 00:03:34 +00:00
[PM-23687] Support free organizations on Payment Details page (#6084)
* Resolve JSON serialization bug in OneOf converters and organize pricing models * Support free organizations for payment method and billing address flows * Run dotnet format
This commit is contained in:
@@ -3,6 +3,7 @@ using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Payment.Commands;
|
||||
using Bit.Core.Billing.Payment.Models;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Test.Billing.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -16,14 +17,15 @@ using static StripeConstants;
|
||||
|
||||
public class UpdateBillingAddressCommandTests
|
||||
{
|
||||
private readonly IStripeAdapter _stripeAdapter;
|
||||
private readonly ISubscriberService _subscriberService = Substitute.For<ISubscriberService>();
|
||||
private readonly IStripeAdapter _stripeAdapter = Substitute.For<IStripeAdapter>();
|
||||
private readonly UpdateBillingAddressCommand _command;
|
||||
|
||||
public UpdateBillingAddressCommandTests()
|
||||
{
|
||||
_stripeAdapter = Substitute.For<IStripeAdapter>();
|
||||
_command = new UpdateBillingAddressCommand(
|
||||
Substitute.For<ILogger<UpdateBillingAddressCommand>>(),
|
||||
_subscriberService,
|
||||
_stripeAdapter);
|
||||
}
|
||||
|
||||
@@ -86,6 +88,66 @@ public class UpdateBillingAddressCommandTests
|
||||
Arg.Is<SubscriptionUpdateOptions>(options => options.AutomaticTax.Enabled == true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Run_PersonalOrganization_NoCurrentCustomer_MakesCorrectInvocations_ReturnsBillingAddress()
|
||||
{
|
||||
var organization = new Organization
|
||||
{
|
||||
PlanType = PlanType.FamiliesAnnually,
|
||||
GatewaySubscriptionId = "sub_123"
|
||||
};
|
||||
|
||||
var input = new BillingAddress
|
||||
{
|
||||
Country = "US",
|
||||
PostalCode = "12345",
|
||||
Line1 = "123 Main St.",
|
||||
Line2 = "Suite 100",
|
||||
City = "New York",
|
||||
State = "NY"
|
||||
};
|
||||
|
||||
var customer = new Customer
|
||||
{
|
||||
Address = new Address
|
||||
{
|
||||
Country = "US",
|
||||
PostalCode = "12345",
|
||||
Line1 = "123 Main St.",
|
||||
Line2 = "Suite 100",
|
||||
City = "New York",
|
||||
State = "NY"
|
||||
},
|
||||
Subscriptions = new StripeList<Subscription>
|
||||
{
|
||||
Data =
|
||||
[
|
||||
new Subscription
|
||||
{
|
||||
Id = organization.GatewaySubscriptionId,
|
||||
AutomaticTax = new SubscriptionAutomaticTax { Enabled = false }
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
_stripeAdapter.CustomerUpdateAsync(organization.GatewayCustomerId, Arg.Is<CustomerUpdateOptions>(options =>
|
||||
options.Address.Matches(input) &&
|
||||
options.HasExpansions("subscriptions")
|
||||
)).Returns(customer);
|
||||
|
||||
var result = await _command.Run(organization, input);
|
||||
|
||||
Assert.True(result.IsT0);
|
||||
var output = result.AsT0;
|
||||
Assert.Equivalent(input, output);
|
||||
|
||||
await _subscriberService.Received(1).CreateStripeCustomer(organization);
|
||||
|
||||
await _stripeAdapter.Received(1).SubscriptionUpdateAsync(organization.GatewaySubscriptionId,
|
||||
Arg.Is<SubscriptionUpdateOptions>(options => options.AutomaticTax.Enabled == true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Run_BusinessOrganization_MakesCorrectInvocations_ReturnsBillingAddress()
|
||||
{
|
||||
|
||||
@@ -45,7 +45,8 @@ public class UpdatePaymentMethodCommandTests
|
||||
{
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = Guid.NewGuid()
|
||||
Id = Guid.NewGuid(),
|
||||
GatewayCustomerId = "cus_123"
|
||||
};
|
||||
|
||||
var customer = new Customer
|
||||
@@ -100,13 +101,75 @@ public class UpdatePaymentMethodCommandTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Run_BankAccount_StripeToPayPal_MakesCorrectInvocations_ReturnsMaskedBankAccount()
|
||||
public async Task Run_BankAccount_NoCurrentCustomer_MakesCorrectInvocations_ReturnsMaskedBankAccount()
|
||||
{
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = Guid.NewGuid()
|
||||
};
|
||||
|
||||
var customer = new Customer
|
||||
{
|
||||
Address = new Address
|
||||
{
|
||||
Country = "US",
|
||||
PostalCode = "12345"
|
||||
},
|
||||
Metadata = new Dictionary<string, string>()
|
||||
};
|
||||
|
||||
_subscriberService.GetCustomer(organization).Returns(customer);
|
||||
|
||||
const string token = "TOKEN";
|
||||
|
||||
var setupIntent = new SetupIntent
|
||||
{
|
||||
Id = "seti_123",
|
||||
PaymentMethod =
|
||||
new PaymentMethod
|
||||
{
|
||||
Type = "us_bank_account",
|
||||
UsBankAccount = new PaymentMethodUsBankAccount { BankName = "Chase", Last4 = "9999" }
|
||||
},
|
||||
NextAction = new SetupIntentNextAction
|
||||
{
|
||||
VerifyWithMicrodeposits = new SetupIntentNextActionVerifyWithMicrodeposits()
|
||||
},
|
||||
Status = "requires_action"
|
||||
};
|
||||
|
||||
_stripeAdapter.SetupIntentList(Arg.Is<SetupIntentListOptions>(options =>
|
||||
options.PaymentMethod == token && options.HasExpansions("data.payment_method"))).Returns([setupIntent]);
|
||||
|
||||
var result = await _command.Run(organization,
|
||||
new TokenizedPaymentMethod { Type = TokenizablePaymentMethodType.BankAccount, Token = token }, new BillingAddress
|
||||
{
|
||||
Country = "US",
|
||||
PostalCode = "12345"
|
||||
});
|
||||
|
||||
Assert.True(result.IsT0);
|
||||
var maskedPaymentMethod = result.AsT0;
|
||||
Assert.True(maskedPaymentMethod.IsT0);
|
||||
var maskedBankAccount = maskedPaymentMethod.AsT0;
|
||||
Assert.Equal("Chase", maskedBankAccount.BankName);
|
||||
Assert.Equal("9999", maskedBankAccount.Last4);
|
||||
Assert.False(maskedBankAccount.Verified);
|
||||
|
||||
await _subscriberService.Received(1).CreateStripeCustomer(organization);
|
||||
|
||||
await _setupIntentCache.Received(1).Set(organization.Id, setupIntent.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Run_BankAccount_StripeToPayPal_MakesCorrectInvocations_ReturnsMaskedBankAccount()
|
||||
{
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
GatewayCustomerId = "cus_123"
|
||||
};
|
||||
|
||||
var customer = new Customer
|
||||
{
|
||||
Address = new Address
|
||||
@@ -170,7 +233,8 @@ public class UpdatePaymentMethodCommandTests
|
||||
{
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = Guid.NewGuid()
|
||||
Id = Guid.NewGuid(),
|
||||
GatewayCustomerId = "cus_123"
|
||||
};
|
||||
|
||||
var customer = new Customer
|
||||
@@ -227,7 +291,8 @@ public class UpdatePaymentMethodCommandTests
|
||||
{
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = Guid.NewGuid()
|
||||
Id = Guid.NewGuid(),
|
||||
GatewayCustomerId = "cus_123"
|
||||
};
|
||||
|
||||
var customer = new Customer
|
||||
@@ -282,7 +347,8 @@ public class UpdatePaymentMethodCommandTests
|
||||
{
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = Guid.NewGuid()
|
||||
Id = Guid.NewGuid(),
|
||||
GatewayCustomerId = "cus_123"
|
||||
};
|
||||
|
||||
var customer = new Customer
|
||||
@@ -343,7 +409,8 @@ public class UpdatePaymentMethodCommandTests
|
||||
{
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = Guid.NewGuid()
|
||||
Id = Guid.NewGuid(),
|
||||
GatewayCustomerId = "cus_123"
|
||||
};
|
||||
|
||||
var customer = new Customer
|
||||
|
||||
@@ -25,6 +25,27 @@ public class MaskedPaymentMethodTests
|
||||
Assert.Equivalent(input.AsT0, output.AsT0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Write_Read_BankAccount_WithOptions_Succeeds()
|
||||
{
|
||||
MaskedPaymentMethod input = new MaskedBankAccount
|
||||
{
|
||||
BankName = "Chase",
|
||||
Last4 = "9999",
|
||||
Verified = true
|
||||
};
|
||||
|
||||
var jsonSerializerOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
|
||||
|
||||
var json = JsonSerializer.Serialize(input, jsonSerializerOptions);
|
||||
|
||||
var output = JsonSerializer.Deserialize<MaskedPaymentMethod>(json, jsonSerializerOptions);
|
||||
Assert.NotNull(output);
|
||||
Assert.True(output.IsT0);
|
||||
|
||||
Assert.Equivalent(input.AsT0, output.AsT0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Write_Read_Card_Succeeds()
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ using Bit.Core.Test.Billing.Extensions;
|
||||
using Braintree;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ReturnsExtensions;
|
||||
using Stripe;
|
||||
using Xunit;
|
||||
using Customer = Stripe.Customer;
|
||||
@@ -35,6 +36,23 @@ public class GetPaymentMethodQueryTests
|
||||
_subscriberService);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Run_NoCustomer_ReturnsNull()
|
||||
{
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = Guid.NewGuid()
|
||||
};
|
||||
|
||||
_subscriberService.GetCustomer(organization,
|
||||
Arg.Is<CustomerGetOptions>(options =>
|
||||
options.HasExpansions("default_source", "invoice_settings.default_payment_method"))).ReturnsNull();
|
||||
|
||||
var maskedPaymentMethod = await _query.Run(organization);
|
||||
|
||||
Assert.Null(maskedPaymentMethod);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Run_NoPaymentMethod_ReturnsNull()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user