@smartico/public-api 0.0.379 → 0.0.381

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