Saltar al contenido

Cuándo una Shopify Function es la herramienta adecuada

Una Function se ejecuta dentro del checkout de Shopify: ahí están su fuerza y su límite. Qué puede hacer cada target, qué exige Plus de verdad y cuándo conviene otra solución.

Guía17 de junio de 20268 min de lectura

Una Shopify Function es un módulo pequeño de WebAssembly que le entregas a Shopify. Shopify lo ejecuta en su propia infraestructura en el momento exacto en que calcula el precio de un carrito, valida un checkout o arma la lista de opciones de entrega que ve el comprador. Tu servidor no participa. Salvo una excepción estrecha —un fetch target, limitado a apps a medida en tiendas Enterprise con acceso de red concedido— no hay llamada HTTP, no hay presupuesto de latencia, y ninguna caída tuya puede detener una compra.

Esa sola propiedad — corre donde se toma la decisión — es toda la razón para usar una. Y es también la razón por la que una Function es la respuesta equivocada más seguido de lo que la gente supone. El lugar donde puedes cambiar el precio es un lugar donde no puedes consultar tu base de datos, no puedes leer el reloj, y tienes once millones de instrucciones para decidirte.

Lo que sigue es la decisión que conviene tener escrita antes de abrir una terminal: de qué está hecha una Function, qué puede cada target, cuáles exigen Plus de verdad, y cuándo conviene una app normal o un cambio de tema.

De qué está hecha una Function

Tres partes, y ser exacto con ellas explica casi todos los límites con los que uno se topa después.

Un input query. Un documento GraphQL que tú escribes y que declara los datos que tu lógica necesita: líneas del carrito, cantidades, los tags del cliente, si un producto pertenece a cierta colección, un metafield con la configuración. Shopify lo ejecuta y te entrega el resultado como un objeto plano. No hay una segunda vuelta. Lo que el input query no pidió, para tu código no existe.

Un run target. Un punto de entrada, una entrada, un valor de retorno. Rust compilado a WebAssembly es el camino mejor soportado y al que solemos ir; JavaScript funciona y es más fácil de contratar. Esa elección afecta al tooling de build, no a lo que la Function tiene permitido hacer.

Una lista de operaciones. Tu Function no muta nada. Devuelve operaciones — expande esta línea, oculta ese método de pago, agrega este descuento — y Shopify las aplica o las rechaza por inválidas. Es una función pura de carrito a intención, y por eso resulta tan cómoda de probar.

Los límites de recursos son públicos y muerden. Para carritos de hasta doscientas líneas: once millones de instrucciones por ejecución, 128 kB de entrada, 20 kB de salida. Para todas las Functions: 256 kB de binario compilado, 10,000 kB de memoria lineal y un kilobyte de logs antes de truncar. Shopify además prohíbe el no determinismo de plano: nada de aleatoriedad, y ningún reloj que puedas leer. El tiempo no está cerrado del todo: varias Function APIs exponen un objeto localTime cuyos predicados timeBefore, timeAfter, timeBetween y dateTimeBetween responden preguntas sobre el momento actual sin entregarte nunca una marca de tiempo. Así que "envío gratis los viernes" es expresable; "sella el pedido con la hora en que se calculó" no.

Desliza la figura para verla completa

Los targets, y qué puede hacer cada uno

Descuentos

La Discount Function API se reparte en dos run targets. El de líneas de carrito devuelve operaciones para las clases PRODUCT y ORDER; el de opciones de entrega devuelve la clase SHIPPING. Qué targets se ejecutan lo controla el campo discountClasses del descuento que creó el comerciante, así que una Function que devuelve descuentos de producto para un descuento configurado solo como shipping no hará nada, y en silencio. Es lo primero que hay que revisar cuando un descuento "no aplica".

Es el target para todo lo que los tipos nativos no expresan: umbrales escalonados, apilamiento condicional, precio según un tag de cliente, compra-X-lleva-Y con lista de exclusiones.

Cart Transform

Tres operaciones, y las diferencias importan más de lo que sugieren los nombres. lineExpand convierte una línea del carrito en varias: así se añade una línea de cargo, un envoltorio de regalo, o los componentes de un bundle. linesMerge colapsa varias líneas en una variante padre, la misma idea en la dirección contraria. lineUpdate cambia una línea en su lugar.

Cart Transform es donde vive un cargo añadido, y el target de nuestra tarifa de importación configurable con exclusiones de producto y de tarjeta de regalo. También es de donde sale el bug de cantidad de más abajo.

Personalización de envío y de pago

Estas ocultan, reordenan y renombran opciones que se le ofrecen al comprador. No crean opciones y no las cotizan: una delivery customization no puede inventar una tarifa de envío que tus ajustes de envío no produzcan ya. La de pago tiene bordes duros que conviene conocer antes de prometer nada: no puedes renombrar un método cuyo nombre es un logo, como Shop Pay o Apple Pay, y estas Functions no corren en Point of Sale. Crear opciones en vez de filtrarlas es otro target distinto: los generadores de pickup local y de puntos de recogida.

Validación de carrito y checkout

La que se niega. Una Function de validación bloquea el avance cuando el carrito no cumple una regla: regiones restringidas, mínimos de pedido, combinaciones incompatibles, un límite de compra por cliente. Como corre en los servidores de Shopify y no en el navegador del comprador, no la puede saltar quien abra devtools o le pegue directo a la API del carrito. Ese es el punto entero: si una regla importa comercialmente, va aquí y no en el tema.

Desliza la figura para verla completa

Qué exige Plus de verdad

El resumen habitual — "las Functions son solo para Plus" — está mal de una forma que le cuesta a la gente decisiones reales de alcance. La regla precisa es sobre cómo se entrega la Function, no sobre lo que hace.

Una tienda en cualquier plan puede instalar una app pública del Shopify App Store que contenga Functions, y esas Functions corren. Solo las tiendas en Shopify Plus pueden instalar una app custom que contenga Function APIs. Así que la pregunta del plan es en realidad una pregunta de distribución. Construir para un solo comerciante sobre una app custom significa que necesita Plus. Construir algo que vas a publicar significa que su plan deja de ser la restricción y la revisión del App Store empieza a serlo.

Encima hay compuertas por capacidad. La operación lineUpdate de Cart Transform se rechaza si la tienda no está en Plus o en una tienda de desarrollo. Las Checkout UI extensions en los pasos de información, envío y pago son solo de Plus, lo cual importa en cuanto una Function necesita una interfaz que la acompañe. Revisa el target específico contra el plan específico antes de cotizar, no después: son cinco minutos que evitan una cotización que no puedes cumplir.

La trampa de por unidad contra por línea

Es la forma más común en que este trabajo llega roto a un cliente, y vale la pena entenderla aunque nunca escribas una línea de Rust.

Una línea de carrito no es un artículo. Es una referencia de mercancía más una cantidad. Cuando un Cart Transform expande una línea, el precio que entregas es fixedPricePerUnit: por unidad, no por línea. Mientras tanto el input te da cost.amountPerQuantity, que también es por unidad, y quantity justo al lado.

Así que quien implementa "agrega un cargo de manejo de doce dólares" tiene cuatro arreglos plausibles de los mismos tres números, y tres están mal. Dos de esos tres se ven perfectamente correctos en una tienda de desarrollo, porque quien prueba agrega uno de cada producto. El bug aparece cuando un cliente real compra once.

El arreglo no es astucia. Es escribir la regla en prosa — "doce dólares por unidad, solo sobre unidades elegibles" — antes de escribir el query, y después probar con una cantidad que no sea uno. La versión larga está en construir un cart transform que cuenta bien.

¿Function, app o cambio de tema?

Tres preguntas, en orden.

¿La regla tiene que sostenerse en el checkout, incluso ante un comprador que intenta esquivarla? Si sí, es una Function. El JavaScript del tema y la lógica de la página de carrito son consultivos: se pueden saltar, y una regla que se puede saltar no es una regla. Esta pregunta resuelve sola la mayoría de los casos.

¿La lógica necesita algo que Shopify no puede entregarle en el input? Un dato de inventario en vivo desde tu ERP, un score de fraude, un límite de crédito que vive en un sistema externo. Las Functions no tienen acceso general a red, así que la respuesta es una app que sincronice ese valor a un metafield de forma programada, y después, si la regla hay que hacerla cumplir, una Function que lea el metafield. Dos piezas, no una.

¿Es presentación? Un distintivo, una explicación, un selector de fecha de entrega, un mensaje sobre por qué un artículo queda excluido. Eso es un cambio de tema o una Checkout UI extension. Una Function no dibuja nada, nunca. Decide, y el comprador solo ve el resultado.

El modo de fallo honesto aquí es ir por una Function porque es lo interesante de construir. Buena parte de las peticiones etiquetadas "necesitamos una Function" son un descuento que los tipos nativos ya cubren, o un mensaje que va en la página de producto.

Por dónde empezar

Escribe la regla como prosa que un comerciante firmaría, incluyendo qué excluye y qué pasa con cantidad dos. Después elige el target. Después revisa el plan contra ese target. Después constrúyelo, con la configuración en metafields para que los números cambien sin desplegar.

En ese orden esto es una pieza de software pequeña, fácil de probar y duradera. Fuera de ese orden es un cargo que le cobró mal a once clientes antes de que alguien lo notara. Nuestra página de desarrollo de Shopify Functions describe la secuencia completa.

Blog