mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 15:42:48 -05:00
[PM-20084] [PM-20086] Add TrialLength
parameter to trial initiation endpoint and email (#5770)
* Add trial length parameter to trial initiation endpoint and email * Add feature flag that pegs trial length to 7 when disabled * Add optionality to Identity * Move feature service injection to identity accounts controller
This commit is contained in:
@ -7,4 +7,5 @@ public class TrialSendVerificationEmailRequestModel : RegisterSendVerificationEm
|
||||
{
|
||||
public ProductTierType ProductTier { get; set; }
|
||||
public IEnumerable<ProductType> Products { get; set; }
|
||||
public int? TrialLength { get; set; }
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
using Bit.Core.Auth.Models.Mail;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.Billing.Models.Mail;
|
||||
|
||||
@ -16,13 +17,26 @@ public class TrialInitiationVerifyEmail : RegisterVerifyEmail
|
||||
$"&email={Email}" +
|
||||
$"&fromEmail=true" +
|
||||
$"&productTier={(int)ProductTier}" +
|
||||
$"&product={string.Join(",", Product.Select(p => (int)p))}";
|
||||
$"&product={string.Join(",", Product.Select(p => (int)p))}" +
|
||||
$"&trialLength={TrialLength}";
|
||||
}
|
||||
|
||||
public string VerifyYourEmailHTMLCopy =>
|
||||
TrialLength == 7
|
||||
? "Verify your email address below to finish signing up for your free trial."
|
||||
: $"Verify your email address below to finish signing up for your {ProductTier.GetDisplayName()} plan.";
|
||||
|
||||
public string VerifyYourEmailTextCopy =>
|
||||
TrialLength == 7
|
||||
? "Verify your email address using the link below and start your free trial of Bitwarden."
|
||||
: $"Verify your email address using the link below and start your {ProductTier.GetDisplayName()} Bitwarden plan.";
|
||||
|
||||
public ProductTierType ProductTier { get; set; }
|
||||
|
||||
public IEnumerable<ProductType> Product { get; set; }
|
||||
|
||||
public int TrialLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Currently we only support one product type at a time, despite Product being a collection.
|
||||
/// If we receive both PasswordManager and SecretsManager, we'll send the user to the PM trial route
|
||||
|
@ -10,5 +10,6 @@ public interface ISendTrialInitiationEmailForRegistrationCommand
|
||||
string? name,
|
||||
bool receiveMarketingEmails,
|
||||
ProductTierType productTier,
|
||||
IEnumerable<ProductType> products);
|
||||
IEnumerable<ProductType> products,
|
||||
int trialLength);
|
||||
}
|
||||
|
@ -22,7 +22,8 @@ public class SendTrialInitiationEmailForRegistrationCommand(
|
||||
string? name,
|
||||
bool receiveMarketingEmails,
|
||||
ProductTierType productTier,
|
||||
IEnumerable<ProductType> products)
|
||||
IEnumerable<ProductType> products,
|
||||
int trialLength)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(email, nameof(email));
|
||||
|
||||
@ -43,7 +44,12 @@ public class SendTrialInitiationEmailForRegistrationCommand(
|
||||
|
||||
await PerformConstantTimeOperationsAsync();
|
||||
|
||||
await mailService.SendTrialInitiationSignupEmailAsync(userExists, email, token, productTier, products);
|
||||
if (trialLength != 0 && trialLength != 7)
|
||||
{
|
||||
trialLength = 7;
|
||||
}
|
||||
|
||||
await mailService.SendTrialInitiationSignupEmailAsync(userExists, email, token, productTier, products, trialLength);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -150,6 +150,7 @@ public static class FeatureFlagKeys
|
||||
public const string PM199566_UpdateMSPToChargeAutomatically = "pm-199566-update-msp-to-charge-automatically";
|
||||
public const string PM19956_RequireProviderPaymentMethodDuringSetup = "pm-19956-require-provider-payment-method-during-setup";
|
||||
public const string UseOrganizationWarningsService = "use-organization-warnings-service";
|
||||
public const string PM20322_AllowTrialLength0 = "pm-20322-allow-trial-length-0";
|
||||
|
||||
/* Data Insights and Reporting Team */
|
||||
public const string RiskInsightsCriticalApplication = "pm-14466-risk-insights-critical-application";
|
||||
|
18
src/Core/Enums/EnumExtensions.cs
Normal file
18
src/Core/Enums/EnumExtensions.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Bit.Core.Enums;
|
||||
|
||||
public static class EnumExtensions
|
||||
{
|
||||
public static string GetDisplayName(this Enum value)
|
||||
{
|
||||
var field = value.GetType().GetField(value.ToString());
|
||||
if (field?.GetCustomAttribute<DisplayAttribute>() is { } attribute)
|
||||
{
|
||||
return attribute.Name ?? value.ToString();
|
||||
}
|
||||
|
||||
return value.ToString();
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; box-sizing: border-box; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||
<tr style="margin: 0; box-sizing: border-box; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: left;" valign="top" align="center">
|
||||
Verify your email address below to finish signing up for your free trial.
|
||||
{{VerifyYourEmailHTMLCopy}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="margin: 0; box-sizing: border-box; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||
|
@ -1,5 +1,5 @@
|
||||
{{#>BasicTextLayout}}
|
||||
Verify your email address using the link below and start your free trial of Bitwarden.
|
||||
{{VerifyYourEmailTextCopy}}
|
||||
|
||||
If you did not request this email from Bitwarden, you can safely ignore it.
|
||||
|
||||
|
@ -21,7 +21,8 @@ public interface IMailService
|
||||
string email,
|
||||
string token,
|
||||
ProductTierType productTier,
|
||||
IEnumerable<ProductType> products);
|
||||
IEnumerable<ProductType> products,
|
||||
int trialLength);
|
||||
Task SendVerifyDeleteEmailAsync(string email, Guid userId, string token);
|
||||
Task SendCannotDeleteClaimedAccountEmailAsync(string email);
|
||||
Task SendChangeEmailAlreadyExistsEmailAsync(string fromEmail, string toEmail);
|
||||
|
@ -84,7 +84,8 @@ public class HandlebarsMailService : IMailService
|
||||
string email,
|
||||
string token,
|
||||
ProductTierType productTier,
|
||||
IEnumerable<ProductType> products)
|
||||
IEnumerable<ProductType> products,
|
||||
int trialLength)
|
||||
{
|
||||
var message = CreateDefaultMessage("Verify your email", email);
|
||||
var model = new TrialInitiationVerifyEmail
|
||||
@ -95,7 +96,8 @@ public class HandlebarsMailService : IMailService
|
||||
WebVaultUrl = _globalSettings.BaseServiceUri.VaultWithHash,
|
||||
SiteName = _globalSettings.SiteName,
|
||||
ProductTier = productTier,
|
||||
Product = products
|
||||
Product = products,
|
||||
TrialLength = trialLength
|
||||
};
|
||||
await AddMessageContentAsync(message, "Billing.TrialInitiationVerifyEmail", model);
|
||||
message.MetaData.Add("SendGridBypassListManagement", true);
|
||||
|
@ -33,7 +33,8 @@ public class NoopMailService : IMailService
|
||||
string email,
|
||||
string token,
|
||||
ProductTierType productTier,
|
||||
IEnumerable<ProductType> products)
|
||||
IEnumerable<ProductType> products,
|
||||
int trailLength)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
using Bit.Core.Billing.Models.Api.Requests.Accounts;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Billing.Models.Api.Requests.Accounts;
|
||||
using Bit.Core.Billing.TrialInitiation.Registration;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Tools.Enums;
|
||||
using Bit.Core.Tools.Models.Business;
|
||||
using Bit.Core.Tools.Services;
|
||||
@ -15,18 +17,24 @@ namespace Bit.Identity.Billing.Controller;
|
||||
public class AccountsController(
|
||||
ICurrentContext currentContext,
|
||||
ISendTrialInitiationEmailForRegistrationCommand sendTrialInitiationEmailForRegistrationCommand,
|
||||
IReferenceEventService referenceEventService) : Microsoft.AspNetCore.Mvc.Controller
|
||||
IReferenceEventService referenceEventService,
|
||||
IFeatureService featureService) : Microsoft.AspNetCore.Mvc.Controller
|
||||
{
|
||||
[HttpPost("trial/send-verification-email")]
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
public async Task<IActionResult> PostTrialInitiationSendVerificationEmailAsync([FromBody] TrialSendVerificationEmailRequestModel model)
|
||||
{
|
||||
var allowTrialLength0 = featureService.IsEnabled(FeatureFlagKeys.PM20322_AllowTrialLength0);
|
||||
|
||||
var trialLength = allowTrialLength0 ? model.TrialLength ?? 7 : 7;
|
||||
|
||||
var token = await sendTrialInitiationEmailForRegistrationCommand.Handle(
|
||||
model.Email,
|
||||
model.Name,
|
||||
model.ReceiveMarketingEmails,
|
||||
model.ProductTier,
|
||||
model.Products);
|
||||
model.Products,
|
||||
trialLength);
|
||||
|
||||
var refEvent = new ReferenceEvent
|
||||
{
|
||||
|
@ -145,6 +145,7 @@ public class Startup
|
||||
// Services
|
||||
services.AddBaseServices(globalSettings);
|
||||
services.AddDefaultServices(globalSettings);
|
||||
services.AddOptionality();
|
||||
services.AddCoreLocalizationServices();
|
||||
services.AddBillingOperations();
|
||||
|
||||
|
Reference in New Issue
Block a user