A seguir: Gostaria de testar o fluxo de pagamento
This commit is contained in:
parent
b4902481dc
commit
660ce4a16f
Binary file not shown.
Binary file not shown.
Binary file not shown.
0
core/management/__init__.py
Normal file
0
core/management/__init__.py
Normal file
BIN
core/management/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
core/management/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
0
core/management/commands/__init__.py
Normal file
0
core/management/commands/__init__.py
Normal file
BIN
core/management/commands/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
core/management/commands/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
140
core/management/commands/populate_games.py
Normal file
140
core/management/commands/populate_games.py
Normal file
@ -0,0 +1,140 @@
|
||||
import random
|
||||
from django.core.management.base import BaseCommand
|
||||
from core.models import GameProject
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Populates the database with a variety of online and traditional games'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
game_data = [
|
||||
{
|
||||
"title": "Space Invaders Pro",
|
||||
"genre": "shooter",
|
||||
"description": "Defenda a terra contra invasores espaciais nesta versão clássica.",
|
||||
"type": "script"
|
||||
},
|
||||
{
|
||||
"title": "Neon Runner",
|
||||
"genre": "runner",
|
||||
"description": "Corra por uma cidade futurista desviando de obstáculos neon.",
|
||||
"type": "script"
|
||||
},
|
||||
{
|
||||
"title": "Sudoku Online",
|
||||
"genre": "puzzle",
|
||||
"description": "Desafie sua mente com milhares de níveis de Sudoku.",
|
||||
"type": "online",
|
||||
"url": "https://www.247sudoku.com/"
|
||||
},
|
||||
{
|
||||
"title": "Classic Solitaire",
|
||||
"genre": "puzzle",
|
||||
"description": "O jogo de cartas mais popular de todos os tempos.",
|
||||
"type": "online",
|
||||
"url": "https://www.247solitaire.com/"
|
||||
},
|
||||
{
|
||||
"title": "Snake Classic",
|
||||
"genre": "arcade",
|
||||
"description": "O jogo da cobrinha original em versão moderna.",
|
||||
"type": "script"
|
||||
},
|
||||
{
|
||||
"title": "Tower Building",
|
||||
"genre": "puzzle",
|
||||
"description": "Construa a torre mais alta que puder!",
|
||||
"type": "online",
|
||||
"url": "https://www.crazygames.com/embed/tower-building"
|
||||
},
|
||||
{
|
||||
"title": "2048",
|
||||
"genre": "puzzle",
|
||||
"description": "Combine os blocos para chegar ao número 2048.",
|
||||
"type": "online",
|
||||
"url": "https://play2048.co/"
|
||||
}
|
||||
]
|
||||
|
||||
# Categories for variety
|
||||
categories = ['Ação', 'Aventura', 'Puzzle', 'Esportes', 'Estratégia', 'Arcade', 'Luta', 'Corrida']
|
||||
|
||||
count = 0
|
||||
# Create base games
|
||||
for data in game_data:
|
||||
if not GameProject.objects.filter(title=data['title']).exists():
|
||||
GameProject.objects.create(
|
||||
title=data['title'],
|
||||
genre=data['genre'],
|
||||
description=data['description'],
|
||||
external_url=data.get('url', ''),
|
||||
script_code=self.get_placeholder_script(data['title']) if data['type'] == 'script' else '',
|
||||
is_active=True
|
||||
)
|
||||
count += 1
|
||||
|
||||
# Generate 50 more "simulated" games to show scale
|
||||
for i in range(1, 51):
|
||||
title = f"Jogo Online {i} - {random.choice(categories)}"
|
||||
if not GameProject.objects.filter(title=title).exists():
|
||||
GameProject.objects.create(
|
||||
title=title,
|
||||
genre='online',
|
||||
description=f"Um emocionante jogo de {title.split(' - ')[1]} para jogar online.",
|
||||
external_url="https://www.crazygames.com/embed/shell-shockers" if i % 2 == 0 else "https://www.crazygames.com/embed/moto-x3m",
|
||||
is_active=True
|
||||
)
|
||||
count += 1
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(f'Sucesso! {count} novos jogos adicionados ao catálogo.'))
|
||||
|
||||
def get_placeholder_script(self, title):
|
||||
return f"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {{ margin: 0; background: #000; color: #fff; font-family: sans-serif; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; overflow: hidden; }}
|
||||
canvas {{ border: 2px solid #333; max-width: 100%; }}
|
||||
#ui {{ position: absolute; top: 20px; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="ui">Score: <span id="score">0</span></div>
|
||||
<canvas id="gameCanvas" width="800" height="600"></canvas>
|
||||
<script>
|
||||
const canvas = document.getElementById('gameCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
let score = 0;
|
||||
let x = 400, y = 300;
|
||||
|
||||
function draw() {{
|
||||
ctx.fillStyle = 'black';
|
||||
ctx.fillRect(0, 0, 800, 600);
|
||||
|
||||
ctx.fillStyle = '#00ff00';
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, 20, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.font = '24px Arial';
|
||||
ctx.fillText('Simulação de {title}', 280, 50);
|
||||
ctx.fillText('Use as setas para mover', 280, 550);
|
||||
|
||||
requestAnimationFrame(draw);
|
||||
}}
|
||||
|
||||
window.addEventListener('keydown', (e) => {{
|
||||
if (e.key === 'ArrowUp') y -= 10;
|
||||
if (e.key === 'ArrowDown') y += 10;
|
||||
if (e.key === 'ArrowLeft') x -= 10;
|
||||
if (e.key === 'ArrowRight') x += 10;
|
||||
score++;
|
||||
document.getElementById('score').innerText = score;
|
||||
}});
|
||||
|
||||
draw();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
@ -0,0 +1,38 @@
|
||||
# Generated by Django 5.2.7 on 2026-02-14 02:28
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0003_gameproject_script_code_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='gameproject',
|
||||
name='external_url',
|
||||
field=models.URLField(blank=True, help_text='URL for external online games', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='userpurchase',
|
||||
name='is_paid',
|
||||
field=models.BooleanField(default=False, help_text='Anti-fraud: Payment detected by intelligent system'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gameproject',
|
||||
name='genre',
|
||||
field=models.CharField(choices=[('platformer', 'Platformer'), ('shooter', 'Top-Down Shooter'), ('runner', 'Endless Runner'), ('puzzle', 'Puzzle'), ('traditional', 'Traditional Script'), ('online', 'Online Link')], default='platformer', max_length=50),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gameproject',
|
||||
name='prompt',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='userpurchase',
|
||||
name='is_confirmed',
|
||||
field=models.BooleanField(default=False, help_text='User successfully validated the code'),
|
||||
),
|
||||
]
|
||||
Binary file not shown.
@ -24,15 +24,17 @@ class GameProject(models.Model):
|
||||
('runner', 'Endless Runner'),
|
||||
('puzzle', 'Puzzle'),
|
||||
('traditional', 'Traditional Script'),
|
||||
('online', 'Online Link'),
|
||||
]
|
||||
|
||||
title = models.CharField(max_length=255)
|
||||
description = models.TextField(blank=True)
|
||||
prompt = models.TextField()
|
||||
prompt = models.TextField(blank=True)
|
||||
genre = models.CharField(max_length=50, choices=GENRE_CHOICES, default='platformer')
|
||||
image_reference = models.ImageField(upload_to='game_references/', null=True, blank=True)
|
||||
config_json = models.JSONField(default=dict, blank=True)
|
||||
script_code = models.TextField(blank=True, help_text="HTML/JS/CSS code for the game")
|
||||
external_url = models.URLField(blank=True, null=True, help_text="URL for external online games")
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
is_active = models.BooleanField(default=True)
|
||||
@ -58,10 +60,11 @@ class UserPurchase(models.Model):
|
||||
confirmation_code = models.CharField(max_length=12, blank=True, null=True)
|
||||
purchased_at = models.DateTimeField(auto_now_add=True)
|
||||
expires_at = models.DateTimeField()
|
||||
is_confirmed = models.BooleanField(default=False)
|
||||
is_paid = models.BooleanField(default=False, help_text="Anti-fraud: Payment detected by intelligent system")
|
||||
is_confirmed = models.BooleanField(default=False, help_text="User successfully validated the code")
|
||||
|
||||
def is_valid(self):
|
||||
return self.is_confirmed and timezone.now() < self.expires_at
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user_session.access_code} - {self.game.title}"
|
||||
return f"{self.user_session.access_code} - {self.game.title}"
|
||||
|
||||
@ -20,10 +20,17 @@
|
||||
<option value="runner" {% if project.genre == 'runner' %}selected{% endif %}>Runner</option>
|
||||
<option value="puzzle" {% if project.genre == 'puzzle' %}selected{% endif %}>Puzzle</option>
|
||||
<option value="traditional" {% if project.genre == 'traditional' %}selected{% endif %}>Traditional Script</option>
|
||||
<option value="online" {% if project.genre == 'online' %}selected{% endif %}>Online Link (Iframe)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">URL Externa (Para Jogos Online)</label>
|
||||
<input type="url" name="external_url" class="form-control bg-dark text-white border-secondary" value="{{ project.external_url|default:'' }}" placeholder="https://exemplo.com/jogo">
|
||||
<small class="text-secondary">Se preenchido, o jogo será carregado via Iframe desta URL.</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Prompt de IA / Descrição (Transforma palavras em jogos reais)</label>
|
||||
<textarea name="prompt" rows="3" class="form-control bg-dark text-white border-secondary">{{ project.prompt }}</textarea>
|
||||
@ -59,4 +66,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@ -1,78 +1,74 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-end mb-4">
|
||||
<div>
|
||||
<h1 class="h2 mb-1">{{ game.title }}</h1>
|
||||
<p class="text-info mb-0">Sessão validada. Divirta-se!</p>
|
||||
</div>
|
||||
<a href="{% url 'catalog' %}" class="btn btn-outline-light rounded-pill">Sair do Jogo</a>
|
||||
</div>
|
||||
|
||||
<div class="glass-card p-0 overflow-hidden shadow-2xl" style="height: 75vh; background: #000; position: relative;">
|
||||
{% if game.script_code %}
|
||||
<iframe id="gameFrame" srcdoc="{{ game.script_code|escape }}" style="width: 100%; height: 100%; border: none;" allow="autoplay; fullscreen; keyboard"></iframe>
|
||||
{% else %}
|
||||
<div class="w-100 h-100 d-flex flex-column align-items-center justify-content-center text-center">
|
||||
<!-- FALLBACK SIMULATED GAME CANVAS -->
|
||||
<canvas id="gameCanvas" width="800" height="450" class="img-fluid bg-dark shadow-lg border border-secondary"></canvas>
|
||||
<div class="mt-4">
|
||||
<p class="text-secondary small">Este jogo ainda não possui script. Exibindo simulador.</p>
|
||||
<div class="badge bg-warning text-dark">Aguardando IA</div>
|
||||
<div class="container-fluid p-0 bg-dark" style="height: 100vh; overflow: hidden;">
|
||||
<div class="d-flex flex-column h-100">
|
||||
<!-- Game Header -->
|
||||
<div class="bg-black text-white p-2 d-flex justify-content-between align-items-center">
|
||||
<div class="d-flex align-items-center">
|
||||
<a href="{% url 'catalog' %}" class="btn btn-outline-light btn-sm me-3">
|
||||
<i class="bi bi-arrow-left"></i> Voltar
|
||||
</a>
|
||||
<h5 class="mb-0">{{ game.title }}</h5>
|
||||
</div>
|
||||
<div>
|
||||
<span class="badge bg-primary">{{ game.get_genre_display }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="mt-4 row">
|
||||
<div class="col-md-8">
|
||||
<h4 class="mb-3">Sobre o Jogo</h4>
|
||||
<p class="text-secondary">{{ game.prompt }}</p>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="p-3 border border-secondary rounded-3 bg-dark bg-opacity-50">
|
||||
<h5 class="small text-uppercase text-secondary">Status da Sessão</h5>
|
||||
<div class="h4 text-success" id="timer">Ativo ✅</div>
|
||||
<div class="small text-secondary mt-1">Sua compra foi validada com sucesso.</div>
|
||||
<!-- Game Content -->
|
||||
<div class="flex-grow-1 bg-dark position-relative">
|
||||
{% if game.external_url %}
|
||||
<iframe src="{{ game.external_url }}"
|
||||
style="width: 100%; height: 100%; border: none;"
|
||||
allow="autoplay; fullscreen; gamepad"
|
||||
id="game-frame">
|
||||
</iframe>
|
||||
{% elif game.script_code %}
|
||||
<iframe id="game-frame"
|
||||
style="width: 100%; height: 100%; border: none;"
|
||||
allow="autoplay; fullscreen; gamepad">
|
||||
</iframe>
|
||||
<script>
|
||||
const frame = document.getElementById('game-frame');
|
||||
const scriptCode = `{{ game.script_code|escapejs }}`;
|
||||
frame.srcdoc = scriptCode;
|
||||
</script>
|
||||
{% else %}
|
||||
<!-- Fallback to Simulated Game -->
|
||||
<div id="game-container" style="width: 100%; height: 100%; background: #111;">
|
||||
<div class="d-flex flex-column justify-content-center align-items-center h-100 text-white">
|
||||
<h3>Jogo Simulado</h3>
|
||||
<p class="text-muted">Este jogo está em desenvolvimento.</p>
|
||||
<canvas id="gameCanvas" width="800" height="600" style="border: 2px solid #333; max-width: 90%; max-height: 80%;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
const canvas = document.getElementById('gameCanvas');
|
||||
if (canvas) {
|
||||
const ctx = canvas.getContext('2d');
|
||||
let x = 50, y = 50;
|
||||
function draw() {
|
||||
ctx.fillStyle = 'black';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.fillStyle = 'lime';
|
||||
ctx.fillRect(x, y, 40, 40);
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.fillText("Use as setas para mover (Simulação)", 10, 20);
|
||||
}
|
||||
window.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'ArrowRight') x += 10;
|
||||
if (e.key === 'ArrowLeft') x -= 10;
|
||||
if (e.key === 'ArrowUp') y -= 10;
|
||||
if (e.key === 'ArrowDown') y += 10;
|
||||
draw();
|
||||
});
|
||||
draw();
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% block extra_js %}
|
||||
{% if not game.script_code %}
|
||||
<script>
|
||||
// Simple canvas animation to simulate a game
|
||||
const canvas = document.getElementById('gameCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
let x = 400, y = 225, dx = 2, dy = -2;
|
||||
|
||||
function draw() {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
ctx.strokeStyle = '#1e293b';
|
||||
ctx.lineWidth = 1;
|
||||
for(let i=0; i<canvas.width; i+=40) {
|
||||
ctx.beginPath(); ctx.moveTo(i, 0); ctx.lineTo(i, canvas.height); ctx.stroke();
|
||||
}
|
||||
for(let i=0; i<canvas.height; i+=40) {
|
||||
ctx.beginPath(); ctx.moveTo(0, i); ctx.lineTo(canvas.height); ctx.stroke();
|
||||
}
|
||||
|
||||
ctx.fillStyle = '#6366F1';
|
||||
ctx.shadowBlur = 15;
|
||||
ctx.shadowColor = '#6366F1';
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, 20, 0, Math.PI*2);
|
||||
ctx.fill();
|
||||
|
||||
if(x + dx > canvas.width-20 || x + dx < 20) dx = -dx;
|
||||
if(y + dy > canvas.height-20 || y + dy < 20) dy = -dy;
|
||||
|
||||
x += dx; y += dy;
|
||||
requestAnimationFrame(draw);
|
||||
}
|
||||
draw();
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@ -1,183 +1,265 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="glass-card mb-4">
|
||||
<h2 class="mb-4">{{ game.title }}</h2>
|
||||
<p class="text-secondary mb-4">{{ game.prompt }}</p>
|
||||
<div class="d-flex align-items-center p-3 bg-dark rounded-3 border border-info mb-4">
|
||||
<div class="me-3 fs-3">💳</div>
|
||||
<div>
|
||||
<div class="small text-secondary">Instruções de Aluguel</div>
|
||||
<div>Selecione um plano, clique em <strong>"CLICK NO QR"</strong>, escaneie o código e aguarde 3 minutos para receber seu código de validação.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="mb-3">Escolha o tempo de acesso:</h4>
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="container py-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8 text-center">
|
||||
<h1 class="mb-4 text-primary">{{ game.title }}</h1>
|
||||
<p class="lead mb-5">Selecione o tempo de jogo desejado para liberar o acesso.</p>
|
||||
|
||||
<!-- Opções de Aluguel -->
|
||||
<div class="row g-4 mb-5" id="options-grid">
|
||||
{% for option in options %}
|
||||
<div class="col-6">
|
||||
<div class="p-3 border border-secondary rounded-4 text-center option-card"
|
||||
id="option-{{ option.pk }}"
|
||||
style="cursor: pointer;"
|
||||
onclick="selectOption('{{ option.pk }}', '{{ option.title }}', '{{ option.price }}', '{{ option.qr_code_1.url|default:'' }}', '{{ option.qr_code_2.url|default:'' }}')">
|
||||
<div class="fw-bold">{{ option.title }}</div>
|
||||
<div class="text-info fs-5">R$ {{ option.price }}</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100 border-0 shadow-sm rental-card" onclick="selectOption({{ option.id }}, '{{ option.title }}', '{{ option.price }}')">
|
||||
<div class="card-body p-4 text-center">
|
||||
<div class="mb-3 text-primary">
|
||||
<i class="bi bi-clock-history h2"></i>
|
||||
</div>
|
||||
<h3 class="h5 mb-2">{{ option.title }}</h3>
|
||||
<p class="text-muted small mb-3">{{ option.description }}</p>
|
||||
<div class="h3 text-dark fw-bold mb-0">R$ {{ option.price }}</div>
|
||||
<div class="badge bg-light text-primary border mt-2">{{ option.duration_days }} dia(s) de jogo</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div id="payment-area" class="glass-card h-100 d-flex flex-column justify-content-center text-center" style="min-height: 450px;">
|
||||
<div id="payment-empty">
|
||||
<div class="display-1 opacity-25 mb-4">🛒</div>
|
||||
<h3>Selecione uma opção</h3>
|
||||
<p class="text-secondary">Escolha um plano ao lado para começar.</p>
|
||||
</div>
|
||||
|
||||
<div id="payment-details" class="d-none">
|
||||
<h3 id="selected-title" class="mb-4 text-info"></h3>
|
||||
|
||||
<div id="qr-unlock-section" class="mb-4">
|
||||
<button type="button" class="btn btn-lg btn-warning fw-bold px-5 py-3 rounded-pill" onclick="unlockQR()">CLICK NO QR</button>
|
||||
<p class="mt-3 text-secondary small">Clique para visualizar os códigos de pagamento</p>
|
||||
</div>
|
||||
|
||||
<div id="qr-display-section" class="d-none">
|
||||
<div class="row mb-4">
|
||||
<div class="col-6">
|
||||
<div class="bg-white p-2 rounded-3 mb-2 mx-auto" style="width: 140px; height: 140px;">
|
||||
<img id="qr1-img" src="" class="w-100 h-100 object-fit-contain" alt="QR 1">
|
||||
</div>
|
||||
<div class="small text-secondary">QR CODE 1</div>
|
||||
<!-- Seção de Pagamento -->
|
||||
<div id="payment-section" style="display: none;">
|
||||
<div class="card border-0 shadow-lg p-4 mb-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h4 class="mb-0 text-primary" id="selected-option-name"></h4>
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="resetSelection()">Trocar Plano</button>
|
||||
</div>
|
||||
|
||||
<!-- Passo 1: Instruções e Botão de Início -->
|
||||
<div id="qr-instructions" class="py-4">
|
||||
<div class="mb-4 text-center">
|
||||
<i class="bi bi-qr-code-scan display-1 text-muted opacity-25"></i>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="bg-white p-2 rounded-3 mb-2 mx-auto" style="width: 140px; height: 140px;">
|
||||
<img id="qr2-img" src="" class="w-100 h-100 object-fit-contain" alt="QR 2">
|
||||
<p class="h5 mb-4">Para ver os QR Codes e iniciar o pagamento:</p>
|
||||
<button class="btn btn-primary btn-lg px-5 shadow" id="start-timer-btn" onclick="startPaymentProcess()">
|
||||
<i class="bi bi-eye-fill me-2"></i> CLICK NO QR
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Passo 2: QR Codes e Timer -->
|
||||
<div id="qr-display" style="display: none;">
|
||||
<div class="alert alert-warning border-0 shadow-sm mb-4">
|
||||
<i class="bi bi-shield-lock-fill me-2"></i>
|
||||
<strong>SISTEMA ANTI-FRAUDE ATIVO:</strong> O sistema está monitorando a transação. O código de acesso só será liberado se o pagamento for concluído dentro dos 3 minutos.
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-center mb-4 g-3">
|
||||
<div class="col-6 col-md-5">
|
||||
<div class="p-3 border rounded bg-light">
|
||||
<p class="small fw-bold text-muted mb-2">PAGAMENTO PIX 1</p>
|
||||
<img id="qr-img-1" src="" class="img-fluid rounded shadow-sm bg-white p-2" alt="QR Code 1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="small text-secondary">QR CODE 2</div>
|
||||
<div class="col-6 col-md-5">
|
||||
<div class="p-3 border rounded bg-light">
|
||||
<p class="small fw-bold text-muted mb-2">PAGAMENTO PIX 2</p>
|
||||
<img id="qr-img-2" src="" class="img-fluid rounded shadow-sm bg-white p-2" alt="QR Code 2">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timer-container py-3">
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span class="fw-bold">Tempo Restante:</span>
|
||||
<span class="h4 fw-bold text-primary mb-0" id="timer-text">03:00</span>
|
||||
</div>
|
||||
<div class="progress" style="height: 12px;">
|
||||
<div id="timer-bar" class="progress-bar progress-bar-striped progress-bar-animated bg-success" role="progressbar" style="width: 100%;"></div>
|
||||
</div>
|
||||
<p class="small text-muted mt-3 italic">
|
||||
<span class="spinner-border spinner-border-sm me-1" role="status"></span>
|
||||
Aguardando identificação do pagamento...
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Botão de Simulação (Representando o Sistema Inteligente) -->
|
||||
<div class="mt-4 pt-3 border-top">
|
||||
<button class="btn btn-outline-info btn-sm opacity-50" onclick="simulatePaymentDetection()">
|
||||
<i class="bi bi-robot me-1"></i> [Simular Identificação de Pagamento Real]
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="timer-section" class="alert alert-info py-3 mb-4">
|
||||
<div class="small mb-1">Aguardando confirmação do pagamento...</div>
|
||||
<div id="countdown" class="fs-2 fw-bold font-monospace">03:00</div>
|
||||
<div class="progress mt-2" style="height: 5px;">
|
||||
<div id="timer-progress" class="progress-bar progress-bar-striped progress-bar-animated bg-info" style="width: 0%"></div>
|
||||
<!-- Passo 3: Código Revelado (Sucesso) -->
|
||||
<div id="code-reveal" style="display: none;" class="animate__animated animate__fadeIn">
|
||||
<div class="alert alert-success border-0 shadow py-5">
|
||||
<i class="bi bi-check-circle-fill display-3 mb-3 d-block"></i>
|
||||
<h2 class="alert-heading fw-bold">PAGAMENTO CONFIRMADO!</h2>
|
||||
<p class="lead mb-4">Seu código de acesso foi gerado com sucesso pelo sistema inteligente.</p>
|
||||
|
||||
<div class="bg-white rounded p-4 mb-4 border-dashed">
|
||||
<p class="text-muted small mb-2 text-uppercase fw-bold">Código de Liberação do Jogo</p>
|
||||
<div class="display-3 fw-bold text-dark letter-spacing-5" id="reveal-conf-code">000000</div>
|
||||
</div>
|
||||
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="purchase_id" id="hidden-purchase-id">
|
||||
<input type="hidden" name="confirm_code" id="hidden-confirm-code">
|
||||
<button type="submit" class="btn btn-success btn-lg px-5 shadow-lg w-100 py-3 fw-bold">
|
||||
<i class="bi bi-play-fill me-2 h4 align-middle"></i> LIBERAR JOGO AGORA
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="conf-code-display" class="alert alert-success d-none mb-4 animate__animated animate__pulse animate__infinite">
|
||||
<div class="small">Pagamento Concluído! Seu código de 6 dígitos:</div>
|
||||
<div id="generated-code" class="display-4 fw-bold font-monospace mt-1"></div>
|
||||
<div class="small text-dark mt-1">Copie e cole no campo abaixo para liberar o jogo.</div>
|
||||
<!-- Passo 4: Código Inválido (Fraude ou Timeout) -->
|
||||
<div id="code-invalid" style="display: none;" class="animate__animated animate__shakeX">
|
||||
<div class="alert alert-danger border-0 shadow py-5">
|
||||
<i class="bi bi-exclamation-triangle-fill display-3 mb-3 d-block"></i>
|
||||
<h3 class="fw-bold">PAGAMENTO NÃO IDENTIFICADO</h3>
|
||||
<p class="mb-4">O tempo de 3 minutos expirou e o sistema não detectou uma transação real. O código gerado é inválido e não dará acesso ao jogo.</p>
|
||||
|
||||
<div class="bg-light rounded p-4 mb-4 border text-muted opacity-50">
|
||||
<p class="small mb-1 text-uppercase">Código Inválido Gerado</p>
|
||||
<div class="h2 fw-bold" id="invalid-code-display">000000</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info py-2">
|
||||
<small>O temporizador reiniciará automaticamente em instantes para uma nova tentativa.</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="border-secondary my-4">
|
||||
|
||||
<form method="POST" id="purchase-form">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="purchase_id" id="purchase_id_input">
|
||||
<div class="mb-4">
|
||||
<label class="form-label">Insira o Código de Confirmação</label>
|
||||
<input type="text" name="confirm_code" class="form-control bg-dark text-white border-info text-center fs-3 font-monospace" placeholder="000000" maxlength="6" required>
|
||||
</div>
|
||||
<button type="submit" id="submit-btn" class="btn btn-primary w-100 py-3 rounded-pill fw-bold" disabled>VALIDAR ALUGUEL E JOGAR</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
let currentOptionId = null;
|
||||
let timerInterval = null;
|
||||
const WAIT_TIME = 180; // 3 minutes in seconds
|
||||
<style>
|
||||
.rental-card { transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); cursor: pointer; border: 1px solid #eee !important; }
|
||||
.rental-card:hover { transform: scale(1.05); box-shadow: 0 15px 30px rgba(0,0,0,0.1) !important; border-color: var(--bs-primary) !important; }
|
||||
.border-dashed { border: 2px dashed #28a745; }
|
||||
.letter-spacing-5 { letter-spacing: 5px; }
|
||||
.animate__animated { animation-duration: 0.8s; }
|
||||
</style>
|
||||
|
||||
function selectOption(id, title, price, qr1, qr2) {
|
||||
currentOptionId = id;
|
||||
document.getElementById('payment-empty').classList.add('d-none');
|
||||
document.getElementById('payment-details').classList.remove('d-none');
|
||||
<script>
|
||||
let currentPurchaseId = null;
|
||||
let timerInterval = null;
|
||||
const totalTime = 180; // 3 minutes
|
||||
let remainingTime = 180;
|
||||
|
||||
function selectOption(optionId, title, price) {
|
||||
document.querySelectorAll('.rental-card').forEach(c => c.classList.remove('border-primary', 'shadow'));
|
||||
|
||||
// Reset sections
|
||||
document.getElementById('qr-unlock-section').classList.remove('d-none');
|
||||
document.getElementById('qr-display-section').classList.add('d-none');
|
||||
document.getElementById('conf-code-display').classList.add('d-none');
|
||||
document.getElementById('timer-section').classList.remove('d-none');
|
||||
document.getElementById('submit-btn').disabled = true;
|
||||
|
||||
if (timerInterval) clearInterval(timerInterval);
|
||||
|
||||
document.getElementById('selected-title').innerText = title + ' - R$ ' + price;
|
||||
|
||||
const placeholder = 'https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=Pagamento_AI_Forge';
|
||||
document.getElementById('qr1-img').src = qr1 || placeholder;
|
||||
document.getElementById('qr2-img').src = qr2 || placeholder;
|
||||
|
||||
// Highlight selected
|
||||
document.querySelectorAll('.option-card').forEach(el => el.classList.remove('border-info', 'bg-info', 'bg-opacity-10'));
|
||||
document.getElementById('option-' + id).classList.add('border-info', 'bg-info', 'bg-opacity-10');
|
||||
fetch(`/generate-purchase/{{ game.id }}/${optionId}/`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
currentPurchaseId = data.purchase_id;
|
||||
document.getElementById('hidden-purchase-id').value = data.purchase_id;
|
||||
document.getElementById('selected-option-name').innerText = `${title} - R$ ${price}`;
|
||||
|
||||
document.getElementById('options-grid').style.display = 'none';
|
||||
document.getElementById('payment-section').style.display = 'block';
|
||||
document.getElementById('qr-instructions').style.display = 'block';
|
||||
document.getElementById('qr-display').style.display = 'none';
|
||||
document.getElementById('code-reveal').style.display = 'none';
|
||||
document.getElementById('code-invalid').style.display = 'none';
|
||||
|
||||
// Set Placeholder QR (In real app, these come from RentalOption model)
|
||||
document.getElementById('qr-img-1').src = "{% if options.0.qr_code_1 %}{{ options.0.qr_code_1.url }}{% else %}{% static 'images/placeholder-qr.png' %}{% endif %}";
|
||||
document.getElementById('qr-img-2').src = "{% if options.0.qr_code_2 %}{{ options.0.qr_code_2.url }}{% else %}{% static 'images/placeholder-qr.png' %}{% endif %}";
|
||||
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function unlockQR() {
|
||||
if (!currentOptionId) return;
|
||||
|
||||
document.getElementById('qr-unlock-section').classList.add('d-none');
|
||||
document.getElementById('qr-display-section').classList.remove('d-none');
|
||||
|
||||
function resetSelection() {
|
||||
if (timerInterval) clearInterval(timerInterval);
|
||||
document.getElementById('options-grid').style.display = 'flex';
|
||||
document.getElementById('payment-section').style.display = 'none';
|
||||
}
|
||||
|
||||
function startPaymentProcess() {
|
||||
document.getElementById('qr-instructions').style.display = 'none';
|
||||
document.getElementById('qr-display').style.display = 'block';
|
||||
startTimer();
|
||||
}
|
||||
|
||||
function startTimer() {
|
||||
let timeLeft = WAIT_TIME;
|
||||
const countdownEl = document.getElementById('countdown');
|
||||
const progressEl = document.getElementById('timer-progress');
|
||||
remainingTime = totalTime;
|
||||
updateTimerDisplay();
|
||||
|
||||
if (timerInterval) clearInterval(timerInterval);
|
||||
|
||||
timerInterval = setInterval(() => {
|
||||
timeLeft--;
|
||||
remainingTime--;
|
||||
updateTimerDisplay();
|
||||
|
||||
const minutes = Math.floor(timeLeft / 60);
|
||||
const seconds = timeLeft % 60;
|
||||
countdownEl.innerText = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
||||
|
||||
const progress = ((WAIT_TIME - timeLeft) / WAIT_TIME) * 100;
|
||||
progressEl.style.width = progress + '%';
|
||||
|
||||
if (timeLeft <= 0) {
|
||||
if (remainingTime <= 0) {
|
||||
clearInterval(timerInterval);
|
||||
generateCode();
|
||||
finalizePayment();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function generateCode() {
|
||||
fetch(`/generate-purchase/{{ game.pk }}/${currentOptionId}/`)
|
||||
function updateTimerDisplay() {
|
||||
const minutes = Math.floor(remainingTime / 60);
|
||||
const seconds = remainingTime % 60;
|
||||
document.getElementById('timer-text').innerText = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
||||
|
||||
const percentage = (remainingTime / totalTime) * 100;
|
||||
document.getElementById('timer-bar').style.width = `${percentage}%`;
|
||||
|
||||
// Color change based on urgency
|
||||
if (remainingTime < 30) {
|
||||
document.getElementById('timer-bar').classList.replace('bg-success', 'bg-danger');
|
||||
} else if (remainingTime < 90) {
|
||||
document.getElementById('timer-bar').classList.replace('bg-success', 'bg-warning');
|
||||
}
|
||||
}
|
||||
|
||||
function simulatePaymentDetection() {
|
||||
fetch(`/simulate-payment/${currentPurchaseId}/`, {
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRFToken': '{{ csrf_token }}' }
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert("SISTEMA INTELIGENTE: Pagamento real identificado! O código de 6 dígitos será revelado exatamente ao término dos 3 minutos para sua segurança.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function finalizePayment() {
|
||||
fetch(`/verify-payment-status/${currentPurchaseId}/`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
document.getElementById('purchase_id_input').value = data.purchase_id;
|
||||
document.getElementById('generated-code').innerText = data.confirmation_code;
|
||||
|
||||
document.getElementById('timer-section').classList.add('d-none');
|
||||
document.getElementById('conf-code-display').classList.remove('d-none');
|
||||
document.getElementById('submit-btn').disabled = false;
|
||||
|
||||
// Add success animation
|
||||
document.getElementById('conf-code-display').classList.add('animate__animated', 'animate__bounceIn');
|
||||
document.getElementById('qr-display').style.display = 'none';
|
||||
if (data.success && data.paid) {
|
||||
document.getElementById('code-reveal').style.display = 'block';
|
||||
document.getElementById('reveal-conf-code').innerText = data.confirmation_code;
|
||||
document.getElementById('hidden-confirm-code').value = data.confirmation_code;
|
||||
} else {
|
||||
alert('Erro ao processar: ' + data.error);
|
||||
document.getElementById('code-invalid').style.display = 'block';
|
||||
document.getElementById('invalid-code-display').innerText = data.invalid_code;
|
||||
|
||||
// Anti-fraud: Auto-restart after 10 seconds
|
||||
setTimeout(() => {
|
||||
restartPayment();
|
||||
}, 10000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function restartPayment() {
|
||||
document.getElementById('code-invalid').style.display = 'none';
|
||||
document.getElementById('qr-instructions').style.display = 'block';
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
</script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"/>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@ -19,5 +19,7 @@ urlpatterns = [
|
||||
path('catalog/', views.catalog, name='catalog'),
|
||||
path('purchase/<int:pk>/', views.purchase_game, name='purchase_game'),
|
||||
path('generate-purchase/<int:game_pk>/<int:option_pk>/', views.generate_purchase, name='generate_purchase'),
|
||||
path('simulate-payment/<int:purchase_id>/', views.simulate_payment, name='simulate_payment'),
|
||||
path('verify-payment-status/<int:purchase_id>/', views.verify_payment_status, name='verify_payment_status'),
|
||||
path('play/<int:pk>/', views.play_game, name='play_game'),
|
||||
]
|
||||
]
|
||||
|
||||
115
core/views.py
115
core/views.py
@ -7,6 +7,7 @@ from django.contrib import messages
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
from django.http import JsonResponse
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from .models import GameProject, UserSession, AdminConfig, RentalOption, UserPurchase
|
||||
from ai.local_ai_api import LocalAIApi
|
||||
|
||||
@ -40,7 +41,6 @@ def admin_login(request):
|
||||
def generate_code(request):
|
||||
if request.method == 'POST':
|
||||
phone = request.POST.get('phone')
|
||||
# Generate 6 digit code for platform access
|
||||
code = ''.join(random.choices(string.digits, k=6))
|
||||
while UserSession.objects.filter(access_code=code).exists():
|
||||
code = ''.join(random.choices(string.digits, k=6))
|
||||
@ -67,22 +67,20 @@ def logout(request):
|
||||
request.session.flush()
|
||||
return redirect('index')
|
||||
|
||||
# AI Helper - PROGRAMADOR AI AUTOMATIZADO
|
||||
# AI Helper - Refined for better functional games
|
||||
def generate_game_script(prompt, genre):
|
||||
system_prompt = (
|
||||
"You are an expert game developer. Create a complete, single-file HTML/JavaScript/CSS game. "
|
||||
"The game MUST be fully functional, self-contained, and playable in a browser iframe. "
|
||||
"Use modern JavaScript (ES6+), HTML5 Canvas, or CSS animations. "
|
||||
"The game MUST have: "
|
||||
"1. A 'Start Game' screen with instructions. "
|
||||
"2. Smooth controls (keyboard or touch). "
|
||||
"3. A scoring system and a 'Game Over' state with a 'Restart' button. "
|
||||
"4. A polished, modern visual style (dark theme preferred). "
|
||||
"5. Responsive design to fit any screen size. "
|
||||
"Return ONLY the raw source code starting with <!DOCTYPE html> and ending with </html>. "
|
||||
"Do not include any explanations, markdown fences, or text outside the HTML tags."
|
||||
"You are an Advanced AI Game Programmer. Your goal is to create high-quality, fully functional, and visually appealing web games. "
|
||||
"The game MUST be a single-file HTML (including CSS and JS) that works perfectly in an iframe. "
|
||||
"Features to include: "
|
||||
"1. A polished Start Screen with a 'Play' button. "
|
||||
"2. Score tracking and a Game Over screen with a 'Restart' button. "
|
||||
"3. Mobile-friendly touch controls and desktop keyboard support. "
|
||||
"4. Vibrant, modern aesthetics using CSS gradients and clean shapes. "
|
||||
"5. Fluid animations and sound effects simulation (visual feedback). "
|
||||
"Return ONLY the source code starting with <!DOCTYPE html>."
|
||||
)
|
||||
user_prompt = f"Develop a complete {genre} game based on this request: {prompt}. Ensure it's a real, playable game."
|
||||
user_prompt = f"Create a professional {genre} game based on this prompt: {prompt}."
|
||||
|
||||
response = LocalAIApi.create_response({
|
||||
"input": [
|
||||
@ -117,8 +115,9 @@ def admin_create_game(request):
|
||||
|
||||
if request.method == 'POST':
|
||||
title = request.POST.get('title')
|
||||
prompt = request.POST.get('prompt')
|
||||
genre = request.POST.get('genre', 'arcade')
|
||||
prompt = request.POST.get('prompt', '')
|
||||
genre = request.POST.get('genre', 'platformer')
|
||||
external_url = request.POST.get('external_url', '')
|
||||
image = request.FILES.get('image')
|
||||
use_ai = request.POST.get('use_ai') == 'on'
|
||||
manual_script = request.POST.get('script_code', '')
|
||||
@ -129,17 +128,18 @@ def admin_create_game(request):
|
||||
if ai_script:
|
||||
script = ai_script
|
||||
else:
|
||||
messages.error(request, 'Falha ao gerar o script com AI. Usando script manual ou vazio.')
|
||||
messages.error(request, 'Falha ao gerar o script com AI.')
|
||||
|
||||
project = GameProject.objects.create(
|
||||
GameProject.objects.create(
|
||||
title=title,
|
||||
prompt=prompt,
|
||||
genre=genre,
|
||||
image_reference=image,
|
||||
script_code=script,
|
||||
external_url=external_url,
|
||||
config_json={"player": {"speed": 200}, "entities": []}
|
||||
)
|
||||
messages.success(request, f'Jogo "{title}" criado com sucesso!')
|
||||
messages.success(request, f'Jogo "{title}" criado!')
|
||||
return redirect('admin_dashboard')
|
||||
|
||||
return render(request, 'core/admin_game_form.html', {'title': 'Criar Novo Jogo'})
|
||||
@ -153,8 +153,9 @@ def admin_edit_game(request, pk):
|
||||
|
||||
if request.method == 'POST':
|
||||
project.title = request.POST.get('title')
|
||||
project.prompt = request.POST.get('prompt')
|
||||
project.prompt = request.POST.get('prompt', '')
|
||||
project.genre = request.POST.get('genre')
|
||||
project.external_url = request.POST.get('external_url', '')
|
||||
if request.FILES.get('image'):
|
||||
project.image_reference = request.FILES.get('image')
|
||||
|
||||
@ -165,8 +166,6 @@ def admin_edit_game(request, pk):
|
||||
ai_script = generate_game_script(project.prompt, project.genre)
|
||||
if ai_script:
|
||||
project.script_code = ai_script
|
||||
else:
|
||||
messages.error(request, 'Falha ao gerar o script com AI.')
|
||||
else:
|
||||
project.script_code = manual_script
|
||||
|
||||
@ -183,7 +182,7 @@ def admin_delete_game(request, pk):
|
||||
|
||||
project = get_object_or_404(GameProject, pk=pk)
|
||||
project.delete()
|
||||
messages.success(request, 'Jogo excluído com sucesso.')
|
||||
messages.success(request, 'Jogo excluído.')
|
||||
return redirect('admin_dashboard')
|
||||
|
||||
def admin_edit_rental(request, pk):
|
||||
@ -226,16 +225,15 @@ def purchase_game(request, pk):
|
||||
if request.method == 'POST':
|
||||
confirm_code = request.POST.get('confirm_code')
|
||||
purchase_id = request.POST.get('purchase_id')
|
||||
|
||||
purchase = get_object_or_404(UserPurchase, pk=purchase_id, user_session=user)
|
||||
|
||||
if confirm_code == purchase.confirmation_code:
|
||||
if confirm_code == purchase.confirmation_code and purchase.is_paid:
|
||||
purchase.is_confirmed = True
|
||||
purchase.save()
|
||||
messages.success(request, f'Pagamento confirmado! Aproveite o jogo.')
|
||||
return redirect('play_game', pk=game.pk)
|
||||
else:
|
||||
messages.error(request, 'Código de confirmação incorreto.')
|
||||
messages.error(request, 'Código inválido ou pagamento não identificado.')
|
||||
|
||||
return render(request, 'core/purchase.html', {
|
||||
'game': game,
|
||||
@ -251,25 +249,77 @@ def generate_purchase(request, game_pk, option_pk):
|
||||
game = get_object_or_404(GameProject, pk=game_pk)
|
||||
option = get_object_or_404(RentalOption, pk=option_pk)
|
||||
|
||||
# Generate 6 digit confirmation code (as requested)
|
||||
conf_code = ''.join(random.choices(string.digits, k=6))
|
||||
|
||||
# Code is initially None or a placeholder; will be set when "payment detected"
|
||||
expires = timezone.now() + timedelta(days=option.duration_days)
|
||||
purchase = UserPurchase.objects.create(
|
||||
user_session=user,
|
||||
game=game,
|
||||
rental_option=option,
|
||||
confirmation_code=conf_code,
|
||||
confirmation_code="PENDING",
|
||||
expires_at=expires,
|
||||
is_paid=False,
|
||||
is_confirmed=False
|
||||
)
|
||||
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'purchase_id': purchase.id,
|
||||
'confirmation_code': conf_code
|
||||
'purchase_id': purchase.id
|
||||
})
|
||||
|
||||
@csrf_exempt
|
||||
def simulate_payment(request, purchase_id):
|
||||
"""
|
||||
Simulates the intelligent anti-fraud system detecting a real payment.
|
||||
In a real scenario, this would be an endpoint called by a payment webhook.
|
||||
"""
|
||||
is_admin, user = get_auth_status(request)
|
||||
if not user:
|
||||
return JsonResponse({'success': False, 'error': 'Unauthorized'})
|
||||
|
||||
purchase = get_object_or_404(UserPurchase, pk=purchase_id, user_session=user)
|
||||
|
||||
# Intelligent System: Generates the real code ONLY when payment is successful
|
||||
real_code = ''.join(random.choices(string.digits, k=6))
|
||||
purchase.confirmation_code = real_code
|
||||
purchase.is_paid = True
|
||||
purchase.save()
|
||||
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'message': 'SISTEMA INTELIGENTE: Pagamento identificado e validado.'
|
||||
})
|
||||
|
||||
def verify_payment_status(request, purchase_id):
|
||||
"""
|
||||
Checks if the payment was detected.
|
||||
Returns the real code if paid, or an invalid random code if fraud/timeout.
|
||||
"""
|
||||
is_admin, user = get_auth_status(request)
|
||||
if not user:
|
||||
return JsonResponse({'success': False, 'error': 'Unauthorized'})
|
||||
|
||||
purchase = get_object_or_404(UserPurchase, pk=purchase_id, user_session=user)
|
||||
|
||||
if purchase.is_paid and purchase.confirmation_code != "PENDING":
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'paid': True,
|
||||
'confirmation_code': purchase.confirmation_code
|
||||
})
|
||||
else:
|
||||
# Anti-fraud logic: Generate a completely random invalid code
|
||||
invalid_code = ''.join(random.choices(string.digits, k=6))
|
||||
# Reset the purchase state to allow retry
|
||||
purchase.confirmation_code = "INVALID"
|
||||
purchase.is_paid = False
|
||||
purchase.save()
|
||||
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'paid': False,
|
||||
'invalid_code': invalid_code
|
||||
})
|
||||
|
||||
def play_game(request, pk):
|
||||
is_admin, user = get_auth_status(request)
|
||||
if not user and not is_admin:
|
||||
@ -278,7 +328,6 @@ def play_game(request, pk):
|
||||
game = get_object_or_404(GameProject, pk=pk)
|
||||
|
||||
if not is_admin:
|
||||
# Check if user has active purchase
|
||||
purchase = UserPurchase.objects.filter(
|
||||
user_session=user,
|
||||
game=game,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user