
Hola, bienvenido(a), al Post 07 del Taller de Desarrollo de una Tienda en línea con Bootstrap, Laravel 5.1 y la API de Paypal.
Hoy vamos a implementar pagos con Paypal para nuestra tienda, para ello vamos a utilizar el API de Paypal.
Para lograrlo tenemos que seguir una serie de pasos:
1) CUENTA DE PAYPAL
Es necesario que tengamos una cuenta en Paypal, después hay que iniciar sesión y realizar lo siguiente:
- Ir al Dashboard
- Crear una app
- Obtener el client_id y el secret
- Crear 2 usuarios, uno de tipo Business (el que venderá) y otro de tipo Personal (el que realizara los pedidos).
2) INSTALAR PACKAGE
Vamos a instalar el package rest-api-sdk-php, lo buscamos en Packagist, podemos revisar su página en Github para ver los prerrequisitos para instalarlo.
Lo instalamos vía Composer: composer require paypal/rest-api-sdk-php
3) ARCHIVO DE CONFIGURACIÓN
Tenemos que crear un archivo llamado paypal.php en la carpeta config de nuestra app, en donde pondremos los datos client_id y secret, además de establecer si trabajaremos en el modo sandbox (pruebas) o live.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<?php return array( // set your paypal credential 'client_id' => 'DATO-DE-TU-APP-DE-PAYPAL', 'secret' => 'DATO-DE-TU-APP-DE-PAYPAL', /** * SDK configuration */ 'settings' => array( /** * Available option 'sandbox' or 'live' */ 'mode' => 'sandbox', /** * Specify the max request time in seconds */ 'http.ConnectionTimeOut' => 30, /** * Whether want to log to a file */ 'log.LogEnabled' => true, /** * Specify the file that want to write on */ 'log.FileName' => storage_path() . '/logs/paypal.log', /** * Available option 'FINE', 'INFO', 'WARN' or 'ERROR' * * Logging is most verbose in the 'FINE' level and decreases as you * proceed towards ERROR */ 'log.LogLevel' => 'FINE' ), ); |
4) RUTAS
Establecemos las rutas en nuestro Router, la primera servirá para enviar información a Paypal y la segunda para recibir la respuesta que nos de.
1 2 3 4 5 6 7 8 9 |
Route::get('payment', array( 'as' => 'payment', 'uses' => 'PaypalController@postPayment', )); Route::get('payment/status', array( 'as' => 'payment.status', 'uses' => 'PaypalController@getPaymentStatus', )); |
5) CONTROLADOR
Ahora vamos a crear el Controlador PaypalController en el que pondremos los métodos postPayment y getPaymentStatus, lo hacemos desde la línea de comandos:
php artisan make:controller PaypalController –plain
Definimos nuestros métodos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
<?php namespace App\Http\Controllers; use Illuminate\Foundation\Bus\DispatchesCommands; use Illuminate\Routing\Controller as BaseController; use Illuminate\Foundation\Validation\ValidatesRequests; use PayPal\Rest\ApiContext; use PayPal\Auth\OAuthTokenCredential; use PayPal\Api\Amount; use PayPal\Api\Details; use PayPal\Api\Item; use PayPal\Api\ItemList; use PayPal\Api\Payer; use PayPal\Api\Payment; use PayPal\Api\RedirectUrls; use PayPal\Api\ExecutePayment; use PayPal\Api\PaymentExecution; use PayPal\Api\Transaction; use App\Order; use App\OrderItem; class PaypalController extends BaseController { private $_api_context; public function __construct() { // setup PayPal api context $paypal_conf = \Config::get('paypal'); $this->_api_context = new ApiContext(new OAuthTokenCredential($paypal_conf['client_id'], $paypal_conf['secret'])); $this->_api_context->setConfig($paypal_conf['settings']); } public function postPayment() { $payer = new Payer(); $payer->setPaymentMethod('paypal'); $items = array(); $subtotal = 0; $cart = \Session::get('cart'); $currency = 'MXN'; foreach($cart as $producto){ $item = new Item(); $item->setName($producto->name) ->setCurrency($currency) ->setDescription($producto->extract) ->setQuantity($producto->quantity) ->setPrice($producto->price); $items[] = $item; $subtotal += $producto->quantity * $producto->price; } $item_list = new ItemList(); $item_list->setItems($items); $details = new Details(); $details->setSubtotal($subtotal) ->setShipping(100); $total = $subtotal + 100; $amount = new Amount(); $amount->setCurrency($currency) ->setTotal($total) ->setDetails($details); $transaction = new Transaction(); $transaction->setAmount($amount) ->setItemList($item_list) ->setDescription('Pedido de prueba en mi Laravel App Store'); $redirect_urls = new RedirectUrls(); $redirect_urls->setReturnUrl(\URL::route('payment.status')) ->setCancelUrl(\URL::route('payment.status')); $payment = new Payment(); $payment->setIntent('Sale') ->setPayer($payer) ->setRedirectUrls($redirect_urls) ->setTransactions(array($transaction)); try { $payment->create($this->_api_context); } catch (\PayPal\Exception\PPConnectionException $ex) { if (\Config::get('app.debug')) { echo "Exception: " . $ex->getMessage() . PHP_EOL; $err_data = json_decode($ex->getData(), true); exit; } else { die('Ups! Algo salió mal'); } } foreach($payment->getLinks() as $link) { if($link->getRel() == 'approval_url') { $redirect_url = $link->getHref(); break; } } // add payment ID to session \Session::put('paypal_payment_id', $payment->getId()); if(isset($redirect_url)) { // redirect to paypal return \Redirect::away($redirect_url); } return \Redirect::route('cart-show') ->with('message', 'Ups! Error desconocido.'); } public function getPaymentStatus() { // Get the payment ID before session clear $payment_id = \Session::get('paypal_payment_id'); // clear the session payment ID \Session::forget('paypal_payment_id'); $payerId = \Input::get('PayerID'); $token = \Input::get('token'); if (empty($payerId) || empty($token)) { return \Redirect::route('home') ->with('message', 'Hubo un problema al intentar pagar con Paypal'); } $payment = Payment::get($payment_id, $this->_api_context); $execution = new PaymentExecution(); $execution->setPayerId(\Input::get('PayerID')); $result = $payment->execute($execution, $this->_api_context); if ($result->getState() == 'approved') { $this->saveOrder(); \Session::forget('cart'); return \Redirect::route('home') ->with('message', 'Compra realizada de forma correcta'); } return \Redirect::route('home') ->with('message', 'La compra fue cancelada'); } protected function saveOrder() { $subtotal = 0; $cart = \Session::get('cart'); $shipping = 100; foreach($cart as $producto){ $subtotal += $producto->quantity * $producto->price; } $order = Order::create([ 'subtotal' => $subtotal, 'shipping' => $shipping, 'user_id' => \Auth::user()->id ]); foreach($cart as $producto){ $this->saveOrderItem($producto, $order->id); } } protected function saveOrderItem($producto, $order_id) { OrderItem::create([ 'price' => $producto->price, 'quantity' => $producto->quantity, 'product_id' => $producto->id, 'order_id' => $order_id ]); } } |
6) VISTA MESSAGE
Cuando Paypal redirecciona al usuario a nuestro sitio y se concreta o cancela la venta lo vamos a redireccionar al home y le vamos a mostrar un mensaje indicándole que la compra se realizo de forma satisfactoria o que se cancelo, dicho mensaje se lo pasamos mediante una variable de sesión de tipo flash (solo se mostrará una vez).
Para lograr esto tenemos que crear la vista parcial message dentro de views/store/partials:
1 2 3 4 5 6 |
<div class="alert alert-info alert-dismissible text-center" role="alert"> <button type="button" class="close" data-dismiss="alert" aria-label="Close"> <span aria-hidden="true">×</span> </button> <h2><strong><i class="fa fa-info-circle"></i></strong> {{ \Session::get('message') }}</h2> </div> |
Ahora incluimos esa vista en nuestro template:
1 2 3 4 5 6 7 8 9 |
... <body> @if(\Session::has('message')) @include('store.partials.message') @endif @include('store.partials.nav') ... |
7) MODELOS Y MIGRACIONES DE ORDER Y ORDERITEM
Para guardar la información de los pedidos es necesario que creemos nuestros Modelos y Migraciones para las tablas orders y order_items, lo hacemos desde la línea de comandos:
php artisan make:model Order -m
y
php artisan make:model OrderItem -m
Ahora modificamos los archivos de migraciones de acuerdo al esquema de nuestra base de datos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
... public function up() { Schema::create('orders', function (Blueprint $table) { $table->increments('id'); $table->decimal('subtotal', 5, 2); $table->decimal('shipping', 5,2); $table->integer('user_id')->unsigned(); $table->foreign('user_id') ->references('id') ->on('users') ->onDelete('cascade'); $table->timestamps(); }); } ... |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
... public function up() { Schema::create('order_items', function (Blueprint $table) { $table->increments('id'); $table->decimal('price', 5, 2); $table->integer('quantity')->unsigned(); $table->integer('product_id')->unsigned(); $table->foreign('product_id') ->references('id') ->on('products') ->onDelete('cascade'); $table->integer('order_id')->unsigned(); $table->foreign('order_id') ->references('id') ->on('orders') ->onDelete('cascade');; }); } ... |
La información la guardamos dentro del método getPaymentStatus en nuestro controlador PaypalController llamando al método saveOrder, que se encargara de guardar la información del pedido y este a su vez llamara al método saveOrderItem por cada item del pedido para guardar la información de cada producto.
Lo único que falta es cambiar el enlace que desencadenara todo lo que hemos hecho, dicho enlace se encuentra en nuestra vista order_detail:
1 2 3 |
<a href="{{ route('payment') }}" class="btn btn-warning"> Pagar con <i class="fa fa-cc-paypal fa-2x"></i> </a> |
Para seguir pasa a paso toda la implementación te dejo a continuación el vídeo de la clase:
Pronto actualizare la versión del proyecto que tenemos en Github con el nombre de tienda, para que lo compares con tu propia versión.
Te comparto también algunos enlaces con información valiosa para este tutorial:
- Página developers de Paypal
- Package rest-api-sdk-php en Packagist
- Documentación de la API de Paypal
- Listado de códigos para monedas de diferentes países
- Ejemplos REST API
- Tutorial: Integrate PayPal SDK Into Laravel 4
Eso es todo para este post, compártelo por favor.
Con este post terminamos todo el front de nuestra app, es decir toda la parte de nuestra app que le ofrecemos al usuario, solo falta desarrollar un Back o Panel de Administración para que el admin pueda gestionar la información de la tienda, eso lo haremos en los siguientes posts.
Espero tus comentarios y nos vemos en el siguiente 🙂