diff --git a/README.md b/README.md index c1885aa..dce58f8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# 馃摫 Gananza +# 馃摫 Gananza - v2.0 ## 馃専 Visi贸n **Gananza** busca convertirse en la plataforma l铆der para la organizaci贸n y participaci贸n en rifas, ofreciendo una experiencia segura, intuitiva y accesible para todos los usuarios. Nuestra visi贸n es facilitar el acceso a rifas en cualquier formato, ya sea virtual, presencial o h铆brido, promoviendo la interacci贸n y el entretenimiento. diff --git a/how v2.0 b/how v2.0 new file mode 100644 index 0000000..14acbf5 --- /dev/null +++ b/how v2.0 @@ -0,0 +1,688 @@ +commit 5449984500fe2e61179ed3048e4d737a717cc840 (tag: v1.0, origin/test/payment-method) +Author: DiegoAndresMejia +Date: Mon Dec 2 01:43:07 2024 -0500 + + create: add the mercadopago payment method (in progress) + +diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php +index 1225dfb..1a0442e 100644 +--- a/app/Http/Controllers/PaymentController.php ++++ b/app/Http/Controllers/PaymentController.php +@@ -7,6 +7,8 @@ + use Modules\Payment\Models\Payment; + use Modules\Raffle\Models\Raffle; + use Modules\Ticket\Models\Ticket; ++use MercadoPago\MercadoPagoConfig; ++use MercadoPago\Client\Preference\PreferenceClient; +  + class PaymentController extends Controller + { +@@ -56,4 +58,74 @@ public function store(Request $request) + 'ticket' => $ticket, + 'payment' => $payment, + ]); +- }} ++ } ++ public function createPreference(Request $request) ++ { ++ // Configura el token de acceso de MercadoPago ++ MercadoPagoConfig::setAccessToken(env('MERCADOPAGO_ACCESS_TOKEN')); ++  ++ // Obtiene al usuario autenticado ++ $user = auth()->user(); ++  ++ // Obtiene el monto y los n煤meros seleccionados ++ $amount = $request->input('amount'); // Monto enviado desde el frontend ++ $numbers = $request->input('numbers', []); // N煤meros seleccionados (opcional) ++  ++ // Crear la preferencia ++ $client = new PreferenceClient(); ++ $preference = $client->create([ ++ "items" => [ ++ [ ++ "title" => "Compra de Tickets de Rifa", ++ "quantity" => 1, ++ "unit_price" => $amount, // Monto din谩mico enviado desde el frontend ++ ] ++ ], ++ "payer" => [ ++ "name" => $user->name, ++ "surname" => $user->lastname, ++ "email" => $user->email, ++ "phone" => [ ++ "area_code" => substr($user->phone_number, 0, 3), // Primera parte del n煤mero ++ "number" => substr($user->phone_number, 3), // Resto del n煤mero ++ ], ++ "identification" => [ ++ "type" => $user->document_type, // Tipo de documento (ejemplo: DNI, CC) ++ "number" => $user->document, // N煤mero del documento ++ ], ++ ], ++ "back_urls" => [ ++ "success" => route('payment.success'), ++ "failure" => route('payment.failure'), ++ "pending" => route('payment.pending'), ++ ], ++ "auto_return" => "approved", ++ "external_reference" => "TICKETS_RIFA-" . uniqid(), // Referencia 煤nica ++ "statement_descriptor" => "RIFA_TICKETS", // Descripci贸n en extracto bancario ++ ]); ++  ++ // Retorna el ID de la preferencia al frontend ++ return response()->json([ ++ 'id' => $preference->id, ++ 'numbers' => $numbers, // Devuelve los n煤meros seleccionados (opcional) ++ ]); ++ } ++  ++ public function handleSuccess() ++ { ++ // Opcional: puedes procesar informaci贸n del pago aqu铆 ++ return redirect('/dashboard')->with('success', 'Pago completado exitosamente.'); ++ } ++ ++ public function handleFailure() ++ { ++ // Opcional: puedes registrar errores o mostrar mensajes ++ return redirect('/dashboard')->with('error', 'El pago no se pudo completar. Int茅ntalo de nuevo.'); ++ } ++ ++ public function handlePending() ++ { ++ // Opcional: maneja pagos pendientes ++ return redirect('/dashboard')->with('info', 'El pago est谩 pendiente de aprobaci贸n.'); ++ } ++} +diff --git a/composer.json b/composer.json +index b67fae1..2c0dff4 100644 +--- a/composer.json ++++ b/composer.json +@@ -16,6 +16,7 @@ + "laravel/socialite": "^5.16", + "laravel/telescope": "^5.2", + "laravel/tinker": "^2.9", ++ "mercadopago/dx-php": "3.0.7", + "nwidart/laravel-modules": "^11.1", + "php-imap/php-imap": "^2.0", + "spatie/laravel-permission": "^6.9", +diff --git a/composer.lock b/composer.lock +index 06f9206..b97b4d5 100644 +--- a/composer.lock ++++ b/composer.lock +@@ -4,7 +4,7 @@ + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], +- "content-hash": "8663b66401d53b74273676a3c99b7ae7", ++ "content-hash": "ddbe8f81e50b86b1374753c8535fb151", + "packages": [ + { + "name": "almasaeed2010/adminlte", +@@ -4017,6 +4017,45 @@ + }, + "time": "2024-03-31T07:05:07+00:00" + }, ++ { ++ "name": "mercadopago/dx-php", ++ "version": "3.0.7", ++ "source": { ++ "type": "git", ++ "url": "https://github.com/mercadopago/sdk-php.git", ++ "reference": "3e52ce992bbe79116eafbe9b7b658f0c3f105290" ++ }, ++ "dist": { ++ "type": "zip", ++ "url": "https://api.github.com/repos/mercadopago/sdk-php/zipball/3e52ce992bbe79116eafbe9b7b658f0c3f105290", ++ "reference": "3e52ce992bbe79116eafbe9b7b658f0c3f105290", ++ "shasum": "" ++ }, ++ "require": { ++ "php": ">=8.2" ++ }, ++ "require-dev": { ++ "friendsofphp/php-cs-fixer": "^3.22", ++ "phpunit/phpunit": "^10.2", ++ "squizlabs/php_codesniffer": "3.*" ++ }, ++ "type": "library", ++ "autoload": { ++ "psr-4": { ++ "MercadoPago\\": "src/MercadoPago" ++ } ++ }, ++ "notification-url": "https://packagist.org/downloads/", ++ "license": [ ++ "MIT" ++ ], ++ "description": "Mercado Pago PHP SDK", ++ "homepage": "https://github.com/mercadopago/sdk-php", ++ "support": { ++ "source": "https://github.com/mercadopago/sdk-php/tree/3.0.7" ++ }, ++ "time": "2024-07-04T17:06:16+00:00" ++ }, + { + "name": "mobiledetect/mobiledetectlib", + "version": "4.8.06", +diff --git a/package-lock.json b/package-lock.json +index 5a2b34e..9e67baa 100644 +--- a/package-lock.json ++++ b/package-lock.json +@@ -6,6 +6,7 @@ + "": { + "dependencies": { + "@headlessui/vue": "^1.7.23", ++ "@inertiajs/inertia": "^0.11.1", + "@splidejs/vue-splide": "^0.6.12", + "chart.js": "^4.4.6", + "lucide-vue-next": "^0.454.0", +@@ -19,7 +20,7 @@ + "@tailwindcss/typography": "^0.5.10", + "@vitejs/plugin-vue": "^5.0.0", + "autoprefixer": "^10.4.16", +- "axios": "^1.7.4", ++ "axios": "^1.7.8", + "concurrently": "^9.0.1", + "laravel-vite-plugin": "^1.0", + "postcss": "^8.4.32", +@@ -506,6 +507,26 @@ + "qs": "^6.9.0" + } + }, ++ "node_modules/@inertiajs/inertia": { ++ "version": "0.11.1", ++ "resolved": "https://registry.npmjs.org/@inertiajs/inertia/-/inertia-0.11.1.tgz", ++ "integrity": "sha512-btmV53c54oW4Z9XF0YyTdIUnM7ue0ONy3/KJOz6J1C5CYIwimiKfDMpz8ZbGJuxS+SPdOlNsqj2ZhlHslpJRZg==", ++ "license": "MIT", ++ "dependencies": { ++ "axios": "^0.21.1", ++ "deepmerge": "^4.0.0", ++ "qs": "^6.9.0" ++ } ++ }, ++ "node_modules/@inertiajs/inertia/node_modules/axios": { ++ "version": "0.21.4", ++ "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", ++ "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", ++ "license": "MIT", ++ "dependencies": { ++ "follow-redirects": "^1.14.0" ++ } ++ }, + "node_modules/@inertiajs/vue3": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@inertiajs/vue3/-/vue3-1.2.0.tgz", +@@ -1197,9 +1218,9 @@ + } + }, + "node_modules/axios": { +- "version": "1.7.7", +- "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", +- "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", ++ "version": "1.7.8", ++ "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.8.tgz", ++ "integrity": "sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==", + "dev": true, + "license": "MIT", + "dependencies": { +@@ -1288,7 +1309,6 @@ + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", +- "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", +@@ -1616,7 +1636,6 @@ + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", +- "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" +@@ -1626,7 +1645,6 @@ + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", +- "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", +@@ -1707,7 +1725,6 @@ + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", +- "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" +@@ -1720,7 +1737,6 @@ + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", +- "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" +@@ -1851,7 +1867,6 @@ + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", +- "dev": true, + "funding": [ + { + "type": "individual", +@@ -1933,7 +1948,6 @@ + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", +- "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" +@@ -1952,7 +1966,6 @@ + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", +- "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", +@@ -2006,7 +2019,6 @@ + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", +- "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" +@@ -2029,7 +2041,6 @@ + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", +- "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" +@@ -2042,7 +2053,6 @@ + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", +- "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" +@@ -2055,7 +2065,6 @@ + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", +- "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" +@@ -2068,7 +2077,6 @@ + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", +- "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" +@@ -2468,7 +2476,6 @@ + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", +- "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" +@@ -2922,7 +2929,6 @@ + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", +- "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" +@@ -3104,7 +3110,6 @@ + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", +- "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", +@@ -3155,7 +3160,6 @@ + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", +- "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", +diff --git a/package.json b/package.json +index 3d50587..cc07832 100644 +--- a/package.json ++++ b/package.json +@@ -11,7 +11,7 @@ + "@tailwindcss/typography": "^0.5.10", + "@vitejs/plugin-vue": "^5.0.0", + "autoprefixer": "^10.4.16", +- "axios": "^1.7.4", ++ "axios": "^1.7.8", + "concurrently": "^9.0.1", + "laravel-vite-plugin": "^1.0", + "postcss": "^8.4.32", +@@ -21,6 +21,7 @@ + }, + "dependencies": { + "@headlessui/vue": "^1.7.23", ++ "@inertiajs/inertia": "^0.11.1", + "@splidejs/vue-splide": "^0.6.12", + "chart.js": "^4.4.6", + "lucide-vue-next": "^0.454.0", +diff --git a/resources/js/Components/Dashboard/RaffleCard.vue b/resources/js/Components/Dashboard/RaffleCard.vue +index aacf29c..2581146 100644 +--- a/resources/js/Components/Dashboard/RaffleCard.vue ++++ b/resources/js/Components/Dashboard/RaffleCard.vue +@@ -2,11 +2,8 @@ +  +
 +  +-  ++ Prize +  +

{{ raffle.name }}

 +

Organizador: {{ raffle.organizer.name }}

 +@@ -14,58 +11,34 @@ + Ticket de precios ${{ raffle.ticket_price }} +

 +  +-  ++  +  +  +-  +-  +-  ++  ++  ++  +  +  +
 +
 +-  ++  +
 +  +
 +-  ++  + P谩gina {{ currentPage }} de {{ totalPages }} +-  ++  +
 +@@ -83,10 +56,7 @@ +
{{ selectedNumber.join(', ') || 'Ninguno' }} +
 +
 +-  ++  +
 +@@ -96,29 +66,13 @@ +  +  +  +-  +-  +-  ++  ++  ++  +  +
 +  +-  ++  +  +  +  +@@ -139,16 +93,10 @@ +
Nombre
 +
 +  +-  ++  +-  ++  +
 +@@ -157,28 +105,18 @@ +
 +  +  +-  +-  +-  ++  ++  ++  +  +-

Aqu铆 va la pasarela de pagos

 +-  +-  ++

Procesar Pago

 ++ ++  ++
 ++ ++  ++  +
 +@@ -194,6 +132,8 @@ import { ref, computed } from 'vue'; + import { usePage } from '@inertiajs/vue3'; + import { useDarkMode } from '@/composables/useDarkMode'; + import { TransitionRoot, TransitionChild, Dialog, DialogPanel } from '@headlessui/vue'; ++import axios from 'axios'; ++ +  + /* Props and Reactive Data */ + const raffleProps = defineProps({ raffle: Object }); +@@ -204,6 +144,8 @@ const totalTickets = ref(raffleProps.raffle.total_tickets || 0); + const referenceNumber = ref(''); + const numbers = ref([]); + const selectedNumber = ref([]); ++const preferenceId = ref(null); ++ +  + /* Pagination Variables */ + const currentPage = ref(1); +@@ -282,6 +224,7 @@ const closeModal = () => { + }; +  + const openVerificationModal = () => { ++ initializeMercadoPago(); + closeModal(); + showVerificationModal.value = true; + }; +@@ -289,6 +232,56 @@ const openVerificationModal = () => { + const closeVerificationModal = () => { + showVerificationModal.value = false; + }; ++ ++const initializeMercadoPago = async () => { ++ try { ++ // Calcula el monto total din谩mico basado en la cantidad de n煤meros seleccionados ++ const totalAmount = selectedNumber.value.length > 0  ++ ? raffleProps.raffle.ticket_price * selectedNumber.value.length  ++ : 0; ++ ++ // Validaci贸n: Aseg煤rate de que el monto sea mayor a 0 ++ if (totalAmount <= 0) { ++ console.error('El monto total debe ser mayor a 0.'); ++ alert('Selecciona al menos un n煤mero para continuar con la compra.'); ++ return; ++ } ++ ++ // Prepara los datos para enviar al backend ++ const payload = { ++ amount: totalAmount, // Monto din谩mico calculado ++ numbers: [...selectedNumber.value], // N煤meros seleccionados (valores planos) ++ }; ++ ++ // Realiza la solicitud al backend ++ const response = await axios.post('/payment/preference', payload); ++ ++ // Valida que la respuesta contenga un ID de preferencia ++ if (!response.data.id) { ++ throw new Error('No se recibi贸 un ID de preferencia v谩lido del backend.'); ++ } ++ ++ // Asigna el ID de la preferencia ++ preferenceId.value = response.data.id; ++ ++ // Inicializa MercadoPago y renderiza el bot贸n ++ const mp = new window.MercadoPago('APP_USR-3f0baf72-345b-40ac-ba99-745f71d22b81', { ++ locale: 'es-MX', // Ajusta el idioma al espa帽ol de M茅xico ++ }); ++ ++ // Renderiza el bot贸n de pago ++ mp.bricks().create('wallet', 'wallet_container', { ++ initialization: { ++ preferenceId: preferenceId.value, ++ }, ++ }); ++ } catch (error) { ++ // Manejo de errores ++ console.error('Error inicializando MercadoPago:', error); ++ alert('Ocurri贸 un error al procesar el pago. Por favor, int茅ntalo de nuevo m谩s tarde.'); ++ } ++}; ++ +  +  +