> ## Documentation Index
> Fetch the complete documentation index at: https://developers.mageloyalty.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Loyalty modes

> Every shop runs on either points or store credit. How to detect the mode, format values, and redeem correctly in both.

Every Mage shop runs in one of two loyalty modes, chosen by the merchant:

* **`points`** The loyalty balance is plain points.
* **`store_credit`** The loyalty balance is Mage store credit, denominated in money.

The loyalty engine and every SDK method behave identically in both modes. The SDK returns the same fields either way. What changes is how you **interpret and display** the numbers, and, for redemption, which method a customer uses.

<Note>
  Store credit here means Mage's internal store credit, not Shopify native store credit.
</Note>

## How each mode denominates values

Every loyalty number the SDK returns (`points`, `lifetimePoints`, `redeemedPoints`, a reward's `pointsCost`, an earning rule's award) is read in the shop's loyalty unit:

|                     | `points` mode                                                                                          | `store_credit` mode                                                         |
| ------------------- | ------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------- |
| Unit                | Whole points                                                                                           | Currency minor units                                                        |
| `points: 450` means | 450 points                                                                                             | 4.50 in the shop currency (or 450 for zero-decimal currencies like JPY/KRW) |
| Displayed as        | `"450"`                                                                                                | `"$4.50"`                                                                   |
| Redemption          | [`redeemReward`](/js-sdk/rewards/redeem-reward) or [`redeemFlexible`](/js-sdk/rewards/redeem-flexible) | [`redeemFlexible`](/js-sdk/rewards/redeem-flexible) only                    |

Because the raw numbers look the same in both modes, never assume points. Read the mode and format through the helpers below.

## Detecting the mode

Methods that carry loyalty amounts return `loyaltyMode` and `currency` alongside their data. These are `getCustomerDetails`, `getCustomerActivity`, `getShopRewards`, `getEarningRules`, and `getShopVipTiers`.

```javascript theme={null}
MageSDK.getCustomerDetails().then(function(resp) {
  if (!resp.success) return;

  var customer = resp.data.customer;
  var mode = resp.data.loyaltyMode;   // 'points' | 'store_credit'
  var currency = resp.data.currency;  // e.g. 'USD'

  var balance = MageSDK.formatLoyaltyValue(customer.points, mode, currency);
  document.getElementById('balance').textContent = balance;
  // points mode  -> "450"
  // credit mode  -> "$4.50"
});
```

The same `formatLoyaltyValue` call handles both modes, so your rendering code stays mode-agnostic. Pass the mode and currency straight through and let the helper decide.

## Formatting helpers

The SDK exposes helpers on `window.MageSDK` so you never reimplement the unit math. Each one works in both modes.

<ResponseField name="formatLoyaltyValue(value, mode?, currency?)" type="string">
  Formats a loyalty value for display. Points mode returns the number with locale grouping (`"1,250"`). Store-credit mode returns a localized currency string (`"$12.50"`).
</ResponseField>

<ResponseField name="formatSignedLoyaltyValue(value, mode?, currency?)" type="string">
  Same as `formatLoyaltyValue` but prefixes a `+` or `-` sign. Useful for activity rows (`"+150"`, `"+$3.00"`, `"-300"`).
</ResponseField>

<ResponseField name="loyaltyUnitLabel(mode?, count?)" type="string">
  Returns `"point"` or `"points"` in points mode (based on `count`), and an empty string in store-credit mode, where the money value already describes itself.
</ResponseField>

<ResponseField name="resolveLoyaltyMode(mode?)" type="string">
  Normalizes any input to `'points'` or `'store_credit'`. Anything that isn't exactly `'store_credit'` resolves to `'points'`, so an older server or a missing value safely defaults to points.
</ResponseField>

<ResponseField name="isStoreCredit(mode?)" type="boolean">
  Convenience check. Returns `true` when the resolved mode is `'store_credit'`.
</ResponseField>

<ResponseField name="minorToMajor(amountMinor, currency?)" type="number">
  Converts a minor-unit amount to major units (`300` becomes `3` for USD, `500` stays `500` for JPY). Relevant in store-credit mode when you need the raw number. For display, prefer `formatLoyaltyValue`.
</ResponseField>

```javascript theme={null}
// Points mode
MageSDK.formatLoyaltyValue(1250, 'points', 'USD');            // "1,250"
MageSDK.formatSignedLoyaltyValue(150, 'points', 'USD');       // "+150"
MageSDK.loyaltyUnitLabel('points', 1);                        // "point"

// Store-credit mode
MageSDK.formatLoyaltyValue(1250, 'store_credit', 'USD');      // "$12.50"
MageSDK.formatLoyaltyValue(500, 'store_credit', 'JPY');       // "¥500"
MageSDK.formatSignedLoyaltyValue(-300, 'store_credit', 'USD'); // "-$3.00"
MageSDK.isStoreCredit('store_credit');                        // true
```

## The storeCredit convenience object

In store-credit mode, `getCustomerDetails` attaches a ready-to-display `storeCredit` object to the customer. In points mode it is `null`, so you can branch on its presence.

```json theme={null}
{
  "storeCredit": {
    "balanceMinor": 450,
    "balance": "4.50",
    "balanceFormatted": "$4.50",
    "currency": "USD"
  }
}
```

<ResponseField name="balanceMinor" type="number">
  The raw balance in minor units. Identical to `customer.points` in store-credit mode.
</ResponseField>

<ResponseField name="balance" type="string">
  The balance in major units as a decimal string, e.g. `"4.50"`.
</ResponseField>

<ResponseField name="balanceFormatted" type="string">
  A localized display string, e.g. `"$4.50"`.
</ResponseField>

<ResponseField name="currency" type="string">
  The ISO currency code.
</ResponseField>

## Redemption differs by mode

Redemption is the one behavior that is not identical across modes.

**Points mode** supports both redemption methods:

* [`redeemReward`](/js-sdk/rewards/redeem-reward) for `fixed` rewards (a set cost for a set discount).
* [`redeemFlexible`](/js-sdk/rewards/redeem-flexible) for `flexible` rewards (the customer chooses how much to spend).

**Store-credit mode** supports only flexible redemption. Calling `redeemReward` there returns:

```json theme={null}
{
  "success": false,
  "error": "This reward cannot be redeemed in store credit mode"
}
```

So in store-credit shops, redeem through [`redeemFlexible`](/js-sdk/rewards/redeem-flexible). A reward is redeemable this way when its `redemptionMode` is `"flexible"`; read the amount bounds from the reward's `flexibleConfig` (`minPoints` / `maxPoints`).

```javascript theme={null}
MageSDK.getShopRewards().then(function(resp) {
  if (!resp.success) return;

  var flexible = resp.data.regularRewards.filter(function(r) {
    return r.redemptionMode === 'flexible';
  });

  var reward = flexible[0];
  if (!reward) return;

  // Spend the customer's chosen amount (here, the configured minimum)
  var amount = reward.flexibleConfig.minPoints;

  MageSDK.redeemFlexible(reward.id, amount).then(function(res) {
    if (res.success) {
      console.log('Discount code:', res.data.customerReward.discountCode);
      console.log('Value:', res.data.discountAmount);
    } else {
      console.log('Error:', res.error);
    }
  });
});
```

<Tip>
  If you want redemption code that works regardless of mode, drive it off the reward's `redemptionMode`: call `redeemReward` for `"fixed"` rewards and `redeemFlexible` for `"flexible"` rewards. Store-credit shops only surface flexible rewards, so this pattern naturally does the right thing in both modes.
</Tip>
