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'),
('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}"

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

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

View File

@ -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,