A seguir: Gostaria de testar o fluxo de pagamento

This commit is contained in:
Flatlogic Bot 2026-02-14 03:19:20 +00:00
parent b4902481dc
commit 660ce4a16f
17 changed files with 556 additions and 239 deletions

View File

Binary file not shown.

View File

View 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>
"""

View File

@ -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'),
),
]

View File

@ -24,15 +24,17 @@ class GameProject(models.Model):
('runner', 'Endless Runner'), ('runner', 'Endless Runner'),
('puzzle', 'Puzzle'), ('puzzle', 'Puzzle'),
('traditional', 'Traditional Script'), ('traditional', 'Traditional Script'),
('online', 'Online Link'),
] ]
title = models.CharField(max_length=255) title = models.CharField(max_length=255)
description = models.TextField(blank=True) description = models.TextField(blank=True)
prompt = models.TextField() prompt = models.TextField(blank=True)
genre = models.CharField(max_length=50, choices=GENRE_CHOICES, default='platformer') genre = models.CharField(max_length=50, choices=GENRE_CHOICES, default='platformer')
image_reference = models.ImageField(upload_to='game_references/', null=True, blank=True) image_reference = models.ImageField(upload_to='game_references/', null=True, blank=True)
config_json = models.JSONField(default=dict, 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") 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) created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True) updated_at = models.DateTimeField(auto_now=True)
is_active = models.BooleanField(default=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) confirmation_code = models.CharField(max_length=12, blank=True, null=True)
purchased_at = models.DateTimeField(auto_now_add=True) purchased_at = models.DateTimeField(auto_now_add=True)
expires_at = models.DateTimeField() 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): def is_valid(self):
return self.is_confirmed and timezone.now() < self.expires_at return self.is_confirmed and timezone.now() < self.expires_at
def __str__(self): def __str__(self):
return f"{self.user_session.access_code} - {self.game.title}" return f"{self.user_session.access_code} - {self.game.title}"

View File

@ -20,10 +20,17 @@
<option value="runner" {% if project.genre == 'runner' %}selected{% endif %}>Runner</option> <option value="runner" {% if project.genre == 'runner' %}selected{% endif %}>Runner</option>
<option value="puzzle" {% if project.genre == 'puzzle' %}selected{% endif %}>Puzzle</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="traditional" {% if project.genre == 'traditional' %}selected{% endif %}>Traditional Script</option>
<option value="online" {% if project.genre == 'online' %}selected{% endif %}>Online Link (Iframe)</option>
</select> </select>
</div> </div>
</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"> <div class="mb-3">
<label class="form-label">Prompt de IA / Descrição (Transforma palavras em jogos reais)</label> <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> <textarea name="prompt" rows="3" class="form-control bg-dark text-white border-secondary">{{ project.prompt }}</textarea>
@ -59,4 +66,4 @@
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1,78 +1,74 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load static %}
{% block content %} {% block content %}
<div class="d-flex justify-content-between align-items-end mb-4"> <div class="container-fluid p-0 bg-dark" style="height: 100vh; overflow: hidden;">
<div> <div class="d-flex flex-column h-100">
<h1 class="h2 mb-1">{{ game.title }}</h1> <!-- Game Header -->
<p class="text-info mb-0">Sessão validada. Divirta-se!</p> <div class="bg-black text-white p-2 d-flex justify-content-between align-items-center">
</div> <div class="d-flex align-items-center">
<a href="{% url 'catalog' %}" class="btn btn-outline-light rounded-pill">Sair do Jogo</a> <a href="{% url 'catalog' %}" class="btn btn-outline-light btn-sm me-3">
</div> <i class="bi bi-arrow-left"></i> Voltar
</a>
<div class="glass-card p-0 overflow-hidden shadow-2xl" style="height: 75vh; background: #000; position: relative;"> <h5 class="mb-0">{{ game.title }}</h5>
{% if game.script_code %} </div>
<iframe id="gameFrame" srcdoc="{{ game.script_code|escape }}" style="width: 100%; height: 100%; border: none;" allow="autoplay; fullscreen; keyboard"></iframe> <div>
{% else %} <span class="badge bg-primary">{{ game.get_genre_display }}</span>
<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> </div>
</div> </div>
{% endif %}
</div>
<div class="mt-4 row"> <!-- Game Content -->
<div class="col-md-8"> <div class="flex-grow-1 bg-dark position-relative">
<h4 class="mb-3">Sobre o Jogo</h4> {% if game.external_url %}
<p class="text-secondary">{{ game.prompt }}</p> <iframe src="{{ game.external_url }}"
</div> style="width: 100%; height: 100%; border: none;"
<div class="col-md-4"> allow="autoplay; fullscreen; gamepad"
<div class="p-3 border border-secondary rounded-3 bg-dark bg-opacity-50"> id="game-frame">
<h5 class="small text-uppercase text-secondary">Status da Sessão</h5> </iframe>
<div class="h4 text-success" id="timer">Ativo ✅</div> {% elif game.script_code %}
<div class="small text-secondary mt-1">Sua compra foi validada com sucesso.</div> <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> </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 %}
{% endblock %}

View File

@ -1,183 +1,265 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load static %}
{% block content %} {% block content %}
<div class="row"> <div class="container py-5">
<div class="col-lg-6"> <div class="row justify-content-center">
<div class="glass-card mb-4"> <div class="col-md-8 text-center">
<h2 class="mb-4">{{ game.title }}</h2> <h1 class="mb-4 text-primary">{{ game.title }}</h1>
<p class="text-secondary mb-4">{{ game.prompt }}</p> <p class="lead mb-5">Selecione o tempo de jogo desejado para liberar o acesso.</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> <!-- Opções de Aluguel -->
<div> <div class="row g-4 mb-5" id="options-grid">
<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">
{% for option in options %} {% for option in options %}
<div class="col-6"> <div class="col-md-6">
<div class="p-3 border border-secondary rounded-4 text-center option-card" <div class="card h-100 border-0 shadow-sm rental-card" onclick="selectOption({{ option.id }}, '{{ option.title }}', '{{ option.price }}')">
id="option-{{ option.pk }}" <div class="card-body p-4 text-center">
style="cursor: pointer;" <div class="mb-3 text-primary">
onclick="selectOption('{{ option.pk }}', '{{ option.title }}', '{{ option.price }}', '{{ option.qr_code_1.url|default:'' }}', '{{ option.qr_code_2.url|default:'' }}')"> <i class="bi bi-clock-history h2"></i>
<div class="fw-bold">{{ option.title }}</div> </div>
<div class="text-info fs-5">R$ {{ option.price }}</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>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
</div>
</div>
<div class="col-lg-6"> <!-- Seção de Pagamento -->
<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-section" style="display: none;">
<div id="payment-empty"> <div class="card border-0 shadow-lg p-4 mb-4">
<div class="display-1 opacity-25 mb-4">🛒</div> <div class="d-flex justify-content-between align-items-center mb-4">
<h3>Selecione uma opção</h3> <h4 class="mb-0 text-primary" id="selected-option-name"></h4>
<p class="text-secondary">Escolha um plano ao lado para começar.</p> <button class="btn btn-sm btn-outline-secondary" onclick="resetSelection()">Trocar Plano</button>
</div> </div>
<div id="payment-details" class="d-none"> <!-- Passo 1: Instruções e Botão de Início -->
<h3 id="selected-title" class="mb-4 text-info"></h3> <div id="qr-instructions" class="py-4">
<div class="mb-4 text-center">
<div id="qr-unlock-section" class="mb-4"> <i class="bi bi-qr-code-scan display-1 text-muted opacity-25"></i>
<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>
</div> </div>
<div class="col-6"> <p class="h5 mb-4">Para ver os QR Codes e iniciar o pagamento:</p>
<div class="bg-white p-2 rounded-3 mb-2 mx-auto" style="width: 140px; height: 140px;"> <button class="btn btn-primary btn-lg px-5 shadow" id="start-timer-btn" onclick="startPaymentProcess()">
<img id="qr2-img" src="" class="w-100 h-100 object-fit-contain" alt="QR 2"> <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>
<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> </div>
<div id="timer-section" class="alert alert-info py-3 mb-4"> <!-- Passo 3: Código Revelado (Sucesso) -->
<div class="small mb-1">Aguardando confirmação do pagamento...</div> <div id="code-reveal" style="display: none;" class="animate__animated animate__fadeIn">
<div id="countdown" class="fs-2 fw-bold font-monospace">03:00</div> <div class="alert alert-success border-0 shadow py-5">
<div class="progress mt-2" style="height: 5px;"> <i class="bi bi-check-circle-fill display-3 mb-3 d-block"></i>
<div id="timer-progress" class="progress-bar progress-bar-striped progress-bar-animated bg-info" style="width: 0%"></div> <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> </div>
<div id="conf-code-display" class="alert alert-success d-none mb-4 animate__animated animate__pulse animate__infinite"> <!-- Passo 4: Código Inválido (Fraude ou Timeout) -->
<div class="small">Pagamento Concluído! Seu código de 6 dígitos:</div> <div id="code-invalid" style="display: none;" class="animate__animated animate__shakeX">
<div id="generated-code" class="display-4 fw-bold font-monospace mt-1"></div> <div class="alert alert-danger border-0 shadow py-5">
<div class="small text-dark mt-1">Copie e cole no campo abaixo para liberar o jogo.</div> <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>
</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>
</div> </div>
</div> </div>
{% block extra_js %} <style>
<script> .rental-card { transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); cursor: pointer; border: 1px solid #eee !important; }
let currentOptionId = null; .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; }
let timerInterval = null; .border-dashed { border: 2px dashed #28a745; }
const WAIT_TIME = 180; // 3 minutes in seconds .letter-spacing-5 { letter-spacing: 5px; }
.animate__animated { animation-duration: 0.8s; }
</style>
function selectOption(id, title, price, qr1, qr2) { <script>
currentOptionId = id; let currentPurchaseId = null;
document.getElementById('payment-empty').classList.add('d-none'); let timerInterval = null;
document.getElementById('payment-details').classList.remove('d-none'); 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 fetch(`/generate-purchase/{{ game.id }}/${optionId}/`)
document.getElementById('qr-unlock-section').classList.remove('d-none'); .then(response => response.json())
document.getElementById('qr-display-section').classList.add('d-none'); .then(data => {
document.getElementById('conf-code-display').classList.add('d-none'); if (data.success) {
document.getElementById('timer-section').classList.remove('d-none'); currentPurchaseId = data.purchase_id;
document.getElementById('submit-btn').disabled = true; document.getElementById('hidden-purchase-id').value = data.purchase_id;
document.getElementById('selected-option-name').innerText = `${title} - R$ ${price}`;
if (timerInterval) clearInterval(timerInterval);
document.getElementById('options-grid').style.display = 'none';
document.getElementById('selected-title').innerText = title + ' - R$ ' + price; document.getElementById('payment-section').style.display = 'block';
document.getElementById('qr-instructions').style.display = 'block';
const placeholder = 'https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=Pagamento_AI_Forge'; document.getElementById('qr-display').style.display = 'none';
document.getElementById('qr1-img').src = qr1 || placeholder; document.getElementById('code-reveal').style.display = 'none';
document.getElementById('qr2-img').src = qr2 || placeholder; document.getElementById('code-invalid').style.display = 'none';
// Highlight selected // Set Placeholder QR (In real app, these come from RentalOption model)
document.querySelectorAll('.option-card').forEach(el => el.classList.remove('border-info', 'bg-info', 'bg-opacity-10')); 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('option-' + id).classList.add('border-info', 'bg-info', 'bg-opacity-10'); 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() { function resetSelection() {
if (!currentOptionId) return; if (timerInterval) clearInterval(timerInterval);
document.getElementById('options-grid').style.display = 'flex';
document.getElementById('qr-unlock-section').classList.add('d-none'); document.getElementById('payment-section').style.display = 'none';
document.getElementById('qr-display-section').classList.remove('d-none'); }
function startPaymentProcess() {
document.getElementById('qr-instructions').style.display = 'none';
document.getElementById('qr-display').style.display = 'block';
startTimer(); startTimer();
} }
function startTimer() { function startTimer() {
let timeLeft = WAIT_TIME; remainingTime = totalTime;
const countdownEl = document.getElementById('countdown'); updateTimerDisplay();
const progressEl = document.getElementById('timer-progress');
if (timerInterval) clearInterval(timerInterval); if (timerInterval) clearInterval(timerInterval);
timerInterval = setInterval(() => { timerInterval = setInterval(() => {
timeLeft--; remainingTime--;
updateTimerDisplay();
const minutes = Math.floor(timeLeft / 60); if (remainingTime <= 0) {
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) {
clearInterval(timerInterval); clearInterval(timerInterval);
generateCode(); finalizePayment();
} }
}, 1000); }, 1000);
} }
function generateCode() { function updateTimerDisplay() {
fetch(`/generate-purchase/{{ game.pk }}/${currentOptionId}/`) 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(response => response.json())
.then(data => { .then(data => {
if (data.success) { document.getElementById('qr-display').style.display = 'none';
document.getElementById('purchase_id_input').value = data.purchase_id; if (data.success && data.paid) {
document.getElementById('generated-code').innerText = data.confirmation_code; document.getElementById('code-reveal').style.display = 'block';
document.getElementById('reveal-conf-code').innerText = data.confirmation_code;
document.getElementById('timer-section').classList.add('d-none'); document.getElementById('hidden-confirm-code').value = data.confirmation_code;
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');
} else { } 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> </script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"/>
{% endblock %}
{% endblock %} {% endblock %}

View File

@ -19,5 +19,7 @@ urlpatterns = [
path('catalog/', views.catalog, name='catalog'), path('catalog/', views.catalog, name='catalog'),
path('purchase/<int:pk>/', views.purchase_game, name='purchase_game'), 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('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'), path('play/<int:pk>/', views.play_game, name='play_game'),
] ]

View File

@ -7,6 +7,7 @@ from django.contrib import messages
from django.utils import timezone from django.utils import timezone
from datetime import timedelta from datetime import timedelta
from django.http import JsonResponse from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from .models import GameProject, UserSession, AdminConfig, RentalOption, UserPurchase from .models import GameProject, UserSession, AdminConfig, RentalOption, UserPurchase
from ai.local_ai_api import LocalAIApi from ai.local_ai_api import LocalAIApi
@ -40,7 +41,6 @@ def admin_login(request):
def generate_code(request): def generate_code(request):
if request.method == 'POST': if request.method == 'POST':
phone = request.POST.get('phone') phone = request.POST.get('phone')
# Generate 6 digit code for platform access
code = ''.join(random.choices(string.digits, k=6)) code = ''.join(random.choices(string.digits, k=6))
while UserSession.objects.filter(access_code=code).exists(): while UserSession.objects.filter(access_code=code).exists():
code = ''.join(random.choices(string.digits, k=6)) code = ''.join(random.choices(string.digits, k=6))
@ -67,22 +67,20 @@ def logout(request):
request.session.flush() request.session.flush()
return redirect('index') return redirect('index')
# AI Helper - PROGRAMADOR AI AUTOMATIZADO # AI Helper - Refined for better functional games
def generate_game_script(prompt, genre): def generate_game_script(prompt, genre):
system_prompt = ( system_prompt = (
"You are an expert game developer. Create a complete, single-file HTML/JavaScript/CSS game. " "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 fully functional, self-contained, and playable in a browser iframe. " "The game MUST be a single-file HTML (including CSS and JS) that works perfectly in an iframe. "
"Use modern JavaScript (ES6+), HTML5 Canvas, or CSS animations. " "Features to include: "
"The game MUST have: " "1. A polished Start Screen with a 'Play' button. "
"1. A 'Start Game' screen with instructions. " "2. Score tracking and a Game Over screen with a 'Restart' button. "
"2. Smooth controls (keyboard or touch). " "3. Mobile-friendly touch controls and desktop keyboard support. "
"3. A scoring system and a 'Game Over' state with a 'Restart' button. " "4. Vibrant, modern aesthetics using CSS gradients and clean shapes. "
"4. A polished, modern visual style (dark theme preferred). " "5. Fluid animations and sound effects simulation (visual feedback). "
"5. Responsive design to fit any screen size. " "Return ONLY the source code starting with <!DOCTYPE html>."
"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."
) )
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({ response = LocalAIApi.create_response({
"input": [ "input": [
@ -117,8 +115,9 @@ def admin_create_game(request):
if request.method == 'POST': if request.method == 'POST':
title = request.POST.get('title') title = request.POST.get('title')
prompt = request.POST.get('prompt') prompt = request.POST.get('prompt', '')
genre = request.POST.get('genre', 'arcade') genre = request.POST.get('genre', 'platformer')
external_url = request.POST.get('external_url', '')
image = request.FILES.get('image') image = request.FILES.get('image')
use_ai = request.POST.get('use_ai') == 'on' use_ai = request.POST.get('use_ai') == 'on'
manual_script = request.POST.get('script_code', '') manual_script = request.POST.get('script_code', '')
@ -129,17 +128,18 @@ def admin_create_game(request):
if ai_script: if ai_script:
script = ai_script script = ai_script
else: 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, title=title,
prompt=prompt, prompt=prompt,
genre=genre, genre=genre,
image_reference=image, image_reference=image,
script_code=script, script_code=script,
external_url=external_url,
config_json={"player": {"speed": 200}, "entities": []} 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 redirect('admin_dashboard')
return render(request, 'core/admin_game_form.html', {'title': 'Criar Novo Jogo'}) 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': if request.method == 'POST':
project.title = request.POST.get('title') 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.genre = request.POST.get('genre')
project.external_url = request.POST.get('external_url', '')
if request.FILES.get('image'): if request.FILES.get('image'):
project.image_reference = 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) ai_script = generate_game_script(project.prompt, project.genre)
if ai_script: if ai_script:
project.script_code = ai_script project.script_code = ai_script
else:
messages.error(request, 'Falha ao gerar o script com AI.')
else: else:
project.script_code = manual_script project.script_code = manual_script
@ -183,7 +182,7 @@ def admin_delete_game(request, pk):
project = get_object_or_404(GameProject, pk=pk) project = get_object_or_404(GameProject, pk=pk)
project.delete() project.delete()
messages.success(request, 'Jogo excluído com sucesso.') messages.success(request, 'Jogo excluído.')
return redirect('admin_dashboard') return redirect('admin_dashboard')
def admin_edit_rental(request, pk): def admin_edit_rental(request, pk):
@ -226,16 +225,15 @@ def purchase_game(request, pk):
if request.method == 'POST': if request.method == 'POST':
confirm_code = request.POST.get('confirm_code') confirm_code = request.POST.get('confirm_code')
purchase_id = request.POST.get('purchase_id') purchase_id = request.POST.get('purchase_id')
purchase = get_object_or_404(UserPurchase, pk=purchase_id, user_session=user) 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.is_confirmed = True
purchase.save() purchase.save()
messages.success(request, f'Pagamento confirmado! Aproveite o jogo.') messages.success(request, f'Pagamento confirmado! Aproveite o jogo.')
return redirect('play_game', pk=game.pk) return redirect('play_game', pk=game.pk)
else: 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', { return render(request, 'core/purchase.html', {
'game': game, 'game': game,
@ -251,25 +249,77 @@ def generate_purchase(request, game_pk, option_pk):
game = get_object_or_404(GameProject, pk=game_pk) game = get_object_or_404(GameProject, pk=game_pk)
option = get_object_or_404(RentalOption, pk=option_pk) option = get_object_or_404(RentalOption, pk=option_pk)
# Generate 6 digit confirmation code (as requested) # Code is initially None or a placeholder; will be set when "payment detected"
conf_code = ''.join(random.choices(string.digits, k=6))
expires = timezone.now() + timedelta(days=option.duration_days) expires = timezone.now() + timedelta(days=option.duration_days)
purchase = UserPurchase.objects.create( purchase = UserPurchase.objects.create(
user_session=user, user_session=user,
game=game, game=game,
rental_option=option, rental_option=option,
confirmation_code=conf_code, confirmation_code="PENDING",
expires_at=expires, expires_at=expires,
is_paid=False,
is_confirmed=False is_confirmed=False
) )
return JsonResponse({ return JsonResponse({
'success': True, 'success': True,
'purchase_id': purchase.id, 'purchase_id': purchase.id
'confirmation_code': conf_code
}) })
@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): def play_game(request, pk):
is_admin, user = get_auth_status(request) is_admin, user = get_auth_status(request)
if not user and not is_admin: if not user and not is_admin:
@ -278,7 +328,6 @@ def play_game(request, pk):
game = get_object_or_404(GameProject, pk=pk) game = get_object_or_404(GameProject, pk=pk)
if not is_admin: if not is_admin:
# Check if user has active purchase
purchase = UserPurchase.objects.filter( purchase = UserPurchase.objects.filter(
user_session=user, user_session=user,
game=game, game=game,