@smartico/public-api 0.0.379 → 0.0.380

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/.nvmrc +1 -1
  2. package/README.md +31 -2
  3. package/dist/Store/BuyStoreItemErrorCode.d.ts +9 -1
  4. package/dist/Tournaments/TournamentRegistrationError.d.ts +3 -1
  5. package/dist/WSAPI/WSAPI.d.ts +13 -1235
  6. package/dist/WSAPI/WSAPIAvatars.d.ts +78 -0
  7. package/dist/WSAPI/WSAPIBase.d.ts +71 -0
  8. package/dist/WSAPI/WSAPIBonuses.d.ts +26 -0
  9. package/dist/WSAPI/WSAPIClans.d.ts +388 -0
  10. package/dist/WSAPI/WSAPIGamePick.d.ts +308 -0
  11. package/dist/WSAPI/WSAPIGeneral.d.ts +30 -0
  12. package/dist/WSAPI/WSAPIInbox.d.ts +126 -0
  13. package/dist/WSAPI/WSAPIJackpots.d.ts +109 -0
  14. package/dist/WSAPI/WSAPILeaderBoard.d.ts +25 -0
  15. package/dist/WSAPI/WSAPIMiniGames.d.ts +94 -0
  16. package/dist/WSAPI/WSAPIMissions.d.ts +517 -0
  17. package/dist/WSAPI/WSAPIRaffles.d.ts +119 -0
  18. package/dist/WSAPI/WSAPIStore.d.ts +656 -0
  19. package/dist/WSAPI/WSAPITournaments.d.ts +501 -0
  20. package/dist/WSAPI/WSAPITypes.d.ts +145 -85
  21. package/dist/WSAPI/WSAPIUser.d.ts +383 -0
  22. package/dist/index.js +3818 -1528
  23. package/dist/index.js.map +1 -1
  24. package/dist/index.modern.mjs +3180 -981
  25. package/dist/index.modern.mjs.map +1 -1
  26. package/docs/api/README.md +56 -2
  27. package/docs/api/classes/WSAPIAvatars.md +116 -0
  28. package/docs/api/classes/WSAPIBonuses.md +50 -0
  29. package/docs/api/classes/WSAPIClans.md +66 -0
  30. package/docs/api/classes/WSAPIGamePick.md +181 -0
  31. package/docs/api/classes/WSAPIGeneral.md +63 -0
  32. package/docs/api/classes/WSAPIInbox.md +277 -0
  33. package/docs/api/classes/WSAPIJackpots.md +185 -0
  34. package/docs/api/classes/WSAPILeaderBoard.md +39 -0
  35. package/docs/api/classes/WSAPIMiniGames.md +175 -0
  36. package/docs/api/classes/WSAPIMissions.md +611 -0
  37. package/docs/api/classes/WSAPIRaffles.md +195 -0
  38. package/docs/api/classes/WSAPIStore.md +744 -0
  39. package/docs/api/classes/WSAPITournaments.md +104 -0
  40. package/docs/api/classes/WSAPIUser.md +200 -0
  41. package/docs/api/enumerations/AchievementClaimPeriodTypeId.md +13 -0
  42. package/docs/api/enumerations/BuyStoreItemErrorCode.md +32 -0
  43. package/docs/api/interfaces/ClanPrizePublicMeta.md +41 -0
  44. package/docs/api/interfaces/ClanPrizeStructureEntry.md +7 -1
  45. package/docs/api/interfaces/ClanPublicMeta.md +4 -0
  46. package/docs/api/interfaces/SAWTemplateUI.md +10 -0
  47. package/docs/api/interfaces/TAchCategory.md +19 -4
  48. package/docs/api/interfaces/TBuyStoreItemResult.md +26 -8
  49. package/docs/api/interfaces/TMissionClaimRewardResult.md +14 -3
  50. package/docs/api/interfaces/TMissionOptInResult.md +10 -3
  51. package/docs/api/interfaces/TMissionOrBadge.md +6 -2
  52. package/docs/api/interfaces/TStoreItem.md +21 -1
  53. package/docs/api/interfaces/TTournamentDetailed.md +8 -0
  54. package/docs/api/interfaces/UserAchievement.md +1 -1
  55. package/docs/ui/clans/UIGuide_getClanInfo.md +153 -0
  56. package/docs/ui/clans/UIGuide_getClans.md +172 -0
  57. package/docs/ui/clans/UIGuide_joinClan.md +196 -0
  58. package/docs/ui/missions/UIGuide_getAchCategories.md +17 -0
  59. package/docs/ui/missions/UIGuide_getBadges.md +237 -0
  60. package/docs/ui/missions/UIGuide_getMissions.md +268 -0
  61. package/docs/ui/missions/UIGuide_requestMissionClaimReward.md +21 -0
  62. package/docs/ui/missions/UIGuide_requestMissionOptIn.md +16 -0
  63. package/docs/ui/store/UIGuide_buyStoreItem.md +133 -0
  64. package/docs/ui/store/UIGuide_getStoreCategories.md +19 -0
  65. package/docs/ui/store/UIGuide_getStoreItems.md +264 -0
  66. package/docs/ui/store/UIGuide_getStorePurchasedItems.md +122 -0
  67. package/docs/ui/tournaments/UIGuide_getClanTournamentPlayers.md +140 -0
  68. package/docs/ui/tournaments/UIGuide_getTournamentInstanceInfo.md +265 -0
  69. package/docs/ui/tournaments/UIGuide_getTournamentsList.md +236 -0
  70. package/docs/ui/tournaments/UIGuide_registerInTournament.md +156 -0
  71. package/docs/ui/user/UIGuide_getLevels.md +162 -0
  72. package/docs/ui/user/UIGuide_getUserProfile.md +213 -0
  73. package/package.json +3 -3
  74. package/scripts/postdoc-cleanup.js +195 -0
  75. package/src/Missions/MissionsUtils.ts +5 -2
  76. package/src/Store/BuyStoreItemErrorCode.ts +8 -0
  77. package/src/Store/StoreItem.ts +1 -0
  78. package/src/Store/StoreItemPurchased.ts +1 -0
  79. package/src/Store/StoreItemType.ts +1 -1
  80. package/src/Tournaments/TournamentRegistrationError.ts +4 -0
  81. package/src/WSAPI/WSAPI.ts +27 -2007
  82. package/src/WSAPI/WSAPIAvatars.ts +127 -0
  83. package/src/WSAPI/WSAPIBase.ts +93 -0
  84. package/src/WSAPI/WSAPIBonuses.ts +54 -0
  85. package/src/WSAPI/WSAPIClans.ts +417 -0
  86. package/src/WSAPI/WSAPIGamePick.ts +382 -0
  87. package/src/WSAPI/WSAPIGeneral.ts +57 -0
  88. package/src/WSAPI/WSAPIInbox.ts +222 -0
  89. package/src/WSAPI/WSAPIJackpots.ts +212 -0
  90. package/src/WSAPI/WSAPILeaderBoard.ts +40 -0
  91. package/src/WSAPI/WSAPIMiniGames.ts +181 -0
  92. package/src/WSAPI/WSAPIMissions.ts +572 -0
  93. package/src/WSAPI/WSAPIRaffles.ts +175 -0
  94. package/src/WSAPI/WSAPIStore.ts +732 -0
  95. package/src/WSAPI/WSAPITournaments.ts +541 -0
  96. package/src/WSAPI/WSAPITypes.ts +145 -87
  97. package/src/WSAPI/WSAPIUser.ts +456 -0
  98. package/tsconfig.json +17 -1
  99. package/docs/api/classes/WSAPI.md +0 -1927
@@ -0,0 +1,744 @@
1
+ # Class: WSAPIStore
2
+ ## Methods
3
+
4
+ ### getStoreItems()
5
+
6
+ > **getStoreItems**(`params?`): `Promise`\<[`TStoreItem`](../interfaces/TStoreItem.md)[]\>
7
+
8
+ Returns the catalog of buyable store items visible to the current user,
9
+ scoped server-side to what the user is qualified to see. Optionally
10
+ subscribes to live updates via `onUpdate`: the callback is invoked with
11
+ the full refreshed catalog after every purchase the user makes from this
12
+ device.
13
+
14
+ The returned list is the canonical source of truth for store state
15
+ (`can_buy`, `pool`, `discounted_price`, `active_till_date`, etc.).
16
+ Consumers should NEVER mutate it optimistically — always re-fetch (or
17
+ wait for the `onUpdate` callback) to observe the new state.
18
+
19
+ #### Parameters
20
+
21
+ ##### params?
22
+
23
+ Optional. Omit to fetch without subscribing.
24
+
25
+ ###### onUpdate?
26
+
27
+ (`data`) => `void`
28
+
29
+ Callback invoked with the full refreshed
30
+ catalog after every [buyStoreItem](#buystoreitem)
31
+ round-trip on this connection. Each call to
32
+ `getStoreItems` overwrites the prior
33
+ callback. Never fires in visitor mode.
34
+
35
+ #### Returns
36
+
37
+ `Promise`\<[`TStoreItem`](../interfaces/TStoreItem.md)[]\>
38
+
39
+ Promise resolving to the catalog of store
40
+ items visible to the user. Empty array if
41
+ the user has no items (or if the visitor
42
+ proxy user has none configured).
43
+
44
+ #### Remarks
45
+
46
+ **Preconditions**
47
+ The catalog is per-user. Visitor mode is supported with caveats (see
48
+ "Visitor mode" below). For the authenticated path, the user's balances
49
+ (`ach_points_balance`, `ach_gems_balance`, `ach_diamonds_balance` from
50
+ [getUserProfile](WSAPIUser.md#getuserprofile)) must be tracked client-side to drive the
51
+ affordability state of the Buy button — the SDK does not pre-compute
52
+ affordability into `can_buy`.
53
+
54
+ **Subscription model (`onUpdate`)**
55
+ The callback registered via `onUpdate` is invoked with the FULL refreshed
56
+ catalog — never a diff/patch — after any `buyStoreItem(...)` call
57
+ resolves on this connection (regardless of `err_code`). It is the ONLY
58
+ trigger for the store catalog. Side effects covered:
59
+ - Pool decrement on the item the user just bought.
60
+ - `purchased_today` / `purchased_this_week` / `purchased_this_month`
61
+ flag flips (these appear only in the purchase-history list, but the
62
+ underlying `can_buy` field on the main catalog re-evaluates against
63
+ the operator's "max bought" rules after every purchase).
64
+ - `can_buy === false` after a per-period purchase cap is hit.
65
+
66
+ Each subsequent call to `getStoreItems({ onUpdate })` REPLACES the prior
67
+ callback — there is only one active subscriber at a time. Pass
68
+ `onUpdate: undefined` (or omit it) to keep the prior callback in place;
69
+ the callback is never auto-cleared.
70
+
71
+ **What does NOT trigger an `onUpdate`** (consumer-relevant)
72
+ - Operator-side BO catalog edits (price, ribbons, pool seed, segment
73
+ conditions, new/removed items) do NOT push to connected clients —
74
+ those changes surface only on the next cache miss.
75
+ - Pool depletion caused by OTHER users' concurrent purchases does NOT
76
+ push. The current user's view of `pool` may be stale until they
77
+ trigger a refresh themselves.
78
+ - User balance changes (points/gems/diamonds going up from gameplay)
79
+ do NOT refresh the catalog. The catalog is independent of balance;
80
+ the consumer must drive button affordability from a separately
81
+ tracked balance source (e.g. the user's gamification profile,
82
+ updated independently of this catalog).
83
+ - Items going past their `active_till_date` while the page is open do
84
+ NOT push — the item will simply disappear on the next refresh.
85
+
86
+ **Server-side filtering** (what's already excluded before the SDK sees it)
87
+ - Items outside the `[active_from_date, active_till_date]` window — past
88
+ the till-date, items are ALWAYS filtered out server-side.
89
+ - Items the user fails visibility-segment / visibility-condition checks for.
90
+ - Items with `shop_pool_hide_when_empty = true` whose pool has reached 0
91
+ (operator-configured per item). Items without that flag are still
92
+ returned with `pool === 0` so a "Sold out" state can be rendered.
93
+ - Items where the per-period bought-count cap is reached (and the
94
+ operator chose to hide rather than just disable).
95
+
96
+ **Field semantics** (non-obvious fields on `TStoreItem`)
97
+ - `price` is typed as `number` on the SDK surface; internally the server
98
+ stores it as a string and the SDK converts. Always coerce defensively
99
+ if you arithmetic against it.
100
+ - `purchase_type` is per-item and may differ across items in the same
101
+ catalog: `'points'`, `'gems'`, or `'diamonds'`. There is no
102
+ label-level currency — operators can mix currencies freely.
103
+ - `can_buy` reflects segment conditions, per-period purchase caps, and
104
+ item-active-window — but NOT the user's current balance. Consumers
105
+ must check affordability separately by comparing `price` to the
106
+ live balance for `purchase_type`.
107
+ - `pool` is `null` for unlimited items, a positive integer for limited
108
+ stock, and `0` for sold-out items that the operator kept visible. It
109
+ is NOT live: only the buyer's own pool-decrementing purchase updates
110
+ it client-side.
111
+ - `active_till_date` is a unix-ms timestamp; items past this point are
112
+ filtered server-side, so consumers should NOT need to render an
113
+ "Expired" state for catalog items (only for purchase-history rows).
114
+ A live countdown ("flash sale ends in HH:MM:SS") is rendered only
115
+ when the operator has enabled the per-item `show_timer` flag.
116
+ - `discounted_price` + `discount_price_ribbon` are the discount-display
117
+ pair: render `price` with a strikethrough and `discounted_price` as
118
+ the active price. `discount_price_ribbon` is either a percentage
119
+ preset (`'10'`, `'15'`, `'20'`, `'25'`, `'50'`) styled by the
120
+ consumer's CSS, or the literal `'custom'` — in which case
121
+ `custom_ribbon_image` (250×300 px URL) is the artwork.
122
+ - `ribbon` (general item ribbon) accepts the presets `'sale'`, `'hot'`,
123
+ `'new'`, `'vip'` (built-in styling) or a custom-image URL (250×300 px).
124
+ - `category_ids` maps to `TStoreCategory.id` from
125
+ [getStoreCategories](#getstorecategories) — an item can appear in multiple categories;
126
+ render it as multiple cards or once per active-tab as the consumer's
127
+ navigation prefers.
128
+ - `only_in_custom_section: true` means the item should be HIDDEN from
129
+ the main store view and shown only inside the operator-defined
130
+ custom section (`custom_section_id`). The catalog still includes it —
131
+ filter client-side for general views.
132
+ - `type` distinguishes the post-purchase server effect (see "Side
133
+ effects" below). UI rendering of the CATALOG card is identical across
134
+ types — there is no built-in card variation per type.
135
+ - `purchase_ts`, `purchase_points_amount`, `purchased_today`,
136
+ `purchased_this_week`, `purchased_this_month` are populated only on
137
+ rows returned from [getStorePurchasedItems](#getstorepurchaseditems) (purchase history),
138
+ NOT on rows from `getStoreItems`.
139
+
140
+ **Idempotency**: yes. `getStoreItems` is a pure read. Calling it
141
+ repeatedly within the 30-second cache window resolves from the cache
142
+ without a network round-trip.
143
+
144
+ **Side effects** (of a successful purchase that triggers `onUpdate`)
145
+ Documented here because the consumer's `onUpdate` handler will observe
146
+ the resulting state changes on the next refreshed array:
147
+ - Points / gems / diamonds balance deducted per `purchase_type`. The
148
+ new balance becomes visible on the user's gamification profile via
149
+ the SDK's user-properties update channel (`ach_points_balance` /
150
+ `ach_gems_balance` / `ach_diamonds_balance`).
151
+ - `pool` decremented for limited-stock items.
152
+ - `type = 'bonus'` → a CRM bonus is granted; surfaces via
153
+ [getBonuses](#getbonuses).
154
+ - `type = 'raffleticket'` → raffle tickets granted; surfaces via
155
+ `getRaffles`.
156
+ - `type = 'changelevel'` → user's level adjusted; surfaces via
157
+ [getCurrentLevel](WSAPIUser.md#getcurrentlevel).
158
+ - `type = 'minigamespin'` → SAW spin attempts granted; visible via the
159
+ mini-game APIs.
160
+ - `type = 'prizedrop'` → one prize-drop attempt granted.
161
+ - `type = 'tangible'` → no automatic gameplay effect; the purchase is
162
+ recorded for the operator's manual fulfillment (e.g. physical merch).
163
+
164
+ **UI guidance**: see [UI Guide — `getStoreItems`](../../ui/store/UIGuide_getStoreItems.md).
165
+
166
+ #### Example
167
+
168
+ ```ts
169
+ // 1. Initial fetch + subscribe to live updates
170
+ const items = await window._smartico.api.getStoreItems({
171
+ onUpdate: (refreshed) => {
172
+ console.log('[smartico] store catalog refreshed after a purchase — re-render the entire store UI from this array, do not merge with prior state:', refreshed);
173
+
174
+ // 2. onUpdate fires only after the current user's own purchase
175
+ // round-trip. Pool counts and can_buy flags are now current
176
+ // for this user.
177
+ const soldOut = refreshed.filter(i => i.pool === 0);
178
+ if (soldOut.length > 0) {
179
+ console.log('[smartico] these items are now sold out — render an "Out of stock" overlay on each:', soldOut.map(i => i.id));
180
+ }
181
+ },
182
+ });
183
+
184
+ // 3. Live affordability per item. Compare each item's price against
185
+ // the user's balance for its purchase_type (read from
186
+ // getUserProfile(), kept live by the SDK's user-properties channel).
187
+ const profile = window._smartico.api.getUserProfile();
188
+ const balances = {
189
+ points: profile?.ach_points_balance ?? 0,
190
+ gems: profile?.ach_gems_balance ?? 0,
191
+ diamonds: profile?.ach_diamonds_balance ?? 0,
192
+ };
193
+
194
+ // 4. Filter out items that are custom-section-only for the main view.
195
+ const mainViewItems = items.filter(i => !i.only_in_custom_section);
196
+
197
+ // 5. Bucket each item's button state for rendering.
198
+ for (const i of mainViewItems) {
199
+ const activePrice = i.discounted_price ?? i.price;
200
+ const canAfford = balances[i.purchase_type] >= activePrice;
201
+ if (i.pool === 0) {
202
+ console.log('[smartico] item', i.id, 'is sold out — render disabled Buy with "Out of stock" overlay');
203
+ } else if (i.can_buy === false) {
204
+ console.log('[smartico] item', i.id, 'is not eligible — render disabled Buy and surface cant_buy_message on the popup:', i.cant_buy_message);
205
+ } else if (!canAfford) {
206
+ console.log('[smartico] item', i.id, 'is unaffordable — render disabled Buy and show "short by" line on popup; deficit =', activePrice - balances[i.purchase_type], i.purchase_type);
207
+ } else {
208
+ console.log('[smartico] item', i.id, 'is buyable — render enabled Buy at price', activePrice, i.purchase_type);
209
+ }
210
+ }
211
+
212
+ // 6. Visitor-mode equivalent: onUpdate is accepted but never fires.
213
+ // Re-poll if you need fresh data; buyStoreItem is not supported.
214
+ // const visitorItems = await window._smartico.vapi('EN').getStoreItems();
215
+ // console.log('[smartico] visitor catalog is the brand proxy user\'s — per-user fields are not meaningful');
216
+ ```
217
+
218
+ ***
219
+
220
+ ### buyStoreItem()
221
+
222
+ > **buyStoreItem**(`item_id`): `Promise`\<[`TBuyStoreItemResult`](../interfaces/TBuyStoreItemResult.md)\>
223
+
224
+ Purchase a specific store item on behalf of the current user. Spends
225
+ the user's points / gems / diamonds (depending on the item's
226
+ `purchase_type`) and triggers reward delivery (bonus issuance, mini-game
227
+ spin grant, level change, prize-drop, raffle ticket, or tangible item).
228
+
229
+ A successful response (`err_code === 0`) means the funds have already
230
+ been debited and the reward has been queued / granted server-side. The
231
+ user's balance change arrives over a separate push channel, not in this
232
+ response — see "Refresh after success" below.
233
+
234
+ #### Parameters
235
+
236
+ ##### item\_id
237
+
238
+ `number`
239
+
240
+ The store item `id` (from `getStoreItems()`).
241
+
242
+ #### Returns
243
+
244
+ `Promise`\<[`TBuyStoreItemResult`](../interfaces/TBuyStoreItemResult.md)\>
245
+
246
+ `{ err_code, err_message }`; success when `err_code === 0`.
247
+
248
+ #### Remarks
249
+
250
+ **Preconditions**
251
+ Read the candidate item from `getStoreItems()` first and gate the call
252
+ on these fields:
253
+ - `can_buy === true` — server-derived; combines segment / visibility
254
+ conditions and any operator-set restrictions.
255
+ - `pool == null || pool > 0` — `null` means unlimited stock; a positive
256
+ number means N units remain.
257
+ - User's balance for the item's `purchase_type`
258
+ (`'points'` / `'gems'` / `'diamonds'`) is `>= price` (or
259
+ `>= discounted_price` if set). Balances are not on `TStoreItem` —
260
+ read them from the user properties returned by `getUserState()`.
261
+ - If `active_till_date` is set, it is in the future.
262
+
263
+ Calling without satisfying these will most likely return a non-zero
264
+ `err_code` (one of the codes below). The SDK does not pre-validate
265
+ — it forwards the request to the server unconditionally.
266
+
267
+ **Error codes** (in `err_code`)
268
+ - `0` — success; funds debited and reward delivered.
269
+ - `1` — generic server error / control-group rejection. Treat as a
270
+ transient failure; show a "try again later" message.
271
+ - `106` — label mismatch detected before the purchase transaction
272
+ (e.g. cross-label item id). From a consumer perspective: the item is
273
+ not purchasable by this user.
274
+ - `11000` (`SHOP_NO_BALANCE`) — insufficient **points** balance.
275
+ Show an insufficient-balance message that names how many points are
276
+ missing (`price - current_points_balance`). The Buy button on the
277
+ item should already be disabled when this is the case; this code
278
+ normally fires only on a stale local balance.
279
+ - `11001` (`SHOP_WRONG_LABEL`) — user-vs-item label mismatch detected
280
+ inside the purchase transaction. Distinct from `106`; both mean
281
+ "not purchasable by this user" from the consumer's perspective.
282
+ Surface as a generic "item not available" message.
283
+ - `11002` (`SHOP_FAILED_TO_BUY_BONUS`) — the bonus configured on the
284
+ store item couldn't be issued (bad / disabled bonus config on the
285
+ operator side). Funds are NOT debited. Surface a generic error and
286
+ advise the user to contact support if it persists.
287
+ - `11003` (`SHOP_FAILED_TO_BUY_SHOP_ITEM_CONDITION`) — the item's
288
+ **visibility** segment condition is not satisfied. Shouldn't be
289
+ reachable through a correctly-rendered UI (the item shouldn't be
290
+ shown in the first place); usually indicates a stale local store
291
+ list. Re-fetch via `getStoreItems()`.
292
+ - `11004` (`SHOP_FAILED_TO_BUY_SHOP_ITEM_CONDITION_PURSHASE`) — the
293
+ item's **purchase** segment condition is not satisfied (the item is
294
+ visible but the user is not in the buyable segment). Surface a
295
+ localized "try again later" / "you're not eligible" message; do not
296
+ retry automatically.
297
+ - `11005` (`SHOP_FAILED_TO_BUY_MATCHING_BONUS`) — matching-bonus
298
+ coupon issuance failed (variant of `11002` for matching-deposit
299
+ bonuses). Funds are NOT debited. Surface a generic error.
300
+ - `11006` (`SHOP_FAILED_MAX_BOUGHT_ITEMS_REACHED`) — per-user purchase
301
+ cap reached (e.g. "max 1 per day"). Surface the item's
302
+ `limit_message` if set, otherwise `err_message`. Do not retry.
303
+ - `11007` — bonus was issued but a follow-up redemption step failed
304
+ server-side. Funds **are** debited and the purchase event is recorded;
305
+ the consumer's UI should refresh the store list (auto-refresh will
306
+ fire) and surface a soft warning rather than a hard error.
307
+ - `11009` (`SHOP_FAILED_POOL_EMPTY`) — stock pool is empty / sold out.
308
+ Show a "sold out" message; auto-refresh will mark the item as such
309
+ in subsequent `getStoreItems()` updates.
310
+ - `11011` (`SHOP_NO_BALANCE_GEMS`) — insufficient **gems** balance.
311
+ Same handling as `11000` but for the gems currency.
312
+ - `11012` (`SHOP_NO_BALANCE_DIAMONDS`) — insufficient **diamonds**
313
+ balance. Same handling as `11000` but for the diamonds currency.
314
+ - `11014` (`SHOP_FAILED_PURCHASE_LIMITATION`) — outside the configured
315
+ purchase window (purchase weekday / time-of-day / window). Surface
316
+ the item's `purchase_limit_message` if set, otherwise `err_message`.
317
+ - `9999` — uncaught server exception. Treat as a transient failure;
318
+ surface a generic error and allow retry.
319
+ - other non-zero — generic server error. Surface `err_message` if any.
320
+
321
+ **Idempotency**: NOT idempotent. The server performs no de-duplication —
322
+ a second successful call deducts funds again and grants the reward
323
+ again (subject to per-user purchase caps). The SDK itself does **not**
324
+ enforce a single in-flight call. The consumer MUST guard the call site
325
+ against double-clicks (set a local "buying" flag on click, clear it on
326
+ response).
327
+
328
+ **Refresh after success (and after failure)**
329
+ The SDK automatically re-fetches the store item list and the
330
+ purchased-items history on every response (success or error). Any
331
+ `onUpdate` callback registered via `getStoreItems({ onUpdate })` (or
332
+ the purchased-items equivalent) fires shortly after with the updated
333
+ arrays — reflecting the new `pool`, `can_buy`, and any `purchased_*`
334
+ flags. The user's balance (points / gems / diamonds) is exposed on the
335
+ user-properties channel and updates independently; if the UI displays
336
+ the balance, subscribe to user-property updates separately (it does
337
+ NOT flow through `getStoreItems`).
338
+
339
+ **Side effects** (consumer-observable on success)
340
+ - User's points / gems / diamonds balance decreases by `price`
341
+ (or `discounted_price` if set).
342
+ - Item's `pool` decrements by 1 (when `pool != null`).
343
+ - Per-user purchase counters update — `purchased_today`,
344
+ `purchased_this_week`, `purchased_this_month` on the next
345
+ `getStoreItems()` refresh.
346
+ - The reward is delivered: bonus issuance, mini-game spin grant,
347
+ level change, prize drop, raffle ticket, or tangible-item record.
348
+ Each reward type pushes its own follow-up update (e.g. a new
349
+ bonus appears in `getBonuses()`, the spin shows up in
350
+ `getMiniGames()`). Subscribe to each relevant channel separately.
351
+ - A purchase row is appended to the user's purchase history,
352
+ surfaced via the purchased-items list.
353
+
354
+ **UI guidance**: see [UI Guide — `buyStoreItem`](../../ui/store/UIGuide_buyStoreItem.md).
355
+
356
+ #### Example
357
+
358
+ ```ts
359
+ const items = await window._smartico.api.getStoreItems({
360
+ onUpdate: (next) => console.log('[smartico] store items updated — re-render store grid with this fresh array', next),
361
+ });
362
+ const item = items.find(i => i.id === itemId);
363
+
364
+ if (!item) {
365
+ console.log('[smartico] item not in current store list — keep UI as-is, possibly refresh getStoreItems');
366
+ return;
367
+ }
368
+
369
+ // Local pre-checks: keep these aligned with the Buy-button decision matrix above.
370
+ const effectivePrice = item.discounted_price ?? item.price;
371
+ const inStock = item.pool == null || item.pool > 0;
372
+ const inWindow = !item.active_till_date || Date.now() < item.active_till_date;
373
+ if (!item.can_buy || !inStock || !inWindow) {
374
+ console.log('[smartico] buy not applicable — show disabled state with operator copy if any:', item.cant_buy_message);
375
+ return;
376
+ }
377
+
378
+ console.log('[smartico] buy starting — set in-flight flag, show loading dots on Buy button, keep detail modal open');
379
+ const r = await window._smartico.api.buyStoreItem(item.id);
380
+ console.log('[smartico] buy response received — clear in-flight flag');
381
+
382
+ if (r.err_code === 0) {
383
+ console.log('[smartico] purchase successful — close the detail modal, show a success toast; getStoreItems onUpdate above will fire with the refreshed list (do NOT manually re-fetch)');
384
+ } else if (r.err_code === 11000 || r.err_code === 11011 || r.err_code === 11012) {
385
+ console.error('[smartico] insufficient balance for this purchase_type — show insufficient-balance UI and how many units are missing:', item.purchase_type, effectivePrice);
386
+ } else if (r.err_code === 11009) {
387
+ console.error('[smartico] item sold out — show a dedicated "Sold out" message; the auto-refresh will mark the card as such shortly');
388
+ } else if (r.err_code === 11006) {
389
+ console.error('[smartico] per-user purchase cap reached — surface the operator-defined limit message:', item.limit_message || r.err_message);
390
+ } else if (r.err_code === 11014) {
391
+ console.error('[smartico] outside purchase window — surface the operator-defined purchase-limit message:', item.purchase_limit_message || r.err_message);
392
+ } else if (r.err_code === 11004 || r.err_code === 11003) {
393
+ console.error('[smartico] user not eligible / stale visibility — show a localized "try again later" message and let auto-refresh reconcile');
394
+ } else {
395
+ console.error('[smartico] buy failed — show a generic error toast/modal with this message:', r.err_message);
396
+ }
397
+ ```
398
+
399
+ ***
400
+
401
+ ### getStoreCategories()
402
+
403
+ > **getStoreCategories**(): `Promise`\<[`TStoreCategory`](../interfaces/TStoreCategory.md)[]\>
404
+
405
+ Returns the list of active store categories configured for the current label.
406
+ Categories are operator-defined groupings used to organize store items into tabs,
407
+ sections, or filter chips in the gamification store UI.
408
+
409
+ #### Returns
410
+
411
+ `Promise`\<[`TStoreCategory`](../interfaces/TStoreCategory.md)[]\>
412
+
413
+ Array of `{ id, name, order }`. Empty array if no active categories are
414
+ configured for the label. `name` is pre-translated. Order is NOT applied — caller
415
+ must sort.
416
+
417
+ #### Remarks
418
+
419
+ **What categories are**
420
+ - Operator-defined groupings, configured per-label by the brand operator.
421
+ Only active categories are returned; inactive / archived / deleted ones are
422
+ filtered out server-side.
423
+ - Each `TStoreCategory` exposes `{ id, name, order }`. `name` is the display label;
424
+ `order` is the relative position (lower = appears first).
425
+ - The same set of categories is returned to every user of the label — there is no
426
+ per-segment / per-currency / per-device filtering on the category list. Item-level
427
+ segment visibility happens on [getStoreItems](#getstoreitems), not here.
428
+
429
+ **Localization / translation**
430
+ - `name` is **pre-translated server-side** to the authenticated user's stored language.
431
+ In visitor mode, the language passed to `_smartico.vapi(lang)` drives the translation.
432
+ Consumers never need to translate `name` themselves.
433
+ - Fallback if no translation exists for the user's language: the operator-set default
434
+ (typically the brand's English) value is returned. The field is never null for an
435
+ active category.
436
+
437
+ **Sort order**
438
+ The server does NOT pre-sort. Sort client-side before rendering:
439
+ `categories.sort((a, b) => a.order - b.order)`.
440
+
441
+ **How store items join to categories**
442
+ Each `TStoreItem` exposes `category_ids: number[]` (zero-or-more, many-to-many).
443
+ A single store item can belong to multiple categories, or to none. To render a
444
+ "category → items" view:
445
+ `items.filter(i => i.category_ids.includes(cat.id))`.
446
+
447
+ **Uncategorized items**
448
+ Items with `category_ids: []` are NOT represented by a synthetic "Other" entry
449
+ in the response. The convention used by the reference store UI is to synthesize
450
+ a client-side bucket (e.g. labelled "General") for items that have no category, OR
451
+ for items whose `category_ids` reference a category that is no longer returned by
452
+ this method (e.g. archived since the item was tagged). If you choose to render
453
+ such a synthetic bucket, place it first (the reference UI uses an `order: -1`
454
+ convention so it precedes all operator-defined categories).
455
+
456
+ **Empty categories**
457
+ A category may be returned even if zero items currently reference it (since this
458
+ method does not join against items). The reference store UI hides such empty
459
+ categories from its tab strip — replicate that filter if you don't want to render
460
+ tabs that lead to empty content panels.
461
+
462
+ **Refresh cadence / cache**
463
+ - The SDK caches the response for 30 seconds. Subsequent calls within that window
464
+ return the cached array without a network round-trip.
465
+ - The cache is NOT invalidated by store-item push events or purchases — operator-driven
466
+ category changes propagate at the next 30 s cache miss.
467
+ - Cache is fully cleared on login / logout.
468
+ - In **visitor mode**, additional server-side caching applies on top of the SDK cache;
469
+ worst-case staleness for an edited/added category is on the order of a few minutes
470
+ for anonymous viewers.
471
+
472
+ **Idempotency**: safe. Read-only metadata fetch. Cache de-duplicates concurrent calls.
473
+
474
+ **Side effects**: none — pure metadata read.
475
+
476
+ **UI guidance**: see [UI Guide — `getStoreCategories`](../../ui/store/UIGuide_getStoreCategories.md).
477
+
478
+ #### Example
479
+
480
+ ```ts
481
+ const [categories, items] = await Promise.all([
482
+ window._smartico.api.getStoreCategories(),
483
+ window._smartico.api.getStoreItems(),
484
+ ]);
485
+
486
+ if (categories.length === 0) {
487
+ console.log('[smartico] no store categories configured — render items as a flat grid without tabs:', items.length);
488
+ } else {
489
+ // Sort categories; build per-category item buckets; hide empty categories
490
+ const sorted = [...categories].sort((a, b) => a.order - b.order);
491
+ const tabs = sorted
492
+ .map(cat => ({ cat, items: items.filter(i => i.category_ids?.includes(cat.id)) }))
493
+ .filter(t => t.items.length > 0);
494
+
495
+ // Optional: synthesize an "Other" bucket for items with no (or unknown) category
496
+ const knownIds = new Set(categories.map(c => c.id));
497
+ const orphans = items.filter(i => !i.category_ids?.length || !i.category_ids.some(id => knownIds.has(id)));
498
+ if (orphans.length > 0) {
499
+ console.log('[smartico] uncategorized store items — render a client-synthesized "Other" tab at the front of the tab strip:', orphans.length);
500
+ }
501
+
502
+ console.log('[smartico] render store tabs in this order with these counts:', tabs.map(t => `${t.cat.name} (${t.items.length})`));
503
+ }
504
+
505
+ // Visitor mode — same shape, language driven by vapi(lang) argument
506
+ const visitorCats = await window._smartico.vapi('DE').getStoreCategories();
507
+ console.log('[smartico] visitor-mode store categories (names translated to DE):', visitorCats);
508
+ ```
509
+
510
+ ***
511
+
512
+ ### getStorePurchasedItems()
513
+
514
+ > **getStorePurchasedItems**(`params?`): `Promise`\<[`TStoreItem`](../interfaces/TStoreItem.md)[]\>
515
+
516
+ Fetches the authenticated user's **store purchase history** — a paginated,
517
+ newest-first list of past store purchases (one row per redemption).
518
+
519
+ Designed for a "My Purchases" / "Order History" screen. Each entry carries
520
+ the same descriptive fields as the catalog card plus three history-only
521
+ fields: `purchase_ts` (when the purchase happened), `purchase_points_amount`
522
+ (the amount actually paid at that time — a price snapshot, not the
523
+ current `price`), and `purchase_type` (`'points' | 'gems' | 'diamonds'`).
524
+
525
+ #### Parameters
526
+
527
+ ##### params?
528
+
529
+ Optional pagination + callback bag.
530
+
531
+ ###### limit?
532
+
533
+ `number`
534
+
535
+ Page size. Defaults to `20`. Use `20` as the
536
+ conventional value (matches the default Smartico UI end-to-end). Avoid
537
+ non-positive values.
538
+
539
+ ###### offset?
540
+
541
+ `number`
542
+
543
+ Number of rows to skip from the newest end.
544
+ Defaults to `0`. For load-more pagination pass the current array
545
+ length.
546
+
547
+ ###### onUpdate?
548
+
549
+ (`data`) => `void`
550
+
551
+ Optional callback invoked after a successful
552
+ [buyStoreItem](#buystoreitem) elsewhere in the session. Receives the refreshed
553
+ page for the same `(limit, offset)` used on the call that registered
554
+ this callback. Only ONE callback can be registered; each new call
555
+ overwrites the previous one. Pass a stable handler or
556
+ re-register intentionally on screen entry.
557
+
558
+ #### Returns
559
+
560
+ `Promise`\<[`TStoreItem`](../interfaces/TStoreItem.md)[]\>
561
+
562
+ Promise resolving to an array of `TStoreItem` history entries
563
+ in newest-first order. Empty array if the user has no purchases (or
564
+ the requested `offset` is beyond the end). Each entry carries
565
+ `purchase_ts`, `purchase_points_amount`, `purchase_type`, and the
566
+ `purchased_today/this_week/this_month` convenience flags in addition
567
+ to the standard item fields.
568
+
569
+ #### Remarks
570
+
571
+ **Preconditions**
572
+ - User must be authenticated. Visitor mode is not supported (the SDK only
573
+ exposes this method on the authenticated `_smartico.api`, not on
574
+ `_smartico.vapi`).
575
+ - No prerequisite calls — calling without first fetching
576
+ [getStoreItems](#getstoreitems) or [getStoreCategories](#getstorecategories) is fine.
577
+
578
+ **Pagination semantics**
579
+ - **Offset-based**, newest-first. The server orders rows by purchase
580
+ timestamp **descending** and the SDK does NOT re-sort — items arrive
581
+ ready to render in display order.
582
+ - `limit` — page size. **The SDK default is 20.** There is no server-side
583
+ hard cap, but 20 matches what the default Smartico UI uses end-to-end; the
584
+ protocol and storage layers were sized for this page size. Treat `20`
585
+ as the conventional/recommended value; passing a much larger limit will
586
+ return more rows but is not the supported integration pattern.
587
+ Avoid `limit <= 0` — the server peeks `limit + 1` rows internally, so
588
+ `limit = 0` returns 1 row with a misleading "has more" signal, and
589
+ negative values produce undefined results.
590
+ - `offset` — number of items to skip from the start. The SDK default is
591
+ `0` (first page). For load-more pagination, pass
592
+ `offset = (previously-loaded items).length` on each subsequent call.
593
+ - **No `hasMore` on the returned array.** `TStoreItem[]` is a flat list;
594
+ the underlying protocol response carries `hasMore` but it is not
595
+ surfaced through this method. Two ways for consumers to know they
596
+ reached the end:
597
+ 1. The returned array is shorter than `limit` (definitive end), or
598
+ 2. A subsequent call at `offset = previous.length` returns `[]`.
599
+ Either is reliable.
600
+ - No date-range / category / status filter parameters — pagination is the
601
+ only server-side slicing. Apply any further filtering client-side.
602
+
603
+ **No per-item fulfillment status**
604
+ - History entries do NOT carry a `delivered` / `pending` / `cancelled` /
605
+ `refunded` field. The list contains rows for committed purchase
606
+ attempts; the server query does not filter by transaction-status, so in
607
+ practice the rows that appear are completed purchases (failed
608
+ transactions roll back and don't write a row). Treat every item as a
609
+ completed transaction unless a future SDK release adds an explicit
610
+ status field.
611
+
612
+ **Bonus-type entries**
613
+ - When the user redeemed a bonus item, the entry's `type` field surfaces
614
+ as `'bonus'`. **Known limitation:** the SDK transform currently maps
615
+ both `Bonus` and `Tangible` server-side item types to the same string
616
+ `'bonus'` — so the `type` field on history rows cannot distinguish a
617
+ bonus redemption from a tangible-prize redemption. The actual bonus
618
+ payload (wallet credit, free spins, etc.) is dispatched by the server
619
+ asynchronously and is NOT embedded in the history entry — consumers
620
+ that need bonus details should subscribe to bonus push events
621
+ separately rather than reading them from this list.
622
+ - History rows render identically across types — there is no deep-link to
623
+ the bonus wallet or re-claim affordance on the row itself.
624
+
625
+ **History-only fields on `TStoreItem`**
626
+ The following fields are populated **only** for entries returned by this
627
+ method (NOT by [getStoreItems](#getstoreitems)):
628
+ - `purchase_ts` — Unix milliseconds, server-recorded UTC moment of
629
+ purchase. Format on the consumer side in the **user's browser local
630
+ timezone** (the default Smartico UI uses
631
+ `new Date(purchase_ts).toLocaleString(undefined, { dateStyle: 'short', timeStyle: 'medium' })`).
632
+ No relative labels ("Today" / "Yesterday") are produced by the SDK —
633
+ render those client-side if desired.
634
+ - `purchase_points_amount` — the amount the user actually paid at the
635
+ time of purchase, in the currency given by `purchase_type`. Historical
636
+ discounts are preserved here; the item's current `price` may differ.
637
+ - `purchase_type` — `'points' | 'gems' | 'diamonds'`. The currency
638
+ actually charged. Render the matching balance icon next to
639
+ `purchase_points_amount`.
640
+ - `purchased_today` / `purchased_this_week` / `purchased_this_month` —
641
+ convenience booleans computed by the SDK at transform time using the
642
+ **consumer's local clock**, not server-stamped. Useful for client-side
643
+ bucketing (e.g. "Today" / "This Week" group headers).
644
+
645
+ **Catalog fields that ARE present but commonly ignored on a history row**
646
+ - `ribbon`, `discounted_price`, `discount_price_ribbon`,
647
+ `custom_ribbon_image`, `active_till_date` — reflect the current
648
+ catalog state of the item, not its state at purchase time. The
649
+ default Smartico UI hides them on history rows to avoid mis-implying that
650
+ the historical purchase carried that current promotional banner.
651
+ - `description`, `hint_text` (T&C), `related_games` — descriptive
652
+ marketing fields; typically suppressed on a compact history row.
653
+ - `can_buy`, `pool` — reflect **current** purchasability of the same
654
+ item, useful only if you render a "Buy Again" affordance on the history
655
+ row (see UI guidance below).
656
+ - `custom_data` — present, but unlike `getStoreItems` the history
657
+ transform does NOT auto-parse JSON strings. If you store JSON there,
658
+ call `JSON.parse()` yourself.
659
+
660
+ **Cache & refresh**
661
+ - The SDK caches each `(limit, offset)` page separately for 30 seconds.
662
+ Repeated calls within that window resolve from cache without a
663
+ network round-trip.
664
+ - The cache is invalidated and `onUpdate` (when registered) is invoked
665
+ automatically after a successful [buyStoreItem](#buystoreitem) call — the SDK
666
+ re-fetches the same page parameters that were registered together with
667
+ the current `onUpdate` callback and pushes the fresh array.
668
+ - Multi-tab caveat: if the user has the same brand open in multiple
669
+ tabs, **all open tabs** receive the `onUpdate` event on a purchase,
670
+ not only the tab that initiated the buy. The event is scoped to the
671
+ current authenticated user — a different user's purchase never fires
672
+ this callback.
673
+ - Only ONE `onUpdate` callback can be registered at a time. Each call to
674
+ `getStorePurchasedItems({ onUpdate })` overwrites the previous handler.
675
+
676
+ **Idempotency**: safe. Pure read.
677
+
678
+ **Side effects**: none — read-only.
679
+
680
+ **UI guidance**: see [UI Guide — `getStorePurchasedItems`](../../ui/store/UIGuide_getStorePurchasedItems.md).
681
+
682
+ #### Example
683
+
684
+ ```ts
685
+ // Initial load + subscribe to refresh-on-purchase.
686
+ const handleHistoryUpdate = (items: TStoreItem[]) => {
687
+ console.log(
688
+ '[smartico] purchase history refreshed — re-render the history list with these',
689
+ items.length,
690
+ 'items (newest first):',
691
+ items,
692
+ );
693
+ };
694
+
695
+ try {
696
+ const firstPage = await _smartico.api.getStorePurchasedItems({
697
+ limit: 20,
698
+ offset: 0,
699
+ onUpdate: handleHistoryUpdate,
700
+ });
701
+
702
+ if (firstPage.length === 0) {
703
+ console.log('[smartico] no purchase history — render the empty state ("No purchases yet")');
704
+ } else {
705
+ console.log(
706
+ '[smartico] render',
707
+ firstPage.length,
708
+ 'history rows (newest first). Show "Load more" button if length === 20:',
709
+ firstPage,
710
+ );
711
+
712
+ // Inspect the first entry's history-only fields.
713
+ const first = firstPage[0];
714
+ console.log(
715
+ '[smartico] most-recent purchase: name=', first.name,
716
+ 'paid', first.purchase_points_amount, first.purchase_type,
717
+ 'at', new Date(first.purchase_ts ?? 0).toLocaleString(),
718
+ '— format with toLocaleString (user-local time) on the row.',
719
+ );
720
+ }
721
+
722
+ // Load-more pattern: fetch page 2 once the user scrolls / clicks "Load more".
723
+ if (firstPage.length === 20) {
724
+ const nextPage = await _smartico.api.getStorePurchasedItems({
725
+ limit: 20,
726
+ offset: firstPage.length,
727
+ });
728
+ const isLastPage = nextPage.length < 20;
729
+ console.log(
730
+ '[smartico] appended page 2 —',
731
+ nextPage.length,
732
+ 'rows.',
733
+ isLastPage ? 'Hide "Load more" — end reached.' : 'Keep "Load more" visible.',
734
+ );
735
+ }
736
+ } catch (e) {
737
+ console.error(
738
+ '[smartico] failed to load purchase history — show a generic error placeholder or retry with backoff:',
739
+ e,
740
+ );
741
+ }
742
+ ```
743
+
744
+ ***