# Class: WSAPIBonuses
## Methods

### getBonuses()

> **getBonuses**(`params?`): `Promise`\<[`TBonus`](../interfaces/TBonus.md)[]\>

Returns the bonuses awarded to the current user — both pending
(player-claim required) and already-redeemed. Each bonus carries
a `bonus_status_id` ([BonusStatus](../enumerations/BonusStatus.md)) describing its lifecycle
stage and an `is_redeemable` flag that signals whether the
consumer must trigger a manual [claimBonus](#claimbonus) call.

Bonuses originate from many sources (CRM rules, mission/tournament
rewards, store redemptions, mini-game prizes, manual operator
awards) and all surface through this single list. The consumer
cannot distinguish the source server-side without operator
coordination.

#### Parameters

##### params?

Optional. Omit to fetch without subscribing.

###### onUpdate?

(`data`) => `void`

Callback invoked with the full refreshed
                           bonuses list after every
                           [claimBonus](#claimbonus) resolves. Each call
                           to `getBonuses` overwrites the prior
                           callback.

#### Returns

`Promise`\<[`TBonus`](../interfaces/TBonus.md)[]\>

Promise resolving to the bonuses list.
                           Empty array if no bonuses are visible.

#### Remarks

**Subscription model (`onUpdate`)**
The callback receives the FULL refreshed bonuses list (never a
diff/patch). Each subsequent call to `getBonuses({ onUpdate })`
REPLACES the prior callback. Pass `onUpdate: undefined` (or omit
it) to keep the prior callback in place; the callback is never
auto-cleared.

**Update triggers** — the callback fires after every
[claimBonus](#claimbonus) call resolves on this connection (success OR
failure). This is the ONLY trigger.

Does NOT fire when: a new bonus is awarded server-side
(CRM rule, mission completion, store purchase, etc.), a bonus
expires, or an operator manually issues / cancels a bonus. Those
changes surface only on the next cache miss (after the 30 s TTL).
Re-call `getBonuses()` manually if your UI needs near-live state
for server-driven awards.

**Reading state from the returned bonus**
Drive UI bucketing from `bonus_status_id` (enum [BonusStatus](../enumerations/BonusStatus.md)):

- `COUPON_ISSUED` (2) — actionable; player must claim. Pair with
  `is_redeemable === true` to gate the Claim button.
- `REDEEM_FAILED` (4) — also actionable; the previous claim
  attempt failed but the bonus is still valid. Surface a retry CTA
  — the default Smartico UI treats this identically to
  `COUPON_ISSUED` from the player's perspective.
- `REDEEMED` (3) — historical; show in a "claimed" tab.
- `New` (1), `COUPON_ISSUE_FAILED` (5), `EXPIRED` (6) — these
  should not normally reach the client and are typically filtered
  out by operator-side widget configuration; if they do appear,
  they have no player-actionable state.

`is_redeemable` is the authoritative gate for showing the Claim
button — it's `true` only when the bonus is in a claimable status
AND the operator's integration uses a coupon model (where the
player must explicitly trigger delivery). Auto-redeemed bonuses
arrive already in `REDEEMED` state with `is_redeemable: false`.

**Field hierarchy — display amount**
Bonuses carry both template-level (`label_bonus_template_meta_map.description`)
and instance-level (`bonus_meta_map.uiAmount`) display strings.
Prefer `bonus_meta_map.uiAmount` when present — it's the dynamic
amount computed at issuance time (e.g. from a dynamic-bonus
formula). Fall back to `label_bonus_template_meta_map.description`
(which can include HTML; sanitize before injecting).

**Date format**
`create_date` and `redeem_date` are ISO 8601 UTC strings WITHOUT
a timezone suffix (`"YYYY-MM-DDTHH:MM:SS"`). Parse as UTC and
display in the user's local timezone. `redeem_date` is absent
until the bonus reaches `REDEEMED` status.

**Cache TTL**: the SDK caches the response for 30 seconds. Cache
is fully cleared on login / logout.

**Idempotency / Side effects**: safe. Read-only.

**UI guidance**: see [UI Guide — `getBonuses`](../_media/UIGuide_getBonuses.md).

**Visitor mode**: not supported.

#### Example

```ts
const bonuses = await window._smartico.api.getBonuses({
  onUpdate: (refreshed) => {
    console.log('[smartico] bonuses refreshed (after claimBonus) — re-render the bonuses UI from this array:', refreshed);
  },
});

// Bucket into pending vs redeemed tabs. The default Smartico UI groups
// COUPON_ISSUED + REDEEM_FAILED into the same "Pending" tab.
const pending = bonuses.filter(b =>
  b.bonus_status_id === 2 || b.bonus_status_id === 4  // COUPON_ISSUED or REDEEM_FAILED
);
const redeemed = bonuses.filter(b => b.bonus_status_id === 3);  // REDEEMED

console.log('[smartico] render bonus tabs: pending=', pending.length, 'redeemed=', redeemed.length);

// Render each pending bonus card; show Claim button only when redeemable.
for (const b of pending) {
  const amount = b.bonus_meta_map?.uiAmount || b.label_bonus_template_meta_map?.description;
  if (b.is_redeemable) {
    console.log('[smartico] render Claim button for bonus', b.bonus_id, '— amount:', amount);
  } else {
    console.log('[smartico] render bonus as informational (no Claim button) — amount:', amount);
  }
}

// Server-awarded bonuses (e.g. from a CRM campaign) do NOT push;
// poll if you need near-live awareness.
setInterval(async () => {
  const fresh = await window._smartico.api.getBonuses();
  const newCount = fresh.filter(b => b.bonus_status_id === 2 || b.bonus_status_id === 4).length;
  console.log('[smartico] poll tick — pending bonuses now:', newCount);
}, 30_000);
```

***

### claimBonus()

> **claimBonus**(`bonus_id`): `Promise`\<[`TClaimBonusResult`](../interfaces/TClaimBonusResult.md)\>

Triggers manual redemption of a coupon-model bonus the user has
been awarded. For bonuses with `is_redeemable === true` returned
by [getBonuses](#getbonuses), this is the call that asks the operator's
integration to actually credit the bonus to the player's account.
Bonuses on integrations that auto-redeem arrive already in
`REDEEMED` state and do not require this call.

Use this for `bonus_status_id` of `COUPON_ISSUED` (2) as the
first-time claim, and for `REDEEM_FAILED` (4) as a retry after a
prior unsuccessful attempt.

#### Parameters

##### bonus\_id

`number`

The `bonus_id` from a `TBonus` returned by
                 [getBonuses](#getbonuses).

#### Returns

`Promise`\<[`TClaimBonusResult`](../interfaces/TClaimBonusResult.md)\>

`{ err_code, err_message, success? }`; success when
         `err_code === 0` (or `err_code === -1` when treated as
         idempotent no-op). The `success` field is unreliable —
         ignore it.

#### Remarks

**Preconditions**
Read the candidate bonus from [getBonuses](#getbonuses) and gate the
call on `is_redeemable === true`. The SDK forwards the request
unconditionally — calling on a non-claimable bonus returns
`err_code = -1`.

**Error codes** (in `err_code`)

The SDK currently types `err_code` as [SAWSpinErrorCode](../enumerations/SAWSpinErrorCode.md) for
historical reasons, but the values returned by this method come
from the server's general error space and are NOT mini-game codes.
Branch on the numeric values:

- `0` — success; the bonus has been redeemed and the operator's
  integration has credited the player's account.
- `-1` — bonus not claimable: not found, already claimed by
  another session, or its status is no longer `COUPON_ISSUED` /
  `REDEEM_FAILED`. Treat as idempotent — refresh
  [getBonuses](#getbonuses) and reconcile.
- `1` — generic server error. Transient; allow retry after a brief
  delay.
- `9999` — uncaught server exception. Treat as a transient failure;
  surface a generic error and allow retry.
- other non-zero — generic server error. Surface `err_message` if
  any.

**`success` field is unreliable**: the wire response does not
carry a `success` boolean — the field on `TClaimBonusResult` will
be `undefined` in practice. Always branch on `err_code === 0`
for the success check.

**Refresh after success (and after failure)**
The SDK automatically refreshes the bonuses cache on every
response (success OR failure) and fires any `onUpdate` callback
registered via [getBonuses](#getbonuses)'s `onUpdate`. After
`err_code === 0`, the redeemed bonus shifts from
`COUPON_ISSUED` / `REDEEM_FAILED` to `REDEEMED` on the refreshed
array, with `redeem_date` populated. After a failure, the bonus
typically lands in `REDEEM_FAILED` and remains claimable for a
retry.

**Idempotency**: NOT idempotent at the SDK level. A second call
returns `err_code = -1` once the first has succeeded — treat that
as success in the UI rather than as a real error. Guard the call
site against double-clicks (set a local "claiming" flag on click,
clear it on response).

**Side effects** (on `err_code === 0`)
- Bonus status transitions to `REDEEMED`; `redeem_date` is set.
- The operator's integration credits the player's account
  externally. The SDK does not observe that credit directly —
  balance updates flow over the user-properties channel from the
  operator's side, not from this response.
- Server-side analytics events fire downstream (not directly
  observable from the SDK).

**UI guidance**: see [UI Guide — `claimBonus`](../_media/UIGuide_claimBonus.md).

**Visitor mode**: not supported.

#### Example

```ts
const bonuses = await window._smartico.api.getBonuses({
  onUpdate: (refreshed) => console.log('[smartico] bonuses refreshed — re-render from this array', refreshed),
});
const bonus = bonuses.find(b => b.bonus_id === bonusId);

if (!bonus) {
  console.log('[smartico] bonus no longer visible — refresh list and hide CTA');
  return;
}
if (!bonus.is_redeemable) {
  console.log('[smartico] bonus is not claimable (auto-redeemed or already finalised) — keep CTA hidden');
  return;
}

console.log('[smartico] claim starting — set in-flight flag, show loading dots on the Claim button, keep the modal open');
const r = await window._smartico.api.claimBonus(bonus.bonus_id);
console.log('[smartico] claim response received — clear in-flight flag');

if (r.err_code === 0 || r.err_code === -1) {
  console.log('[smartico] claim succeeded (or was already done) — show a success toast; the getBonuses onUpdate above will fire with the refreshed list');
} else if (r.err_code === 9999) {
  console.error('[smartico] uncaught server exception — show a generic error toast and allow retry');
} else {
  console.error('[smartico] claim failed — surface this error message to the user; the bonus will move to REDEEM_FAILED state and remain claimable for a retry:', r.err_message);
}
```

***
