Loyalty.lt
SDKsJavaScript

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.

On this page