mirror of
https://github.com/bitwarden/server
synced 2025-12-25 20:53:16 +00:00
[PM-25088] - refactor premium purchase endpoint (#6262)
* [PM-25088] add feature flag for new premium subscription flow * [PM-25088] refactor premium endpoint * forgot the punctuation change in the test * [PM-25088] - pr feedback * [PM-25088] - pr feedback round two
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
using Bit.Api.Utilities;
|
||||
|
||||
namespace Bit.Api.Billing.Attributes;
|
||||
|
||||
public class PaymentMethodTypeValidationAttribute : StringMatchesAttribute
|
||||
{
|
||||
private static readonly string[] _acceptedValues = ["bankAccount", "card", "payPal"];
|
||||
|
||||
public PaymentMethodTypeValidationAttribute() : base(_acceptedValues)
|
||||
{
|
||||
ErrorMessage = $"Payment method type must be one of: {string.Join(", ", _acceptedValues)}";
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
#nullable enable
|
||||
using Bit.Api.Billing.Attributes;
|
||||
using Bit.Api.Billing.Models.Requests.Payment;
|
||||
using Bit.Api.Billing.Models.Requests.Premium;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Billing.Payment.Commands;
|
||||
using Bit.Core.Billing.Payment.Queries;
|
||||
using Bit.Core.Billing.Premium.Commands;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
@@ -16,6 +19,7 @@ namespace Bit.Api.Billing.Controllers.VNext;
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
public class AccountBillingVNextController(
|
||||
ICreateBitPayInvoiceForCreditCommand createBitPayInvoiceForCreditCommand,
|
||||
ICreatePremiumCloudHostedSubscriptionCommand createPremiumCloudHostedSubscriptionCommand,
|
||||
IGetCreditQuery getCreditQuery,
|
||||
IGetPaymentMethodQuery getPaymentMethodQuery,
|
||||
IUpdatePaymentMethodCommand updatePaymentMethodCommand) : BaseBillingController
|
||||
@@ -61,4 +65,17 @@ public class AccountBillingVNextController(
|
||||
var result = await updatePaymentMethodCommand.Run(user, paymentMethod, billingAddress);
|
||||
return Handle(result);
|
||||
}
|
||||
|
||||
[HttpPost("subscription")]
|
||||
[RequireFeature(FeatureFlagKeys.PM23385_UseNewPremiumFlow)]
|
||||
[InjectUser]
|
||||
public async Task<IResult> CreateSubscriptionAsync(
|
||||
[BindNever] User user,
|
||||
[FromBody] PremiumCloudHostedSubscriptionRequest request)
|
||||
{
|
||||
var (paymentMethod, billingAddress, additionalStorageGb) = request.ToDomain();
|
||||
var result = await createPremiumCloudHostedSubscriptionCommand.Run(
|
||||
user, paymentMethod, billingAddress, additionalStorageGb);
|
||||
return Handle(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#nullable enable
|
||||
using Bit.Api.Billing.Attributes;
|
||||
using Bit.Api.Billing.Models.Requests.Premium;
|
||||
using Bit.Api.Utilities;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Billing.Models.Business;
|
||||
using Bit.Core.Billing.Premium.Commands;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace Bit.Api.Billing.Controllers.VNext;
|
||||
|
||||
[Authorize("Application")]
|
||||
[Route("account/billing/vnext/self-host")]
|
||||
[SelfHosted(SelfHostedOnly = true)]
|
||||
public class SelfHostedAccountBillingController(
|
||||
ICreatePremiumSelfHostedSubscriptionCommand createPremiumSelfHostedSubscriptionCommand) : BaseBillingController
|
||||
{
|
||||
[HttpPost("license")]
|
||||
[RequireFeature(FeatureFlagKeys.PM23385_UseNewPremiumFlow)]
|
||||
[InjectUser]
|
||||
public async Task<IResult> UploadLicenseAsync(
|
||||
[BindNever] User user,
|
||||
PremiumSelfHostedSubscriptionRequest request)
|
||||
{
|
||||
var license = await ApiHelpers.ReadJsonFileFromBody<UserLicense>(HttpContext, request.License);
|
||||
if (license == null)
|
||||
{
|
||||
throw new BadRequestException("Invalid license.");
|
||||
}
|
||||
var result = await createPremiumSelfHostedSubscriptionCommand.Run(user, license);
|
||||
return Handle(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#nullable enable
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Api.Billing.Attributes;
|
||||
using Bit.Core.Billing.Payment.Models;
|
||||
|
||||
namespace Bit.Api.Billing.Models.Requests.Payment;
|
||||
|
||||
public class MinimalTokenizedPaymentMethodRequest
|
||||
{
|
||||
[Required]
|
||||
[PaymentMethodTypeValidation]
|
||||
public required string Type { get; set; }
|
||||
|
||||
[Required]
|
||||
public required string Token { get; set; }
|
||||
|
||||
public TokenizedPaymentMethod ToDomain()
|
||||
{
|
||||
return new TokenizedPaymentMethod
|
||||
{
|
||||
Type = TokenizablePaymentMethodTypeExtensions.From(Type),
|
||||
Token = Token
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#nullable enable
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Api.Utilities;
|
||||
using Bit.Api.Billing.Attributes;
|
||||
using Bit.Core.Billing.Payment.Models;
|
||||
|
||||
namespace Bit.Api.Billing.Models.Requests.Payment;
|
||||
@@ -8,8 +8,7 @@ namespace Bit.Api.Billing.Models.Requests.Payment;
|
||||
public class TokenizedPaymentMethodRequest
|
||||
{
|
||||
[Required]
|
||||
[StringMatches("bankAccount", "card", "payPal",
|
||||
ErrorMessage = "Payment method type must be one of: bankAccount, card, payPal")]
|
||||
[PaymentMethodTypeValidation]
|
||||
public required string Type { get; set; }
|
||||
|
||||
[Required]
|
||||
@@ -21,14 +20,7 @@ public class TokenizedPaymentMethodRequest
|
||||
{
|
||||
var paymentMethod = new TokenizedPaymentMethod
|
||||
{
|
||||
Type = Type switch
|
||||
{
|
||||
"bankAccount" => TokenizablePaymentMethodType.BankAccount,
|
||||
"card" => TokenizablePaymentMethodType.Card,
|
||||
"payPal" => TokenizablePaymentMethodType.PayPal,
|
||||
_ => throw new InvalidOperationException(
|
||||
$"Invalid value for {nameof(TokenizedPaymentMethod)}.{nameof(TokenizedPaymentMethod.Type)}")
|
||||
},
|
||||
Type = TokenizablePaymentMethodTypeExtensions.From(Type),
|
||||
Token = Token
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
#nullable enable
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Api.Billing.Models.Requests.Payment;
|
||||
using Bit.Core.Billing.Payment.Models;
|
||||
|
||||
namespace Bit.Api.Billing.Models.Requests.Premium;
|
||||
|
||||
public class PremiumCloudHostedSubscriptionRequest
|
||||
{
|
||||
[Required]
|
||||
public required MinimalTokenizedPaymentMethodRequest TokenizedPaymentMethod { get; set; }
|
||||
|
||||
[Required]
|
||||
public required MinimalBillingAddressRequest BillingAddress { get; set; }
|
||||
|
||||
[Range(0, 99)]
|
||||
public short AdditionalStorageGb { get; set; } = 0;
|
||||
|
||||
public (TokenizedPaymentMethod, BillingAddress, short) ToDomain()
|
||||
{
|
||||
var paymentMethod = TokenizedPaymentMethod.ToDomain();
|
||||
var billingAddress = BillingAddress.ToDomain();
|
||||
|
||||
return (paymentMethod, billingAddress, AdditionalStorageGb);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#nullable enable
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Bit.Api.Billing.Models.Requests.Premium;
|
||||
|
||||
public class PremiumSelfHostedSubscriptionRequest
|
||||
{
|
||||
[Required]
|
||||
public required IFormFile License { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user