Login to bookmark this video
Buy Access to Course
18.

Escuchar eventos Javascript de LemonSqueezy

|

Share this awesome video!

|

Keep on Learning!

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Ahora mismo, cada vez que queremos guardar localmente un ID de cliente LemonSqueezy en la entidad de usuario correspondiente, tenemos que configurar nuestros webhooks. Ngrok definitivamente ayuda, pero sigue siendo un poco pesado. Tenemos que ejecutar Ngrok en segundo plano antes de empezar a recibir webhooks. Y aún tenemos que actualizar la URL del webhook cada vez que reiniciamos el agente Ngrok si no tenemos un plan Ngrok de pago. Eso... no es lo ideal.

Exploremos una forma alternativa: escuchar los eventos JavaScript de LemonSqueezy y establecer el ID de cliente en un pago correcto. ¡LemonSqueezy tiene un evento especial para esto! Abre los documentos, ve a "Guías", busca "Usando Lemon.js" a la izquierda, y a la derecha, haz clic en "Manejo de eventos".

Aquí podemos ver que cuando la compra se realiza correctamente, LemonSqueezy lanza un eventoCheckout.Success. Incluso nos dan un código de ejemplo para manejarlo. Esto devuelve un montón de datos útiles, incluido el ID del cliente que estamos buscando.

Escuchar el evento LemonSqueezy Checkout.Success

¡Es hora de ponerse manos a la obra! Abre assets/controllers/lemon-squeezy_controller.js. Busca el método connect() y, al final, empieza conwindow.LemonSqueezy.Setup(). Dentro, pasa eventHandler: (data) => {}, y dentro de eso, escribe if (data.event === 'Checkout.Success'). Obtén el ID de cliente con data.data.customer_id y ponlo en una variable lsCustomerId.

// ... lines 1 - 2
export default class extends Controller {
// ... lines 4 - 7
connect() {
// ... lines 9 - 17
window.LemonSqueezy.Setup({
eventHandler: (data) => {
if (data.event === 'Checkout.Success') {
// ... line 21
const lsCustomerId = data.data.customer_id;
this.#handleCheckout(lsCustomerId);
}
},
});
}
// ... lines 28 - 80
}

Pasaremos el ID a this.#handleCheckout(). Éste aún no existe, así que créalo a continuación, con lsCustomerId como parámetro.

// ... lines 1 - 2
export default class extends Controller {
// ... lines 4 - 66
#handleCheckout(lsCustomerId) {
}
// ... lines 69 - 80
}

Añadir una nueva ruta para crear la URL de pago

A continuación, tenemos que crear una ruta en nuestra aplicación que gestione y guarde el ID de cliente del usuario. Para ello, abre src/Controller/OrderController.phpy crea un nuevo método: public function handleCheckout(). Registra este#[Route] con una ruta - /checkout/handle y nómbraloapp_order_checkout_handle. Queremos que este método sólo funcione para peticiones a POST.

Esto necesita una petición y el usuario actual, así que inyecta Request $request y#[CurrentUser] User $user.

110 lines | src/Controller/OrderController.php
// ... lines 1 - 17
class OrderController extends AbstractController
{
// ... lines 20 - 79
#[Route('/checkout/handle', name: 'app_order_checkout_handle', methods: ['POST'])]
public function handleCheckout(
Request $request,
#[CurrentUser] User $user,
): Response {
}
// ... lines 87 - 108
}

Supondremos que el ID se pasará a través de una petición POST como lsCustomerId, así que recupéralo de la petición con $request->request->get('lsCustomerId'). A continuación, establécelo en el usuario con $user->setLsCustomerId($lsCustomerId).

111 lines | src/Controller/OrderController.php
// ... lines 1 - 80
public function handleCheckout(
// ... lines 82 - 83
): Response {
$lsCustomerId = $request->request->get('lsCustomerId');
$user->setLsCustomerId($lsCustomerId);
}
// ... lines 88 - 111

Para guardarlo realmente en la base de datos, también tenemos que inyectarEntityManagerInterface $entityManager y, al final, llamar a$entityManager->flush(). Termina con return $this->json([]). Aquí no necesitamos devolver datos reales: basta con una respuesta satisfactoria.

117 lines | src/Controller/OrderController.php
// ... lines 1 - 81
public function handleCheckout(
// ... line 83
EntityManagerInterface $entityManager,
// ... line 85
): Response {
// ... lines 87 - 89
$entityManager->flush();
return $this->json([]);
}
// ... lines 94 - 117

Actualización del controlador Stimulus

En el controlador Stimulus, añade un nuevo valor llamadocheckoutHandleUrl: String y pásale la URL de la plantilla. Para ello, entemplates/order/cart.html.twig, añadedata-lemon-squeezy-checkout-handle-url-value="" y pasa la URL con{{ path('app_order_checkout_handle') }}.

// ... lines 1 - 2
export default class extends Controller {
static values = {
// ... line 5
checkoutHandleUrl: String,
};
// ... lines 8 - 81
}

72 lines | templates/order/cart.html.twig
// ... lines 1 - 4
{% block content %}
// ... lines 6 - 47
<a class="w-[345px] flex ml-2 rounded-3xl border border-[#50272B] bg-[#4F272B] hover:bg-[#1C0000] shadow-inner poppins-bold text-white py-3 pl-5 uppercase"
// ... line 49
data-controller="lemon-squeezy"
// ... lines 51 - 52
data-lemon-squeezy-checkout-handle-url-value="{{ path('app_order_checkout_handle') }}"
>
Checkout with LemonSqueezy
// ... line 56
</a>
// ... lines 58 - 70
{% endblock %}

Con el valor establecido, de vuelta en el controlador, en #handleCheckout(), haz una llamada AJAX con fetch(), pasandothis.checkoutHandleUrlValue. Para las opciones, utiliza method: 'POST', y para las cabeceras,'Content-Type': 'application/x-www-form-urlencoded'. Esto nos permite obtener valores con $request->request->get() - sin necesidad de json_decode() la petición. Para las body, pasa new URLSearchParams() conlsCustomerId: lsCustomerId.

// ... lines 1 - 2
export default class extends Controller {
// ... lines 4 - 67
#handleCheckout(lsCustomerId) {
fetch(this.checkoutHandleUrlValue, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
lsCustomerId: lsCustomerId,
}),
})
// ... lines 78 - 90
}
// ... lines 92 - 103
}

Encadena esta llamada a fetch() con .then(). Dentro, espera response => {}. Si la respuesta no es correcta, lanza un nuevo Error() con el mensaje :

"Network response was not ok" + response.statusText.

Abajo, return response.json(). Esto nos dará el objeto JSON descodificado en el siguiente .then(). Acepta data => {}, y dentro, simplemente deja un comentario recordándonos que no hay nada que hacer aquí, porque no devolvemos ningún dato desde esa ruta. Pero, por si acaso algo va mal, encadena.catch() con console.error('Fetch error:', error).

// ... lines 1 - 67
#handleCheckout(lsCustomerId) {
fetch(this.checkoutHandleUrlValue, {
// ... lines 70 - 76
})
.then(response => {
if (!response.ok) {
throw new Error("Network response was not ok " + response.statusText);
}
return response.json();
})
.then(data => {
// Nothing to do
})
.catch(error => {
console.error('Fetch error:', error);
});
}
// ... lines 92 - 105

Probar y corregir errores

Esto tiene buena pinta, ¡así que vamos a probarlo! En nuestro sitio, añade un producto al carrito y abre la pestaña "Consola" en las Herramientas de desarrollo. Ups... un error.

Uncaught TypeError: No se pueden leer propiedades de undefined (leyendo 'Setup')

Parece que hemos empezado a utilizar LemonSqueezy más rápido de lo que se descargó su script. Hagamos un pequeño truco y envolvamos este código enscript.addEventListener(). Escucha el evento load, pasa una función como segundo argumento e inserta ahí nuestro código.

// ... lines 1 - 8
connect() {
// ... lines 10 - 18
script.addEventListener('load', () => {
window.LemonSqueezy.Setup({
eventHandler: (data) => {
if (data.event === 'Checkout.Success') {
// ... line 23
const lsCustomerId = data.data.customer_id;
this.#handleCheckout(lsCustomerId);
}
},
});
});
}
// ... lines 31 - 107

Si volvemos a actualizar la página... caramba... obtenemos el mismo error.

Vale, parece que primero deberíamos intentar instanciar LemonSqueezy manualmente. Antes de la línea problemática, escribe window.createLemonSqueezy(). Añade un pequeño comentario arriba para recordarnos en el futuro lo que estamos haciendo aquí.

// ... lines 1 - 8
connect() {
// ... lines 10 - 18
script.addEventListener('load', () => {
// The script has loaded, now you can safely call createLemonSqueezy()
window.createLemonSqueezy();
// ... lines 22 - 31
});
}
// ... lines 34 - 110

Actualiza de nuevo, y... ¡no hay errores! ¡Perfecto! Añadamos rápidamenteconsole.log(data) a nuestro código para que sepamos si damos con ese if enCheckout.Success.

// ... lines 1 - 8
connect() {
// ... lines 10 - 18
script.addEventListener('load', () => {
// ... lines 20 - 22
window.LemonSqueezy.Setup({
eventHandler: (data) => {
if (data.event === 'Checkout.Success') {
console.log(data);
// ... lines 27 - 28
}
},
});
});
}
// ... lines 34 - 110

Actualiza el sitio una vez más para que se carguen los cambios... y haz clic en "Pagar con LemonSqueezy". Rellena los datos de pago, la dirección de facturación... haz clic en "Pagar", y... ¡veremos el mensaje de éxito! Y en la consola... podemos ver los datos, así que nuestro código ha funcionado. Entonces... ¿funcionó?

En tu terminal, comprueba la base de datos con:

bin/console doctrine:query:sql "SELECT * FROM user"

¡No ha funcionado! Dice que el valor lsCustomerId es "indefinido". Hmmm, parece que estamos utilizando una ruta incorrecta para el ID de cliente. Si volvemos a comprobar nuestro volcado... sí. La ruta que nos dieron los documentos es incorrecta.

Cambia la ruta a data.data.order.data.attributes.customer_id, e inténtalo una vez más.

// ... lines 1 - 8
connect() {
// ... lines 10 - 18
script.addEventListener('load', () => {
// ... lines 20 - 22
window.LemonSqueezy.Setup({
eventHandler: (data) => {
if (data.event === 'Checkout.Success') {
// ... line 26
const lsCustomerId = data.data.order.data.attributes.customer_id;
// ... line 28
}
},
});
});
}
// ... lines 34 - 110

Actualiza la página, vuelve a pasar por el proceso de compra (lo haré más rápido para ahorrar tiempo), y... ¡éxito! Ahora, de vuelta en nuestro terminal, vuelve a ejecutar la consulta:

bin/console doctrine:query:sql "SELECT * FROM user"

Y... ¡Sí! ¡El ID de cliente se ha configurado correctamente! Ya no necesitamos ese console.log(), así que podemos borrarlo, junto con otro que se nos pasó en#openOverlay.

Así que, aunque no tengamos Ngrok en ejecución, podemos sincronizar el ID de cliente de LemonSqueezy con el usuario mediante eventos de JavaScript. Este enfoque simplifica un poco el desarrollo local, pero ambas formas son totalmente válidas.

A continuación: Vamos a abordar algunos posibles problemas de seguridad evitando el secuestro del ID de cliente.