fix: planos de pagamentos associados ao appSettings
All checks were successful
BCards Deployment Pipeline / Run Tests (push) Successful in 2s
BCards Deployment Pipeline / PR Validation (push) Has been skipped
BCards Deployment Pipeline / Build and Push Image (push) Successful in 14m57s
BCards Deployment Pipeline / Deploy to Production (ARM - OCI) (push) Successful in 2m39s
BCards Deployment Pipeline / Deploy to Staging (x86 - Local) (push) Has been skipped
BCards Deployment Pipeline / Cleanup Old Resources (push) Has been skipped
BCards Deployment Pipeline / Deployment Summary (push) Successful in 0s
All checks were successful
BCards Deployment Pipeline / Run Tests (push) Successful in 2s
BCards Deployment Pipeline / PR Validation (push) Has been skipped
BCards Deployment Pipeline / Build and Push Image (push) Successful in 14m57s
BCards Deployment Pipeline / Deploy to Production (ARM - OCI) (push) Successful in 2m39s
BCards Deployment Pipeline / Deploy to Staging (x86 - Local) (push) Has been skipped
BCards Deployment Pipeline / Cleanup Old Resources (push) Has been skipped
BCards Deployment Pipeline / Deployment Summary (push) Successful in 0s
This commit is contained in:
parent
ad8cc06fd6
commit
61453b65c9
@ -17,6 +17,7 @@ public class StripeWebhookController : ControllerBase
|
|||||||
private readonly ISubscriptionRepository _subscriptionRepository;
|
private readonly ISubscriptionRepository _subscriptionRepository;
|
||||||
private readonly IUserPageService _userPageService;
|
private readonly IUserPageService _userPageService;
|
||||||
private readonly IUserRepository _userRepository;
|
private readonly IUserRepository _userRepository;
|
||||||
|
private readonly IPlanConfigurationService _planConfigurationService;
|
||||||
private readonly string _webhookSecret;
|
private readonly string _webhookSecret;
|
||||||
|
|
||||||
public StripeWebhookController(
|
public StripeWebhookController(
|
||||||
@ -24,12 +25,14 @@ public class StripeWebhookController : ControllerBase
|
|||||||
ISubscriptionRepository subscriptionRepository,
|
ISubscriptionRepository subscriptionRepository,
|
||||||
IUserPageService userPageService,
|
IUserPageService userPageService,
|
||||||
IUserRepository userRepository,
|
IUserRepository userRepository,
|
||||||
|
IPlanConfigurationService planConfigurationService,
|
||||||
IOptions<StripeSettings> stripeSettings)
|
IOptions<StripeSettings> stripeSettings)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_subscriptionRepository = subscriptionRepository;
|
_subscriptionRepository = subscriptionRepository;
|
||||||
_userPageService = userPageService;
|
_userPageService = userPageService;
|
||||||
_userRepository = userRepository;
|
_userRepository = userRepository;
|
||||||
|
_planConfigurationService = planConfigurationService;
|
||||||
_webhookSecret = stripeSettings.Value.WebhookSecret ?? "";
|
_webhookSecret = stripeSettings.Value.WebhookSecret ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,7 +256,7 @@ public class StripeWebhookController : ControllerBase
|
|||||||
var priceId = stripeSubscription.Items.Data.FirstOrDefault()?.Price.Id;
|
var priceId = stripeSubscription.Items.Data.FirstOrDefault()?.Price.Id;
|
||||||
if (!string.IsNullOrEmpty(priceId))
|
if (!string.IsNullOrEmpty(priceId))
|
||||||
{
|
{
|
||||||
subscription.PlanType = MapPriceIdToPlanType(priceId);
|
subscription.PlanType = _planConfigurationService.GetPlanNameFromPriceId(priceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
await _subscriptionRepository.UpdateAsync(subscription);
|
await _subscriptionRepository.UpdateAsync(subscription);
|
||||||
@ -304,7 +307,7 @@ public class StripeWebhookController : ControllerBase
|
|||||||
var priceId = stripeSubscription.Items.Data.FirstOrDefault()?.Price.Id;
|
var priceId = stripeSubscription.Items.Data.FirstOrDefault()?.Price.Id;
|
||||||
if (!string.IsNullOrEmpty(priceId))
|
if (!string.IsNullOrEmpty(priceId))
|
||||||
{
|
{
|
||||||
subscription.PlanType = MapPriceIdToPlanType(priceId);
|
subscription.PlanType = _planConfigurationService.GetPlanNameFromPriceId(priceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
await _subscriptionRepository.UpdateAsync(subscription);
|
await _subscriptionRepository.UpdateAsync(subscription);
|
||||||
@ -346,27 +349,6 @@ public class StripeWebhookController : ControllerBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string MapPriceIdToPlanType(string priceId)
|
|
||||||
{
|
|
||||||
// Map Stripe price IDs to plan types
|
|
||||||
// This would be configured based on your actual Stripe price IDs
|
|
||||||
return priceId switch
|
|
||||||
{
|
|
||||||
"price_1RjUskBMIadsOxJVgLwlVo1y" => "Basic",
|
|
||||||
"price_1RjUv9BMIadsOxJVORqlM4E9" => "Professional",
|
|
||||||
"price_1RjUw0BMIadsOxJVmdouNV1g" => "Premium",
|
|
||||||
"price_basic_yearly_placeholder" => "BasicYearly",
|
|
||||||
"price_professional_yearly_placeholder" => "ProfessionalYearly",
|
|
||||||
"price_premium_yearly_placeholder" => "PremiumYearly",
|
|
||||||
var id when id.Contains("basic") && id.Contains("yearly") => "BasicYearly",
|
|
||||||
var id when id.Contains("professional") && id.Contains("yearly") => "ProfessionalYearly",
|
|
||||||
var id when id.Contains("premium") && id.Contains("yearly") => "PremiumYearly",
|
|
||||||
var id when id.Contains("basic") => "Basic",
|
|
||||||
var id when id.Contains("professional") => "Professional",
|
|
||||||
var id when id.Contains("premium") => "Premium",
|
|
||||||
_ => "Trial"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetSubscriptionId(Event stripeEvent)
|
private string GetSubscriptionId(Event stripeEvent)
|
||||||
{
|
{
|
||||||
@ -403,10 +385,8 @@ public class StripeWebhookController : ControllerBase
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation($"[DEBUG] [TID: {traceId}] - HandleSubscriptionCreatedForNewSubscription started for customer: {stripeSubscription.CustomerId}");
|
_logger.LogInformation($"[DEBUG] [TID: {traceId}] - HandleSubscriptionCreatedForNewSubscription started for customer: {stripeSubscription.CustomerId}");
|
||||||
|
|
||||||
// Find user by Stripe Customer ID using a MongoDB query
|
|
||||||
// Since IUserRepository doesn't have GetByStripeCustomerIdAsync, we'll use MongoDB directly
|
|
||||||
_logger.LogInformation($"[DEBUG] [TID: {traceId}] - Getting MongoDB database");
|
_logger.LogInformation($"[DEBUG] [TID: {traceId}] - Getting MongoDB database");
|
||||||
|
|
||||||
var mongoDatabase = HttpContext.RequestServices.GetRequiredService<IMongoDatabase>();
|
var mongoDatabase = HttpContext.RequestServices.GetRequiredService<IMongoDatabase>();
|
||||||
var usersCollection = mongoDatabase.GetCollection<Models.User>("users");
|
var usersCollection = mongoDatabase.GetCollection<Models.User>("users");
|
||||||
|
|
||||||
@ -423,7 +403,7 @@ public class StripeWebhookController : ControllerBase
|
|||||||
|
|
||||||
// Get plan type from price ID
|
// Get plan type from price ID
|
||||||
var priceId = stripeSubscription.Items.Data.FirstOrDefault()?.Price.Id;
|
var priceId = stripeSubscription.Items.Data.FirstOrDefault()?.Price.Id;
|
||||||
var planType = !string.IsNullOrEmpty(priceId) ? MapPriceIdToPlanType(priceId) : "Trial";
|
var planType = !string.IsNullOrEmpty(priceId) ? _planConfigurationService.GetPlanNameFromPriceId(priceId) : "Trial";
|
||||||
|
|
||||||
_logger.LogInformation($"[TID: {traceId}] - PriceId: {priceId}, PlanType: {planType}, User: {user.Id}");
|
_logger.LogInformation($"[TID: {traceId}] - PriceId: {priceId}, PlanType: {planType}, User: {user.Id}");
|
||||||
|
|
||||||
@ -446,7 +426,6 @@ public class StripeWebhookController : ControllerBase
|
|||||||
await _subscriptionRepository.CreateAsync(newSubscription);
|
await _subscriptionRepository.CreateAsync(newSubscription);
|
||||||
_logger.LogInformation($"[TID: {traceId}] - Created new subscription {newSubscription.Id} for user {user.Id}");
|
_logger.LogInformation($"[TID: {traceId}] - Created new subscription {newSubscription.Id} for user {user.Id}");
|
||||||
|
|
||||||
// 🔥 CORREÇÃO: Update user's CurrentPlan
|
|
||||||
_logger.LogInformation($"[TID: {traceId}] - Updating user {user.Id} CurrentPlan from '{user.CurrentPlan}' to '{planType}'");
|
_logger.LogInformation($"[TID: {traceId}] - Updating user {user.Id} CurrentPlan from '{user.CurrentPlan}' to '{planType}'");
|
||||||
user.CurrentPlan = planType;
|
user.CurrentPlan = planType;
|
||||||
user.UpdatedAt = DateTime.UtcNow;
|
user.UpdatedAt = DateTime.UtcNow;
|
||||||
@ -455,7 +434,6 @@ public class StripeWebhookController : ControllerBase
|
|||||||
await usersCollection2.ReplaceOneAsync(u => u.Id == user.Id, user);
|
await usersCollection2.ReplaceOneAsync(u => u.Id == user.Id, user);
|
||||||
_logger.LogInformation($"[TID: {traceId}] - User {user.Id} CurrentPlan updated to '{planType}'");
|
_logger.LogInformation($"[TID: {traceId}] - User {user.Id} CurrentPlan updated to '{planType}'");
|
||||||
|
|
||||||
// Activate user pages that were pending payment or expired
|
|
||||||
var userPages = await _userPageService.GetUserPagesAsync(user.Id);
|
var userPages = await _userPageService.GetUserPagesAsync(user.Id);
|
||||||
foreach (var page in userPages.Where(p =>
|
foreach (var page in userPages.Where(p =>
|
||||||
p.Status == ViewModels.PageStatus.PendingPayment ||
|
p.Status == ViewModels.PageStatus.PendingPayment ||
|
||||||
@ -473,7 +451,6 @@ public class StripeWebhookController : ControllerBase
|
|||||||
_logger.LogError($"[DEBUG] [TID: {traceId}] - User not found for Stripe customer ID: {stripeSubscription.CustomerId}");
|
_logger.LogError($"[DEBUG] [TID: {traceId}] - User not found for Stripe customer ID: {stripeSubscription.CustomerId}");
|
||||||
_logger.LogInformation($"[DEBUG] [TID: {traceId}] - Will try to list some users to debug");
|
_logger.LogInformation($"[DEBUG] [TID: {traceId}] - Will try to list some users to debug");
|
||||||
|
|
||||||
// Debug: list some users to see what we have
|
|
||||||
var allUsers = await usersCollection.Find(_ => true).Limit(5).ToListAsync();
|
var allUsers = await usersCollection.Find(_ => true).Limit(5).ToListAsync();
|
||||||
foreach (var u in allUsers)
|
foreach (var u in allUsers)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -3,9 +3,10 @@ namespace BCards.Web.Models;
|
|||||||
public enum PlanType
|
public enum PlanType
|
||||||
{
|
{
|
||||||
Trial = 0, // Gratuito por 7 dias
|
Trial = 0, // Gratuito por 7 dias
|
||||||
Basic = 1, // R$ 9,90
|
Basic = 1,
|
||||||
Professional = 2, // R$ 24,90 (Decoy)
|
Professional = 2,
|
||||||
Premium = 3 // R$ 29,90
|
Premium = 4,
|
||||||
|
PremiumAffiliate = 5
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class PlanTypeExtensions
|
public static class PlanTypeExtensions
|
||||||
@ -18,22 +19,28 @@ public static class PlanTypeExtensions
|
|||||||
PlanType.Basic => "Básico",
|
PlanType.Basic => "Básico",
|
||||||
PlanType.Professional => "Profissional",
|
PlanType.Professional => "Profissional",
|
||||||
PlanType.Premium => "Premium",
|
PlanType.Premium => "Premium",
|
||||||
|
PlanType.PremiumAffiliate => "PremiumAffiliate",
|
||||||
_ => "Desconhecido"
|
_ => "Desconhecido"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTA: Preços agora são configurados dinamicamente via IPlanConfigurationService
|
||||||
|
// Este método mantém valores fallback para compatibilidade
|
||||||
public static decimal GetPrice(this PlanType planType)
|
public static decimal GetPrice(this PlanType planType)
|
||||||
{
|
{
|
||||||
return planType switch
|
return planType switch
|
||||||
{
|
{
|
||||||
PlanType.Trial => 0.00m,
|
PlanType.Trial => 0.00m,
|
||||||
PlanType.Basic => 9.90m,
|
PlanType.Basic => 5.90m,
|
||||||
PlanType.Professional => 24.90m,
|
PlanType.Professional => 12.90m,
|
||||||
PlanType.Premium => 29.90m,
|
PlanType.Premium => 19.90m,
|
||||||
|
PlanType.PremiumAffiliate => 29.90m,
|
||||||
_ => 0.00m
|
_ => 0.00m
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTA: Limitações agora são configuradas dinamicamente via IPlanConfigurationService
|
||||||
|
// Este método mantém valores fallback para compatibilidade
|
||||||
public static int GetMaxPages(this PlanType planType)
|
public static int GetMaxPages(this PlanType planType)
|
||||||
{
|
{
|
||||||
return planType switch
|
return planType switch
|
||||||
@ -42,10 +49,13 @@ public static class PlanTypeExtensions
|
|||||||
PlanType.Basic => 3,
|
PlanType.Basic => 3,
|
||||||
PlanType.Professional => 5, // DECOY - not attractive
|
PlanType.Professional => 5, // DECOY - not attractive
|
||||||
PlanType.Premium => 15,
|
PlanType.Premium => 15,
|
||||||
|
PlanType.PremiumAffiliate => 15,
|
||||||
_ => 1
|
_ => 1
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTA: Limitações agora são configuradas dinamicamente via IPlanConfigurationService
|
||||||
|
// Este método mantém valores fallback para compatibilidade
|
||||||
public static int GetMaxLinksPerPage(this PlanType planType)
|
public static int GetMaxLinksPerPage(this PlanType planType)
|
||||||
{
|
{
|
||||||
return planType switch
|
return planType switch
|
||||||
@ -54,6 +64,7 @@ public static class PlanTypeExtensions
|
|||||||
PlanType.Basic => 8,
|
PlanType.Basic => 8,
|
||||||
PlanType.Professional => 20, // DECOY - too expensive for the benefit
|
PlanType.Professional => 20, // DECOY - too expensive for the benefit
|
||||||
PlanType.Premium => int.MaxValue, // Unlimited
|
PlanType.Premium => int.MaxValue, // Unlimited
|
||||||
|
PlanType.PremiumAffiliate => int.MaxValue, // Unlimited
|
||||||
_ => 3
|
_ => 3
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -71,6 +82,7 @@ public static class PlanTypeExtensions
|
|||||||
PlanType.Basic => true,
|
PlanType.Basic => true,
|
||||||
PlanType.Professional => true,
|
PlanType.Professional => true,
|
||||||
PlanType.Premium => true,
|
PlanType.Premium => true,
|
||||||
|
PlanType.PremiumAffiliate => true,
|
||||||
_ => false
|
_ => false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -83,6 +95,7 @@ public static class PlanTypeExtensions
|
|||||||
PlanType.Basic => false,
|
PlanType.Basic => false,
|
||||||
PlanType.Professional => true,
|
PlanType.Professional => true,
|
||||||
PlanType.Premium => true,
|
PlanType.Premium => true,
|
||||||
|
PlanType.PremiumAffiliate => true,
|
||||||
_ => false
|
_ => false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -96,10 +109,11 @@ public static class PlanTypeExtensions
|
|||||||
{
|
{
|
||||||
return planType switch
|
return planType switch
|
||||||
{
|
{
|
||||||
PlanType.Trial => 1, // 1 link de produto para trial
|
PlanType.Trial => 0, // 1 link de produto para trial
|
||||||
PlanType.Basic => 3, // 3 links de produto
|
PlanType.Basic => 0, // 3 links de produto
|
||||||
PlanType.Professional => 8, // DECOY - mais caro para poucos benefícios
|
PlanType.Professional => 0, // DECOY - mais caro para poucos benefícios
|
||||||
PlanType.Premium => int.MaxValue, // Ilimitado
|
PlanType.Premium => 0, // Ilimitado
|
||||||
|
PlanType.PremiumAffiliate => int.MaxValue, // Ilimitado
|
||||||
_ => 0
|
_ => 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -108,10 +122,11 @@ public static class PlanTypeExtensions
|
|||||||
{
|
{
|
||||||
return planType switch
|
return planType switch
|
||||||
{
|
{
|
||||||
PlanType.Trial => 2, // 2 extrações por dia no trial
|
PlanType.Trial => 0, // 2 extrações por dia no trial
|
||||||
PlanType.Basic => 5, // 5 extrações por dia
|
PlanType.Basic => 0, // 5 extrações por dia
|
||||||
PlanType.Professional => 15, // 15 extrações por dia
|
PlanType.Professional => 0, // 15 extrações por dia
|
||||||
PlanType.Premium => int.MaxValue, // Ilimitado
|
PlanType.Premium => 0, // Ilimitado
|
||||||
|
PlanType.PremiumAffiliate => int.MaxValue, // Ilimitado
|
||||||
_ => 0
|
_ => 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -401,6 +401,7 @@ builder.Services.AddScoped<IUserPageService, UserPageService>();
|
|||||||
builder.Services.AddScoped<IThemeService, ThemeService>();
|
builder.Services.AddScoped<IThemeService, ThemeService>();
|
||||||
builder.Services.AddScoped<ISeoService, SeoService>();
|
builder.Services.AddScoped<ISeoService, SeoService>();
|
||||||
builder.Services.AddScoped<IAuthService, AuthService>();
|
builder.Services.AddScoped<IAuthService, AuthService>();
|
||||||
|
builder.Services.AddSingleton<IPlanConfigurationService, PlanConfigurationService>();
|
||||||
builder.Services.AddScoped<IPaymentService, PaymentService>();
|
builder.Services.AddScoped<IPaymentService, PaymentService>();
|
||||||
builder.Services.AddScoped<ICategoryService, CategoryService>();
|
builder.Services.AddScoped<ICategoryService, CategoryService>();
|
||||||
builder.Services.AddScoped<IOpenGraphService, OpenGraphService>();
|
builder.Services.AddScoped<IOpenGraphService, OpenGraphService>();
|
||||||
|
|||||||
57
src/BCards.Web/Services/IPlanConfigurationService.cs
Normal file
57
src/BCards.Web/Services/IPlanConfigurationService.cs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
using BCards.Web.Models;
|
||||||
|
|
||||||
|
namespace BCards.Web.Services;
|
||||||
|
|
||||||
|
public interface IPlanConfigurationService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Mapeia um PriceId do Stripe para o PlanType correspondente
|
||||||
|
/// </summary>
|
||||||
|
PlanType GetPlanTypeFromPriceId(string priceId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mapeia um PriceId do Stripe para o nome string do plano
|
||||||
|
/// </summary>
|
||||||
|
string GetPlanNameFromPriceId(string priceId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obtém as limitações de um plano baseado no PlanType
|
||||||
|
/// </summary>
|
||||||
|
PlanLimitations GetPlanLimitations(PlanType planType);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obtém o PriceId de um plano (mensal por padrão)
|
||||||
|
/// </summary>
|
||||||
|
string GetPriceId(PlanType planType, bool yearly = false);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obtém o preço de um plano
|
||||||
|
/// </summary>
|
||||||
|
decimal GetPlanPrice(PlanType planType, bool yearly = false);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifica se um plano é anual baseado no PriceId
|
||||||
|
/// </summary>
|
||||||
|
bool IsYearlyPlan(string priceId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obtém todas as configurações de um plano pelo nome da seção
|
||||||
|
/// </summary>
|
||||||
|
PlanConfiguration? GetPlanConfiguration(string planSectionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PlanConfiguration
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string PriceId { get; set; } = string.Empty;
|
||||||
|
public decimal Price { get; set; }
|
||||||
|
public int MaxPages { get; set; }
|
||||||
|
public int MaxLinks { get; set; }
|
||||||
|
public bool AllowPremiumThemes { get; set; }
|
||||||
|
public bool AllowProductLinks { get; set; }
|
||||||
|
public bool AllowAnalytics { get; set; }
|
||||||
|
public bool? SpecialModeration { get; set; }
|
||||||
|
public List<string> Features { get; set; } = new();
|
||||||
|
public string Interval { get; set; } = "month";
|
||||||
|
public PlanType BasePlanType { get; set; }
|
||||||
|
}
|
||||||
@ -15,17 +15,20 @@ public class PaymentService : IPaymentService
|
|||||||
private readonly IUserRepository _userRepository;
|
private readonly IUserRepository _userRepository;
|
||||||
private readonly ISubscriptionRepository _subscriptionRepository;
|
private readonly ISubscriptionRepository _subscriptionRepository;
|
||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
|
private readonly IPlanConfigurationService _planConfigurationService;
|
||||||
|
|
||||||
public PaymentService(
|
public PaymentService(
|
||||||
IOptions<StripeSettings> stripeSettings,
|
IOptions<StripeSettings> stripeSettings,
|
||||||
IUserRepository userRepository,
|
IUserRepository userRepository,
|
||||||
ISubscriptionRepository subscriptionRepository,
|
ISubscriptionRepository subscriptionRepository,
|
||||||
IConfiguration configuration)
|
IConfiguration configuration,
|
||||||
|
IPlanConfigurationService planConfigurationService)
|
||||||
{
|
{
|
||||||
_stripeSettings = stripeSettings.Value;
|
_stripeSettings = stripeSettings.Value;
|
||||||
_userRepository = userRepository;
|
_userRepository = userRepository;
|
||||||
_subscriptionRepository = subscriptionRepository;
|
_subscriptionRepository = subscriptionRepository;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
|
_planConfigurationService = planConfigurationService;
|
||||||
|
|
||||||
StripeConfiguration.ApiKey = _stripeSettings.SecretKey;
|
StripeConfiguration.ApiKey = _stripeSettings.SecretKey;
|
||||||
}
|
}
|
||||||
@ -201,70 +204,17 @@ public class PaymentService : IPaymentService
|
|||||||
|
|
||||||
public Task<PlanLimitations> GetPlanLimitationsAsync(string planType)
|
public Task<PlanLimitations> GetPlanLimitationsAsync(string planType)
|
||||||
{
|
{
|
||||||
var limitations = planType.ToLower() switch
|
// Mapear string planType para PlanType enum
|
||||||
|
var planTypeEnum = planType.ToLower() switch
|
||||||
{
|
{
|
||||||
"basic" => new PlanLimitations
|
"basic" or "basicyearly" => PlanType.Basic,
|
||||||
{
|
"professional" or "professionalyearly" => PlanType.Professional,
|
||||||
MaxLinks = 8,
|
"premium" or "premiumyearly" => PlanType.Premium,
|
||||||
AllowCustomThemes = false,
|
"premiumaffiliate" or "premiumaffiliateyearly" => PlanType.PremiumAffiliate,
|
||||||
AllowAnalytics = true,
|
_ => PlanType.Trial
|
||||||
AllowCustomDomain = true, // URL personalizada em todos os planos pagos
|
|
||||||
AllowMultipleDomains = false,
|
|
||||||
PrioritySupport = false,
|
|
||||||
AllowProductLinks = false,
|
|
||||||
MaxProductLinks = 0,
|
|
||||||
PlanType = "basic"
|
|
||||||
},
|
|
||||||
"professional" => new PlanLimitations
|
|
||||||
{
|
|
||||||
MaxLinks = 20,
|
|
||||||
AllowCustomThemes = false,
|
|
||||||
AllowAnalytics = true,
|
|
||||||
AllowCustomDomain = true,
|
|
||||||
AllowMultipleDomains = false,
|
|
||||||
PrioritySupport = false,
|
|
||||||
AllowProductLinks = false,
|
|
||||||
MaxProductLinks = 0,
|
|
||||||
PlanType = "professional"
|
|
||||||
},
|
|
||||||
"premium" => new PlanLimitations
|
|
||||||
{
|
|
||||||
MaxLinks = -1, // Unlimited
|
|
||||||
AllowCustomThemes = true,
|
|
||||||
AllowAnalytics = true,
|
|
||||||
AllowCustomDomain = true,
|
|
||||||
AllowMultipleDomains = true,
|
|
||||||
PrioritySupport = true,
|
|
||||||
AllowProductLinks = false,
|
|
||||||
MaxProductLinks = 0,
|
|
||||||
PlanType = "premium"
|
|
||||||
},
|
|
||||||
"premiumaffiliate" => new PlanLimitations
|
|
||||||
{
|
|
||||||
MaxLinks = -1, // Unlimited
|
|
||||||
AllowCustomThemes = true,
|
|
||||||
AllowAnalytics = true,
|
|
||||||
AllowCustomDomain = true,
|
|
||||||
AllowMultipleDomains = true,
|
|
||||||
PrioritySupport = true,
|
|
||||||
AllowProductLinks = true,
|
|
||||||
MaxProductLinks = 10,
|
|
||||||
PlanType = "premiumaffiliate"
|
|
||||||
},
|
|
||||||
_ => new PlanLimitations
|
|
||||||
{
|
|
||||||
MaxLinks = 3,
|
|
||||||
AllowCustomThemes = false,
|
|
||||||
AllowAnalytics = false,
|
|
||||||
AllowCustomDomain = false,
|
|
||||||
AllowMultipleDomains = false,
|
|
||||||
PrioritySupport = false,
|
|
||||||
AllowProductLinks = false,
|
|
||||||
MaxProductLinks = 0,
|
|
||||||
PlanType = "trial"
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var limitations = _planConfigurationService.GetPlanLimitations(planTypeEnum);
|
||||||
return Task.FromResult(limitations);
|
return Task.FromResult(limitations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
228
src/BCards.Web/Services/PlanConfigurationService.cs
Normal file
228
src/BCards.Web/Services/PlanConfigurationService.cs
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
using BCards.Web.Models;
|
||||||
|
|
||||||
|
namespace BCards.Web.Services;
|
||||||
|
|
||||||
|
public class PlanConfigurationService : IPlanConfigurationService
|
||||||
|
{
|
||||||
|
private readonly IConfiguration _configuration;
|
||||||
|
private readonly Dictionary<string, PlanConfiguration> _plans;
|
||||||
|
private readonly Dictionary<string, string> _priceIdToPlanName;
|
||||||
|
private readonly Dictionary<string, PlanType> _priceIdToPlanType;
|
||||||
|
|
||||||
|
public PlanConfigurationService(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
_configuration = configuration;
|
||||||
|
_plans = LoadPlansFromConfiguration();
|
||||||
|
_priceIdToPlanName = BuildPriceIdToPlanNameMap();
|
||||||
|
_priceIdToPlanType = BuildPriceIdToPlanTypeMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlanType GetPlanTypeFromPriceId(string priceId)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(priceId))
|
||||||
|
return PlanType.Trial;
|
||||||
|
|
||||||
|
return _priceIdToPlanType.TryGetValue(priceId, out var planType) ? planType : PlanType.Trial;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetPlanNameFromPriceId(string priceId)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(priceId))
|
||||||
|
return "Trial";
|
||||||
|
|
||||||
|
return _priceIdToPlanName.TryGetValue(priceId, out var planName) ? planName : "Trial";
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlanLimitations GetPlanLimitations(PlanType planType)
|
||||||
|
{
|
||||||
|
return planType switch
|
||||||
|
{
|
||||||
|
PlanType.Trial => new PlanLimitations
|
||||||
|
{
|
||||||
|
MaxLinks = 3,
|
||||||
|
AllowCustomThemes = false,
|
||||||
|
AllowAnalytics = false,
|
||||||
|
AllowCustomDomain = false,
|
||||||
|
AllowMultipleDomains = false,
|
||||||
|
PrioritySupport = false,
|
||||||
|
AllowProductLinks = false,
|
||||||
|
MaxProductLinks = 0,
|
||||||
|
PlanType = "trial"
|
||||||
|
},
|
||||||
|
PlanType.Basic => new PlanLimitations
|
||||||
|
{
|
||||||
|
MaxLinks = GetConfigValue(PlanType.Basic, "MaxLinks", 8),
|
||||||
|
AllowCustomThemes = GetConfigValue(PlanType.Basic, "AllowPremiumThemes", false),
|
||||||
|
AllowAnalytics = GetConfigValue(PlanType.Basic, "AllowAnalytics", true),
|
||||||
|
AllowCustomDomain = true,
|
||||||
|
AllowMultipleDomains = false,
|
||||||
|
PrioritySupport = false,
|
||||||
|
AllowProductLinks = GetConfigValue(PlanType.Basic, "AllowProductLinks", false),
|
||||||
|
MaxProductLinks = 0,
|
||||||
|
PlanType = "basic"
|
||||||
|
},
|
||||||
|
PlanType.Professional => new PlanLimitations
|
||||||
|
{
|
||||||
|
MaxLinks = GetConfigValue(PlanType.Professional, "MaxLinks", 20),
|
||||||
|
AllowCustomThemes = GetConfigValue(PlanType.Professional, "AllowPremiumThemes", false),
|
||||||
|
AllowAnalytics = GetConfigValue(PlanType.Professional, "AllowAnalytics", true),
|
||||||
|
AllowCustomDomain = true,
|
||||||
|
AllowMultipleDomains = false,
|
||||||
|
PrioritySupport = false,
|
||||||
|
AllowProductLinks = GetConfigValue(PlanType.Professional, "AllowProductLinks", false),
|
||||||
|
MaxProductLinks = 0,
|
||||||
|
PlanType = "professional"
|
||||||
|
},
|
||||||
|
PlanType.Premium => new PlanLimitations
|
||||||
|
{
|
||||||
|
MaxLinks = GetConfigValue(PlanType.Premium, "MaxLinks", -1),
|
||||||
|
AllowCustomThemes = GetConfigValue(PlanType.Premium, "AllowPremiumThemes", true),
|
||||||
|
AllowAnalytics = GetConfigValue(PlanType.Premium, "AllowAnalytics", true),
|
||||||
|
AllowCustomDomain = true,
|
||||||
|
AllowMultipleDomains = true,
|
||||||
|
PrioritySupport = true,
|
||||||
|
AllowProductLinks = GetConfigValue(PlanType.Premium, "AllowProductLinks", false),
|
||||||
|
MaxProductLinks = 0,
|
||||||
|
PlanType = "premium"
|
||||||
|
},
|
||||||
|
PlanType.PremiumAffiliate => new PlanLimitations
|
||||||
|
{
|
||||||
|
MaxLinks = GetConfigValue(PlanType.PremiumAffiliate, "MaxLinks", -1),
|
||||||
|
AllowCustomThemes = GetConfigValue(PlanType.PremiumAffiliate, "AllowPremiumThemes", true),
|
||||||
|
AllowAnalytics = GetConfigValue(PlanType.PremiumAffiliate, "AllowAnalytics", true),
|
||||||
|
AllowCustomDomain = true,
|
||||||
|
AllowMultipleDomains = true,
|
||||||
|
PrioritySupport = true,
|
||||||
|
AllowProductLinks = GetConfigValue(PlanType.PremiumAffiliate, "AllowProductLinks", true),
|
||||||
|
MaxProductLinks = 10,
|
||||||
|
PlanType = "premiumaffiliate"
|
||||||
|
},
|
||||||
|
_ => new PlanLimitations { PlanType = "trial" }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetPriceId(PlanType planType, bool yearly = false)
|
||||||
|
{
|
||||||
|
var planName = planType switch
|
||||||
|
{
|
||||||
|
PlanType.Basic => yearly ? "BasicYearly" : "Basic",
|
||||||
|
PlanType.Professional => yearly ? "ProfessionalYearly" : "Professional",
|
||||||
|
PlanType.Premium => yearly ? "PremiumYearly" : "Premium",
|
||||||
|
PlanType.PremiumAffiliate => yearly ? "PremiumAffiliateYearly" : "PremiumAffiliate",
|
||||||
|
_ => "Trial"
|
||||||
|
};
|
||||||
|
|
||||||
|
return _plans.TryGetValue(planName, out var config) ? config.PriceId : string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public decimal GetPlanPrice(PlanType planType, bool yearly = false)
|
||||||
|
{
|
||||||
|
var planName = planType switch
|
||||||
|
{
|
||||||
|
PlanType.Basic => yearly ? "BasicYearly" : "Basic",
|
||||||
|
PlanType.Professional => yearly ? "ProfessionalYearly" : "Professional",
|
||||||
|
PlanType.Premium => yearly ? "PremiumYearly" : "Premium",
|
||||||
|
PlanType.PremiumAffiliate => yearly ? "PremiumAffiliateYearly" : "PremiumAffiliate",
|
||||||
|
_ => "Trial"
|
||||||
|
};
|
||||||
|
|
||||||
|
return _plans.TryGetValue(planName, out var config) ? config.Price : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsYearlyPlan(string priceId)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(priceId) || !_priceIdToPlanName.TryGetValue(priceId, out var planName))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return _plans.TryGetValue(planName, out var config) && config.Interval == "year";
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlanConfiguration? GetPlanConfiguration(string planSectionName)
|
||||||
|
{
|
||||||
|
return _plans.TryGetValue(planSectionName, out var config) ? config : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dictionary<string, PlanConfiguration> LoadPlansFromConfiguration()
|
||||||
|
{
|
||||||
|
var plans = new Dictionary<string, PlanConfiguration>();
|
||||||
|
var plansSection = _configuration.GetSection("Plans");
|
||||||
|
|
||||||
|
foreach (var planSection in plansSection.GetChildren())
|
||||||
|
{
|
||||||
|
var config = new PlanConfiguration();
|
||||||
|
planSection.Bind(config);
|
||||||
|
|
||||||
|
// Mapear o nome da seção para PlanType base
|
||||||
|
config.BasePlanType = planSection.Key switch
|
||||||
|
{
|
||||||
|
"Basic" or "BasicYearly" => PlanType.Basic,
|
||||||
|
"Professional" or "ProfessionalYearly" => PlanType.Professional,
|
||||||
|
"Premium" or "PremiumYearly" => PlanType.Premium,
|
||||||
|
"PremiumAffiliate" or "PremiumAffiliateYearly" => PlanType.PremiumAffiliate,
|
||||||
|
_ => PlanType.Trial
|
||||||
|
};
|
||||||
|
|
||||||
|
plans[planSection.Key] = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
return plans;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dictionary<string, string> BuildPriceIdToPlanNameMap()
|
||||||
|
{
|
||||||
|
var map = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
foreach (var kvp in _plans)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(kvp.Value.PriceId))
|
||||||
|
{
|
||||||
|
map[kvp.Value.PriceId] = kvp.Key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dictionary<string, PlanType> BuildPriceIdToPlanTypeMap()
|
||||||
|
{
|
||||||
|
var map = new Dictionary<string, PlanType>();
|
||||||
|
|
||||||
|
foreach (var kvp in _plans)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(kvp.Value.PriceId))
|
||||||
|
{
|
||||||
|
map[kvp.Value.PriceId] = kvp.Value.BasePlanType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private T GetConfigValue<T>(PlanType planType, string propertyName, T defaultValue)
|
||||||
|
{
|
||||||
|
// Buscar primeira nas configurações mensais, depois anuais
|
||||||
|
var monthlyPlan = planType switch
|
||||||
|
{
|
||||||
|
PlanType.Basic => "Basic",
|
||||||
|
PlanType.Professional => "Professional",
|
||||||
|
PlanType.Premium => "Premium",
|
||||||
|
PlanType.PremiumAffiliate => "PremiumAffiliate",
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
|
if (monthlyPlan != null && _plans.TryGetValue(monthlyPlan, out var config))
|
||||||
|
{
|
||||||
|
var property = typeof(PlanConfiguration).GetProperty(propertyName);
|
||||||
|
if (property != null)
|
||||||
|
{
|
||||||
|
var value = property.GetValue(config);
|
||||||
|
if (value != null && value is T typedValue)
|
||||||
|
{
|
||||||
|
return typedValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -57,7 +57,7 @@
|
|||||||
"Interval": "month"
|
"Interval": "month"
|
||||||
},
|
},
|
||||||
"PremiumAffiliate": {
|
"PremiumAffiliate": {
|
||||||
"Name": "Premium + Afiliados",
|
"Name": "Premium+Afiliados",
|
||||||
"PriceId": "price_1RycTaBMIadsOxJVeDLseXQq",
|
"PriceId": "price_1RycTaBMIadsOxJVeDLseXQq",
|
||||||
"Price": 29.90,
|
"Price": 29.90,
|
||||||
"MaxPages": 15,
|
"MaxPages": 15,
|
||||||
@ -107,7 +107,7 @@
|
|||||||
"Interval": "year"
|
"Interval": "year"
|
||||||
},
|
},
|
||||||
"PremiumAffiliateYearly": {
|
"PremiumAffiliateYearly": {
|
||||||
"Name": "Premium + Afiliados Anual",
|
"Name": "Premium+Afiliados Anual",
|
||||||
"PriceId": "price_1RycaEBMIadsOxJVEhsdB2Y1",
|
"PriceId": "price_1RycaEBMIadsOxJVEhsdB2Y1",
|
||||||
"Price": 299.00,
|
"Price": 299.00,
|
||||||
"MaxPages": 15,
|
"MaxPages": 15,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user