diff --git a/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php b/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php new file mode 100644 index 00000000..7f12d17b --- /dev/null +++ b/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php @@ -0,0 +1,225 @@ + "MercadoPago", + "RoutesIgnoreCsrf" => [ + "payment/MercadoPagoWebhook" + ], + ]; + } + + public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct, string $totalPriceString): string + { + $user = Auth::user(); + $user = User::findOrFail($user->id); + $url = 'https://api.mercadopago.com/checkout/preferences'; + $settings = new MercadoPagoSettings(); + try { + $response = Http::withHeaders([ + 'Content-Type' => 'application/json', + 'Authorization' => 'Bearer ' . $settings->access_token, + ])->post($url, [ + 'back_urls' => [ + 'success' => route('payment.MercadoPagoChecker'), + 'failure' => route('payment.Cancel'), + 'pending' => route('payment.MercadoPagoChecker'), + ], + 'notification_url' => route('payment.MercadoPagoWebhook'), + 'payer' => [ + 'email' => $user->email, + ], + 'items' => [ + [ + 'title' => "Order #{$payment->id} - " . $shopProduct->name, + 'quantity' => 1, + 'unit_price' => $totalPriceString, + 'currency_id' => $shopProduct->currency_code, + ], + ], + 'metadata' => [ + 'credit_amount' => $shopProduct->quantity, + 'user_id' => $user->id, + 'crtl_panel_payment_id' => $payment->id, + ], + ]); + + if ($response->successful()) { + // preferenceID + $preferenceId = $response->json()['id']; + + // Redirect link + return ("https://www.mercadopago.com/checkout/v1/redirect?preference-id=" . $preferenceId); + } else { + Log::error('MercadoPago Payment: ' . $response->body()); + throw new Exception('Payment failed'); + } + } catch (Exception $ex) { + Log::error('MercadoPago Payment: ' . $ex->getMessage()); + throw new Exception('Payment failed'); + } + } + + static function Checker(Request $request): void + { + // paymentID (not is preferenceID or paymentID for store) + $paymentId = $request->input('payment_id'); + + $MpPayment = self::MpPayment($paymentId, false); + + switch ($MpPayment) { + case "paid": + Redirect::route('home')->with('success', 'Payment successful')->send(); + break; + case "cancelled": + Redirect::route('home')->with('info', 'Your canceled the payment')->send(); + break; + case "processing": + Redirect::route('home')->with('info', 'Your payment is being processed')->send(); + break; + default: + Redirect::route('home')->with('error', 'Your payment is unknown')->send(); + break; + } + } + + static function Webhook(Request $request): JsonResponse + { + $topic = $request->input('topic'); + $msg = 'unset'; + $status = 400; + if ($topic === 'merchant_order') { + $msg = 'ignored'; + $status = 200; + } else if ($topic === 'payment') { + $msg = 'ignored'; + $status = 200; + } else { + try { + $notificationId = $request->input('data.id') ?? $request->input('id') ?? $request->input('payment_id') ?? 'unknown'; + if ($notificationId == 'unknown') { + $msg = 'unknown payment.'; + $status = 400; + } else if ($notificationId == '123456') { + $msg = 'MercadoPago api test'; + $status = 200; + } else { + $MpPayment = self::MpPayment($notificationId, true); + switch ($MpPayment) { + case "paid": + $msg = $MpPayment; + $status = 200; + break; + + case "cancelled": + $msg = $MpPayment; + $status = 200; + break; + + case "processing": + $msg = $MpPayment; + $status = 200; + break; + default: + $msg = 'unknown'; + $status = 400; + break; + } + } + } catch (Exception $ex) { + Log::error('MercadoPago Webhook(IPN) Payment: ' . $ex->getMessage()); + $msg = 'error'; + $status = 500; + } + } + $response = new JsonResponse($msg, $status); + return $response; + } + /** + * Mercado Pago Payment checker + */ + private function MpPayment(string $paymentID, bool $notification): string + { + $MpResponse = "unknown"; + $payment = "unknown"; + $url = "https://api.mercadopago.com/v1/payments/" . $paymentID; + $settings = new MercadoPagoSettings(); + $response = Http::withHeaders([ + 'Content-Type' => 'application/json', + 'Authorization' => 'Bearer ' . $settings->access_token, + ])->get($url); + + if ($response->successful()) { + $mercado = $response->json(); + $status = $mercado->status; + $payment = Payment::findOrFail($mercado->metadata->crtl_panel_payment_id); + $shopProduct = ShopProduct::findOrFail($payment->shop_item_product_id); + + if ($status == "approved") { + // avoids double additions, if the user enters after the webhook has already added the credits + if ($payment->status !== PaymentStatus::PAID) { + $user = User::findOrFail($payment->user_id); + $payment->update([ + 'status' => PaymentStatus::PAID, + 'payment_id' => $paymentID, + ]); + $payment->save(); + if ($notification) { + $user->notify(new ConfirmPaymentNotification($payment)); + } + event(new PaymentEvent($user, $payment, $shopProduct)); + event(new UserUpdateCreditsEvent($user)); + } + $MpResponse = "paid"; + } else { + if ($status == "cancelled") { + $user = User::findOrFail($payment->user_id); + $payment->update([ + 'status' => PaymentStatus::CANCELED, + 'payment_id' => $paymentID, + ]); + $payment->save(); + event(new PaymentEvent($user, $payment, $shopProduct)); + $MpResponse = "cancelled"; + } else { + $user = User::findOrFail($payment->user_id); + $payment->update([ + 'status' => PaymentStatus::PROCESSING, + 'payment_id' => $paymentID, + ]); + $payment->save(); + event(new PaymentEvent($user, $payment, $shopProduct)); + $MpResponse = "processing"; + } + } + } + return $MpResponse; + } +} diff --git a/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoSettings.php b/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoSettings.php new file mode 100644 index 00000000..f9b2059f --- /dev/null +++ b/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoSettings.php @@ -0,0 +1,34 @@ + 'fas fa-dollar-sign', + 'access_token' => [ + 'type' => 'string', + 'label' => 'Access Token Key', + 'description' => 'The Access Token of your Mercado Pago App', + ], + 'enabled' => [ + 'type' => 'boolean', + 'label' => 'Enabled', + 'description' => 'Enable or disable this payment gateway', + ], + ]; + } +} diff --git a/app/Extensions/PaymentGateways/MercadoPago/migrations/2024_01_24_120635_create_mercadopago_settings.php b/app/Extensions/PaymentGateways/MercadoPago/migrations/2024_01_24_120635_create_mercadopago_settings.php new file mode 100644 index 00000000..f27411c3 --- /dev/null +++ b/app/Extensions/PaymentGateways/MercadoPago/migrations/2024_01_24_120635_create_mercadopago_settings.php @@ -0,0 +1,18 @@ +migrator->addEncrypted('mpago.access_token', null); + $this->migrator->add('mpago.enabled', false); + } + + public function down(): void + { + $this->migrator->delete('mpago.access_token'); + $this->migrator->delete('mpago.enabled'); + } +} diff --git a/app/Extensions/PaymentGateways/MercadoPago/web_routes.php b/app/Extensions/PaymentGateways/MercadoPago/web_routes.php new file mode 100644 index 00000000..a43179a6 --- /dev/null +++ b/app/Extensions/PaymentGateways/MercadoPago/web_routes.php @@ -0,0 +1,18 @@ +group(function () { + Route::get( + 'payment/MercadoPagoChecker', + function () { + MercadoPagoExtension::Checker(request()); + } + )->name('payment.MercadoPagoChecker'); +}); + + +Route::post('payment/MercadoPagoWebhook', function () { + MercadoPagoExtension::Webhook(request()); +})->name('payment.MercadoPagoWebhook'); diff --git a/config/permissions_web.php b/config/permissions_web.php index f0918375..4c74caa1 100644 --- a/config/permissions_web.php +++ b/config/permissions_web.php @@ -118,6 +118,9 @@ return [ 'settings.paypal.read', 'settings.paypal.write', + 'settings.mercadopago.read', + 'settings.mercadopago.write', + 'settings.stripe.read', 'settings.stripe.write', diff --git a/themes/BlueInfinity/views/layouts/main.blade.php b/themes/BlueInfinity/views/layouts/main.blade.php index b6549a7a..3f1e0753 100644 --- a/themes/BlueInfinity/views/layouts/main.blade.php +++ b/themes/BlueInfinity/views/layouts/main.blade.php @@ -258,7 +258,7 @@ @endif - @canany(['settings.discord.read','settings.discord.write','settings.general.read','settings.general.write','settings.invoice.read','settings.invoice.write','settings.locale.read','settings.locale.write','settings.mail.read','settings.mail.write','settings.pterodactyl.read','settings.pterodactyl.write','settings.referral.read','settings.referral.write','settings.server.read','settings.server.write','settings.ticket.read','settings.ticket.write','settings.user.read','settings.user.write','settings.website.read','settings.website.write','settings.paypal.read','settings.paypal.write','settings.stripe.read','settings.stripe.write','settings.mollie.read','settings.mollie.write','admin.overview.read','admin.overview.sync','admin.ticket.read','admin.tickets.write','admin.ticket_blacklist.read','admin.ticket_blacklist.write','admin.roles.read','admin.roles.write','admin.api.read','admin.api.write']) + @canany(['settings.discord.read','settings.discord.write','settings.general.read','settings.general.write','settings.invoice.read','settings.invoice.write','settings.locale.read','settings.locale.write','settings.mail.read','settings.mail.write','settings.pterodactyl.read','settings.pterodactyl.write','settings.referral.read','settings.referral.write','settings.server.read','settings.server.write','settings.ticket.read','settings.ticket.write','settings.user.read','settings.user.write','settings.website.read','settings.website.write','settings.paypal.read','settings.paypal.write','settings.stripe.read','settings.stripe.write','settings.mollie.read','settings.mollie.write','settings.mercadopago.read','settings.mercadopago.write','admin.overview.read','admin.overview.sync','admin.ticket.read','admin.tickets.write','admin.ticket_blacklist.read','admin.ticket_blacklist.write','admin.roles.read','admin.roles.write','admin.api.read','admin.api.write']) @endcanany @@ -329,7 +329,9 @@ 'settings.stripe.read', 'settings.stripe.write', 'settings.mollie.read', - 'settings.mollie.write',]) + 'settings.mollie.write', + 'settings.mercadopago.read', + 'settings.mercadopago.write',]) @endcanany @@ -329,7 +329,9 @@ 'settings.stripe.read', 'settings.stripe.write', 'settings.mollie.read', - 'settings.mollie.write',]) + 'settings.mollie.write', + 'settings.mercadopago.read', + 'settings.mercadopago.write',])