Skip to content
Merged

Beta #36

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"dependencies": {
"@headlessui/react": "^2.2.4",
"@heroicons/react": "^2.2.0",
"@stripe/stripe-js": "^7.8.0",
"@vis.gl/react-google-maps": "^1.5.5",
"bcrypt": "^6.0.0",
"lucide-react": "^0.525.0",
Expand Down
17 changes: 1 addition & 16 deletions src/api/routes/games.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,19 +130,4 @@ def get_games_by_platform(platform):
games = [g.serialize() for g in q.all()] #devuelve todos los juegos encontrados
return jsonify({"games": games}), 200

# BUSCAR JUEGOS NAVBAR
#@api.route('/SearchGames/<int:game_id>', methods=["GET"])
#def get_games_by_search():
#query = request.args.get("query")

#if not query:
#return jsonify({"error": "Falta el parámetro 'query'"}), 400

#try:
#results = Games.query.filter(Games.name.ilike(f"%{query}%")).all()
#games_list = [{"id": game.id, "name": game.name} for game in results]
#return jsonify(games_list)
#except Exception as e:
#print("Error en la búsqueda:", e)
#return jsonify({"error": "Juego no encontrado"}), 500


117 changes: 91 additions & 26 deletions src/api/routes/payment.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,108 @@
# #! /usr/bin/env python3.6

# """
# server.py
# Stripe Sample.
# Python 3.6 or newer required.
# """
import os
from flask import Flask, redirect, request
from flask import Flask, redirect, request, jsonify, Blueprint
from api.models.Games import Games
import stripe
from api.database.db import db
# This is your test secret API key.
os.getenv('VITE_FRONT_URL')
stripe.api_key = os.getenv('SECRET_KEY_STRIPE')

app = Flask(__name__,
static_url_path='',
static_folder='public')
from flask_cors import CORS

# Blueprint para las rutas de pago
payment_api = Blueprint("api/payment", __name__)

# Configuración de Stripe
stripe.api_key = os.getenv('SECRET_KEY_STRIPE')
YOUR_DOMAIN = os.getenv('VITE_FRONT_URL')

@app.route("/checkout", methods=['POST'])
@payment_api.route("/create-checkout-session", methods=['POST'])
def create_checkout_session():
try:
# Obtener los datos del carrito desde el frontend
data = request.get_json()
cart_items = data.get('cart_items', [])

if not cart_items:
return jsonify({"error": "El carrito está vacío"}), 400

# Crear line_items para Stripe
line_items = []

for item in cart_items:
# Verificar que el juego existe en la base de datos
game = Games.query.get(item['id'])
if not game:
return jsonify({"error": f"Juego con ID {item['id']} no encontrado"}), 400

line_item = {
'price_data': {
'currency': 'eur', # Cambié a euros ya que veo precios en €
'product_data': {
'name': game.name,
'description': f"Plataforma: {game.platform}",
'images': [game.img] if game.img else [],
},
'unit_amount': int(game.price * 100), # Stripe usa centavos
},
'quantity': item.get('quantity', 1),
}
line_items.append(line_item)

# Crear la sesión de checkout
checkout_session = stripe.checkout.Session.create(
line_items=[
#aqui va la logica del carrito de compra
],
payment_method_types=['card'],
line_items=line_items,
mode='payment',
success_url=YOUR_DOMAIN + '?success=true',
cancel_url=YOUR_DOMAIN + '?canceled=true',
success_url=YOUR_DOMAIN + 'success?session_id={CHECKOUT_SESSION_ID}',
cancel_url=YOUR_DOMAIN + 'cancel',
automatic_tax={'enabled': True}, # Para manejar IVA automáticamente
# Opcional: agregar metadata del usuario
metadata={
'user_id': data.get('user_id', ''),
'total_items': len(cart_items)
}
)

return jsonify({
'checkout_url': checkout_session.url,
'session_id': checkout_session.id
}), 200

except stripe.error.StripeError as e:
return jsonify({"error": f"Error de Stripe: {str(e)}"}), 400
except Exception as e:
return str(e)

return redirect(checkout_session.url, code=303)
return jsonify({"error": f"Error interno: {str(e)}"}), 500

if __name__ == '__main__':
app.run(port=4242)
@payment_api.route("/verify-session/<session_id>", methods=['GET'])
def verify_session(session_id):
"""Verificar el estado de una sesión de pago"""
try:
session = stripe.checkout.Session.retrieve(session_id)
return jsonify({
'payment_status': session.payment_status,
'amount_total': session.amount_total,
'currency': session.currency
}), 200
except stripe.error.StripeError as e:
return jsonify({"error": f"Error de Stripe: {str(e)}"}), 400

# Webhook para manejar eventos de Stripe (opcional pero recomendado)
@payment_api.route("/webhook", methods=['POST'])
def stripe_webhook():
payload = request.get_data(as_text=True)
sig_header = request.headers.get('Stripe-Signature')
endpoint_secret = os.getenv('STRIPE_WEBHOOK_SECRET')

try:
event = stripe.Webhook.construct_event(
payload, sig_header, endpoint_secret
)
except ValueError:
return "Invalid payload", 400
except stripe.error.SignatureVerificationError:
return "Invalid signature", 400

# Manejar el evento
if event['type'] == 'checkout.session.completed':
session = event['data']['object']
# Aquí puedes guardar la compra en tu base de datos
print(f"Pago completado para sesión: {session['id']}")

return jsonify(success=True), 200
4 changes: 4 additions & 0 deletions src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

import api.routes.user as api_user
import api.routes.games as api_games
from api.routes.payment import payment_api as payment_api

from api.admin import setup_admin
from api.commands import setup_commands
from flask_cors import CORS
Expand Down Expand Up @@ -64,6 +66,8 @@
# Add all endpoints form the API with a "api" prefix
app.register_blueprint(api_user.api, url_prefix='/api/user')
app.register_blueprint(api_games.api, url_prefix='/api/games')
app.register_blueprint(payment_api, url_prefix="/api/payment")



# Handle/serialize errors like a JSON object
Expand Down
2 changes: 1 addition & 1 deletion src/front/components/Navbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export default function Navbar({ showDrowpdown, setShowDrowpdown }) {
gamesFilter.map((g) => {
return (
<Link to={`/detailsgames/${g.id}`}>
<span>{g.name}</span>
<span>{g.name},{g.price}</span>
</Link>
)
})
Expand Down
7 changes: 3 additions & 4 deletions src/front/components/maps/Maps.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@ function MapInner() {
return (
<Map
style={{ width: "100%", height: "100%" }}
defaultCenter={position}
defaultZoom={13}
defaultCenter={{ lat: 22.54992, lng: 0 }}
defaultZoom={3}
gestureHandling="greedy"
disableDefaultUI={false}
><Marker position={position}></Marker>
</Map>
/>
);
}

Expand Down
34 changes: 0 additions & 34 deletions src/front/components/searchgames/SearchGames.jsx

This file was deleted.

100 changes: 100 additions & 0 deletions src/front/components/stripe/StripeIntegration.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { loadStripe } from '@stripe/stripe-js';

// Cargar Stripe con tu clave pública
const stripePromise = loadStripe(import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY);

export const StripeService = {
// Crear sesión de checkout
async createCheckoutSession(cartItems, userId = null) {
const backendUrl = import.meta.env.VITE_BACKEND_URL;

try {
const response = await fetch(`${backendUrl}/api/payment/create-checkout-session`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
cart_items: cartItems,
user_id: userId
}),
});

if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Error al crear la sesión de pago');
}

const data = await response.json();
return data;
} catch (error) {
console.error('Error al crear sesión de checkout:', error);
throw error;
}
},

// Redirigir a Stripe Checkout
async redirectToCheckout(sessionData) {
const stripe = await stripePromise;

if (!stripe) {
throw new Error('Stripe no se ha cargado correctamente');
}

// Opción 1: Usar la URL directa (más simple)
window.location.href = sessionData.checkout_url;

// Opción 2: Usar el método de Stripe (alternativa)
// const result = await stripe.redirectToCheckout({
// sessionId: sessionData.session_id,
// });

// if (result.error) {
// throw new Error(result.error.message);
// }
},

// Verificar estado de una sesión
async verifySession(sessionId) {
const backendUrl = import.meta.env.VITE_BACKEND_URL;

try {
const response = await fetch(`${backendUrl}/api/payment/verify-session/${sessionId}`);

if (!response.ok) {
throw new Error('Error al verificar la sesión');
}

return await response.json();
} catch (error) {
console.error('Error al verificar sesión:', error);
throw error;
}
}
};

// Hook personalizado para manejar pagos con Stripe
export const useStripePayment = () => {
const handlePayment = async (cartItems, userId = null) => {
try {
// Validar que el carrito no esté vacío
if (!cartItems || cartItems.length === 0) {
throw new Error('El carrito está vacío');
}

// Crear sesión de checkout
const sessionData = await StripeService.createCheckoutSession(cartItems, userId);

// Redirigir a Stripe Checkout
await StripeService.redirectToCheckout(sessionData);

} catch (error) {
console.error('Error en el proceso de pago:', error);
// Aquí puedes mostrar un mensaje de error al usuario
alert(`Error en el pago: ${error.message}`);
throw error;
}
};

return { handlePayment };
};
Loading