Esta guía es para desarrolladores que crean páginas personalizadas en la tienda. Si solo quieres una página de fidelización ya preparada, usa el Shopify Loyalty Hub.
El Storefront SDK es una pequeña librería de JavaScript que la aplicación JeriCommerce añade a tu tema de Shopify. Da a tus propias páginas los mismos datos y acciones de fidelización que usa el Loyalty Hub: los puntos del cliente, su nivel y su progreso, las recompensas, los códigos de descuento, las formas de ganar puntos, el historial de puntos y la tarjeta de Wallet.
Tú escribes el HTML, los estilos y el diseño. El SDK se encarga del resto: inicia la sesión del cliente a través de Shopify, llama a la API de JeriCommerce, guarda en caché lo que lees, actualiza tus datos después de cada acción y avisa a tu página cuando algo cambia.
Cuándo usar el SDK ¶
- Quieres una página de fidelización con tu propio diseño, más allá de lo que permiten los ajustes del Loyalty Hub.
- Quieres mostrar datos de fidelización en otros lugares de tu tema, como la cabecera, el carrito o una página de producto.
- Quieres abrir el widget de JeriCommerce desde tus propios botones, o ocultar su botón flotante en algunas páginas.
Configúralo ¶
Activa el app embed del SDK
Ve a Shopify Admin > Online Store > Themes > Customize > App embeds y activa SDK de la aplicación JeriCommerce. Guarda el tema.
Este app embed carga el SDK en todas las páginas de tu tienda. Es el mismo embed que muestra el widget de la tienda. El widget es opcional: puedes desactivarlo en JeriCommerce y el SDK sigue funcionando.
Añade tu código al tema
Añade tu página como una sección del tema (por ejemplo sections/jeri-loyalty.liquid) y añade esa sección a una plantilla de página. La guía de la página de fidelización personalizada recorre cada paso con una plantilla completa.
Espera al SDK antes de usarlo
Ejecuta tu código como un script diferido (deferred), un módulo o después de DOMContentLoaded. Después comprueba que el SDK está instalado y espéralo con JeriCommerce.ready().
Cuando el app embed SDK está desactivado, window.JeriCommerce no existe. Comprueba siempre su existencia y muestra un mensaje breve en lugar de una página rota.
Inicio rápido ¶
<div data-jeri-points>Loading your points...</div>
<script type="module">
const box = document.querySelector("[data-jeri-points]");
if (!window.JeriCommerce) {
box.textContent = "Loyalty is not available right now.";
} else {
const jeri = await window.JeriCommerce.ready();
const session = await jeri.auth.getSession();
if (!session.authenticated) {
box.innerHTML = '<button type="button">Sign in to see your points</button>';
box.querySelector("button").addEventListener("click", () => jeri.auth.login());
} else {
const render = data => {
box.textContent = `You have ${data?.customer.loyaltyData?.balance ?? 0} points`;
};
render(await jeri.customer.get());
jeri.on("customer:updated", render);
}
}
</script>
JeriCommerce.ready() descarga el SDK la primera vez que lo llamas y siempre devuelve la misma instancia. No carga ningún dato hasta que llamas a un método.
Cómo funciona el inicio de sesión ¶
El SDK siempre actúa en nombre del cliente que ha iniciado sesión en tu tienda Shopify. Solo hay dos estados:
- Visitante:
auth.getSession()devuelve{ authenticated: false }. Los datos públicos funcionan (programa, niveles, recompensas públicas, formas de ganar puntos, boosts públicos).customer.get()ycustomer.getTierProgress()devuelvennull. Los métodos marcados como Customer en la referencia se rechazan con el código de errorauthentication_required. - Cliente con sesión iniciada:
{ authenticated: true }. Todo funciona para ese cliente.
El SDK nunca acepta un token, un email ni un id de cliente, así que una página nunca puede leer ni modificar los datos de otro cliente. Para pedir a un visitante que inicie sesión, llama a jeri.auth.login(). Lo envía al inicio de sesión de Shopify y de vuelta a la página actual, o a la página que pases como returnTo.
Qué ocurre después de una acción ¶
Cuando un cliente reclama una recompensa, completa una forma de ganar puntos o actualiza su perfil:
- La promesa se resuelve de inmediato con el resultado de esa acción, por ejemplo el código de descuento de una recompensa reclamada. Muéstralo al instante.
- El SDK marca los datos afectados como desactualizados, de modo que la siguiente lectura nunca devuelve los datos de antes de la acción.
- En segundo plano, vuelve a leer los datos que tu página ya había cargado y lanza los eventos correspondientes, como
customer:updatedcon el nuevo saldo.
Tu página nunca necesita llamar a refresh() después de una acción. Suscríbete a los eventos que muestras y vuelve a renderizarlos cuando se lancen. El historial de puntos, el enlace de referido y los enlaces del wallet no tienen evento. Para mantener el historial al día, vuelve a llamar a history.list() cuando se lance customer:updated.
Métodos ¶
Todos los métodos devuelven una promesa. Los métodos marcados como Customer necesitan un cliente con sesión iniciada y se rechazan con authentication_required para los visitantes. Los métodos marcados como Public funcionan para todos.
Sesión y programa
| Método | Acceso | Se resuelve con |
|---|---|---|
auth.getSession() |
Public | { authenticated: boolean } |
auth.login({ returnTo? }) |
Public | Envía al visitante al inicio de sesión de Shopify. returnTo es una ruta de tu tienda que empieza por /; si no, la llamada rechaza con invalid_argument. Por defecto, la página actual |
program.get() |
Public | Program |
config.get() |
Public | ProgramConfig. widget contiene los ajustes activos del widget |
Cliente
| Método | Acceso | Se resuelve con |
|---|---|---|
customer.get() |
Public | CustomerData, o null para un visitante |
customer.getTierProgress() |
Public | TierProgressStatus, o null para un visitante |
customer.updateProfile(fields) |
Customer | Customer. fields: { firstName?, lastName?, phone?, birthdate? } |
customer.updatePreferences(preferences) |
Customer | Customer. Consulta preferencias |
Niveles y recompensas
| Método | Acceso | Se resuelve con |
|---|---|---|
tiers.list() |
Public | Tier[] |
rewards.list({ scope: "public" }) |
Public | PublicRewardDto[], el catálogo para visitantes |
rewards.list({ scope: "customer" }) |
Customer | Reward[], con redeemable para este cliente |
rewards.claim({ rewardId, pointsToRedeem? }) |
Customer | ClaimedReward. pointsToRedeem, un número entero positivo, solo para recompensas de tipo dynamic |
Códigos de descuento y formas de ganar puntos
| Método | Acceso | Se resuelve con |
|---|---|---|
discounts.list({ page?, limit? }) |
Customer | PaginatedCustomerCouponCodes |
earnings.list() |
Public | EngagementFlowWithProgramEngagementFlowDto[] |
earnings.execute({ earningId, action: "flow" }) |
Customer | ExecuteCustomEarningFlowResponseDto |
earnings.execute({ earningId, action: "click", linkId }) |
Customer | Nada. Registra el clic en una red social de una forma de ganar Follow us |
Boosts, referidos, historial y wallet
| Método | Acceso | Se resuelve con |
|---|---|---|
boosts.list({ scope, window? }) |
Public o Customer | CustomerBoostsResponseDto. scope es "public" o "customer" |
referrals.getLink() |
Public | El enlace de referido del cliente, o null |
history.list({ page?, limit? }) |
Customer | PaginatedTransactions |
wallet.getLinks() |
Public | Enlaces { ios, android, generic } para añadir la tarjeta de fidelización a un wallet, o null |
wallet.sendEmail() |
Customer | Nada. Envía por email la tarjeta de Wallet al cliente |
Widget, refresh y eventos
| Método | Acceso | Se resuelve con |
|---|---|---|
widget.open({ page? }) |
Public | Abre el widget. page es una de las páginas del widget |
widget.close() |
Public | Cierra el widget |
widget.showLauncher() / widget.hideLauncher() |
Public | Muestra u oculta el botón del widget en esta vista de página |
refresh() |
Public | Vuelve a leer la sesión y todos los recursos que tu página ya había cargado |
on(event, callback) |
Public | Una función que elimina la suscripción |
Si omites page y limit, la API usa sus propios valores por defecto.
Datos que puedes mostrar ¶
Todos los resultados son los objetos de la API de JeriCommerce, sin modificar. Estos son los campos que más usarás.
CustomerData
Lo devuelve customer.get() y se envía con customer:updated.
| Campo | Tipo | Descripción |
|---|---|---|
customer.email |
string | Email del cliente |
customer.userData.firstName / lastName |
string | Nombre del perfil |
customer.loyaltyData.balance |
number | Puntos que el cliente puede gastar |
customer.loyaltyData.referralLink |
string | Enlace de referido (también disponible con referrals.getLink()) |
customer.customerTier.currentTier |
Tier o null |
Nivel VIP actual |
customer.customerTier.currentTierPoints |
number | Puntos que cuentan para los niveles |
customer.balanceNotifications, tierNotifications, locationNotifications, rewardAvailableNotifications, marketingNotifications |
boolean | Preferencias de notificaciones |
engagementFlows |
object | Progreso por cada forma de ganar puntos, indexado por tipo de acumulación (por ejemplo CUSTOM_1). count es el número de veces que el cliente la completó |
TierProgressStatus
Lo devuelve customer.getTierProgress(). Es el mismo cálculo que usa el Loyalty Hub.
| Campo | Tipo | Descripción |
|---|---|---|
currentTier |
Tier o null |
Nivel actual |
nextTier |
Tier o null |
Siguiente nivel, o null en el nivel más alto |
pointsToNextTier |
number | Puntos de nivel que faltan para llegar a nextTier |
progressValue |
number | Progreso a lo largo de todos los niveles, de 0 (primer nivel) a 100 (nivel más alto). Úsalo como porcentaje en una barra de progreso |
sortedTiers |
Tier[] |
Todos los niveles, del más bajo al más alto |
Tier
| Campo | Tipo | Descripción |
|---|---|---|
id |
string | Id del nivel |
name |
string | Nombre del nivel |
requiredPoints |
number | Puntos de nivel necesarios para alcanzarlo |
content |
string | Texto de beneficios definido en JeriCommerce (HTML). Sanitízalo antes de insertarlo con innerHTML |
image |
string o null |
Imagen del nivel |
Reward y PublicRewardDto
rewards.list({ scope: "customer" }) devuelve Reward, y rewards.list({ scope: "public" }) devuelve PublicRewardDto.
| Campo | Tipo | Descripción |
|---|---|---|
id |
string | Úsalo como rewardId para reclamar |
title, description, image |
string | Contenido de la recompensa |
type |
"fixed" o "dynamic" |
Fixed reward o Points as cash |
cost (customer) / redeemCost (public) |
number | Puntos que cuesta una recompensa fija |
minPoints, maxPoints, currencyPerPoint |
number o null |
Límites y valor de una recompensa Points as cash |
redeemable (customer) |
boolean | El cliente puede reclamarla ahora |
nonRedeemableReason (customer) |
string | Por qué no, por ejemplo NOT_ENOUGH_BALANCE |
ClaimedReward
Lo devuelve rewards.claim().
| Campo | Tipo | Descripción |
|---|---|---|
code |
string | Código de descuento que se muestra al cliente |
pointsRedeemed |
number, opcional | Puntos gastados |
discountValue |
number, opcional | Valor del descuento |
expiresAt |
string o null |
Fecha de caducidad |
PaginatedCustomerCouponCodes
Lo devuelve discounts.list(). data es la lista de códigos; count, total, page y pageCount describen la página. Cada código tiene value (el código), expiresAt, discountValue, claimed y coupon (el cupón al que pertenece).
Formas de ganar puntos
earnings.list() devuelve una entrada por cada tipo de acumulación. Cada entrada tiene name (por ejemplo FOLLOW_US, MEMBER_GET_MEMBER, CUSTOM_1), active y programEngagementFlows, la configuración del programa para ese tipo. Usa el primer elemento:
Campo de programEngagementFlows[0] |
Descripción |
|---|---|
id |
Úsalo como earningId en earnings.execute() |
active |
Muestra solo las formas de ganar activas |
title, description, buttonText, url |
Contenido definido en JeriCommerce |
displayIconUrl |
Icono |
properties.points |
Puntos que se otorgan |
properties.executeOnClick |
true cuando el cliente gana puntos al pulsar un botón: llama a earnings.execute({ earningId, action: "flow" }). No se aplica a FOLLOW_US, MEMBER_GET_MEMBER ni COMPLETE_USER_PROFILE, que rechazan con invalid_argument |
properties.followUs |
Redes sociales de una forma de ganar Follow us, indexadas por red (instagram, facebook, ...). Cada una tiene username y points |
properties.requiredFields |
Campos del perfil que necesita una forma de ganar Complete profile |
Un clic necesita la clave de la red como linkId, por ejemplo earnings.execute({ earningId, action: "click", linkId: "instagram" }). El SDK registra el enlace definido en JeriCommerce; nunca abre una ventana, así que abre tú mismo la página de la red social.
earnings.execute() con action: "flow" se resuelve con pointsAwarded, remainingExecutions y recurrence.
PaginatedTransactions
Lo devuelve history.list(). Cada elemento de data tiene name, amount, balanceChange, status y createdAt.
Boosts
boosts.list() se resuelve con { boosts }. Cada boost tiene name, startsAt, endsAt y isActive. El SDK actualiza la lista cuando un boost empieza o termina.
Actualizar preferencias ¶
customer.updatePreferences() necesita todos los indicadores de notificación, además de language si quieres cambiarlo:
const data = await jeri.customer.get();
await jeri.customer.updatePreferences({
balanceNotifications: data.customer.balanceNotifications,
tierNotifications: data.customer.tierNotifications,
locationNotifications: data.customer.locationNotifications,
rewardAvailableNotifications: data.customer.rewardAvailableNotifications,
marketingNotifications: true,
});
Las solicitudes siempre usan el idioma de la página. Guardar una preferencia de idioma actualiza los datos del cliente, pero no cambia el idioma de la página.
Eventos ¶
jeri.on(event, callback) devuelve una función que elimina la suscripción. Los eventos también se lanzan la primera vez que tu página lee esos datos, y on() no vuelve a enviar el último valor. Renderiza lo que devuelve cada lectura, como en el inicio rápido, y usa los eventos para los cambios posteriores.
| Evento | Cuándo se lanza | Qué recibe el callback |
|---|---|---|
session:changed |
Se conoce la sesión por primera vez, y cada vez que cambia | { authenticated } |
customer:updated |
Cambiaron los datos del cliente, por ejemplo el saldo tras reclamar una recompensa | CustomerData, o null para un visitante |
rewards:updated |
Se actualizó una lista de recompensas que leyó tu página | { scope, rewards }, con la misma lista que rewards.list({ scope }) |
discounts:updated |
Se actualizó una lista de códigos de descuento que leyó tu página | { query, discounts }, con el { page, limit } que leíste y un PaginatedCustomerCouponCodes |
earnings:updated |
Se actualizaron las formas de ganar puntos | La misma lista que earnings.list() |
boosts:updated |
Un boost empezó o terminó, o se actualizaron los boosts | { scope, boosts }, con el mismo valor que boosts.list({ scope }) |
Los eventos solo informan de cambios en los datos. El resultado de una acción es el valor con el que se resuelve su promesa.
Errores ¶
Toda llamada fallida se rechaza con un StorefrontSdkError. Comprueba su code, no el texto del mensaje:
| Código | Significado |
|---|---|
invalid_argument |
La llamada tiene un valor que falta o es incorrecto, por ejemplo un rewardId vacío |
authentication_required |
La llamada necesita un cliente con sesión iniciada, o la sesión del cliente caducó |
session_unavailable |
No se pudo cargar la sesión del cliente. Inténtalo más tarde; nunca se trata como un visitante |
api_error |
JeriCommerce rechazó la solicitud. status contiene el estado HTTP y serverCode el código de la API, como NOT_ENOUGH_POINTS |
network_error |
Una lectura no llegó a JeriCommerce. Una acción que no llega rechaza con mutation_unconfirmed |
widget_unavailable |
El widget está desactivado, o no se pudieron cargar sus ajustes o su código, así que no se puede abrir |
mutation_unconfirmed |
Puede que la acción no se haya aplicado, por ejemplo porque no llegó a JeriCommerce. Vuelve a leer los datos antes de reintentar |
El widget y el SDK ¶
El SDK puede controlar el widget de la tienda en la página actual: jeri.widget.open(), close(), showLauncher() y hideLauncher(). Estas llamadas solo afectan a la vista de la página; los ajustes guardados del widget no cambian. Cuando el widget está desactivado en JeriCommerce, widget.open() se rechaza con widget_unavailable y todo lo demás sigue funcionando.
Páginas del widget ¶
widget.open({ page }) acepta estas páginas:
page |
Abre |
|---|---|
advantages |
Beneficios del programa |
profile |
Perfil |
orders |
Pedidos |
coupons |
Códigos de descuento |
rewards |
Recompensas |
loyalty |
Formas de ganar puntos |
referrals |
Referidos |
Los visitantes siempre ven la página de beneficios del programa y de registro.
Limitaciones ¶
- El SDK solo está disponible en tiendas Shopify a través del app embed. No hay paquete de npm.
- El editor de temas Canvas de Shopify aún no admite app embeds, así que el SDK no está disponible en temas Canvas.
- El SDK no crea cuentas ni registra clientes. Los visitantes inician sesión a través de Shopify.
- Los puntos nunca se calculan en el navegador. Si la API todavía devuelve el saldo anterior, ese es el dato más reciente hasta la siguiente lectura.
Ejemplos ¶
Reclamar una recompensa y mostrar el código
try {
const claimed = await jeri.rewards.claim({ rewardId: reward.id });
showCode(claimed.code); // The balance updates through "customer:updated".
} catch (error) {
if (error.code === "api_error" && error.serverCode === "NOT_ENOUGH_POINTS") {
showMessage("You need more points for this reward.");
} else {
showMessage("Something went wrong. Please try again.");
}
}
Reclamar una recompensa de tipo Points as cash
await jeri.rewards.claim({ rewardId: reward.id, pointsToRedeem: 200 });
Ganar puntos con un botón
const result = await jeri.earnings.execute({ earningId: earning.id, action: "flow" });
showMessage(`+${result.pointsAwarded} points`);
Abrir el widget en la página de recompensas
document.querySelector("[data-open-rewards]").addEventListener("click", () => {
jeri.widget.open({ page: "rewards" }).catch(() => {
window.location.href = "/pages/loyalty"; // The widget is turned off.
});
});
Mantener actualizado el saldo en la cabecera
const badge = document.querySelector("[data-points-badge]");
const show = data => {
badge.hidden = !data;
if (data) badge.textContent = data.customer.loyaltyData.balance;
};
show(await jeri.customer.get());
jeri.on("customer:updated", show);
Una página completa creada con estos métodos está en la guía de la página de fidelización personalizada.