feat: ajustes do stripe
This commit is contained in:
parent
0c176a2abf
commit
3fa95aefd8
@ -112,13 +112,21 @@ namespace QRRapidoApp.Controllers
|
||||
_logger.LogInformation("QR code generated successfully - GenerationTime: {GenerationTimeMs}ms, FromCache: {FromCache}, Size: {Size}px",
|
||||
generationStopwatch.ElapsedMilliseconds, result.FromCache, request.Size);
|
||||
|
||||
// Update counter for free users
|
||||
if (!request.IsPremium && userId != null)
|
||||
// Update counter for all logged users
|
||||
if (userId != null)
|
||||
{
|
||||
var remaining = await _userService.DecrementDailyQRCountAsync(userId);
|
||||
result.RemainingQRs = remaining;
|
||||
|
||||
_logger.LogDebug("Updated QR count for free user - Remaining: {RemainingQRs}", remaining);
|
||||
if (request.IsPremium)
|
||||
{
|
||||
result.RemainingQRs = int.MaxValue; // Premium users have unlimited
|
||||
// Still increment the count for statistics
|
||||
await _userService.IncrementDailyQRCountAsync(userId);
|
||||
}
|
||||
else
|
||||
{
|
||||
var remaining = await _userService.IncrementDailyQRCountAsync(userId);
|
||||
result.RemainingQRs = remaining;
|
||||
_logger.LogDebug("Updated QR count for free user - Remaining: {RemainingQRs}", remaining);
|
||||
}
|
||||
}
|
||||
|
||||
// Save to history if user is logged in (fire and forget)
|
||||
@ -467,11 +475,48 @@ namespace QRRapidoApp.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("GetUserStats")]
|
||||
public async Task<IActionResult> GetUserStats()
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var user = await _userService.GetUserAsync(userId);
|
||||
var isPremium = user?.IsPremium ?? false;
|
||||
|
||||
// For logged users (premium or not), return -1 to indicate unlimited
|
||||
// For consistency with the frontend logic
|
||||
var remainingCount = -1; // Unlimited for all logged users
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
remainingCount = remainingCount,
|
||||
isPremium = isPremium,
|
||||
isUnlimited = true
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting user stats");
|
||||
return StatusCode(500);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> CheckRateLimitAsync(string? userId, Models.User? user)
|
||||
{
|
||||
// Premium users have unlimited QR codes
|
||||
if (user?.IsPremium == true) return true;
|
||||
|
||||
var dailyLimit = userId != null ? 50 : 10; // Logged in: 50/day, Anonymous: 10/day
|
||||
// Logged users (non-premium) have unlimited QR codes
|
||||
if (userId != null) return true;
|
||||
|
||||
// Anonymous users have 3 QR codes per day
|
||||
var dailyLimit = 3;
|
||||
var currentCount = await _userService.GetDailyQRCountAsync(userId);
|
||||
|
||||
return currentCount < dailyLimit;
|
||||
|
||||
@ -67,7 +67,8 @@ namespace QRRapidoApp.Middleware
|
||||
"api/", "health", "_framework/", "lib/", "css/", "js/", "images/",
|
||||
"favicon.ico", "robots.txt", "sitemap.xml",
|
||||
"signin-microsoft", "signin-google", "signout-callback-oidc",
|
||||
"Account/ExternalLoginCallback", "Account/Logout"
|
||||
"Account/ExternalLoginCallback", "Account/Logout", "Pagamento/CreateCheckout",
|
||||
"Pagamento/StripeWebhook"
|
||||
};
|
||||
|
||||
return specialRoutes.Any(route => path.StartsWith(route, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
<PackageReference Include="Serilog.Sinks.Async" Version="2.1.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.1-dev-00953" />
|
||||
<PackageReference Include="Serilog.Sinks.Seq" Version="9.0.0" />
|
||||
<PackageReference Include="Stripe.net" Version="43.15.0" />
|
||||
<PackageReference Include="Stripe.net" Version="48.4.0" />
|
||||
<PackageReference Include="StackExchange.Redis" Version="2.7.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.0.0" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="8.0.0" />
|
||||
|
||||
@ -343,3 +343,7 @@ Este projeto está licenciado sob a licença MIT - veja o arquivo [LICENSE](LICE
|
||||
**QR Rapido** - Velocidade extrema meets experiência excepcional 🚀
|
||||
|
||||
*Desenvolvido com ❤️ para a comunidade de desenvolvedores*
|
||||
|
||||
No WSL - Para rodar o webhook do stripe local.
|
||||
```bash
|
||||
stripe listen --forward-to https://localhost:52428/pagamento/stripewebhook --skip-verify
|
||||
|
||||
@ -774,4 +774,13 @@
|
||||
<data name="AllRightsReserved" xml:space="preserve">
|
||||
<value>All rights reserved.</value>
|
||||
</data>
|
||||
<data name="QRCodesRemainingToday" xml:space="preserve">
|
||||
<value>QR codes remaining today</value>
|
||||
</data>
|
||||
<data name="RateLimitExceeded" xml:space="preserve">
|
||||
<value>Daily limit reached! Login for unlimited access.</value>
|
||||
</data>
|
||||
<data name="AnonymousUserLimit" xml:space="preserve">
|
||||
<value>Anonymous users: 3 QR codes per day</value>
|
||||
</data>
|
||||
</root>
|
||||
@ -774,4 +774,13 @@
|
||||
<data name="AllRightsReserved" xml:space="preserve">
|
||||
<value>Todos los derechos reservados.</value>
|
||||
</data>
|
||||
<data name="QRCodesRemainingToday" xml:space="preserve">
|
||||
<value>Códigos QR restantes hoy</value>
|
||||
</data>
|
||||
<data name="RateLimitExceeded" xml:space="preserve">
|
||||
<value>¡Límite diario alcanzado! Inicia sesión para acceso ilimitado.</value>
|
||||
</data>
|
||||
<data name="AnonymousUserLimit" xml:space="preserve">
|
||||
<value>Usuarios anónimos: 3 códigos QR por día</value>
|
||||
</data>
|
||||
</root>
|
||||
@ -774,4 +774,13 @@
|
||||
<data name="AllRightsReserved" xml:space="preserve">
|
||||
<value>Todos os direitos reservados.</value>
|
||||
</data>
|
||||
<data name="QRCodesRemainingToday" xml:space="preserve">
|
||||
<value>QR codes restantes hoje</value>
|
||||
</data>
|
||||
<data name="RateLimitExceeded" xml:space="preserve">
|
||||
<value>Limite diário atingido! Faça login para acesso ilimitado.</value>
|
||||
</data>
|
||||
<data name="AnonymousUserLimit" xml:space="preserve">
|
||||
<value>Usuários anônimos: 3 QR codes por dia</value>
|
||||
</data>
|
||||
</root>
|
||||
@ -16,7 +16,8 @@ namespace QRRapidoApp.Services
|
||||
Task<User?> GetUserByStripeCustomerIdAsync(string customerId);
|
||||
Task<bool> UpdateUserAsync(User user);
|
||||
Task<int> GetDailyQRCountAsync(string? userId);
|
||||
Task<int> DecrementDailyQRCountAsync(string userId);
|
||||
Task<int> IncrementDailyQRCountAsync(string userId);
|
||||
Task<int> GetRemainingQRCountAsync(string userId);
|
||||
Task<bool> CanGenerateQRAsync(string? userId, bool isPremium);
|
||||
Task SaveQRToHistoryAsync(string? userId, QRGenerationResult qrResult);
|
||||
Task<List<QRCodeHistory>> GetUserQRHistoryAsync(string userId, int limit = 50);
|
||||
|
||||
@ -78,7 +78,7 @@ namespace QRRapidoApp.Services
|
||||
|
||||
switch (stripeEvent.Type)
|
||||
{
|
||||
case Events.CheckoutSessionCompleted:
|
||||
case "checkout.session.completed":
|
||||
var session = stripeEvent.Data.Object as Session;
|
||||
if (session?.SubscriptionId != null)
|
||||
{
|
||||
@ -88,12 +88,27 @@ namespace QRRapidoApp.Services
|
||||
}
|
||||
break;
|
||||
|
||||
case Events.InvoicePaymentSucceeded:
|
||||
case "invoice.finalized":
|
||||
var invoice = stripeEvent.Data.Object as Invoice;
|
||||
if (invoice?.SubscriptionId != null)
|
||||
var subscriptionLineItem = invoice.Lines?.Data
|
||||
.FirstOrDefault(line =>
|
||||
!string.IsNullOrEmpty(line.SubscriptionId) ||
|
||||
line.Subscription != null
|
||||
);
|
||||
|
||||
string subscriptionId = null;
|
||||
|
||||
if (subscriptionLineItem != null)
|
||||
{
|
||||
// Tenta obter o ID da assinatura de duas formas diferentes
|
||||
subscriptionId = subscriptionLineItem.SubscriptionId
|
||||
?? subscriptionLineItem.Subscription?.Id;
|
||||
}
|
||||
|
||||
if (subscriptionId != null)
|
||||
{
|
||||
var subscriptionService = new SubscriptionService();
|
||||
var subscription = await subscriptionService.GetAsync(invoice.SubscriptionId);
|
||||
var subscription = await subscriptionService.GetAsync(subscriptionId);
|
||||
var user = await _userService.GetUserByStripeCustomerIdAsync(subscription.CustomerId);
|
||||
if (user != null)
|
||||
{
|
||||
@ -102,7 +117,7 @@ namespace QRRapidoApp.Services
|
||||
}
|
||||
break;
|
||||
|
||||
case Events.CustomerSubscriptionDeleted:
|
||||
case "customer.subscription.deleted":
|
||||
var deletedSubscription = stripeEvent.Data.Object as Subscription;
|
||||
if (deletedSubscription != null)
|
||||
{
|
||||
@ -118,6 +133,8 @@ namespace QRRapidoApp.Services
|
||||
|
||||
private async Task ProcessSubscriptionActivation(string userId, Subscription subscription)
|
||||
{
|
||||
var service = new SubscriptionItemService();
|
||||
var subItem = service.Get(subscription.Items.Data[0].Id);
|
||||
if (string.IsNullOrEmpty(userId) || subscription == null)
|
||||
{
|
||||
_logger.LogWarning("Could not process subscription activation due to missing userId or subscription data.");
|
||||
@ -136,7 +153,8 @@ namespace QRRapidoApp.Services
|
||||
await _userService.UpdateUserStripeCustomerIdAsync(user.Id, subscription.CustomerId);
|
||||
}
|
||||
|
||||
await _userService.ActivatePremiumStatus(userId, subscription.Id, subscription.CurrentPeriodEnd);
|
||||
await _userService.ActivatePremiumStatus(userId, subscription.Id, subItem.CurrentPeriodEnd);
|
||||
|
||||
_logger.LogInformation($"Successfully processed premium activation/renewal for user {userId}.");
|
||||
}
|
||||
|
||||
|
||||
@ -142,7 +142,7 @@ namespace QRRapidoApp.Services
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> DecrementDailyQRCountAsync(string userId)
|
||||
public async Task<int> IncrementDailyQRCountAsync(string userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -163,23 +163,47 @@ namespace QRRapidoApp.Services
|
||||
user.TotalQRGenerated++;
|
||||
await UpdateUserAsync(user);
|
||||
|
||||
// Calculate remaining QRs for free users
|
||||
var dailyLimit = user.IsPremium ? int.MaxValue : _config.GetValue<int>("Premium:FreeQRLimit", 50);
|
||||
return Math.Max(0, dailyLimit - user.DailyQRCount);
|
||||
// Premium and logged users have unlimited QR codes
|
||||
return int.MaxValue;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error decrementing daily QR count for user {userId}: {ex.Message}");
|
||||
_logger.LogError(ex, $"Error incrementing daily QR count for user {userId}: {ex.Message}");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> GetRemainingQRCountAsync(string userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var user = await GetUserAsync(userId);
|
||||
if (user == null) return 0;
|
||||
|
||||
// Premium users have unlimited
|
||||
if (user.IsPremium) return int.MaxValue;
|
||||
|
||||
// Logged users (non-premium) have unlimited
|
||||
return int.MaxValue;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error getting remaining QR count for user {userId}: {ex.Message}");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> CanGenerateQRAsync(string? userId, bool isPremium)
|
||||
{
|
||||
// Premium users have unlimited QR codes
|
||||
if (isPremium) return true;
|
||||
|
||||
// Logged users (non-premium) have unlimited QR codes
|
||||
if (!string.IsNullOrEmpty(userId)) return true;
|
||||
|
||||
// Anonymous users have 3 QR codes per day
|
||||
var dailyCount = await GetDailyQRCountAsync(userId);
|
||||
var limit = string.IsNullOrEmpty(userId) ? 10 : _config.GetValue<int>("Premium:FreeQRLimit", 50);
|
||||
var limit = 3;
|
||||
|
||||
return dailyCount < limit;
|
||||
}
|
||||
|
||||
@ -12,7 +12,22 @@
|
||||
|
||||
<div class="container">
|
||||
<!-- Hidden input for JavaScript premium status check -->
|
||||
<input type="hidden" id="user-premium-status" value="@(ViewBag.IsPremium == true ? "true" : "false")" />
|
||||
@{
|
||||
var statusValue = "anonymous";
|
||||
if (User.Identity.IsAuthenticated)
|
||||
{
|
||||
var isPremium = await AdService.HasValidPremiumSubscription(userId);
|
||||
statusValue = isPremium ? "premium" : "logged-in";
|
||||
}
|
||||
}
|
||||
<input type="hidden" id="user-premium-status" value="@statusValue" />
|
||||
|
||||
<!-- Hidden localized strings for rate limiting -->
|
||||
<div style="display: none;">
|
||||
<span data-localized="RateLimitExceeded">@Localizer["RateLimitExceeded"]</span>
|
||||
<span data-localized="QRCodesRemainingToday">@Localizer["QRCodesRemainingToday"]</span>
|
||||
<span data-localized="UnlimitedToday">@Localizer["UnlimitedToday"]</span>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- QR Generator Form -->
|
||||
@ -55,18 +70,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 text-end">
|
||||
@if (User.Identity.IsAuthenticated)
|
||||
{
|
||||
<small class="text-muted">
|
||||
<span class="qr-counter">@Localizer["UnlimitedToday"]</span>
|
||||
</small>
|
||||
}
|
||||
else
|
||||
{
|
||||
<small class="text-muted">
|
||||
<span class="qr-counter">@Localizer["QRCodesRemaining"]</span>
|
||||
</small>
|
||||
}
|
||||
<small class="text-muted">
|
||||
<span class="qr-counter">Carregando...</span>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
@using Microsoft.Extensions.Localization
|
||||
@inject IStringLocalizer<QRRapidoApp.Resources.SharedResource> Localizer
|
||||
@{
|
||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||
ViewData["Title"] = "Sucesso";
|
||||
}
|
||||
|
||||
|
||||
@ -21,10 +21,10 @@
|
||||
}
|
||||
},
|
||||
"Stripe": {
|
||||
"PublishableKey": "pk_test_xxxxx",
|
||||
"SecretKey": "sk_test_xxxxx",
|
||||
"WebhookSecret": "whsec_xxxxx",
|
||||
"PriceId": "price_xxxxx"
|
||||
"PublishableKey": "pk_test_51Rs42tBeR5IFYUsBooapyDwQTgh6CFuKbya5R3MVDTrdOUKmgiHQYipU0pgOdG5iKogH77RUYIKBJzbCt5BghUOY00xitV5KiN",
|
||||
"SecretKey": "sk_test_51Rs42tBeR5IFYUsBtycRlJJcdwgoMbh8MfQIKIGelBPTQFwDcOn2iCCbw5uG6hnqlpgNAUuFgWRAUUMA8qkABKun00EIx4odDF",
|
||||
"WebhookSecret": "whsec_2e828803ceb48e7865458b0cf332b68535fdff8753d26d69b1c88ea55cb0e482",
|
||||
"PriceId": "prod_SnfQTxwE3i8r5L"
|
||||
},
|
||||
"AdSense": {
|
||||
"ClientId": "ca-pub-XXXXXXXXXX",
|
||||
@ -41,7 +41,7 @@
|
||||
},
|
||||
"Premium": {
|
||||
"FreeQRLimit": 10,
|
||||
"PremiumPrice": 19.90,
|
||||
"PremiumPrice": 12.90,
|
||||
"Features": {
|
||||
"UnlimitedQR": true,
|
||||
"DynamicQR": true,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// QR Rapido Speed Generator
|
||||
// QR Rapido Speed Generator
|
||||
class QRRapidoGenerator {
|
||||
constructor() {
|
||||
this.startTime = 0;
|
||||
@ -49,7 +49,9 @@ class QRRapidoGenerator {
|
||||
this.checkAdFreeStatus();
|
||||
this.updateLanguage();
|
||||
this.updateStatsCounters();
|
||||
this.initializeUserCounter();
|
||||
this.initializeProgressiveFlow();
|
||||
this.initializeRateLimiting();
|
||||
|
||||
// Validar segurança dos dados após carregamento
|
||||
setTimeout(() => {
|
||||
@ -345,6 +347,9 @@ class QRRapidoGenerator {
|
||||
async generateQRWithTimer(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Check rate limit for anonymous users
|
||||
if (!this.checkRateLimit()) return;
|
||||
|
||||
// Validation
|
||||
if (!this.validateForm()) return;
|
||||
|
||||
@ -708,9 +713,17 @@ class QRRapidoGenerator {
|
||||
generationTime: generationTime
|
||||
};
|
||||
|
||||
// Update counter for free users
|
||||
// Increment rate limit counter after successful generation
|
||||
this.incrementRateLimit();
|
||||
|
||||
// Update counter for logged users
|
||||
if (result.remainingQRs !== undefined) {
|
||||
this.updateRemainingCounter(result.remainingQRs);
|
||||
if (result.remainingQRs === -1 || result.remainingQRs === 2147483647 || result.remainingQRs >= 1000000) {
|
||||
// Logged user - show unlimited
|
||||
this.showUnlimitedCounter();
|
||||
} else {
|
||||
this.updateRemainingCounter(result.remainingQRs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -965,6 +978,27 @@ class QRRapidoGenerator {
|
||||
}, 30000); // Update every 30 seconds
|
||||
}
|
||||
|
||||
async initializeUserCounter() {
|
||||
try {
|
||||
const response = await fetch('/api/QR/GetUserStats');
|
||||
if (response.ok) {
|
||||
const stats = await response.json();
|
||||
console.log('User stats loaded:', stats);
|
||||
console.log('remainingCount:', stats.remainingCount, 'type:', typeof stats.remainingCount);
|
||||
console.log('isUnlimited:', stats.isUnlimited);
|
||||
|
||||
// For logged users, always show unlimited (they all have unlimited QR codes)
|
||||
console.log('Calling showUnlimitedCounter directly for logged user');
|
||||
this.showUnlimitedCounter();
|
||||
} else {
|
||||
console.log('GetUserStats response not ok:', response.status);
|
||||
}
|
||||
} catch (error) {
|
||||
// If not authenticated or error, keep the default "Carregando..." text
|
||||
console.debug('User not authenticated or error loading stats:', error);
|
||||
}
|
||||
}
|
||||
|
||||
trackGenerationEvent(type, time) {
|
||||
// Google Analytics
|
||||
if (typeof gtag !== 'undefined') {
|
||||
@ -1332,8 +1366,15 @@ class QRRapidoGenerator {
|
||||
|
||||
updateRemainingCounter(remaining) {
|
||||
const counterElement = document.querySelector('.qr-counter');
|
||||
if (counterElement) {
|
||||
counterElement.textContent = `${remaining} QR codes restantes hoje`;
|
||||
if (counterElement && remaining !== null && remaining !== undefined) {
|
||||
// If it's unlimited (any special value indicating unlimited)
|
||||
if (remaining === -1 || remaining >= 2147483647 || remaining > 1000000) {
|
||||
this.showUnlimitedCounter();
|
||||
return;
|
||||
}
|
||||
|
||||
const remainingText = this.getLocalizedString('QRCodesRemainingToday');
|
||||
counterElement.textContent = `${remaining} ${remainingText}`;
|
||||
|
||||
if (remaining <= 3) {
|
||||
counterElement.className = 'badge bg-warning qr-counter';
|
||||
@ -1344,6 +1385,18 @@ class QRRapidoGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
showUnlimitedCounter() {
|
||||
console.log('showUnlimitedCounter called');
|
||||
const counterElement = document.querySelector('.qr-counter');
|
||||
if (counterElement) {
|
||||
counterElement.textContent = 'QR Codes ilimitados';
|
||||
counterElement.className = 'badge bg-success qr-counter';
|
||||
console.log('Set counter to: QR Codes ilimitados');
|
||||
} else {
|
||||
console.log('Counter element not found');
|
||||
}
|
||||
}
|
||||
|
||||
showError(message) {
|
||||
this.showAlert(message, 'danger');
|
||||
}
|
||||
@ -1996,6 +2049,258 @@ class QRRapidoGenerator {
|
||||
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
// Rate Limiting Methods
|
||||
initializeRateLimiting() {
|
||||
// Wait a bit for DOM to be fully ready
|
||||
setTimeout(() => {
|
||||
this.updateRateDisplayCounter();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
checkRateLimit() {
|
||||
// Check if user is logged in (unlimited access)
|
||||
const userStatus = document.getElementById('user-premium-status');
|
||||
console.log('🔍 Rate limit check - User status:', userStatus ? userStatus.value : 'not found');
|
||||
|
||||
if (userStatus && userStatus.value === 'logged-in') {
|
||||
console.log('✅ User is logged in - unlimited access');
|
||||
return true; // Unlimited for logged users
|
||||
}
|
||||
|
||||
// For anonymous users, check daily limit
|
||||
const today = new Date().toDateString();
|
||||
const cookieName = 'qr_daily_count';
|
||||
const rateLimitData = this.getCookie(cookieName);
|
||||
|
||||
console.log('📅 Today:', today);
|
||||
console.log('🍪 Cookie data:', rateLimitData);
|
||||
|
||||
let currentData = { date: today, count: 0 };
|
||||
|
||||
if (rateLimitData) {
|
||||
try {
|
||||
currentData = JSON.parse(rateLimitData);
|
||||
console.log('📊 Parsed data:', currentData);
|
||||
|
||||
// Reset count if it's a new day
|
||||
if (currentData.date !== today) {
|
||||
console.log('🔄 New day detected, resetting count');
|
||||
currentData = { date: today, count: 0 };
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('❌ Error parsing cookie:', e);
|
||||
currentData = { date: today, count: 0 };
|
||||
}
|
||||
} else {
|
||||
console.log('🆕 No cookie found, starting fresh');
|
||||
}
|
||||
|
||||
console.log('📈 Current count:', currentData.count);
|
||||
|
||||
// Check if limit exceeded (don't increment here)
|
||||
if (currentData.count >= 3) {
|
||||
console.log('🚫 Rate limit exceeded');
|
||||
this.showRateLimitError();
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('✅ Rate limit check passed');
|
||||
return true;
|
||||
}
|
||||
|
||||
incrementRateLimit() {
|
||||
// Only increment after successful QR generation
|
||||
const userStatus = document.getElementById('user-premium-status');
|
||||
if (userStatus && userStatus.value === 'logged-in') {
|
||||
return; // No limits for logged users
|
||||
}
|
||||
|
||||
const today = new Date().toDateString();
|
||||
const cookieName = 'qr_daily_count';
|
||||
const rateLimitData = this.getCookie(cookieName);
|
||||
|
||||
let currentData = { date: today, count: 0 };
|
||||
|
||||
if (rateLimitData) {
|
||||
try {
|
||||
currentData = JSON.parse(rateLimitData);
|
||||
if (currentData.date !== today) {
|
||||
currentData = { date: today, count: 0 };
|
||||
}
|
||||
} catch (e) {
|
||||
currentData = { date: today, count: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
// Increment count and save
|
||||
currentData.count++;
|
||||
this.setCookie(cookieName, JSON.stringify(currentData), 1);
|
||||
|
||||
// Update display counter
|
||||
this.updateRateDisplayCounter();
|
||||
}
|
||||
|
||||
showRateLimitError() {
|
||||
const message = this.getLocalizedString('RateLimitExceeded') || 'Daily limit reached! Login for unlimited access.';
|
||||
this.showError(message);
|
||||
}
|
||||
|
||||
updateRateDisplayCounter() {
|
||||
const counterElement = document.querySelector('.qr-counter');
|
||||
if (!counterElement) return;
|
||||
|
||||
// Check user status
|
||||
const userStatus = document.getElementById('user-premium-status');
|
||||
|
||||
if (userStatus && userStatus.value === 'premium') {
|
||||
// Premium users have unlimited QRs
|
||||
const unlimitedText = this.getLocalizedString('UnlimitedToday');
|
||||
counterElement.textContent = unlimitedText;
|
||||
counterElement.className = 'badge bg-success qr-counter';
|
||||
return;
|
||||
} else if (userStatus && userStatus.value === 'logged-in') {
|
||||
// Free logged users - we need to get their actual remaining count
|
||||
this.updateLoggedUserCounter();
|
||||
return;
|
||||
}
|
||||
|
||||
// For anonymous users, show remaining count
|
||||
const today = new Date().toDateString();
|
||||
const cookieName = 'qr_daily_count';
|
||||
const rateLimitData = this.getCookie(cookieName);
|
||||
|
||||
let remaining = 3;
|
||||
|
||||
if (rateLimitData) {
|
||||
try {
|
||||
const currentData = JSON.parse(rateLimitData);
|
||||
if (currentData.date === today) {
|
||||
remaining = Math.max(0, 3 - currentData.count);
|
||||
}
|
||||
} catch (e) {
|
||||
remaining = 3;
|
||||
}
|
||||
}
|
||||
|
||||
const remainingText = this.getLocalizedString('QRCodesRemainingToday');
|
||||
counterElement.textContent = `${remaining} ${remainingText}`;
|
||||
}
|
||||
|
||||
async updateLoggedUserCounter() {
|
||||
const counterElement = document.querySelector('.qr-counter');
|
||||
if (!counterElement) return;
|
||||
|
||||
try {
|
||||
// Fetch user's remaining QR count from the server
|
||||
const response = await fetch('/api/QR/GetUserStats', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
|
||||
if (data.isPremium) {
|
||||
// Premium user - show unlimited
|
||||
const unlimitedText = this.getLocalizedString('UnlimitedToday');
|
||||
counterElement.textContent = unlimitedText;
|
||||
counterElement.className = 'badge bg-success qr-counter';
|
||||
} else {
|
||||
// Free user - show remaining count
|
||||
const remaining = data.remainingCount || 0;
|
||||
const remainingText = this.getLocalizedString('QRCodesRemainingToday');
|
||||
if (remaining !== -1) {
|
||||
counterElement.textContent = `${remaining} ${remainingText}`;
|
||||
|
||||
counterElement.className = 'badge bg-primary qr-counter';
|
||||
if (remaining <= 3) {
|
||||
counterElement.className = 'badge bg-warning qr-counter';
|
||||
}
|
||||
if (remaining === 0) {
|
||||
counterElement.className = 'badge bg-danger qr-counter';
|
||||
}
|
||||
}
|
||||
else {
|
||||
const unlimitedText = this.getLocalizedString('UnlimitedToday');
|
||||
counterElement.textContent = unlimitedText;
|
||||
counterElement.className = 'badge bg-success qr-counter';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Fallback to showing 50 remaining if API fails
|
||||
const remainingText = this.getLocalizedString('QRCodesRemainingToday');
|
||||
counterElement.textContent = `50 ${remainingText}`;
|
||||
counterElement.className = 'badge bg-primary qr-counter';
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to fetch user stats:', error);
|
||||
// Fallback to showing 50 remaining if API fails
|
||||
const remainingText = this.getLocalizedString('QRCodesRemainingToday');
|
||||
counterElement.textContent = `50 ${remainingText}`;
|
||||
counterElement.className = 'badge bg-primary qr-counter';
|
||||
}
|
||||
}
|
||||
|
||||
getLocalizedString(key) {
|
||||
// Try to get from server-side localization first
|
||||
const element = document.querySelector(`[data-localized="${key}"]`);
|
||||
if (element) {
|
||||
const text = element.textContent.trim() || element.value;
|
||||
if (text) return text;
|
||||
}
|
||||
|
||||
// Fallback to client-side strings
|
||||
if (this.languageStrings[this.currentLang] && this.languageStrings[this.currentLang][key]) {
|
||||
return this.languageStrings[this.currentLang][key];
|
||||
}
|
||||
|
||||
// Default fallbacks based on key
|
||||
const defaults = {
|
||||
'UnlimitedToday': 'Ilimitado hoje',
|
||||
'QRCodesRemainingToday': 'QR codes restantes hoje',
|
||||
'RateLimitExceeded': 'Limite diário atingido! Faça login para acesso ilimitado.'
|
||||
};
|
||||
|
||||
return defaults[key] || key;
|
||||
}
|
||||
|
||||
setCookie(name, value, days) {
|
||||
const expires = new Date();
|
||||
expires.setTime(expires.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||
document.cookie = `${name}=${value};expires=${expires.toUTCString()};path=/;SameSite=Strict`;
|
||||
}
|
||||
|
||||
getCookie(name) {
|
||||
const nameEQ = name + "=";
|
||||
const ca = document.cookie.split(';');
|
||||
for (let i = 0; i < ca.length; i++) {
|
||||
let c = ca[i];
|
||||
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
|
||||
if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Debug/reset function - call from console if needed
|
||||
resetRateLimit() {
|
||||
// Multiple ways to clear the cookie
|
||||
document.cookie = 'qr_daily_count=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
|
||||
document.cookie = 'qr_daily_count=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=' + window.location.hostname + ';';
|
||||
document.cookie = 'qr_daily_count=; max-age=0; path=/;';
|
||||
|
||||
// Also clear from storage if exists
|
||||
if (typeof Storage !== "undefined") {
|
||||
localStorage.removeItem('qr_daily_count');
|
||||
sessionStorage.removeItem('qr_daily_count');
|
||||
}
|
||||
|
||||
this.updateRateDisplayCounter();
|
||||
console.log('🧹 Rate limit completely reset!');
|
||||
console.log('🍪 All cookies:', document.cookie);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize when DOM loads
|
||||
|
||||
Loading…
Reference in New Issue
Block a user