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 ¶
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.
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.
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()etcustomer.getTierProgress()renvoientnull. Les méthodes marquées Customer dans la référence sont rejetées avec le code d’erreurauthentication_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 :
- 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.
- 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.
- 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:updatedavec 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.