Ce guide s’adresse aux développeurs qui créent des pages de boutique sur mesure. Si vous voulez simplement une page de fidélité prête à l’emploi, utilisez plutôt le Shopify Loyalty Hub.

Le Storefront SDK est une petite bibliothèque JavaScript que l’application JeriCommerce ajoute à votre thème Shopify. Elle donne à vos propres pages les mêmes données et actions de fidélité que le Loyalty Hub : les points du client, son niveau et sa progression, les récompenses, les codes de réduction, les moyens de gagner des points, l’historique des points et la carte wallet.

Vous écrivez le HTML, les styles et la mise en page. Le SDK s’occupe du reste : il connecte le client via Shopify, appelle l’API JeriCommerce, met en cache ce que vous lisez, actualise vos données après chaque action et prévient votre page quand quelque chose change.

Quand utiliser le SDK

  • Vous voulez une page de fidélité avec votre propre design, au-delà de ce que permettent les réglages du Loyalty Hub.
  • Vous voulez afficher des données de fidélité à d’autres endroits de votre thème, comme l’en-tête, le panier ou une page produit.
  • Vous voulez ouvrir le widget JeriCommerce depuis vos propres boutons, ou masquer son lanceur sur certaines pages.

Mise en place

1

Activer l’app embed SDK

Allez dans Shopify Admin > Online Store > Themes > Customize > App embeds et activez SDK de l’application JeriCommerce. Enregistrez le thème.

Cet app embed charge le SDK sur chaque page de votre boutique. C’est le même embed qui affiche le widget Storefront. Le widget est facultatif : vous pouvez le désactiver dans JeriCommerce et le SDK continue de fonctionner.

2

Ajouter votre code au thème

Ajoutez votre page comme section de thème (par exemple sections/jeri-loyalty.liquid) et ajoutez cette section à un modèle de page. Le guide de la page de fidélité personnalisée détaille chaque étape avec un modèle complet.

3

Attendre le SDK avant de l’utiliser

Exécutez votre code comme script différé (deferred), comme module, ou après DOMContentLoaded. Vérifiez ensuite que le SDK est installé et attendez-le avec JeriCommerce.ready().

Quand l’app embed SDK est désactivé, window.JeriCommerce n’existe pas. Vérifiez toujours sa présence et affichez un court message plutôt qu’une page cassée.

Démarrage rapide

<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() télécharge le SDK au premier appel et renvoie toujours la même instance. Il ne charge aucune donnée tant que vous n’appelez pas une méthode.

Comment fonctionne la connexion

Le SDK agit toujours pour le client connecté à votre boutique Shopify. Il n’y a que deux états :

  • Visiteur : auth.getSession() renvoie { authenticated: false }. Les données publiques fonctionnent (programme, niveaux, récompenses publiques, moyens de gagner des points, boosts publics). customer.get() et customer.getTierProgress() renvoient null. Les méthodes marquées Customer dans la référence sont rejetées avec le code d’erreur authentication_required.
  • Client connecté : { authenticated: true }. Tout fonctionne pour ce client.

Le SDK n’accepte jamais de jeton, d’e-mail ni d’identifiant client : une page ne peut donc jamais lire ni modifier les données d’un autre client. Pour demander à un visiteur de se connecter, appelez jeri.auth.login(). Il est envoyé vers la connexion Shopify puis ramené à la page actuelle, ou à la page que vous passez dans returnTo.

Ce qui se passe après une action

Quand un client réclame une récompense, termine un moyen de gagner des points ou met à jour son profil :

  1. La promesse se résout tout de suite avec le résultat de l’action, par exemple le code de réduction d’une récompense réclamée. Affichez-le immédiatement.
  2. Le SDK marque les données concernées comme périmées, pour que la lecture suivante ne renvoie jamais les données d’avant l’action.
  3. En arrière-plan, il relit les données que votre page avait déjà chargées et déclenche les événements correspondants, comme customer:updated avec le nouveau solde.

Votre page n’a jamais besoin d’appeler refresh() après une action. Abonnez-vous aux événements que vous affichez et refaites le rendu quand ils se déclenchent. L’historique des points, le lien de parrainage et les liens du wallet n’ont pas d’événement. Pour garder l’historique à jour, appelez à nouveau history.list() quand customer:updated se déclenche.

Méthodes

Chaque méthode renvoie une promesse. Les méthodes marquées Customer nécessitent un client connecté et sont rejetées avec authentication_required pour les visiteurs. Les méthodes marquées Public fonctionnent pour tout le monde.

Session et programme
Méthode Accès Résultat
auth.getSession() Public { authenticated: boolean }
auth.login({ returnTo? }) Public Envoie le visiteur vers la connexion Shopify. returnTo est un chemin de votre boutique qui commence par /, sinon l’appel est rejeté avec invalid_argument. Par défaut, la page actuelle
program.get() Public Program
config.get() Public ProgramConfig. widget contient les réglages actifs du widget
Client
Méthode Accès Résultat
customer.get() Public CustomerData, ou null pour un visiteur
customer.getTierProgress() Public TierProgressStatus, ou null pour un visiteur
customer.updateProfile(fields) Customer Customer. fields : { firstName?, lastName?, phone?, birthdate? }
customer.updatePreferences(preferences) Customer Customer. Voir préférences
Niveaux et récompenses
Méthode Accès Résultat
tiers.list() Public Tier[]
rewards.list({ scope: "public" }) Public PublicRewardDto[], le catalogue pour les visiteurs
rewards.list({ scope: "customer" }) Customer Reward[], avec redeemable pour ce client
rewards.claim({ rewardId, pointsToRedeem? }) Customer ClaimedReward. pointsToRedeem, un nombre entier positif, uniquement pour les récompenses de type dynamic
Codes de réduction et moyens de gagner des points
Méthode Accès Résultat
discounts.list({ page?, limit? }) Customer PaginatedCustomerCouponCodes
earnings.list() Public EngagementFlowWithProgramEngagementFlowDto[]
earnings.execute({ earningId, action: "flow" }) Customer ExecuteCustomEarningFlowResponseDto
earnings.execute({ earningId, action: "click", linkId }) Customer Rien. Enregistre le clic sur un réseau social d’un gain Follow us
Boosts, parrainage, historique et wallet
Méthode Accès Résultat
boosts.list({ scope, window? }) Public ou Customer CustomerBoostsResponseDto. scope vaut "public" ou "customer"
referrals.getLink() Public Le lien de parrainage du client, ou null
history.list({ page?, limit? }) Customer PaginatedTransactions
wallet.getLinks() Public Liens { ios, android, generic } pour ajouter la carte de fidélité à un wallet, ou null
wallet.sendEmail() Customer Rien. Envoie la carte wallet au client par e-mail
Widget, refresh et événements
Méthode Accès Résultat
widget.open({ page? }) Public Ouvre le widget. page est l’une des pages du widget
widget.close() Public Ferme le widget
widget.showLauncher() / widget.hideLauncher() Public Affiche ou masque le bouton du widget pour cet affichage de page
refresh() Public Relit la session et toutes les ressources que votre page avait déjà chargées
on(event, callback) Public Une fonction qui supprime l’abonnement

Si vous omettez page et limit, l’API utilise ses propres valeurs par défaut.

Données à afficher

Tous les résultats sont les objets de l’API JeriCommerce, tels quels. Voici les champs que vous utiliserez le plus.

CustomerData

Renvoyé par customer.get() et envoyé avec customer:updated.

Champ Type Description
customer.email string E-mail du client
customer.userData.firstName / lastName string Nom du profil
customer.loyaltyData.balance number Points que le client peut dépenser
customer.loyaltyData.referralLink string Lien de parrainage (aussi disponible avec referrals.getLink())
customer.customerTier.currentTier Tier ou null Niveau VIP actuel
customer.customerTier.currentTierPoints number Points qui comptent pour les niveaux
customer.balanceNotifications, tierNotifications, locationNotifications, rewardAvailableNotifications, marketingNotifications boolean Préférences de notification
engagementFlows object Progression par moyen de gagner des points, indexée par type de gain (par exemple CUSTOM_1). count est le nombre de fois où le client l’a terminé
TierProgressStatus

Renvoyé par customer.getTierProgress(). C’est le même calcul que celui du Loyalty Hub.

Champ Type Description
currentTier Tier ou null Niveau actuel
nextTier Tier ou null Niveau suivant, ou null au niveau le plus élevé
pointsToNextTier number Points de niveau manquants pour atteindre nextTier
progressValue number Progression sur l’ensemble des niveaux, de 0 (premier niveau) à 100 (niveau le plus élevé). Utilisez-la comme pourcentage pour une barre de progression
sortedTiers Tier[] Tous les niveaux, du plus bas au plus haut
Tier
Champ Type Description
id string Identifiant du niveau
name string Nom du niveau
requiredPoints number Points de niveau nécessaires pour l’atteindre
content string Texte des avantages défini dans JeriCommerce (HTML). Nettoyez-le avant de l’insérer avec innerHTML
image string ou null Image du niveau
Reward et PublicRewardDto

rewards.list({ scope: "customer" }) renvoie Reward, et rewards.list({ scope: "public" }) renvoie PublicRewardDto.

Champ Type Description
id string Utilisez-le comme rewardId pour réclamer
title, description, image string Contenu de la récompense
type "fixed" ou "dynamic" Fixed reward ou Points as cash
cost (customer) / redeemCost (public) number Points que coûte une récompense fixe
minPoints, maxPoints, currencyPerPoint number ou null Limites et valeur d’une récompense Points as cash
redeemable (customer) boolean Le client peut la réclamer maintenant
nonRedeemableReason (customer) string Pourquoi non, par exemple NOT_ENOUGH_BALANCE
ClaimedReward

Renvoyé par rewards.claim().

Champ Type Description
code string Code de réduction à montrer au client
pointsRedeemed number, facultatif Points dépensés
discountValue number, facultatif Valeur de la réduction
expiresAt string ou null Date d’expiration
PaginatedCustomerCouponCodes

Renvoyé par discounts.list(). data est la liste des codes ; count, total, page et pageCount décrivent la page. Chaque code a value (le code), expiresAt, discountValue, claimed et coupon (le coupon auquel il appartient).

Moyens de gagner des points

earnings.list() renvoie une entrée par type de gain. Chaque entrée a name (par exemple FOLLOW_US, MEMBER_GET_MEMBER, CUSTOM_1), active, et programEngagementFlows, la configuration du programme pour ce type. Utilisez le premier élément :

Champ de programEngagementFlows[0] Description
id Utilisez-le comme earningId dans earnings.execute()
active N’affichez que les gains actifs
title, description, buttonText, url Contenu défini dans JeriCommerce
displayIconUrl Icône
properties.points Points attribués
properties.executeOnClick true quand le client gagne des points en appuyant sur un bouton : appelez earnings.execute({ earningId, action: "flow" }). Ne s’applique jamais à FOLLOW_US, MEMBER_GET_MEMBER ou COMPLETE_USER_PROFILE, qui sont rejetés avec invalid_argument
properties.followUs Réseaux sociaux d’un gain Follow us, indexés par réseau (instagram, facebook, ...). Chacun a username et points
properties.requiredFields Champs du profil nécessaires pour un gain Complete profile

Un clic nécessite la clé du réseau comme linkId, par exemple earnings.execute({ earningId, action: "click", linkId: "instagram" }). Le SDK enregistre le lien défini dans JeriCommerce ; il n’ouvre jamais de fenêtre, c’est donc à vous d’ouvrir la page du réseau social.

earnings.execute() avec action: "flow" se résout avec pointsAwarded, remainingExecutions et recurrence.

PaginatedTransactions

Renvoyé par history.list(). Chaque élément de data a name, amount, balanceChange, status et createdAt.

Boosts

boosts.list() se résout avec { boosts }. Chaque boost a name, startsAt, endsAt et isActive. Le SDK actualise la liste quand un boost commence ou se termine.

Mettre à jour les préférences

customer.updatePreferences() nécessite tous les indicateurs de notification, plus language si vous voulez la changer :

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,
});

Les requêtes utilisent toujours la langue de la page. Enregistrer une préférence de langue actualise les données du client mais ne change pas la langue de la page.

Événements

jeri.on(event, callback) renvoie une fonction qui supprime l’abonnement. Les événements se déclenchent aussi la première fois que votre page lit ces données, et on() ne renvoie pas la dernière valeur. Affichez ce que renvoie chaque lecture, comme dans le démarrage rapide, et utilisez les événements pour les changements suivants.

Événement Quand il se déclenche Ce que reçoit le callback
session:changed La session est connue pour la première fois, puis à chaque changement { authenticated }
customer:updated Les données du client ont changé, par exemple le solde après une réclamation CustomerData, ou null pour un visiteur
rewards:updated Une liste de récompenses lue par votre page a été actualisée { scope, rewards }, avec la même liste que rewards.list({ scope })
discounts:updated Une liste de codes de réduction lue par votre page a été actualisée { query, discounts }, avec le { page, limit } lu et un PaginatedCustomerCouponCodes
earnings:updated Les moyens de gagner des points ont été actualisés La même liste que earnings.list()
boosts:updated Un boost a commencé ou s’est terminé, ou les boosts ont été actualisés { scope, boosts }, avec la même valeur que boosts.list({ scope })

Les événements signalent uniquement des changements de données. Le résultat d’une action est la valeur avec laquelle sa promesse se résout.

Erreurs

Chaque appel en échec est rejeté avec une StorefrontSdkError. Vérifiez son code, pas le texte du message :

Code Signification
invalid_argument L’appel contient une valeur manquante ou incorrecte, par exemple un rewardId vide
authentication_required L’appel nécessite un client connecté, ou la session du client a expiré
session_unavailable La session du client n’a pas pu être chargée. Réessayez plus tard : elle n’est jamais traitée comme un visiteur
api_error JeriCommerce a refusé la requête. status contient le statut HTTP et serverCode le code de l’API, comme NOT_ENOUGH_POINTS
network_error Une lecture n’a pas atteint JeriCommerce. Une action qui ne l’atteint pas est rejetée avec mutation_unconfirmed
widget_unavailable Le widget est désactivé, ou ses paramètres ou son code n’ont pas pu se charger, il ne peut donc pas s’ouvrir
mutation_unconfirmed L’action n’a peut-être pas été appliquée, par exemple parce qu’elle n’a pas atteint JeriCommerce. Relisez les données avant de réessayer

Le widget et le SDK

Le SDK peut contrôler le widget Storefront sur la page actuelle : jeri.widget.open(), close(), showLauncher() et hideLauncher(). Ces appels n’affectent que l’affichage de la page ; les réglages enregistrés du widget ne changent pas. Quand le widget est désactivé dans JeriCommerce, widget.open() est rejeté avec widget_unavailable et tout le reste continue de fonctionner.

Pages du widget

widget.open({ page }) accepte ces pages :

page Ouvre
advantages Avantages du programme
profile Profil
orders Commandes
coupons Codes de réduction
rewards Récompenses
loyalty Moyens de gagner des points
referrals Parrainages

Les visiteurs voient toujours la page des avantages du programme et d’inscription.

Limites

  • Le SDK n’est disponible que sur les boutiques Shopify, via l’app embed. Il n’existe pas de package npm.
  • L’éditeur de thème Canvas de Shopify ne prend pas encore en charge les app embeds : le SDK n’est donc pas disponible sur les thèmes Canvas.
  • Le SDK ne crée pas de comptes et n’inscrit pas les clients. Les visiteurs se connectent via Shopify.
  • Les points ne sont jamais calculés dans le navigateur. Si l’API renvoie encore l’ancien solde, c’est la dernière donnée disponible jusqu’à la prochaine lecture.

Exemples

Réclamer une récompense et afficher le code
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.");
  }
}
Réclamer une récompense Points as cash
await jeri.rewards.claim({ rewardId: reward.id, pointsToRedeem: 200 });
Gagner des points avec un bouton
const result = await jeri.earnings.execute({ earningId: earning.id, action: "flow" });
showMessage(`+${result.pointsAwarded} points`);
Ouvrir le widget sur la page des récompenses
document.querySelector("[data-open-rewards]").addEventListener("click", () => {
  jeri.widget.open({ page: "rewards" }).catch(() => {
    window.location.href = "/pages/loyalty"; // The widget is turned off.
  });
});
Garder le solde de l’en-tête à jour
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);

Une page complète construite avec ces méthodes se trouve dans le guide de la page de fidélité personnalisée.