Antes que te vayas
Inscríbete en nuestro curso gratuito de Laravel
Tenemos varias formas de integrar pagos con PayPal en nuestros sitios web.
Veamos cómo lograrlo haciendo uso de el SDK de PayPal para Javascript, sobre un proyecto Laravel.
Cabe resaltar que la idea será muy similar para proyectos PHP en general, e incluso para cualquier otra tecnología backend, siguiendo los mismos pasos.
El primer paso consiste en dirigirnos al PayPal Developer Dashboard.
Aquí debemos iniciar sesión con nuestra cuenta de PayPal y crear una aplicación usando el botón "Create App".
Si ya tienes una, basta con hacer clic en su nombre y obtener tus credenciales.
Por ejemplo, yo tengo estas 2 aplicaciones PayPal:
Como puedes ver, tenemos aplicaciones en modo Sandbox y modo Live.
La integración debemos hacerla usando credenciales Sandbox, es decir, de prueba.
Una vez que el proyecto funcione correctamente, solo actualizaremos nuestras credenciales a Live.
Los datos que necesitamos son 2:
Estos valores debemos copiarlos porque los usaremos en nuestro proyecto Laravel.
Para que puedas ver el valor de "Secret" solo haz clic en "Show".
PayPal puede implementarse de muchas formas. Lo que vamos a usar en esta ocasión se llama Standard Payments.
Lo primero que debemos hacer es decidir dónde vamos a mostrar el formulario de PayPal en nuestro proyecto web.
El SDK de PayPal nos pregunta sobre qué elemento HTML queremos cargar el formulario de pago, y entonces lo hará sobre ese contenedor.
Para que esta lógica se active debemos cargar el SDK de PayPal, de la siguiente manera:
<script src="https://www.paypal.com/sdk/js?client-id=TU_CLIENT_ID¤cy=USD"></script>
Ten en cuenta que:
client-id
en la URL del script, con tu ID de cliente que obtuviste en el paso anterior.currency=USD
significa que los pagos se realizarán en dólares.Si bien puedes escribir tu client-id
directamente en la URL, lo recomendable es organizar tus credenciales en el archivo .env
de tu proyecto.
Entonces, allí debes definir las siguientes 3 variables:
PAYPAL_CLIENT_ID=AfjYp...
PAYPAL_SECRET=EHJdU...
PAYPAL_MODE=sandbox
He puesto unos puntos suspensivos pero tú debes escribir tus datos completos.
Luego de cargar el SDK de PayPal lo siguiente es ejecutar el siguiente código Javascript para mostrar el formulario de pago:
paypal.Buttons({
createOrder: function(data, actions) {
return actions.order.create({
// ...
purchase_units: [{
amount: {
value: 100
}
}],
});
},
onApprove: function(data, actions) {
// ...
}
}).render('#paypal-button-container');
Este fragmento de código:
<script>
. Sólo asegúrate de ejecutarlo.render
es el que cargará el formulario sobre el elemento que se le indique. En este caso, sobre un elemento con id paypal-button-container
.Con esto, el formulario se mostrará en tu proyecto web, de manera similar a la siguiente:
Esto, sin embargo, se puede personalizar.
Es posible personalizar:
Aunque esto es posible desde la URL del script que cargamos, es preferible personalizar a través de código Javascript.
Por ejemplo, cuando invocamos al método paypal.Buttons
le pasamos un objeto con la configuración que necesitamos.
fundingSource
y nos permite definir qué opciones de pago queremos mostrar.createOrder
nos permite llenar los campos del formulario de pago con datos que ya tenemos.onApprove
nos permite decidir qué hacer luego de confirmar los datos del usuario, tan pronto estemos listos para procesar la orden.Para el proyecto que estoy desarrollando, por ejemplo:
Entonces indicamos todo ello, además del precio, de la siguiente manera:
paypal.Buttons({
// ...
createOrder: function(data, actions) {
return actions.order.create({
application_context: {
shipping_preference: "NO_SHIPPING"
},
payer: {
email_address: '{{ $email }}',
name: {
given_name: '{{ $firstName }}',
surname: '{{ $lastName }}'
},
address: {
country_code: "{{ $country->code }}"
}
},
purchase_units: [{
amount: {
value: {{ $solution->getAmountToPay() }}
}
}],
});
},
// ...
})
En este código de ejemplo:
Esto es útil, porque así los usuarios no necesitan escribir nuevamente toda su información.
El monto a pagar, en este ejemplo, está determinado por la expresión $solution->getAmountToPay()
.
Lo que ocurre es que, para el proyecto que estoy desarrollando, los usuarios pagan por acceder a sus resultados, luego de resolver un test online.
Es decir:
Llegados a este punto estamos listos para completar nuestro pago de lado del cliente.
Una vez que un usuario aprueba un pago, nosotros recibimos información del mismo a través de nuestro callback onApprove
.
Para empezar, puedes dejar esta función sin lógica y simplemente imprimir la información que recibe.
Por ejemplo:
paypal.Buttons({
fundingSource: paypal.FUNDING.CARD,
createOrder: function(data, actions) {
// ...
},
onApprove: function(data, actions) {
console.log('data', data);
console.log('actions', actions);
}
}).render('#paypal-button-container');
createOrder
pero es la que mostramos más arriba, adaptada a lo que necesites.Si te preguntas qué datos de prueba puedes usar, no te preocupes.
Esta información la encuentras en el Dashboard de PayPal Developer, en la opción Sandbox > Accounts.
Desde aquí puedes crear cuentas Sandbox o bien usar una ya existente.
Una vez que hayas ingresado los datos de un usuario de prueba, el formulario se verá de la siguiente manera:
Y una vez que termine de cargar, en nuestra consola nos encontraremos con los objetos que hemos decidido imprimir:
Si bien todos los datos son importantes, el que necesitamos principalmente es el ID de la orden. Ya que de esta manera vamos a poder capturar el pago (hasta este punto el estado es Approved
).
Si tienes dudas de cómo completar estos datos, puedes ver el video que tengo publicado en YouTube, respecto a este tema, donde seguimos cada uno de los pasos:
Si bien el pago podría ser capturado de lado del cliente, en nuestro caso vamos a implementarlo en nuestro backend.
Allí mismo vamos a registrar en nuestra base de datos la información de cada orden exitosa.
Hasta el paso anterior hemos visto únicamente implementación de lado del cliente.
Ahora sí, finalmente, veremos código backend.
Debemos tener una ruta para atender las órdenes aprobadas por nuestros usuarios, a fin de capturar tales pagos.
Route::get('/paypal/process/{orderId}', '[email protected]')->name('paypal.process');
Esta ruta tiene un parámetro de ruta orderId
, que usaremos desde nuestro controlador para capturar el pago y así mismo registrarlo en nuestra base de datos.
Así, en nuestro controlador tendremos un método process
encargado de procesar el pago.
El método estará definido de esta manera:
public function process($orderId, Request $request)
{
$accessToken = $this->getAccessToken();
$requestUrl = "/v2/checkout/orders/$orderId/capture";
$response = $this->client->request('POST', $requestUrl, [
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'Authorization' => "Bearer $accessToken"
]
]);
$data = json_decode($response->getBody(), true);
dd($data);
// ...
}
accessToken
.capture
de la orden.json_decode
de la respuesta que nos da PayPal e imprimimos esta data usando la función dd
de Laravel.Hasta aquí la idea es que puedas analizar qué formato tiene la respuesta que estás recibiendo.
Una vez que tienes una respuesta de ejemplo, puedes hacer lo que necesites.
Luego de hacer una petición para capturar una orden (a partir de su id), lo siguiente es ejecutar código con nuestra lógica, según la respuesta que nos da PayPal.
Por ejemplo, para el proyecto que estoy implementando, he comentado el llamado a dd
y he escrito lo siguiente:
if ($data['status'] === 'COMPLETED') {
// Ubicar la solución del usuario
$solutionId = $request->input('solution_id');
$solution = Solution::findOrFail($solutionId);
// Obtener el paymentId y el monto pagado, de $data
$payPalPaymentId = $data['purchase_units'][0]['payments']['captures'][0]['id'];
$amount = $data['purchase_units'][0]['payments']['captures'][0]['amount']['value'];
// Registrar un pago exitoso en la BD
$payment = $this->registerSuccessfulPayment($solution, $amount, $payPalPaymentId);
// Dar una respuesta de error si el pago no se pudo registrar
if (!$payment) {
return $this->responseFailure();
}
// Dar una respuesta de éxito si todo salió bien
return [
'success' => true,
'url' => $solution->getResultsLink(),
'payment_id' => $payment->id,
'amount' => $amount
];
}
// Dar una respuesta de error si el status no es COMPLETED
return $this->responseFailure();
En este caso hablo de una solución, ya que el usuario ha resuelto un test y está pagando para ver sus resultados.
En tu proyecto podría tratarse de un modelo Product, Item, u otro, según lo que estés implementando.
Lo importante aquí es entender que desde $data
puedes acceder:
Es importante que guardes estos valores en tu base de datos, por si en algún momento quieres contrastar los registros de tu base de datos con PayPal.
Importante:
En el ejemplo anterior estoy invocando al método de clase getAccessToken()
.
Éste método lo he definido en el mismo controlador de la siguiente manera:
private function getAccessToken()
{
$response = $this->client->request('POST', '/v1/oauth2/token', [
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/x-www-form-urlencoded',
],
'body' => 'grant_type=client_credentials',
'auth' => [
$this->clientId, $this->secret, 'basic'
]
]
);
$data = json_decode($response->getBody(), true);
return $data['access_token'];
}
Como puedes ver, estamos haciendo una petición POST al endpoint /v1/oauth2/token
, enviando nuestro clientId
y secret
, para obtener un accessToken como respuesta.
Las peticiones HTTP las puedes hacer usando un cliente de Guzzle, que puedes definir en el constructor, donde además accedemos a nuestras credenciales de PayPal.
private $client;
private $clientId;
private $secret;
public function __construct()
{
$this->client = new Client([
// 'base_uri' => 'https://api-m.paypal.com'
'base_uri' => 'https://api-m.sandbox.paypal.com'
]);
$this->clientId = env('PAYPAL_CLIENT_ID');
$this->secret = env('PAYPAL_SECRET');
}
El método registerSuccessfulPayment
para el proyecto que estoy haciendo consiste en registrar un pago en la base de datos, y actualizar la columna payment_id
para la solución del usuario que está pagando por sus resultados.
En tu caso tendrás que hacer lo equivalente, según los requerimientos de tu proyecto.
Luego de capturar un pago puedes hacer muchas cosas.
Por ejemplo:
Esto lo puedes hacer en el método onApprove
.
¿Recuerdas que lo definimos en un inicio sólo para imprimir el valor de data
y actions
?
Es momento de actualizarlo, para actuar frente a la respuesta que devuelve nuestro backend.
Para el proyecto que he desarrollado lo tengo de la siguiente manera:
return fetch('/paypal/process/' + data.orderID + '?solution_id=' + {{ $solution->id }})
.then(res => res.json())
.then(function(response) {
// Show a failure message
if (!response.success) {
const failureMessage = 'Sorry, your transaction could not be processed.';
alert(failureMessage);
return;
}
processSuccessfulPayment(response);
});
Aquí:
Entonces:
En mi caso, la función processSuccessfulPayment
simplemente:
sendPurchaseEvent(response.payment_id, response.amount);
document.getElementById('sectionPayments').classList.add('hidden');
document.getElementById('loading').classList.remove('hidden');
setTimeout(function () {
location.href = response.url;
}, 200);
Siéntete libre de actualizar este código de ejemplo según tus necesidades.
Generalmente, integrar pagos online se considera como uno de los temas más avanzados en cuanto a desarrollo de software.
Sin embargo, como puedes ver, una solución básica como la que hemos visto en este tutorial no es tan complicada de implementar.
Si esta guía te ha sido de ayuda y/o te gustaría aprender más sobre Laravel y Javascript, te invito a seguir mis cursos, que aparecen aquí en la parte inferior.
Comparte este post si te fue de ayuda 🙂.
Cualquier duda y/o sugerencia es bienvenida.
Regístrate
Inicia sesión para acceder a nuestros cursos y llevar un control de tu progreso.
Cursos recomendados
Aprende Laravel desde cero y desarrolla aplicaciones web reales, en tiempo récord, de la mano de Laravel.
Ingresar al cursoActualiza tus proyectos desde cualquier versión hasta la última versión estable de Laravel.
Ingresar al cursoDesarrollemos un Messenger! Aprende sobre Channels, Queues, Vuex, JWT, Sesiones, BootstrapVue y mucho más.
Ingresar al cursoInscríbete en nuestro curso gratuito de Laravel