feat: ajustes de pagamento e account/logoff
This commit is contained in:
parent
52849487a1
commit
6aafb1d067
@ -17,6 +17,7 @@ namespace QRRapidoApp.Controllers
|
|||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
private readonly StripeService _stripeService;
|
private readonly StripeService _stripeService;
|
||||||
private readonly ILogger<PagamentoController> _logger;
|
private readonly ILogger<PagamentoController> _logger;
|
||||||
|
private readonly List<string> languages = new List<string> { "pt-BR", "es-PY", "es" };
|
||||||
|
|
||||||
public PagamentoController(IPlanService planService, IUserService userService, StripeService stripeService, ILogger<PagamentoController> logger, AdDisplayService adDisplayService)
|
public PagamentoController(IPlanService planService, IUserService userService, StripeService stripeService, ILogger<PagamentoController> logger, AdDisplayService adDisplayService)
|
||||||
{
|
{
|
||||||
@ -31,7 +32,7 @@ namespace QRRapidoApp.Controllers
|
|||||||
public async Task<IActionResult> SelecaoPlano()
|
public async Task<IActionResult> SelecaoPlano()
|
||||||
{
|
{
|
||||||
var plans = await _planService.GetActivePlansAsync();
|
var plans = await _planService.GetActivePlansAsync();
|
||||||
var countryCode = GetUserCountryCode(); // Implement this method based on your needs
|
var countryCode = GetUserCountryCodeComplete(); // Implement this method based on your needs
|
||||||
_adDisplayService.SetViewBagAds(ViewBag);
|
_adDisplayService.SetViewBagAds(ViewBag);
|
||||||
|
|
||||||
var model = new SelecaoPlanoViewModel
|
var model = new SelecaoPlanoViewModel
|
||||||
@ -44,7 +45,7 @@ namespace QRRapidoApp.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> CreateCheckout(string planId)
|
public async Task<IActionResult> CreateCheckout(string planId, string lang)
|
||||||
{
|
{
|
||||||
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||||
if (string.IsNullOrEmpty(userId))
|
if (string.IsNullOrEmpty(userId))
|
||||||
@ -59,13 +60,18 @@ namespace QRRapidoApp.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
var countryCode = GetUserCountryCode();
|
var countryCode = GetUserCountryCode();
|
||||||
|
if (countryCode != lang && languages.Contains(lang))
|
||||||
|
{
|
||||||
|
countryCode = lang;
|
||||||
|
}
|
||||||
|
|
||||||
var priceId = plan.PricesByCountry.ContainsKey(countryCode)
|
var priceId = plan.PricesByCountry.ContainsKey(countryCode)
|
||||||
? plan.PricesByCountry[countryCode].StripePriceId
|
? plan.PricesByCountry[countryCode].StripePriceId
|
||||||
: plan.StripePriceId;
|
: plan.StripePriceId;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var checkoutUrl = await _stripeService.CreateCheckoutSessionAsync(userId, priceId);
|
var checkoutUrl = await _stripeService.CreateCheckoutSessionAsync(userId, priceId, lang);
|
||||||
return Json(new { success = true, url = checkoutUrl });
|
return Json(new { success = true, url = checkoutUrl });
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -84,11 +90,22 @@ namespace QRRapidoApp.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public IActionResult Cancelar()
|
public async Task<IActionResult> Cancelar()
|
||||||
{
|
{
|
||||||
_adDisplayService.SetViewBagAds(ViewBag);
|
_adDisplayService.SetViewBagAds(ViewBag);
|
||||||
ViewBag.CancelMessage = "O pagamento foi cancelado. Você pode tentar novamente a qualquer momento.";
|
ViewBag.CancelMessage = "O pagamento foi cancelado. Você pode tentar novamente a qualquer momento.";
|
||||||
return View("SelecaoPlano");
|
|
||||||
|
var plans = await _planService.GetActivePlansAsync();
|
||||||
|
var countryCode = GetUserCountryCode(); // Implement this method based on your needs
|
||||||
|
_adDisplayService.SetViewBagAds(ViewBag);
|
||||||
|
|
||||||
|
var model = new SelecaoPlanoViewModel
|
||||||
|
{
|
||||||
|
Plans = plans,
|
||||||
|
CountryCode = countryCode
|
||||||
|
};
|
||||||
|
|
||||||
|
return View("SelecaoPlano", model);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
@ -118,7 +135,37 @@ namespace QRRapidoApp.Controllers
|
|||||||
|
|
||||||
private string GetUserCountryCode()
|
private string GetUserCountryCode()
|
||||||
{
|
{
|
||||||
// Prioritize Cloudflare header, fallback to a default or other methods
|
// Check current culture from URL first
|
||||||
|
var culture = HttpContext.Request.RouteValues["culture"]?.ToString() ??
|
||||||
|
HttpContext.Features.Get<Microsoft.AspNetCore.Localization.IRequestCultureFeature>()?.RequestCulture?.Culture?.Name;
|
||||||
|
|
||||||
|
var countryMap = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "pt-BR", "BR" },
|
||||||
|
{ "es-PY", "PY" },
|
||||||
|
{ "es", "PY" }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(culture) && countryMap.ContainsKey(culture))
|
||||||
|
{
|
||||||
|
return countryMap[culture];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to Cloudflare header or default
|
||||||
|
return HttpContext.Request.Headers["CF-IPCountry"].FirstOrDefault() ?? "BR";
|
||||||
|
}
|
||||||
|
private string GetUserCountryCodeComplete()
|
||||||
|
{
|
||||||
|
// Check current culture from URL first
|
||||||
|
var culture = HttpContext.Request.RouteValues["culture"]?.ToString() ??
|
||||||
|
HttpContext.Features.Get<Microsoft.AspNetCore.Localization.IRequestCultureFeature>()?.RequestCulture?.Culture?.Name;
|
||||||
|
|
||||||
|
if (languages.Contains(culture))
|
||||||
|
{
|
||||||
|
return culture;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to Cloudflare header or default
|
||||||
return HttpContext.Request.Headers["CF-IPCountry"].FirstOrDefault() ?? "BR";
|
return HttpContext.Request.Headers["CF-IPCountry"].FirstOrDefault() ?? "BR";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -643,7 +643,7 @@
|
|||||||
<value>Geração prioritária (0.4s)</value>
|
<value>Geração prioritária (0.4s)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AcceleratePrice" xml:space="preserve">
|
<data name="AcceleratePrice" xml:space="preserve">
|
||||||
<value>Acelerar por R$ 19,90/mês</value>
|
<value>Acelerar por R$ 12,90/mês</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ChooseTypeFirst" xml:space="preserve">
|
<data name="ChooseTypeFirst" xml:space="preserve">
|
||||||
<value>Primeiro, escolha o tipo de QR Code</value>
|
<value>Primeiro, escolha o tipo de QR Code</value>
|
||||||
|
|||||||
@ -24,7 +24,7 @@ namespace QRRapidoApp.Services
|
|||||||
StripeConfiguration.ApiKey = _config["Stripe:SecretKey"];
|
StripeConfiguration.ApiKey = _config["Stripe:SecretKey"];
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> CreateCheckoutSessionAsync(string userId, string priceId)
|
public async Task<string> CreateCheckoutSessionAsync(string userId, string priceId, string lang = "pt-BR")
|
||||||
{
|
{
|
||||||
var user = await _userService.GetUserAsync(userId);
|
var user = await _userService.GetUserAsync(userId);
|
||||||
if (user == null)
|
if (user == null)
|
||||||
@ -58,7 +58,7 @@ namespace QRRapidoApp.Services
|
|||||||
Customer = customerId,
|
Customer = customerId,
|
||||||
ClientReferenceId = userId,
|
ClientReferenceId = userId,
|
||||||
SuccessUrl = $"{_config["App:BaseUrl"]}/Pagamento/Sucesso",
|
SuccessUrl = $"{_config["App:BaseUrl"]}/Pagamento/Sucesso",
|
||||||
CancelUrl = $"{_config["App:BaseUrl"]}/Pagamento/Cancelar",
|
CancelUrl = $"{_config["App:BaseUrl"]}/{lang}/Pagamento/SelecaoPlano",
|
||||||
AllowPromotionCodes = true,
|
AllowPromotionCodes = true,
|
||||||
Metadata = new Dictionary<string, string> { { "user_id", userId } }
|
Metadata = new Dictionary<string, string> { { "user_id", userId } }
|
||||||
};
|
};
|
||||||
|
|||||||
@ -182,9 +182,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<a href="/Account/Logout" class="btn btn-outline-danger w-100">
|
<form method="post" action="/Account/Logout" class="d-inline w-100">
|
||||||
<i class="fas fa-sign-out-alt me-2"></i>Sair
|
<button type="submit" class="btn btn-outline-danger w-100">
|
||||||
</a>
|
<i class="fas fa-sign-out-alt me-2"></i>Sair
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -9,6 +9,8 @@
|
|||||||
var monthlyPrice = monthlyPlan?.PricesByCountry.GetValueOrDefault(Model.CountryCode)?.Amount ?? 0;
|
var monthlyPrice = monthlyPlan?.PricesByCountry.GetValueOrDefault(Model.CountryCode)?.Amount ?? 0;
|
||||||
var yearlyPrice = yearlyPlan?.PricesByCountry.GetValueOrDefault(Model.CountryCode)?.Amount ?? 0;
|
var yearlyPrice = yearlyPlan?.PricesByCountry.GetValueOrDefault(Model.CountryCode)?.Amount ?? 0;
|
||||||
var yearlySavings = (monthlyPrice * 12) - yearlyPrice;
|
var yearlySavings = (monthlyPrice * 12) - yearlyPrice;
|
||||||
|
var currency = monthlyPlan?.PricesByCountry.GetValueOrDefault(Model.CountryCode)?.Currency ?? "BRL";
|
||||||
|
var currencySymbol = currency == "PYG" ? "₲" : "R$";
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="container mt-5">
|
<div class="container mt-5">
|
||||||
@ -26,7 +28,7 @@
|
|||||||
<div class="card-body d-flex flex-column">
|
<div class="card-body d-flex flex-column">
|
||||||
<h3 class="card-title text-center">@Localizer["MonthlyPlan"]</h3>
|
<h3 class="card-title text-center">@Localizer["MonthlyPlan"]</h3>
|
||||||
<div class="text-center my-4">
|
<div class="text-center my-4">
|
||||||
<span class="display-4 fw-bold">R$ @monthlyPrice.ToString("0.00")</span>
|
<span class="display-4 fw-bold" id="monthly-price">@currencySymbol @(currency == "PYG" ? monthlyPrice.ToString("N0") : monthlyPrice.ToString("0.00"))</span>
|
||||||
<span class="text-muted">@Localizer["PerMonth"]</span>
|
<span class="text-muted">@Localizer["PerMonth"]</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-center text-muted">@Localizer["IdealToStartExploring"]</p>
|
<p class="text-center text-muted">@Localizer["IdealToStartExploring"]</p>
|
||||||
@ -47,13 +49,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-body d-flex flex-column">
|
<div class="card-body d-flex flex-column">
|
||||||
<div class="text-center my-4">
|
<div class="text-center my-4">
|
||||||
<span class="display-4 fw-bold">R$ @yearlyPrice.ToString("0.00")</span>
|
<span class="display-4 fw-bold" id="yearly-price">@currencySymbol @(currency == "PYG" ? yearlyPrice.ToString("N0") : yearlyPrice.ToString("0.00"))</span>
|
||||||
<span class="text-muted">@Localizer["PerYear"]</span>
|
<span class="text-muted">@Localizer["PerYear"]</span>
|
||||||
</div>
|
</div>
|
||||||
@if (yearlySavings > 0)
|
@if (yearlySavings > 0)
|
||||||
{
|
{
|
||||||
<div class="text-center mb-3">
|
<div class="text-center mb-3">
|
||||||
<span class="badge bg-success">@Localizer["SaveMoney"] @yearlySavings.ToString("0.00")!</span>
|
<span class="badge bg-success" id="yearly-savings">@Localizer["SaveMoney"] @(currency == "PYG" ? yearlySavings.ToString("N0") : yearlySavings.ToString("0.00"))!</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<p class="text-center text-muted">@Localizer["BestValueFrequentUsers"]</p>
|
<p class="text-center text-muted">@Localizer["BestValueFrequentUsers"]</p>
|
||||||
@ -81,19 +83,123 @@
|
|||||||
|
|
||||||
@section Scripts {
|
@section Scripts {
|
||||||
<script>
|
<script>
|
||||||
|
// Plans data for dynamic pricing
|
||||||
|
const plansData = @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.Plans.Select(p => new {
|
||||||
|
Id = p.Id,
|
||||||
|
Interval = p.Interval,
|
||||||
|
PricesByCountry = p.PricesByCountry.ToDictionary(kvp => kvp.Key, kvp => new {
|
||||||
|
Amount = kvp.Value.Amount,
|
||||||
|
Currency = kvp.Value.Currency,
|
||||||
|
StripePriceId = kvp.Value.StripePriceId
|
||||||
|
})
|
||||||
|
})));
|
||||||
|
|
||||||
|
// Current country code
|
||||||
|
let currentCountryCode = '@Model.CountryCode';
|
||||||
|
|
||||||
|
// Update prices based on country
|
||||||
|
function updatePrices(countryCode) {
|
||||||
|
const monthlyPlan = plansData.find(p => p.Interval === 'month');
|
||||||
|
const yearlyPlan = plansData.find(p => p.Interval === 'year');
|
||||||
|
|
||||||
|
if (!monthlyPlan || !yearlyPlan) return;
|
||||||
|
|
||||||
|
const monthlyPrice = monthlyPlan.PricesByCountry[countryCode];
|
||||||
|
const yearlyPrice = yearlyPlan.PricesByCountry[countryCode];
|
||||||
|
|
||||||
|
if (!monthlyPrice || !yearlyPrice) return;
|
||||||
|
|
||||||
|
const currencySymbol = monthlyPrice.Currency === 'PYG' ? '₲' : 'R$';
|
||||||
|
const isGuarani = monthlyPrice.Currency === 'PYG';
|
||||||
|
|
||||||
|
// Update monthly price
|
||||||
|
const monthlyPriceEl = document.getElementById('monthly-price');
|
||||||
|
if (monthlyPriceEl) {
|
||||||
|
monthlyPriceEl.textContent = currencySymbol + ' ' + (isGuarani ?
|
||||||
|
monthlyPrice.Amount.toLocaleString('es-PY') :
|
||||||
|
monthlyPrice.Amount.toFixed(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update yearly price
|
||||||
|
const yearlyPriceEl = document.getElementById('yearly-price');
|
||||||
|
if (yearlyPriceEl) {
|
||||||
|
yearlyPriceEl.textContent = currencySymbol + ' ' + (isGuarani ?
|
||||||
|
yearlyPrice.Amount.toLocaleString('es-PY') :
|
||||||
|
yearlyPrice.Amount.toFixed(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update yearly savings
|
||||||
|
const yearlySavings = (monthlyPrice.Amount * 12) - yearlyPrice.Amount;
|
||||||
|
const yearlySavingsEl = document.getElementById('yearly-savings');
|
||||||
|
if (yearlySavingsEl && yearlySavings > 0) {
|
||||||
|
yearlySavingsEl.innerHTML = '@Html.Raw(Localizer["SaveMoney"])' + ' ' + (isGuarani ?
|
||||||
|
yearlySavings.toLocaleString('es-PY') :
|
||||||
|
yearlySavings.toFixed(2)) + '!';
|
||||||
|
}
|
||||||
|
|
||||||
|
currentCountryCode = countryCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect country based on current culture
|
||||||
|
function getCountryFromCulture() {
|
||||||
|
const path = window.location.pathname;
|
||||||
|
const segments = path.split('/').filter(s => s);
|
||||||
|
|
||||||
|
if (segments.length > 0) {
|
||||||
|
const culture = segments[0];
|
||||||
|
const countryMap = {
|
||||||
|
'pt-BR': 'BR',
|
||||||
|
'es-PY': 'PY',
|
||||||
|
'es': 'PY'
|
||||||
|
};
|
||||||
|
return countryMap[culture] || 'BR';
|
||||||
|
}
|
||||||
|
return 'BR';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for page visibility changes (when user switches language and page reloads)
|
||||||
|
document.addEventListener('visibilitychange', function() {
|
||||||
|
if (!document.hidden) {
|
||||||
|
const newCountryCode = getCountryFromCulture();
|
||||||
|
if (newCountryCode !== currentCountryCode) {
|
||||||
|
updatePrices(newCountryCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initial setup
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const detectedCountry = getCountryFromCulture();
|
||||||
|
if (detectedCountry !== currentCountryCode) {
|
||||||
|
updatePrices(detectedCountry);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function obterIdiomaDaUrl() {
|
||||||
|
const path = window.location.pathname;
|
||||||
|
const segments = path.split('/').filter(segment => segment !== '');
|
||||||
|
|
||||||
|
// O primeiro segmento após o domínio é o idioma
|
||||||
|
return segments[0] || 'pt-BR'; // retorna 'pt-BR' como padrão se não encontrar
|
||||||
|
}
|
||||||
|
|
||||||
document.querySelectorAll('.checkout-btn').forEach(button => {
|
document.querySelectorAll('.checkout-btn').forEach(button => {
|
||||||
button.addEventListener('click', async function() {
|
button.addEventListener('click', async function() {
|
||||||
const planId = this.dataset.planId;
|
const planId = this.dataset.planId;
|
||||||
|
const idioma = obterIdiomaDaUrl();
|
||||||
this.disabled = true;
|
this.disabled = true;
|
||||||
this.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> @Localizer["Redirecting"]';
|
this.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> @Localizer["Redirecting"]';
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/Pagamento/CreateCheckout', {
|
const response = await fetch('/Pagamento/CreateCheckout', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
},
|
},
|
||||||
body: `planId=${planId}`
|
body: `planId=${planId}&lang=${idioma}`
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user