fix: ajustar para configuração de plano ficam só no mongondb.
This commit is contained in:
parent
59e04fedc7
commit
2edb4e1196
@ -29,7 +29,9 @@
|
||||
"Bash(nc:*)",
|
||||
"Bash(ssh:*)",
|
||||
"Read(//mnt/c/vscode/**)",
|
||||
"Read(//mnt/c/**)"
|
||||
"Read(//mnt/c/**)",
|
||||
"Bash(chmod +x /mnt/c/vscode/qrrapido/Scripts/update-plans.sh)",
|
||||
"Bash(netstat -tln)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
|
||||
142
Controllers/AdminController.cs
Normal file
142
Controllers/AdminController.cs
Normal file
@ -0,0 +1,142 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QRRapidoApp.Data;
|
||||
using QRRapidoApp.Models;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace QRRapidoApp.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// Admin controller - ONLY accessible from localhost for security
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class AdminController : ControllerBase
|
||||
{
|
||||
private readonly MongoDbContext _context;
|
||||
private readonly ILogger<AdminController> _logger;
|
||||
|
||||
public AdminController(MongoDbContext context, ILogger<AdminController> logger)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Seed/Update MongoDB Plans collection
|
||||
/// Only accessible from localhost (127.0.0.1 or ::1)
|
||||
/// </summary>
|
||||
[HttpPost("SeedPlans")]
|
||||
public async Task<IActionResult> SeedPlans([FromBody] List<Plan> plans)
|
||||
{
|
||||
// SECURITY: Only allow from localhost
|
||||
var remoteIp = HttpContext.Connection.RemoteIpAddress;
|
||||
var isLocalhost = remoteIp != null &&
|
||||
(remoteIp.ToString() == "127.0.0.1" ||
|
||||
remoteIp.ToString() == "::1" ||
|
||||
remoteIp.ToString() == "localhost");
|
||||
|
||||
if (!isLocalhost)
|
||||
{
|
||||
_logger.LogWarning($"Unauthorized admin access attempt from {remoteIp}");
|
||||
return Forbid("This endpoint is only accessible from localhost");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogInformation($"SeedPlans called from localhost - Upserting {plans.Count} plans");
|
||||
|
||||
foreach (var plan in plans)
|
||||
{
|
||||
// Upsert based on interval (month/year)
|
||||
var filter = Builders<Plan>.Filter.Eq(p => p.Interval, plan.Interval);
|
||||
var options = new ReplaceOptions { IsUpsert = true };
|
||||
|
||||
await _context.Plans.ReplaceOneAsync(filter, plan, options);
|
||||
_logger.LogInformation($"Upserted plan: {plan.Interval}");
|
||||
}
|
||||
|
||||
return Ok(new {
|
||||
success = true,
|
||||
message = $"{plans.Count} plans seeded successfully",
|
||||
plans = plans.Select(p => new {
|
||||
interval = p.Interval,
|
||||
priceIds = p.PricesByCountry.ToDictionary(
|
||||
kvp => kvp.Key,
|
||||
kvp => kvp.Value.StripePriceId
|
||||
)
|
||||
})
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error seeding plans");
|
||||
return StatusCode(500, new { success = false, error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all plans from MongoDB
|
||||
/// Only accessible from localhost
|
||||
/// </summary>
|
||||
[HttpGet("Plans")]
|
||||
public async Task<IActionResult> GetPlans()
|
||||
{
|
||||
// SECURITY: Only allow from localhost
|
||||
var remoteIp = HttpContext.Connection.RemoteIpAddress;
|
||||
var isLocalhost = remoteIp != null &&
|
||||
(remoteIp.ToString() == "127.0.0.1" ||
|
||||
remoteIp.ToString() == "::1" ||
|
||||
remoteIp.ToString() == "localhost");
|
||||
|
||||
if (!isLocalhost)
|
||||
{
|
||||
_logger.LogWarning($"Unauthorized admin access attempt from {remoteIp}");
|
||||
return Forbid("This endpoint is only accessible from localhost");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var plans = await _context.Plans.Find(_ => true).ToListAsync();
|
||||
return Ok(new { success = true, count = plans.Count, plans });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error retrieving plans");
|
||||
return StatusCode(500, new { success = false, error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete all plans from MongoDB
|
||||
/// Only accessible from localhost
|
||||
/// </summary>
|
||||
[HttpDelete("Plans")]
|
||||
public async Task<IActionResult> DeleteAllPlans()
|
||||
{
|
||||
// SECURITY: Only allow from localhost
|
||||
var remoteIp = HttpContext.Connection.RemoteIpAddress;
|
||||
var isLocalhost = remoteIp != null &&
|
||||
(remoteIp.ToString() == "127.0.0.1" ||
|
||||
remoteIp.ToString() == "::1" ||
|
||||
remoteIp.ToString() == "localhost");
|
||||
|
||||
if (!isLocalhost)
|
||||
{
|
||||
_logger.LogWarning($"Unauthorized admin access attempt from {remoteIp}");
|
||||
return Forbid("This endpoint is only accessible from localhost");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = await _context.Plans.DeleteManyAsync(_ => true);
|
||||
_logger.LogInformation($"Deleted {result.DeletedCount} plans");
|
||||
return Ok(new { success = true, deletedCount = result.DeletedCount });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error deleting plans");
|
||||
return StatusCode(500, new { success = false, error = ex.Message });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,7 +6,7 @@
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "https://localhost:52428;http://localhost:52429"
|
||||
"applicationUrl": "https://0.0.0.0:52428;http://0.0.0.0:52429"
|
||||
}
|
||||
}
|
||||
}
|
||||
272
Scripts/README-ADMIN-API.md
Normal file
272
Scripts/README-ADMIN-API.md
Normal file
@ -0,0 +1,272 @@
|
||||
# 🔧 Admin API - Gerenciamento de Planos
|
||||
|
||||
Este diretório contém ferramentas para gerenciar os planos do Stripe no MongoDB via API local.
|
||||
|
||||
## 🔒 Segurança
|
||||
|
||||
**IMPORTANTE**: O endpoint `/api/Admin/*` **só funciona quando acessado de localhost** (127.0.0.1). Qualquer acesso externo retornará `403 Forbidden`.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Arquivos
|
||||
|
||||
- **`plans.json`** - Configuração dos planos com Price IDs do Stripe
|
||||
- **`update-plans.sh`** - Script bash para atualizar planos via curl
|
||||
- **`seed-mongodb-plans.js`** - Script MongoDB direto (método alternativo)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Método 1: API Endpoint (Recomendado)
|
||||
|
||||
### Vantagens
|
||||
- ✅ Não precisa de credenciais do MongoDB
|
||||
- ✅ Validação automática
|
||||
- ✅ Logs no sistema
|
||||
- ✅ Funciona com app rodando
|
||||
|
||||
### Como Usar
|
||||
|
||||
#### 1️⃣ Edite o `plans.json` com seus Price IDs
|
||||
|
||||
```json
|
||||
{
|
||||
"pricesByCountry": {
|
||||
"BR": {
|
||||
"stripePriceId": "price_SEU_ID_AQUI"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2️⃣ Inicie a aplicação
|
||||
|
||||
```bash
|
||||
dotnet run
|
||||
# ou
|
||||
dotnet watch run
|
||||
```
|
||||
|
||||
#### 3️⃣ Execute o script de atualização
|
||||
|
||||
```bash
|
||||
# Linux/Mac/WSL
|
||||
cd Scripts
|
||||
./update-plans.sh
|
||||
|
||||
# Especificar porta diferente
|
||||
./update-plans.sh 5000
|
||||
```
|
||||
|
||||
#### 4️⃣ Ou use curl diretamente
|
||||
|
||||
```bash
|
||||
# Atualizar planos
|
||||
curl -k -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @Scripts/plans.json \
|
||||
https://localhost:52428/api/Admin/SeedPlans
|
||||
|
||||
# Listar planos
|
||||
curl -k https://localhost:52428/api/Admin/Plans
|
||||
|
||||
# Deletar todos os planos
|
||||
curl -k -X DELETE https://localhost:52428/api/Admin/Plans
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🗄️ Método 2: Script MongoDB Direto
|
||||
|
||||
### Vantagens
|
||||
- ✅ Funciona sem a app rodando
|
||||
- ✅ Acesso direto ao MongoDB
|
||||
|
||||
### Desvantagens
|
||||
- ❌ Precisa de credenciais do MongoDB
|
||||
- ❌ Sem validação automática
|
||||
|
||||
### Como Usar
|
||||
|
||||
```bash
|
||||
# Localhost
|
||||
mongosh "mongodb://localhost:27017/QrRapido" < Scripts/seed-mongodb-plans.js
|
||||
|
||||
# Produção
|
||||
mongosh "mongodb://user:pass@host:27017/QrRapido?replicaSet=rs0&authSource=admin" < Scripts/seed-mongodb-plans.js
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 Endpoints Disponíveis
|
||||
|
||||
### POST `/api/Admin/SeedPlans`
|
||||
Cria ou atualiza planos no MongoDB
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"interval": "month",
|
||||
"stripePriceId": "price_...",
|
||||
"pricesByCountry": { ... }
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "2 plans seeded successfully",
|
||||
"plans": [ ... ]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET `/api/Admin/Plans`
|
||||
Lista todos os planos do MongoDB
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"count": 2,
|
||||
"plans": [ ... ]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### DELETE `/api/Admin/Plans`
|
||||
Remove todos os planos do MongoDB
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"deletedCount": 2
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ Proteção de Segurança
|
||||
|
||||
O código verifica o IP de origem:
|
||||
|
||||
```csharp
|
||||
var remoteIp = HttpContext.Connection.RemoteIpAddress;
|
||||
var isLocalhost = remoteIp != null &&
|
||||
(remoteIp.ToString() == "127.0.0.1" ||
|
||||
remoteIp.ToString() == "::1" ||
|
||||
remoteIp.ToString() == "localhost");
|
||||
|
||||
if (!isLocalhost) {
|
||||
return Forbid("This endpoint is only accessible from localhost");
|
||||
}
|
||||
```
|
||||
|
||||
**Tentativas de acesso externo são logadas:**
|
||||
```
|
||||
[Warning] Unauthorized admin access attempt from 192.168.1.100
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testando
|
||||
|
||||
```bash
|
||||
# 1. Listar planos atuais
|
||||
curl -k https://localhost:52428/api/Admin/Plans | jq '.'
|
||||
|
||||
# 2. Deletar planos
|
||||
curl -k -X DELETE https://localhost:52428/api/Admin/Plans
|
||||
|
||||
# 3. Criar novos planos
|
||||
curl -k -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @Scripts/plans.json \
|
||||
https://localhost:52428/api/Admin/SeedPlans | jq '.'
|
||||
|
||||
# 4. Verificar se foram criados
|
||||
curl -k https://localhost:52428/api/Admin/Plans | jq '.plans[] | {interval, priceIds: .pricesByCountry}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Troubleshooting
|
||||
|
||||
### Erro: "Connection refused"
|
||||
- ✅ Certifique-se que a app está rodando: `dotnet run`
|
||||
|
||||
### Erro: "Forbidden" mesmo em localhost
|
||||
- ✅ Verifique se está usando `https://localhost` (não `http://`)
|
||||
- ✅ Verifique se está usando `localhost` ou `127.0.0.1` (não o IP da máquina)
|
||||
|
||||
### Erro: "SSL certificate problem"
|
||||
- ✅ Use o flag `-k` no curl para aceitar certificados auto-assinados
|
||||
|
||||
---
|
||||
|
||||
## 📊 Estrutura do plans.json
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": {
|
||||
"pt-BR": "Nome em português",
|
||||
"es-PY": "Nombre en español",
|
||||
"en": "Name in english"
|
||||
},
|
||||
"description": { ... },
|
||||
"features": { ... },
|
||||
"interval": "month" ou "year",
|
||||
"stripePriceId": "price_default",
|
||||
"pricesByCountry": {
|
||||
"BR": {
|
||||
"amount": 9.90,
|
||||
"currency": "BRL",
|
||||
"stripePriceId": "price_BR_ID"
|
||||
},
|
||||
"PY": {
|
||||
"amount": 35000,
|
||||
"currency": "PYG",
|
||||
"stripePriceId": "price_PY_ID"
|
||||
}
|
||||
},
|
||||
"isActive": true,
|
||||
"displayOrder": 1,
|
||||
"badge": { ... } // Opcional
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Fluxo Recomendado
|
||||
|
||||
1. **Desenvolvimento (localhost)**:
|
||||
```bash
|
||||
# Editar plans.json com Price IDs de teste
|
||||
./update-plans.sh
|
||||
```
|
||||
|
||||
2. **Staging**:
|
||||
```bash
|
||||
# SSH no servidor staging
|
||||
ssh user@staging-server
|
||||
cd /app/qrrapido
|
||||
./Scripts/update-plans.sh 5000
|
||||
```
|
||||
|
||||
3. **Produção**:
|
||||
```bash
|
||||
# Opção 1: Via API (se tiver acesso SSH)
|
||||
ssh user@prod-server
|
||||
cd /app/qrrapido
|
||||
./Scripts/update-plans.sh 5001
|
||||
|
||||
# Opção 2: Via MongoDB direto
|
||||
mongosh "connection_string" < Scripts/seed-mongodb-plans.js
|
||||
```
|
||||
134
Scripts/plans.json
Normal file
134
Scripts/plans.json
Normal file
@ -0,0 +1,134 @@
|
||||
[
|
||||
{
|
||||
"name": {
|
||||
"pt-BR": "Premium Mensal",
|
||||
"es-PY": "Premium Mensual",
|
||||
"en": "Premium Monthly"
|
||||
},
|
||||
"description": {
|
||||
"pt-BR": "Acesso ilimitado a todos os recursos premium - Cobrança mensal",
|
||||
"es-PY": "Acceso ilimitado a todas las funciones premium - Facturación mensual",
|
||||
"en": "Unlimited access to all premium features - Monthly billing"
|
||||
},
|
||||
"features": {
|
||||
"pt-BR": [
|
||||
"QR Codes ilimitados",
|
||||
"Sem anúncios",
|
||||
"Customização avançada",
|
||||
"Suporte para logos",
|
||||
"Histórico e downloads",
|
||||
"Suporte prioritário",
|
||||
"Cancele a qualquer momento"
|
||||
],
|
||||
"es-PY": [
|
||||
"Códigos QR ilimitados",
|
||||
"Sin anuncios",
|
||||
"Personalización avanzada",
|
||||
"Soporte para logos",
|
||||
"Historial y descargas",
|
||||
"Soporte prioritario",
|
||||
"Cancela cuando quieras"
|
||||
],
|
||||
"en": [
|
||||
"Unlimited QR Codes",
|
||||
"No ads",
|
||||
"Advanced customization",
|
||||
"Logo support",
|
||||
"History and downloads",
|
||||
"Priority support",
|
||||
"Cancel anytime"
|
||||
]
|
||||
},
|
||||
"interval": "month",
|
||||
"stripePriceId": "price_1SJwebB6bFjHQirAloEqXWd6",
|
||||
"pricesByCountry": {
|
||||
"BR": {
|
||||
"amount": 9.90,
|
||||
"currency": "BRL",
|
||||
"stripePriceId": "price_1SJwebB6bFjHQirAloEqXWd6"
|
||||
},
|
||||
"PY": {
|
||||
"amount": 35000,
|
||||
"currency": "PYG",
|
||||
"stripePriceId": "price_1SK4Y0B6bFjHQirAaxNHxILi"
|
||||
},
|
||||
"US": {
|
||||
"amount": 1.99,
|
||||
"currency": "USD",
|
||||
"stripePriceId": "price_XXXXX_monthly_us"
|
||||
}
|
||||
},
|
||||
"isActive": true,
|
||||
"displayOrder": 1
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"pt-BR": "Premium Anual",
|
||||
"es-PY": "Premium Anual",
|
||||
"en": "Premium Yearly"
|
||||
},
|
||||
"description": {
|
||||
"pt-BR": "Acesso ilimitado a todos os recursos premium - Economia de 20% no plano anual!",
|
||||
"es-PY": "Acceso ilimitado a todas las funciones premium - ¡Ahorra 20% con el plan anual!",
|
||||
"en": "Unlimited access to all premium features - Save 20% with yearly billing!"
|
||||
},
|
||||
"features": {
|
||||
"pt-BR": [
|
||||
"QR Codes ilimitados",
|
||||
"Sem anúncios",
|
||||
"Customização avançada",
|
||||
"Suporte para logos",
|
||||
"Histórico e downloads",
|
||||
"Suporte prioritário",
|
||||
"💰 Economia de 20%",
|
||||
"Cobrança anual única"
|
||||
],
|
||||
"es-PY": [
|
||||
"Códigos QR ilimitados",
|
||||
"Sin anuncios",
|
||||
"Personalización avanzada",
|
||||
"Soporte para logos",
|
||||
"Historial y descargas",
|
||||
"Soporte prioritario",
|
||||
"💰 Ahorro del 20%",
|
||||
"Facturación anual única"
|
||||
],
|
||||
"en": [
|
||||
"Unlimited QR Codes",
|
||||
"No ads",
|
||||
"Advanced customization",
|
||||
"Logo support",
|
||||
"History and downloads",
|
||||
"Priority support",
|
||||
"💰 Save 20%",
|
||||
"Billed annually"
|
||||
]
|
||||
},
|
||||
"interval": "year",
|
||||
"stripePriceId": "price_1SK4X7B6bFjHQirAdMtviPw4",
|
||||
"pricesByCountry": {
|
||||
"BR": {
|
||||
"amount": 95.04,
|
||||
"currency": "BRL",
|
||||
"stripePriceId": "price_1SK4X7B6bFjHQirAdMtviPw4"
|
||||
},
|
||||
"PY": {
|
||||
"amount": 336000,
|
||||
"currency": "PYG",
|
||||
"stripePriceId": "price_1SK4Y0B6bFjHQirAaxNHxILi"
|
||||
},
|
||||
"US": {
|
||||
"amount": 19.10,
|
||||
"currency": "USD",
|
||||
"stripePriceId": "price_XXXXX_yearly_us"
|
||||
}
|
||||
},
|
||||
"isActive": true,
|
||||
"displayOrder": 2,
|
||||
"badge": {
|
||||
"pt-BR": "MELHOR VALOR",
|
||||
"es-PY": "MEJOR VALOR",
|
||||
"en": "BEST VALUE"
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -51,22 +51,22 @@ db.Plans.insertOne({
|
||||
]
|
||||
},
|
||||
interval: "month",
|
||||
stripePriceId: "price_XXXXX_monthly_us", // Default price (USA) - UPDATE from appsettings.json > Stripe:Plans:Monthly:US
|
||||
stripePriceId: "price_1SJwebB6bFjHQirAloEqXWd6", // Default price (BR)
|
||||
pricesByCountry: {
|
||||
"BR": {
|
||||
amount: 9.90,
|
||||
currency: "BRL",
|
||||
stripePriceId: "price_XXXXX_monthly_br" // UPDATE from appsettings.json > Stripe:Plans:Monthly:BR
|
||||
stripePriceId: "price_1SJwebB6bFjHQirAloEqXWd6"
|
||||
},
|
||||
"PY": {
|
||||
amount: 35000,
|
||||
currency: "PYG",
|
||||
stripePriceId: "price_XXXXX_monthly_py" // UPDATE from appsettings.json > Stripe:Plans:Monthly:PY
|
||||
stripePriceId: "price_1SK4Y0B6bFjHQirAaxNHxILi"
|
||||
},
|
||||
"US": {
|
||||
amount: 1.99,
|
||||
currency: "USD",
|
||||
stripePriceId: "price_XXXXX_monthly_us" // UPDATE from appsettings.json > Stripe:Plans:Monthly:US
|
||||
stripePriceId: "price_XXXXX_monthly_us" // TODO: Update with real Stripe Price ID
|
||||
}
|
||||
},
|
||||
isActive: true,
|
||||
@ -120,22 +120,22 @@ db.Plans.insertOne({
|
||||
]
|
||||
},
|
||||
interval: "year",
|
||||
stripePriceId: "price_XXXXX_yearly_us", // Default price (USA) - UPDATE from appsettings.json > Stripe:Plans:Yearly:US
|
||||
stripePriceId: "price_1SK4X7B6bFjHQirAdMtviPw4", // Default price (BR)
|
||||
pricesByCountry: {
|
||||
"BR": {
|
||||
amount: 95.04, // 9.90 * 12 * 0.80 = Economia de 20%
|
||||
currency: "BRL",
|
||||
stripePriceId: "price_XXXXX_yearly_br" // UPDATE from appsettings.json > Stripe:Plans:Yearly:BR
|
||||
stripePriceId: "price_1SK4X7B6bFjHQirAdMtviPw4"
|
||||
},
|
||||
"PY": {
|
||||
amount: 336000, // 35000 * 12 * 0.80 = Economia de 20%
|
||||
currency: "PYG",
|
||||
stripePriceId: "price_XXXXX_yearly_py" // UPDATE from appsettings.json > Stripe:Plans:Yearly:PY
|
||||
stripePriceId: "price_1SK4Y0B6bFjHQirAaxNHxILi"
|
||||
},
|
||||
"US": {
|
||||
amount: 19.10, // 1.99 * 12 * 0.80 = Economia de 20%
|
||||
currency: "USD",
|
||||
stripePriceId: "price_XXXXX_yearly_us" // UPDATE from appsettings.json > Stripe:Plans:Yearly:US
|
||||
stripePriceId: "price_XXXXX_yearly_us" // TODO: Update with real Stripe Price ID
|
||||
}
|
||||
},
|
||||
isActive: true,
|
||||
|
||||
63
Scripts/update-plans.sh
Normal file
63
Scripts/update-plans.sh
Normal file
@ -0,0 +1,63 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script to update MongoDB Plans via localhost-only API endpoint
|
||||
# Usage: ./update-plans.sh [port]
|
||||
|
||||
PORT=${1:-52428}
|
||||
API_URL="https://localhost:$PORT/api/Admin/SeedPlans"
|
||||
PLANS_FILE="$(dirname "$0")/plans.json"
|
||||
|
||||
echo "🔧 QR Rapido - Update Plans Script"
|
||||
echo "=================================="
|
||||
echo ""
|
||||
echo "📋 Plans file: $PLANS_FILE"
|
||||
echo "🌐 API URL: $API_URL"
|
||||
echo ""
|
||||
|
||||
# Check if plans.json exists
|
||||
if [ ! -f "$PLANS_FILE" ]; then
|
||||
echo "❌ Error: plans.json not found at $PLANS_FILE"
|
||||
echo ""
|
||||
echo "Please create plans.json with your Stripe Price IDs"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if the app is running
|
||||
echo "🔍 Checking if app is running on port $PORT..."
|
||||
if ! curl -k -s "https://localhost:$PORT/health" > /dev/null 2>&1; then
|
||||
echo "❌ Error: App not responding on https://localhost:$PORT"
|
||||
echo ""
|
||||
echo "Please start the app first:"
|
||||
echo " dotnet run"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ App is running"
|
||||
echo ""
|
||||
|
||||
# Send request
|
||||
echo "📤 Sending plans to API..."
|
||||
response=$(curl -k -s -w "\n%{http_code}" \
|
||||
-X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @"$PLANS_FILE" \
|
||||
"$API_URL")
|
||||
|
||||
http_code=$(echo "$response" | tail -n1)
|
||||
body=$(echo "$response" | sed '$d')
|
||||
|
||||
echo ""
|
||||
|
||||
if [ "$http_code" -eq 200 ]; then
|
||||
echo "✅ Success! Plans updated in MongoDB"
|
||||
echo ""
|
||||
echo "$body" | jq '.' 2>/dev/null || echo "$body"
|
||||
else
|
||||
echo "❌ Error! HTTP Status: $http_code"
|
||||
echo ""
|
||||
echo "$body"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🎉 Done!"
|
||||
@ -33,6 +33,24 @@ namespace QRRapidoApp.Services
|
||||
}
|
||||
|
||||
var customerId = user.StripeCustomerId;
|
||||
var customerService = new CustomerService();
|
||||
|
||||
// Verify if customer exists in Stripe, create new if not
|
||||
if (!string.IsNullOrEmpty(customerId))
|
||||
{
|
||||
try
|
||||
{
|
||||
// Try to retrieve the customer to verify it exists
|
||||
await customerService.GetAsync(customerId);
|
||||
_logger.LogInformation($"Using existing Stripe customer {customerId} for user {userId}");
|
||||
}
|
||||
catch (StripeException ex) when (ex.StripeError?.Code == "resource_missing")
|
||||
{
|
||||
_logger.LogWarning($"Stripe customer {customerId} not found, creating new one for user {userId}");
|
||||
customerId = null; // Force creation of new customer
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(customerId))
|
||||
{
|
||||
var customerOptions = new CustomerCreateOptions
|
||||
@ -41,10 +59,10 @@ namespace QRRapidoApp.Services
|
||||
Name = user.Name,
|
||||
Metadata = new Dictionary<string, string> { { "app_user_id", user.Id } }
|
||||
};
|
||||
var customerService = new CustomerService();
|
||||
var customer = await customerService.CreateAsync(customerOptions);
|
||||
customerId = customer.Id;
|
||||
await _userService.UpdateUserStripeCustomerIdAsync(userId, customerId);
|
||||
_logger.LogInformation($"Created new Stripe customer {customerId} for user {userId}");
|
||||
}
|
||||
|
||||
var options = new SessionCreateOptions
|
||||
|
||||
@ -145,15 +145,9 @@
|
||||
else if (User.Identity.IsAuthenticated)
|
||||
{
|
||||
var isPremium = await AdService.HasValidPremiumSubscription(userId);
|
||||
if (isPremium)
|
||||
{
|
||||
<div class="alert alert-success ad-free-notice mb-3">
|
||||
<i class="fas fa-crown text-warning"></i>
|
||||
<span><strong>@Localizer["PremiumUserNoAds"]</strong></span>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
if (!isPremium)
|
||||
{
|
||||
@* Only show upgrade notice for non-premium users *@
|
||||
<div class="alert alert-info upgrade-notice mb-3">
|
||||
<i class="fas fa-star text-warning"></i>
|
||||
<span><strong>@Localizer["UpgradePremiumRemoveAds"]</strong></span>
|
||||
@ -162,4 +156,5 @@ else if (User.Identity.IsAuthenticated)
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
@* Premium users: render nothing (100% invisible) *@
|
||||
}
|
||||
|
||||
@ -38,6 +38,24 @@
|
||||
"GrowthRateWarningMBPerHour": 200,
|
||||
"IncludeCollectionStats": true
|
||||
},
|
||||
"Stripe": {
|
||||
"PublishableKey": "pk_test_51Rs42SB6bFjHQirAJ6kzbFCbBuAobyNbmlgpULFsInl8KRzlpclUoqOZICqvp2S51kquw3Bc04CPO9bNUEgDLDgd00XbAHT7Fh",
|
||||
"SecretKey": "sk_test_51Rs42SB6bFjHQirANOUg8jgzPALbNdVWULSVRMycFRBTzE0QUGA6pkpoQaTVsCIoV3XGRgoJ7E3CA6Y67vWlM76q00QBoKW0aH",
|
||||
"WebhookSecret": "whsec_667402ff1d753b181f626636d556975f2749b5fec4d1231d44f040b057fb3009",
|
||||
"ProductId": "prod_TGTbombliOUYmQ",
|
||||
"Plans": {
|
||||
"Monthly": {
|
||||
"BR": "price_1SJwebB6bFjHQirAloEqXWd6",
|
||||
"PY": "price_1SK4Y0B6bFjHQirAaxNHxILi",
|
||||
"US": "price_XXXXX_monthly_us"
|
||||
},
|
||||
"Yearly": {
|
||||
"BR": "price_1SK4X7B6bFjHQirAdMtviPw4",
|
||||
"PY": "price_1SK4Y0B6bFjHQirAaxNHxILi",
|
||||
"US": "price_XXXXX_yearly_us"
|
||||
}
|
||||
}
|
||||
},
|
||||
"HealthChecks": {
|
||||
"MongoDB": {
|
||||
"TimeoutSeconds": 10,
|
||||
|
||||
@ -21,22 +21,9 @@
|
||||
}
|
||||
},
|
||||
"Stripe": {
|
||||
"PublishableKey": "pk_test_51Rs42tBeR5IFYUsBooapyDwQTgh6CFuKbya5R3MVDTrdOUKmgiHQYipU0pgOdG5iKogH77RUYIKBJzbCt5BghUOY00xitV5KiN",
|
||||
"SecretKey": "sk_test_51Rs42tBeR5IFYUsBtycRlJJcdwgoMbh8MfQIKIGelBPTQFwDcOn2iCCbw5uG6hnqlpgNAUuFgWRAUUMA8qkABKun00EIx4odDF",
|
||||
"WebhookSecret": "whsec_2e828803ceb48e7865458b0cf332b68535fdff8753d26d69b1c88ea55cb0e482",
|
||||
"ProductId": "prod_SnfQTxwE3i8r5L",
|
||||
"Plans": {
|
||||
"Monthly": {
|
||||
"BR": "price_1Rs45OBeR5IFYUsBfsnOpOiv",
|
||||
"PY": "price_XXXXX_monthly_py",
|
||||
"US": "price_XXXXX_monthly_us"
|
||||
},
|
||||
"Yearly": {
|
||||
"BR": "price_1Rs4AyBeR5IFYUsB8kRSNUIM",
|
||||
"PY": "price_XXXXX_yearly_py",
|
||||
"US": "price_XXXXX_yearly_us"
|
||||
}
|
||||
}
|
||||
"PublishableKey": "pk_test_51Rs42SB6bFjHQirAJ6kzbFCbBuAobyNbmlgpULFsInl8KRzlpclUoqOZICqvp2S51kquw3Bc04CPO9bNUEgDLDgd00XbAHT7Fh",
|
||||
"SecretKey": "sk_test_51Rs42SB6bFjHQirANOUg8jgzPALbNdVWULSVRMycFRBTzE0QUGA6pkpoQaTVsCIoV3XGRgoJ7E3CA6Y67vWlM76q00QBoKW0aH",
|
||||
"WebhookSecret": "whsec_667402ff1d753b181f626636d556975f2749b5fec4d1231d44f040b057fb3009"
|
||||
},
|
||||
"AdSense": {
|
||||
"ClientId": "ca-pub-3475956393038764",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user