1
0
mirror of https://github.com/bitwarden/server synced 2026-02-26 01:13:35 +00:00

fix(billing): return null subscription when resource_missing (#7068)

This commit is contained in:
Alex Morask
2026-02-25 08:25:18 -06:00
committed by GitHub
parent d57428b684
commit 5ac8293a55
4 changed files with 63 additions and 19 deletions

View File

@@ -31,7 +31,7 @@ public abstract class BaseBillingCommand<T>(
{
return await function();
}
catch (StripeException stripeException) when (ErrorCodes.Get().Contains(stripeException.StripeError.Code))
catch (StripeException stripeException) when (ErrorCodes.InputErrors().Contains(stripeException.StripeError.Code))
{
return stripeException.StripeError.Code switch
{

View File

@@ -1,6 +1,4 @@
using System.Reflection;
namespace Bit.Core.Billing.Constants;
namespace Bit.Core.Billing.Constants;
public static class StripeConstants
{
@@ -51,14 +49,18 @@ public static class StripeConstants
public const string PaymentMethodMicroDepositVerificationAttemptsExceeded = "payment_method_microdeposit_verification_attempts_exceeded";
public const string PaymentMethodMicroDepositVerificationDescriptorCodeMismatch = "payment_method_microdeposit_verification_descriptor_code_mismatch";
public const string PaymentMethodMicroDepositVerificationTimeout = "payment_method_microdeposit_verification_timeout";
public const string ResourceMissing = "resource_missing";
public const string TaxIdInvalid = "tax_id_invalid";
public static string[] Get() =>
typeof(ErrorCodes)
.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)
.Where(fi => fi is { IsLiteral: true, IsInitOnly: false } && fi.FieldType == typeof(string))
.Select(fi => (string)fi.GetValue(null)!)
.ToArray();
public static string[] InputErrors() =>
[
CustomerTaxLocationInvalid,
InvoiceUpcomingNone,
PaymentMethodMicroDepositVerificationAttemptsExceeded,
PaymentMethodMicroDepositVerificationDescriptorCodeMismatch,
PaymentMethodMicroDepositVerificationTimeout,
TaxIdInvalid
];
}
public static class InvoiceStatus

View File

@@ -46,16 +46,12 @@ public class GetBitwardenSubscriptionQuery(
return null;
}
var subscription = await stripeAdapter.GetSubscriptionAsync(user.GatewaySubscriptionId, new SubscriptionGetOptions
var subscription = await FetchSubscriptionAsync(user);
if (subscription == null)
{
Expand =
[
"customer.discount.coupon.applies_to",
"discounts.coupon.applies_to",
"items.data.price.product",
"test_clock"
]
});
return null;
}
var cart = await GetPremiumCartAsync(subscription);
@@ -248,5 +244,27 @@ public class GetBitwardenSubscriptionQuery(
return (cartLevel.FirstOrDefault(), productLevel);
}
private async Task<Subscription?> FetchSubscriptionAsync(User user)
{
try
{
return await stripeAdapter.GetSubscriptionAsync(user.GatewaySubscriptionId, new SubscriptionGetOptions
{
Expand =
[
"customer.discount.coupon.applies_to",
"discounts.coupon.applies_to",
"items.data.price.product",
"test_clock"
]
});
}
catch (StripeException stripeException) when (stripeException.StripeError?.Code == ErrorCodes.ResourceMissing)
{
logger.LogError("Subscription ({SubscriptionID}) for User ({UserID}) was not found", user.GatewaySubscriptionId, user.Id);
return null;
}
}
#endregion
}

View File

@@ -55,6 +55,30 @@ public class GetBitwardenSubscriptionQueryTests
await _stripeAdapter.DidNotReceive().GetSubscriptionAsync(Arg.Any<string>(), Arg.Any<SubscriptionGetOptions>());
}
[Fact]
public async Task Run_StripeSubscriptionNotFound_ReturnsNull()
{
var user = CreateUser();
_stripeAdapter.GetSubscriptionAsync(user.GatewaySubscriptionId, Arg.Any<SubscriptionGetOptions>())
.ThrowsAsync(new StripeException { StripeError = new StripeError { Code = ErrorCodes.ResourceMissing } });
var result = await _query.Run(user);
Assert.Null(result);
}
[Fact]
public async Task Run_StripeExceptionNotResourceMissing_Throws()
{
var user = CreateUser();
_stripeAdapter.GetSubscriptionAsync(user.GatewaySubscriptionId, Arg.Any<SubscriptionGetOptions>())
.ThrowsAsync(new StripeException { StripeError = new StripeError { Code = "api_error" } });
await Assert.ThrowsAsync<StripeException>(() => _query.Run(user));
}
[Fact]
public async Task Run_IncompleteStatus_ReturnsBitwardenSubscriptionWithSuspension()
{