Skip to content

Showing a loyalty panel

Question: The customer is logged in. What do you show them, and where does each value come from?


Loyalty panel mockup: member name and tier badge, a spendable-points figure with a progress bar to the next tier, two coupon cards (one active, one redeemed), and a stamp card showing 3 of 10 stamps filled

Every number on this mockup is the exact example from Core concepts — points: 250, qualification_points: 340, remaining_points: 160, level_title: "Gold", next_level_points: 500 — so you can check your own rendering against it field by field.

One call gets almost everything:

GET /api/v2.0/stores/{storeId}/members/{memberId}

Plus coupons, if you are showing them:

GET /api/v2.0/stores/{storeId}/members/{memberId}/coupons/

DisplaySourceWatch out
Points balancemembership_detail.pointsNot remaining_points — see below
Tiermembership_detail.level_titleMay be null if the store has no tiers
Points to next tiermembership_detail.remaining_pointsCan go negative; at or below zero means reached
Progress barqualification_points vs next_level_pointsNot points
Tier expirymembership_detail.level_expire_dateOften null
CouponsGET /coupons/Filter out spent ones; unlimited ones have no count
Stamp progressstamps_collected / required_stamps

const {
qualification_points, remaining_points, next_level_points, level_title, next_level_title,
} = member.membership_detail;
const atTopTier = !next_level_title || next_level_points <= 0;
const progress = atTopTier
? null
: Math.min(100, Math.round((qualification_points / next_level_points) * 100));
const caption = atTopTier
? level_title
: remaining_points > 0
? `${remaining_points} points to ${next_level_title}` // "160 points to Platinum"
: `${next_level_title} reached`;

At the top tier there is no next level: next_level_title is empty and next_level_points is 0. Hide the bar rather than dividing by zero.


Compare dates in the store’s timezone (time_zone on the store object), not your server’s. A coupon expiring “today” means today where the shop is.

valid_from and valid_to carry no offset — they are the store’s local time. Passing them to new Date() would read them in your server’s timezone instead, so compare them as strings against the current time in the store’s timezone:

// The current store-local time, in the same format as valid_from / valid_to.
function storeNow(timeZone) {
return new Intl.DateTimeFormat('sv-SE', {
timeZone,
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit', second: '2-digit',
hourCycle: 'h23',
}).format(new Date()).replace(' ', 'T');
}
const UNLIMITED = 10_000_000; // what an unlimited coupon carries on a member's list
const now = storeNow(store.time_zone); // e.g. "2026-09-10T12:30:00"
const usable = coupons
.filter(c => c.valid_from <= now && c.valid_to >= now)
.map(c => ({
...c,
usesLeft: c.max_redemptions >= UNLIMITED ? null : c.max_redemptions - c.times_redeemed,
}))
.filter(c => c.usesLeft === null || c.usesLeft > 0);

usesLeft: null means unlimited — show no count at all, or the customer reads “9,999,999 uses left”.


  • Don’t trust a member id from the browser. Resolve it from the server-side session. If the client can pass a member id, a customer can read someone else’s points and coupons by changing a number.
  • Don’t cache the panel across sessions. Points change on every purchase, and a stale balance is a support ticket.
  • Don’t block page render on Diller. Load the panel asynchronously and degrade to nothing if the call fails or times out. See degrading gracefully.
  • Don’t show is_visible_to_follower: false segments. The merchant marked those internal on purpose.

If you do not want to build this at all:

GET /api/v2.0/stores/{storeId}/members/{memberId}/widgets/profile
{ "details_url": "https://diller.app/member/widgets/profile/?token=..." }

Drop that in an iframe. The URL is signed and expires in about 15 minutes, so fetch it per page render and never cache it.

Good for getting a merchant live quickly; replace it with your own UI when you want it to match your storefront.


See also: Core concepts, Members reference.