Diese Anleitung richtet sich an Entwickler, die eigene Storefront-Seiten bauen. Wenn du nur eine fertige Loyalty-Seite möchtest, nutze stattdessen den Shopify Loyalty Hub.
Das Storefront SDK ist eine kleine JavaScript-Bibliothek, die die JeriCommerce-App zu deinem Shopify-Theme hinzufügt. Es gibt deinen eigenen Seiten dieselben Loyalty-Daten und Aktionen wie dem Loyalty Hub: Punkte, Stufe und Fortschritt des Kunden, Belohnungen, Rabattcodes, Sammelmöglichkeiten, Punkteverlauf und Wallet-Karte.
Du schreibst HTML, Styles und Layout. Den Rest übernimmt das SDK: Es meldet den Kunden über Shopify an, ruft die JeriCommerce-API auf, speichert gelesene Daten im Cache, aktualisiert deine Daten nach jeder Aktion und meldet deiner Seite, wenn sich etwas ändert.
Wann das SDK sinnvoll ist ¶
- Du möchtest eine Loyalty-Seite mit eigenem Design, die über die Einstellungen des Loyalty Hub hinausgeht.
- Du möchtest Loyalty-Daten an anderen Stellen deines Themes anzeigen, zum Beispiel im Header, im Warenkorb oder auf einer Produktseite.
- Du möchtest das JeriCommerce-Widget über eigene Buttons öffnen oder seinen Launcher auf manchen Seiten ausblenden.
Einrichten ¶
SDK-App-Embed aktivieren
Gehe zu Shopify Admin → Online Store → Themes → Customize → App embeds und aktiviere SDK der JeriCommerce-App. Speichere das Theme.
Dieses App-Embed lädt das SDK auf jeder Seite deines Shops. Es ist dasselbe Embed, das das Storefront-Widget anzeigt. Das Widget ist optional: Du kannst es in JeriCommerce ausschalten, und das SDK funktioniert weiter.
Code zum Theme hinzufügen
Füge deine Seite als Theme-Section hinzu (zum Beispiel sections/jeri-loyalty.liquid) und füge diese Section zu einem Seitentemplate hinzu. Die Anleitung zur eigenen Loyalty-Seite führt dich Schritt für Schritt mit einem vollständigen Template durch.
Auf das SDK warten, bevor du es nutzt
Führe deinen Code als verzögertes Script, als Modul oder nach DOMContentLoaded aus. Prüfe dann, ob das SDK installiert ist, und warte mit JeriCommerce.ready() darauf.
Wenn das App-Embed SDK ausgeschaltet ist, existiert window.JeriCommerce nicht. Prüfe das immer und zeige eine kurze Meldung statt einer kaputten Seite.
Schnellstart ¶
<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() lädt das SDK beim ersten Aufruf herunter und liefert immer dieselbe Instanz zurück. Es lädt keine Daten, bevor du eine Methode aufrufst.
So funktioniert die Anmeldung ¶
Das SDK handelt immer für den Kunden, der in deinem Shopify-Shop angemeldet ist. Es gibt nur zwei Zustände:
- Besucher:
auth.getSession()liefert{ authenticated: false }. Öffentliche Daten funktionieren (Programm, Stufen, öffentliche Belohnungen, Sammelmöglichkeiten, öffentliche Boosts).customer.get()undcustomer.getTierProgress()liefernnull. Die Methoden, die in der Referenz mit Customer markiert sind, werden mit dem Fehlercodeauthentication_requiredabgelehnt. - Angemeldeter Kunde:
{ authenticated: true }. Alles funktioniert für diesen Kunden.
Das SDK akzeptiert nie ein Token, eine E-Mail-Adresse oder eine Kunden-ID, daher kann eine Seite nie die Daten eines anderen Kunden lesen oder ändern. Um einen Besucher zur Anmeldung aufzufordern, rufe jeri.auth.login() auf. Das leitet ihn zum Shopify-Login und danach zurück zur aktuellen Seite oder zu der Seite, die du als returnTo übergibst.
Was nach einer Aktion passiert ¶
Wenn ein Kunde eine Belohnung einlöst, eine Sammelmöglichkeit abschließt oder sein Profil aktualisiert:
- Das Promise wird sofort mit dem Ergebnis dieser Aktion aufgelöst, zum Beispiel mit dem Rabattcode einer eingelösten Belohnung. Zeige ihn direkt an.
- Das SDK markiert die betroffenen Daten als veraltet, damit der nächste Lesezugriff nie die Daten von vor der Aktion liefert.
- Im Hintergrund liest es die Daten, die deine Seite bereits geladen hat, erneut und löst die passenden Events aus, zum Beispiel
customer:updatedmit dem neuen Kontostand.
Deine Seite muss nach einer Aktion nie refresh() aufrufen. Abonniere die Events der Daten, die du anzeigst, und rendere sie neu, wenn sie ausgelöst werden. Der Punkteverlauf, der Empfehlungslink und die Wallet-Links haben kein Event. Damit der Verlauf aktuell bleibt, rufe history.list() erneut auf, wenn customer:updated ausgelöst wird.
Methoden ¶
Jede Methode gibt ein Promise zurück. Methoden mit der Markierung Customer benötigen einen angemeldeten Kunden und werden für Besucher mit authentication_required abgelehnt. Methoden mit der Markierung Public funktionieren für alle.
Sitzung und Programm
| Methode | Zugriff | Liefert |
|---|---|---|
auth.getSession() |
Public | { authenticated: boolean } |
auth.login({ returnTo? }) |
Public | Leitet den Besucher zum Shopify-Login. returnTo ist ein Pfad deines Shops, der mit / beginnt, sonst schlägt der Aufruf mit invalid_argument fehl. Standardmäßig die aktuelle Seite |
program.get() |
Public | Program |
config.get() |
Public | ProgramConfig. widget enthält die aktiven Widget-Einstellungen |
Kunde
| Methode | Zugriff | Liefert |
|---|---|---|
customer.get() |
Public | CustomerData, oder null für einen Besucher |
customer.getTierProgress() |
Public | TierProgressStatus, oder null für einen Besucher |
customer.updateProfile(fields) |
Customer | Customer. fields: { firstName?, lastName?, phone?, birthdate? } |
customer.updatePreferences(preferences) |
Customer | Customer. Siehe Einstellungen |
Stufen und Belohnungen
| Methode | Zugriff | Liefert |
|---|---|---|
tiers.list() |
Public | Tier[] |
rewards.list({ scope: "public" }) |
Public | PublicRewardDto[], der Katalog für Besucher |
rewards.list({ scope: "customer" }) |
Customer | Reward[], mit redeemable für diesen Kunden |
rewards.claim({ rewardId, pointsToRedeem? }) |
Customer | ClaimedReward. pointsToRedeem, eine positive ganze Zahl, nur für Belohnungen vom Typ dynamic |
Rabattcodes und Sammelmöglichkeiten
| Methode | Zugriff | Liefert |
|---|---|---|
discounts.list({ page?, limit? }) |
Customer | PaginatedCustomerCouponCodes |
earnings.list() |
Public | EngagementFlowWithProgramEngagementFlowDto[] |
earnings.execute({ earningId, action: "flow" }) |
Customer | ExecuteCustomEarningFlowResponseDto |
earnings.execute({ earningId, action: "click", linkId }) |
Customer | Nichts. Zeichnet den Klick auf ein soziales Netzwerk bei einer Follow us-Sammelmöglichkeit auf |
Boosts, Empfehlungen, Verlauf und Wallet
| Methode | Zugriff | Liefert |
|---|---|---|
boosts.list({ scope, window? }) |
Public oder Customer | CustomerBoostsResponseDto. scope ist "public" oder "customer" |
referrals.getLink() |
Public | Der Empfehlungslink des Kunden, oder null |
history.list({ page?, limit? }) |
Customer | PaginatedTransactions |
wallet.getLinks() |
Public | { ios, android, generic } Links, um die Loyalty-Karte zu einer Wallet hinzuzufügen, oder null |
wallet.sendEmail() |
Customer | Nichts. Sendet die Wallet-Karte per E-Mail an den Kunden |
Widget, Refresh und Events
| Methode | Zugriff | Liefert |
|---|---|---|
widget.open({ page? }) |
Public | Öffnet das Widget. page ist eine der Widget-Seiten |
widget.close() |
Public | Schließt das Widget |
widget.showLauncher() / widget.hideLauncher() |
Public | Zeigt oder verbirgt den Widget-Button in dieser Seitenansicht |
refresh() |
Public | Liest die Sitzung und jede Ressource, die deine Seite bereits geladen hat, erneut |
on(event, callback) |
Public | Eine Funktion, die das Abonnement entfernt |
Wenn du page und limit weglässt, verwendet die API ihre eigenen Standardwerte.
Daten, die du rendern kannst ¶
Alle Ergebnisse sind unveränderte Objekte der JeriCommerce-API. Dies sind die Felder, die du am häufigsten brauchst.
CustomerData
Wird von customer.get() zurückgegeben und mit customer:updated gesendet.
| Feld | Typ | Beschreibung |
|---|---|---|
customer.email |
string | E-Mail-Adresse des Kunden |
customer.userData.firstName / lastName |
string | Name im Profil |
customer.loyaltyData.balance |
number | Punkte, die der Kunde ausgeben kann |
customer.loyaltyData.referralLink |
string | Empfehlungslink (auch mit referrals.getLink() verfügbar) |
customer.customerTier.currentTier |
Tier oder null |
Aktuelle VIP-Stufe |
customer.customerTier.currentTierPoints |
number | Punkte, die für Stufen zählen |
customer.balanceNotifications, tierNotifications, locationNotifications, rewardAvailableNotifications, marketingNotifications |
boolean | Benachrichtigungseinstellungen |
engagementFlows |
object | Fortschritt pro Sammelmöglichkeit, nach Sammeltyp geordnet (zum Beispiel CUSTOM_1). count gibt an, wie oft der Kunde sie abgeschlossen hat |
TierProgressStatus
Wird von customer.getTierProgress() zurückgegeben. Es ist dieselbe Berechnung, die der Loyalty Hub verwendet.
| Feld | Typ | Beschreibung |
|---|---|---|
currentTier |
Tier oder null |
Aktuelle Stufe |
nextTier |
Tier oder null |
Nächste Stufe, oder null bei der höchsten Stufe |
pointsToNextTier |
number | Stufenpunkte, die bis nextTier fehlen |
progressValue |
number | Fortschritt über alle Stufen, von 0 (erste Stufe) bis 100 (höchste Stufe). Verwende ihn als Prozentwert für einen Fortschrittsbalken |
sortedTiers |
Tier[] |
Alle Stufen von der niedrigsten bis zur höchsten |
Tier
| Feld | Typ | Beschreibung |
|---|---|---|
id |
string | Stufen-ID |
name |
string | Name der Stufe |
requiredPoints |
number | Stufenpunkte, die zum Erreichen nötig sind |
content |
string | In JeriCommerce festgelegter Vorteilstext (HTML). Bereinige ihn, bevor du ihn mit innerHTML einfügst |
image |
string oder null |
Bild der Stufe |
Reward und PublicRewardDto
rewards.list({ scope: "customer" }) liefert Reward, und rewards.list({ scope: "public" }) liefert PublicRewardDto.
| Feld | Typ | Beschreibung |
|---|---|---|
id |
string | Verwende sie als rewardId zum Einlösen |
title, description, image |
string | Inhalt der Belohnung |
type |
"fixed" oder "dynamic" |
Fixed reward oder Points as cash |
cost (customer) / redeemCost (public) |
number | Punkte, die eine feste Belohnung kostet |
minPoints, maxPoints, currencyPerPoint |
number oder null |
Grenzen und Wert einer Points as cash-Belohnung |
redeemable (customer) |
boolean | Der Kunde kann sie jetzt einlösen |
nonRedeemableReason (customer) |
string | Warum nicht, zum Beispiel NOT_ENOUGH_BALANCE |
ClaimedReward
Wird von rewards.claim() zurückgegeben.
| Feld | Typ | Beschreibung |
|---|---|---|
code |
string | Rabattcode, den du dem Kunden anzeigst |
pointsRedeemed |
number, optional | Ausgegebene Punkte |
discountValue |
number, optional | Wert des Rabatts |
expiresAt |
string oder null |
Ablaufdatum |
PaginatedCustomerCouponCodes
Wird von discounts.list() zurückgegeben. data ist die Liste der Codes; count, total, page und pageCount beschreiben die Seite. Jeder Code hat value (den Code), expiresAt, discountValue, claimed und coupon (den Coupon, zu dem er gehört).
Sammelmöglichkeiten
earnings.list() liefert einen Eintrag pro Sammeltyp. Jeder Eintrag hat name (zum Beispiel FOLLOW_US, MEMBER_GET_MEMBER, CUSTOM_1), active und programEngagementFlows, die Einrichtung des Programms für diesen Typ. Verwende das erste Element:
Feld von programEngagementFlows[0] |
Beschreibung |
|---|---|
id |
Verwende sie als earningId in earnings.execute() |
active |
Zeige nur aktive Sammelmöglichkeiten an |
title, description, buttonText, url |
In JeriCommerce festgelegter Inhalt |
displayIconUrl |
Symbol |
properties.points |
Vergebene Punkte |
properties.executeOnClick |
true, wenn der Kunde durch Drücken eines Buttons Punkte sammelt: Rufe earnings.execute({ earningId, action: "flow" }) auf. Gilt nie für FOLLOW_US, MEMBER_GET_MEMBER oder COMPLETE_USER_PROFILE, die mit invalid_argument fehlschlagen |
properties.followUs |
Soziale Netzwerke einer Follow us-Sammelmöglichkeit, nach Netzwerk geordnet (instagram, facebook, ...). Jedes hat username und points |
properties.requiredFields |
Profilfelder, die eine Complete profile-Sammelmöglichkeit benötigt |
Ein Klick braucht den Netzwerkschlüssel als linkId, zum Beispiel earnings.execute({ earningId, action: "click", linkId: "instagram" }). Das SDK zeichnet den in JeriCommerce festgelegten Link auf; es öffnet nie ein Fenster, öffne die Social-Media-Seite also selbst.
earnings.execute() mit action: "flow" liefert pointsAwarded, remainingExecutions und recurrence.
PaginatedTransactions
Wird von history.list() zurückgegeben. Jedes Element von data hat name, amount, balanceChange, status und createdAt.
Boosts
boosts.list() liefert { boosts }. Jeder Boost hat name, startsAt, endsAt und isActive. Das SDK aktualisiert die Liste, wenn ein Boost beginnt oder endet.
Einstellungen aktualisieren ¶
customer.updatePreferences() benötigt alle Benachrichtigungs-Flags, plus language, wenn du die Sprache ändern möchtest:
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,
});
Anfragen verwenden immer die Sprache der Seite. Das Speichern einer Spracheinstellung aktualisiert die Daten des Kunden, ändert aber nicht die Sprache der Seite.
Events ¶
jeri.on(event, callback) gibt eine Funktion zurück, die das Abonnement entfernt. Events werden auch ausgelöst, wenn deine Seite diese Daten zum ersten Mal liest, und on() sendet den letzten Wert nicht erneut. Rendere das Ergebnis jeder Abfrage wie im Schnellstart und nutze die Events für spätere Änderungen.
| Event | Wann es ausgelöst wird | Was der Callback erhält |
|---|---|---|
session:changed |
Die Sitzung ist zum ersten Mal bekannt und bei jeder Änderung | { authenticated } |
customer:updated |
Die Daten des Kunden haben sich geändert, zum Beispiel der Kontostand nach einer Einlösung | CustomerData, oder null für einen Besucher |
rewards:updated |
Eine Liste von Belohnungen, die deine Seite gelesen hat, wurde aktualisiert | { scope, rewards }, mit derselben Liste wie rewards.list({ scope }) |
discounts:updated |
Eine Liste von Rabattcodes, die deine Seite gelesen hat, wurde aktualisiert | { query, discounts }, mit dem gelesenen { page, limit } und einem PaginatedCustomerCouponCodes |
earnings:updated |
Die Sammelmöglichkeiten wurden aktualisiert | Dieselbe Liste wie earnings.list() |
boosts:updated |
Ein Boost hat begonnen oder geendet, oder die Boosts wurden aktualisiert | { scope, boosts }, mit demselben Wert wie boosts.list({ scope }) |
Events melden nur Datenänderungen. Das Ergebnis einer Aktion ist der Wert, mit dem ihr Promise aufgelöst wird.
Fehler ¶
Jeder fehlgeschlagene Aufruf wird mit einem StorefrontSdkError abgelehnt. Prüfe seinen code, nicht den Meldungstext:
| Code | Bedeutung |
|---|---|
invalid_argument |
Dem Aufruf fehlt ein Wert oder er ist falsch, zum Beispiel eine leere rewardId |
authentication_required |
Der Aufruf benötigt einen angemeldeten Kunden, oder die Sitzung des Kunden ist abgelaufen |
session_unavailable |
Die Sitzung des Kunden konnte nicht geladen werden. Versuche es später erneut; sie wird nie als Besucher behandelt |
api_error |
JeriCommerce hat die Anfrage abgelehnt. status enthält den HTTP-Status und serverCode den API-Code, zum Beispiel NOT_ENOUGH_POINTS |
network_error |
Eine Abfrage hat JeriCommerce nicht erreicht. Eine Aktion, die es nicht erreicht, schlägt mit mutation_unconfirmed fehl |
widget_unavailable |
Das Widget ist ausgeschaltet, oder seine Einstellungen oder sein Code konnten nicht geladen werden, daher kann es nicht geöffnet werden |
mutation_unconfirmed |
Die Aktion wurde möglicherweise nicht ausgeführt, zum Beispiel weil sie JeriCommerce nicht erreicht hat. Lies die Daten erneut, bevor du es noch einmal versuchst |
Das Widget und das SDK ¶
Das SDK kann das Storefront-Widget auf der aktuellen Seite steuern: jeri.widget.open(), close(), showLauncher() und hideLauncher(). Diese Aufrufe wirken nur auf die Seitenansicht; die gespeicherten Widget-Einstellungen ändern sich nicht. Wenn das Widget in JeriCommerce ausgeschaltet ist, wird widget.open() mit widget_unavailable abgelehnt, und alles andere funktioniert weiter.
Widget-Seiten ¶
widget.open({ page }) akzeptiert diese Seiten:
page |
Öffnet |
|---|---|
advantages |
Programmvorteile |
profile |
Profil |
orders |
Bestellungen |
coupons |
Rabattcodes |
rewards |
Belohnungen |
loyalty |
Sammelmöglichkeiten |
referrals |
Empfehlungen |
Besucher sehen immer die Programmvorteile und die Anmeldeseite.
Einschränkungen ¶
- Das SDK ist nur auf Shopify-Storefronts über das App-Embed verfügbar. Es gibt kein npm-Paket.
- Der Theme-Editor Canvas von Shopify unterstützt App-Embeds noch nicht, daher ist das SDK in Canvas-Themes nicht verfügbar.
- Das SDK erstellt keine Konten und registriert keine Kunden. Besucher melden sich über Shopify an.
- Punkte werden nie im Browser berechnet. Wenn die API noch den vorherigen Kontostand liefert, sind das die aktuellsten Daten bis zum nächsten Lesezugriff.
Beispiele ¶
Eine Belohnung einlösen und den Code anzeigen
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.");
}
}
Eine Points-as-cash-Belohnung einlösen
await jeri.rewards.claim({ rewardId: reward.id, pointsToRedeem: 200 });
Mit einem Button Punkte sammeln
const result = await jeri.earnings.execute({ earningId: earning.id, action: "flow" });
showMessage(`+${result.pointsAwarded} points`);
Das Widget auf der Belohnungsseite öffnen
document.querySelector("[data-open-rewards]").addEventListener("click", () => {
jeri.widget.open({ page: "rewards" }).catch(() => {
window.location.href = "/pages/loyalty"; // The widget is turned off.
});
});
Den Kontostand im Header aktuell halten
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);
Eine vollständige Seite, die mit diesen Methoden gebaut wurde, findest du in der Anleitung zur eigenen Loyalty-Seite.