@smartico/public-api 0.0.378 → 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.
- package/.nvmrc +1 -1
- package/README.md +31 -2
- package/dist/Store/BuyStoreItemErrorCode.d.ts +9 -1
- package/dist/Tournaments/TournamentRegistrationError.d.ts +3 -1
- package/dist/WSAPI/WSAPI.d.ts +13 -1235
- package/dist/WSAPI/WSAPIAvatars.d.ts +78 -0
- package/dist/WSAPI/WSAPIBase.d.ts +71 -0
- package/dist/WSAPI/WSAPIBonuses.d.ts +26 -0
- package/dist/WSAPI/WSAPIClans.d.ts +388 -0
- package/dist/WSAPI/WSAPIGamePick.d.ts +308 -0
- package/dist/WSAPI/WSAPIGeneral.d.ts +30 -0
- package/dist/WSAPI/WSAPIInbox.d.ts +126 -0
- package/dist/WSAPI/WSAPIJackpots.d.ts +109 -0
- package/dist/WSAPI/WSAPILeaderBoard.d.ts +25 -0
- package/dist/WSAPI/WSAPIMiniGames.d.ts +94 -0
- package/dist/WSAPI/WSAPIMissions.d.ts +517 -0
- package/dist/WSAPI/WSAPIRaffles.d.ts +119 -0
- package/dist/WSAPI/WSAPIStore.d.ts +656 -0
- package/dist/WSAPI/WSAPITournaments.d.ts +501 -0
- package/dist/WSAPI/WSAPITypes.d.ts +145 -85
- package/dist/WSAPI/WSAPIUser.d.ts +383 -0
- package/dist/index.js +3818 -1528
- package/dist/index.js.map +1 -1
- package/dist/index.modern.mjs +3180 -981
- package/dist/index.modern.mjs.map +1 -1
- package/docs/api/README.md +56 -2
- package/docs/api/classes/WSAPIAvatars.md +116 -0
- package/docs/api/classes/WSAPIBonuses.md +50 -0
- package/docs/api/classes/WSAPIClans.md +66 -0
- package/docs/api/classes/WSAPIGamePick.md +181 -0
- package/docs/api/classes/WSAPIGeneral.md +63 -0
- package/docs/api/classes/WSAPIInbox.md +277 -0
- package/docs/api/classes/WSAPIJackpots.md +185 -0
- package/docs/api/classes/WSAPILeaderBoard.md +39 -0
- package/docs/api/classes/WSAPIMiniGames.md +175 -0
- package/docs/api/classes/WSAPIMissions.md +611 -0
- package/docs/api/classes/WSAPIRaffles.md +195 -0
- package/docs/api/classes/WSAPIStore.md +744 -0
- package/docs/api/classes/WSAPITournaments.md +104 -0
- package/docs/api/classes/WSAPIUser.md +200 -0
- package/docs/api/enumerations/AchievementClaimPeriodTypeId.md +13 -0
- package/docs/api/enumerations/BuyStoreItemErrorCode.md +32 -0
- package/docs/api/interfaces/ClanPrizePublicMeta.md +41 -0
- package/docs/api/interfaces/ClanPrizeStructureEntry.md +7 -1
- package/docs/api/interfaces/ClanPublicMeta.md +4 -0
- package/docs/api/interfaces/SAWTemplateUI.md +10 -0
- package/docs/api/interfaces/TAchCategory.md +19 -4
- package/docs/api/interfaces/TBuyStoreItemResult.md +26 -8
- package/docs/api/interfaces/TMissionClaimRewardResult.md +14 -3
- package/docs/api/interfaces/TMissionOptInResult.md +10 -3
- package/docs/api/interfaces/TMissionOrBadge.md +6 -2
- package/docs/api/interfaces/TStoreItem.md +21 -1
- package/docs/api/interfaces/TTournamentDetailed.md +8 -0
- package/docs/api/interfaces/UserAchievement.md +1 -1
- package/docs/ui/clans/UIGuide_getClanInfo.md +153 -0
- package/docs/ui/clans/UIGuide_getClans.md +172 -0
- package/docs/ui/clans/UIGuide_joinClan.md +196 -0
- package/docs/ui/missions/UIGuide_getAchCategories.md +17 -0
- package/docs/ui/missions/UIGuide_getBadges.md +237 -0
- package/docs/ui/missions/UIGuide_getMissions.md +268 -0
- package/docs/ui/missions/UIGuide_requestMissionClaimReward.md +21 -0
- package/docs/ui/missions/UIGuide_requestMissionOptIn.md +16 -0
- package/docs/ui/store/UIGuide_buyStoreItem.md +133 -0
- package/docs/ui/store/UIGuide_getStoreCategories.md +19 -0
- package/docs/ui/store/UIGuide_getStoreItems.md +264 -0
- package/docs/ui/store/UIGuide_getStorePurchasedItems.md +122 -0
- package/docs/ui/tournaments/UIGuide_getClanTournamentPlayers.md +140 -0
- package/docs/ui/tournaments/UIGuide_getTournamentInstanceInfo.md +265 -0
- package/docs/ui/tournaments/UIGuide_getTournamentsList.md +236 -0
- package/docs/ui/tournaments/UIGuide_registerInTournament.md +156 -0
- package/docs/ui/user/UIGuide_getLevels.md +162 -0
- package/docs/ui/user/UIGuide_getUserProfile.md +213 -0
- package/package.json +3 -3
- package/scripts/postdoc-cleanup.js +195 -0
- package/src/Missions/MissionsUtils.ts +5 -2
- package/src/Store/BuyStoreItemErrorCode.ts +8 -0
- package/src/Store/StoreItem.ts +1 -0
- package/src/Store/StoreItemPurchased.ts +1 -0
- package/src/Store/StoreItemType.ts +1 -1
- package/src/Tournaments/TournamentRegistrationError.ts +4 -0
- package/src/WSAPI/WSAPI.ts +27 -2007
- package/src/WSAPI/WSAPIAvatars.ts +127 -0
- package/src/WSAPI/WSAPIBase.ts +93 -0
- package/src/WSAPI/WSAPIBonuses.ts +54 -0
- package/src/WSAPI/WSAPIClans.ts +417 -0
- package/src/WSAPI/WSAPIGamePick.ts +382 -0
- package/src/WSAPI/WSAPIGeneral.ts +57 -0
- package/src/WSAPI/WSAPIInbox.ts +222 -0
- package/src/WSAPI/WSAPIJackpots.ts +212 -0
- package/src/WSAPI/WSAPILeaderBoard.ts +40 -0
- package/src/WSAPI/WSAPIMiniGames.ts +181 -0
- package/src/WSAPI/WSAPIMissions.ts +572 -0
- package/src/WSAPI/WSAPIRaffles.ts +175 -0
- package/src/WSAPI/WSAPIStore.ts +732 -0
- package/src/WSAPI/WSAPITournaments.ts +541 -0
- package/src/WSAPI/WSAPITypes.ts +145 -87
- package/src/WSAPI/WSAPIUser.ts +456 -0
- package/tsconfig.json +17 -1
- 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
|
+
***
|