Superposición de pago de LemonSqueezy
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeLos clientes pueden comprar productos en nuestro sitio web, pero para finalizar la compra, les hemos estado redirigiendo al sitio de LemonSqueezy, que está alojado en un dominio completamente distinto. Utilicemos las herramientas JavaScript de LemonSqueezy para mejorar este flujo de trabajo
En lugar de redirigir a los clientes a la página de pago de LemonSqueezy, podemos presentar esa información en una "superposición de pago", un elegante iFrame que vive directamente en nuestro sitio. Así que pongámonos manos a la obra y salpiquemos nuestro sitio web con un poco de magia JavaScript.
Añadir JavaScript de LemonSqueezy a la página del carrito
En primer lugar, tenemos que añadir la herramienta JavaScript de LemonSqueezy - lemon.js
- a nuestra página del carrito. Abre templates/order/cart.html.twig
y añade un nuevo bloque. Llámalojavascripts
... y ciérralo con endblock
. Dentro, añade una etiqueta script
, y establece src
en https://app.lemonsqueezy.com/js/lemon.js
. Añade también el atributodefer
.
// ... lines 1 - 4 | |
{% block javascripts %} | |
// ... line 6 | |
<script src="https://app.lemonsqueezy.com/js/lemon.js" defer></script> | |
{% endblock %} | |
// ... lines 9 - 73 |
LemonSqueezy aconseja no autoalojar el archivo lemon.js
, ya que podrías perderte nuevas funciones y parches de seguridad cruciales. Asegúrate de enlazarlo directamente, para mantener los asuntos relacionados con el pago lo más seguros posible.
También tenemos que llamar a la función {{ parent() }}
dentro de javascripts
para evitar anular completamente este bloque. Genial.
// ... lines 1 - 4 | |
{% block javascripts %} | |
{{ parent() }} | |
// ... line 7 | |
{% endblock %} | |
// ... lines 9 - 73 |
A continuación, añade una clase CSS única al enlace de pago: lemonsqueezy-button
. Cuando nos dirigimos y actualizamos la página del carrito, es sutil, pero te darás cuenta de que ahora estamos cargando la página de pago de LemonSqueezy bajo nuestra URL. Si inspeccionaras el código fuente, verías que LemonSqueezy está sustituyendo toda la página por su propio contenido. Eso es genial, pero podemos hacerlo aún mejor.
Crear un controlador especial de Stimulus
Elimina la clase lemonsqueezy-button
que añadimos antes, y cámbiala por algo un poco más flexible. En assets/controllers/
, crea un nuevo controlador llamado lemon-squeezy_controller.js
.
Dentro, añade import { Controller } from '@hotwired/stimulus'
, y debajo,export default class extends Controller
. Dentro de la clase, añade dos métodos:connect()
y openOverlay()
.
import { Controller } from '@hotwired/stimulus'; | |
export default class extends Controller { | |
connect() { | |
} | |
openOverlay() { | |
} | |
} |
Ahora, vamos a conectar este controlador en cart.html.twig
. Añade una nueva línea al enlace de pago y establece data-controller="lemon-squeezy"
. Esto conecta este enlace a nuestro controlador Stimulus. Debajo, añade data-action="lemon-squeezy#openOverlay"
, que indica a Stimulus que llame al método openOverlay()
cuando se haga clic en el enlace.
// ... lines 1 - 9 | |
{% block content %} | |
// ... lines 11 - 12 | |
{% if not cart().empty %} | |
// ... lines 14 - 52 | |
<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 54 | |
data-controller="lemon-squeezy" | |
data-action="lemon-squeezy#openOverlay" | |
> | |
// ... lines 58 - 59 | |
</a> | |
// ... lines 61 - 70 | |
{% endif %} | |
// ... lines 72 - 73 | |
{% endblock %} |
También necesitamos pasar la URL del Pedido LemonSqueezy, pero en lugar de generarla cada vez que se carga la página del carrito, vamos a generarla sólo cuando se haga clic en el enlace.
Añadir una nueva acción al OrderController
Necesitamos una nueva acción en OrderController
. Ve asrc/Controller/OrderController.php
y, justo antes del método success()
, añade otro: public function createCheckout()
.
Esto devolverá un Response
. Encima, añade el atributo #[Route]
con una ruta - /checkout/create
. Nómbralo app_order_checkout_create
y sólo permite los métodos POST
.
Para las dependencias, inyecta LemonSqueezyApi $lsApi
, así como el usuario actual con #[CurrentUser] User $user
.
Dentro, return $this->json()
con un array: ['targetUrl' => $lsApi->createCheckoutUrl($user)]
.
// ... lines 1 - 17 | |
class OrderController extends AbstractController | |
{ | |
// ... lines 20 - 69 | |
'/checkout/create', name: 'app_order_checkout_create', methods: ['POST']) | (|
public function createCheckout( | |
LemonSqueezyApi $lsApi, | |
#[CurrentUser] User $user, | |
): Response { | |
return $this->json([ | |
'targetUrl' => $lsApi->createCheckoutUrl($user), | |
]); | |
} | |
// ... lines 79 - 100 | |
} |
De vuelta en nuestro controlador lemon-squeezy
Stimulus, registra un nuevo valor. Escribestatic values = {}
, y dentro, checkoutCreateUrl: String
.
// ... lines 1 - 2 | |
export default class extends Controller { | |
static values = { | |
checkoutCreateUrl: String, | |
}; | |
// ... lines 7 - 13 | |
} |
En la plantilla del carrito, añade un nuevo atributo de valor de datos -data-lemon-squeezy-checkout-create-url-value
- y pasa{{ path('app_order_checkout_create') }}
.
// ... lines 1 - 9 | |
{% block content %} | |
// ... lines 11 - 12 | |
{% if not cart().empty %} | |
// ... lines 14 - 52 | |
<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" | |
// ... lines 54 - 56 | |
data-lemon-squeezy-checkout-create-url-value="{{ path('app_order_checkout_create') }}" | |
> | |
// ... lines 59 - 60 | |
</a> | |
// ... lines 62 - 71 | |
{% endif %} | |
// ... lines 73 - 74 | |
{% endblock %} |
Dejaré href
tal cual, para que, si por alguna razón un usuario no tiene JS activado (¿sigue existiendo eso?), pueda realizar la compra. De vuelta a nuestro métodoopenOverlay()
, añade e
como parámetro, y luego llama a e.preventDefault()
para impedir que los navegadores con JS habilitado sigan el enlace.
// ... lines 1 - 2 | |
export default class extends Controller { | |
// ... lines 4 - 10 | |
openOverlay(e) { | |
e.preventDefault(); | |
} | |
} |
Para el resto de este método, coge el elemento enlace con const linkEl = e.currentTarget
. A continuación, necesitamos ejecutar una petición AJAX al checkoutCreateUrl
que pasamos como valor. Para ello, utiliza la función fetch()
para this.checkoutCreateUrlValue
. Para las opciones, añade method: 'POST'
, y headers: {'Content-Type': 'application/json'}
.
// ... lines 1 - 10 | |
openOverlay(e) { | |
// ... lines 12 - 13 | |
const linkEl = e.currentTarget; | |
fetch(this.checkoutCreateUrlValue, { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
}, | |
}) | |
// ... lines 22 - 34 | |
} | |
} |
A continuación, encadena esta llamada a fetch()
con .then()
. Dentro, espera unresponse
, y añade una comprobación de sanidad - if (!response.ok)
,throw new Error()
con Network response was not OK
, seguido de response.statusText
.
Si no, return response.json()
. Eso debería pasar los datos JSON como un objeto al siguiente .then()
, donde esperamos data
.
// ... lines 1 - 10 | |
openOverlay(e) { | |
// ... lines 12 - 15 | |
fetch(this.checkoutCreateUrlValue, { | |
// ... lines 17 - 20 | |
}) | |
.then(response => { | |
if (!response.ok) { | |
throw new Error("Network response was not ok " + response.statusText); | |
} | |
return response.json(); | |
}) | |
// ... lines 29 - 34 | |
} | |
// ... lines 36 - 37 |
Vamos a pedir a LemonSqueezy que abra esta URL, así que llama awindow.LemonSqueezy.Url.Open()
y pásale data.targetUrl
, que devolvimos de la acción createCheckout()
.
// ... lines 1 - 10 | |
openOverlay(e) { | |
// ... lines 12 - 15 | |
fetch(this.checkoutCreateUrlValue, { | |
// ... lines 17 - 20 | |
}) | |
// ... lines 22 - 28 | |
.then(data => { | |
window.LemonSqueezy.Url.Open(data.targetUrl); | |
}) | |
// ... lines 32 - 34 | |
} | |
// ... lines 36 - 37 |
Por último, añade un catch()
, esperando un error
. Dentro, escribe console.error()
con un mensaje Fetch error:
, pasando error
como segundo argumento.
// ... lines 1 - 10 | |
openOverlay(e) { | |
// ... lines 12 - 15 | |
fetch(this.checkoutCreateUrlValue, { | |
// ... lines 17 - 20 | |
}) | |
// ... lines 22 - 31 | |
.catch(error => { | |
console.error('Fetch error:', error); | |
}); | |
} | |
// ... lines 36 - 37 |
Vale, esto tiene buena pinta, así que vamos a probarlo. Abre nuestro sitio, y abre también las Herramientas del desarrollador en la pestaña Consola para ver los registros de JavaScript. Recarga la página y... ¡aquí está nuestro controlador lemon-squeezy
!
Si hacemos clic en el botón "Pago con LemonSqueezy", se carga y... ¡se abre la página de pago de LemonSqueezy bajo nuestro dominio! ¡Sigue funcionando!
De nuevo, esto es sutil, así que lo siguiente: hagámoslo aún mejor mostrando la página de pago sobre la página de nuestro carrito, en un modal.