Examples
Two complete integrations: an online shop and a POS till
Examples
Online shop checkout
Award points when an order is paid, and take them back when it is refunded. Two
rules matter: use your own order ID as order_id, and never let a loyalty failure
block the order.
import { LoyaltySDK, LoyaltySDKError } from '@loyaltylt/sdk';
const sdk = new LoyaltySDK({
apiKey: process.env.LOYALTY_API_KEY!,
apiSecret: process.env.LOYALTY_API_SECRET!,
environment: process.env.NODE_ENV === 'production' ? 'production' : 'staging',
});
export async function onOrderPaid(order) {
try {
await sdk.createTransaction({
user_email: order.customerEmail,
order_id: order.id,
order_total: order.total,
currency: order.currency,
shop_id: SHOP_ID,
points_redeemed: order.pointsSpent,
points_discount_amount: order.pointsDiscount,
cart_items: order.lines.map((l) => ({
product_id: l.productId,
product_name: l.name,
sku: l.sku,
quantity: l.quantity,
unit_price: l.price,
total_price: l.price * l.quantity,
})),
});
} catch (error) {
if (error instanceof LoyaltySDKError) log.warn({ err: error }, 'loyalty award failed');
else throw error;
}
}
export async function onOrderRefunded(order) {
await sdk.createTransaction({
user_email: order.customerEmail,
order_id: order.id, // the same order_id
type: 'refund',
order_total: order.total,
});
}Show the points before checkout
import { calculatePointsFromAmount, calculateMaxRedeemablePoints } from '@loyaltylt/sdk';
const card = await sdk.getLoyaltyCardInfo({ card_number: cardNumber });
const balance = await sdk.getPointsBalance({ card_number: cardNumber });
const willEarn = calculatePointsFromAmount(cartTotal, rules);
const canSpend = calculateMaxRedeemablePoints(cartTotal, balance.current_points, rules);Note
While the customer is on the checkout page, hold their points with
POST /{locale}/shop/points/reserve so a second tab cannot spend them, then release
with points/release if checkout is abandoned. The SDKs do not wrap these yet.
POS till
Identify the customer with a QR scan, apply their coupons, then register the sale.
class Till {
constructor(private sdk: LoyaltySDK, private shopId: number) {}
/** Show a QR code and resolve once the customer has scanned it. */
async identifyCustomer() {
const session = await this.sdk.generateQrCardSession('Till 3', this.shopId);
renderQr(session.qr_code);
return new Promise((resolve) => {
this.sdk.subscribeToQrCardScan(session.session_id, ({ data }) => resolve(data.card_data));
});
}
async applyCoupon(code: string, cartTotal: number) {
const check = await this.sdk.verifyCoupon(code, this.shopId);
const { discount } = await this.sdk.calculateCouponDiscount(code, cartTotal);
await this.sdk.setCouponPending(check.coupon.id, this.shopId);
return { couponId: check.coupon.id, discount };
}
async completeSale(card, total: number, couponId?: number) {
await this.sdk.createTransaction({
user_id: card.user.id,
order_id: `POS-${Date.now()}`,
order_total: total,
currency: 'EUR',
shop_id: this.shopId,
});
if (couponId) await this.sdk.redeemCoupon(couponId, this.shopId);
// Stamp cards, if the shop runs any
for (const game of await this.sdk.getCardGames(card.loyalty_card_id, this.shopId)) {
await this.sdk.addStamps(game.id, card.loyalty_card_id, this.shopId, 1);
}
}
async cancelSale(couponId?: number) {
if (couponId) await this.sdk.cancelCouponPending(couponId, this.shopId);
}
}Order matters: verify the coupon before showing a discount, mark it pending while the sale is open, and redeem it only once payment succeeds. If the sale falls through, cancel the pending state so the customer keeps the coupon.
Test it
Both examples run against staging with the demo cards — see Staging.