@huloglobal/vendure-plugin-visitor-analytics 0.7.0 → 0.8.1

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 (62) hide show
  1. package/CHANGELOG.md +165 -15
  2. package/dist/abandoned-cart.controller.d.ts +78 -0
  3. package/dist/abandoned-cart.controller.d.ts.map +1 -0
  4. package/dist/abandoned-cart.controller.js +251 -0
  5. package/dist/abandoned-cart.controller.js.map +1 -0
  6. package/dist/abandoned-cart.entity.d.ts +61 -0
  7. package/dist/abandoned-cart.entity.d.ts.map +1 -0
  8. package/dist/abandoned-cart.entity.js +143 -0
  9. package/dist/abandoned-cart.entity.js.map +1 -0
  10. package/dist/abandoned-cart.scanner.d.ts +34 -0
  11. package/dist/abandoned-cart.scanner.d.ts.map +1 -0
  12. package/dist/abandoned-cart.scanner.js +85 -0
  13. package/dist/abandoned-cart.scanner.js.map +1 -0
  14. package/dist/abandoned-cart.service.d.ts +92 -0
  15. package/dist/abandoned-cart.service.d.ts.map +1 -0
  16. package/dist/abandoned-cart.service.js +276 -0
  17. package/dist/abandoned-cart.service.js.map +1 -0
  18. package/dist/hulo-storefront.d.ts +17 -0
  19. package/dist/hulo-storefront.d.ts.map +1 -0
  20. package/dist/hulo-storefront.js +289 -0
  21. package/dist/hulo-storefront.js.map +1 -0
  22. package/dist/index.d.ts +6 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +13 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/journey-buffs.controller.d.ts +43 -0
  27. package/dist/journey-buffs.controller.d.ts.map +1 -0
  28. package/dist/journey-buffs.controller.js +79 -0
  29. package/dist/journey-buffs.controller.js.map +1 -0
  30. package/dist/journey-buffs.service.d.ts +53 -0
  31. package/dist/journey-buffs.service.d.ts.map +1 -0
  32. package/dist/journey-buffs.service.js +141 -0
  33. package/dist/journey-buffs.service.js.map +1 -0
  34. package/dist/plugin.d.ts +5 -0
  35. package/dist/plugin.d.ts.map +1 -1
  36. package/dist/plugin.js +30 -3
  37. package/dist/plugin.js.map +1 -1
  38. package/dist/product-co-view.entity.d.ts +19 -0
  39. package/dist/product-co-view.entity.d.ts.map +1 -0
  40. package/dist/product-co-view.entity.js +52 -0
  41. package/dist/product-co-view.entity.js.map +1 -0
  42. package/dist/recommendations.controller.d.ts +72 -0
  43. package/dist/recommendations.controller.d.ts.map +1 -0
  44. package/dist/recommendations.controller.js +123 -0
  45. package/dist/recommendations.controller.js.map +1 -0
  46. package/dist/recommendations.service.d.ts +62 -0
  47. package/dist/recommendations.service.d.ts.map +1 -0
  48. package/dist/recommendations.service.js +167 -0
  49. package/dist/recommendations.service.js.map +1 -0
  50. package/dist/search-analytics.controller.d.ts +36 -0
  51. package/dist/search-analytics.controller.d.ts.map +1 -0
  52. package/dist/search-analytics.controller.js +86 -0
  53. package/dist/search-analytics.controller.js.map +1 -0
  54. package/dist/search-analytics.service.d.ts +36 -0
  55. package/dist/search-analytics.service.d.ts.map +1 -0
  56. package/dist/search-analytics.service.js +105 -0
  57. package/dist/search-analytics.service.js.map +1 -0
  58. package/dist/storefront-helper.controller.d.ts +16 -0
  59. package/dist/storefront-helper.controller.d.ts.map +1 -0
  60. package/dist/storefront-helper.controller.js +56 -0
  61. package/dist/storefront-helper.controller.js.map +1 -0
  62. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,46 +2,181 @@
2
2
 
3
3
  All notable changes to `@huloglobal/vendure-plugin-visitor-analytics` are
4
4
  documented here. The format follows
5
- [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this
6
- project adheres to [semantic versioning](https://semver.org/spec/v2.0.0.html).
5
+ [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project
6
+ adheres to [semantic versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [0.4.1]
8
+ ## [0.8.0] — 2026-07-04
9
+
10
+ ### Added
11
+
12
+ **Cart abandonment — end-to-end.**
13
+ - New `AbandonedCart` entity, keyed on session id. One row per
14
+ abandoned session, refreshed in place until it either converts
15
+ (order placed) or expires (recovery window elapses).
16
+ - `AbandonedCartService.scan()` — periodic sweep finds sessions with
17
+ `cart_snapshot` events but no `checkout_completed` in the
18
+ abandonment window (default 30 min). Auto-promotes previously
19
+ abandoned rows to `converted` when the customer later checks out.
20
+ - Ships with a boot-time timer (worker-only, 5-minute cadence).
21
+ Idempotent — safe to horizontally scale, only the worker runs it.
22
+ - Signed recovery links — `POST /ees/abandoned-carts/:id/recovery-link`
23
+ returns a time-bounded opaque token the storefront exchanges via
24
+ `GET /ees/recover-cart?t=…` to rebuild the exact cart. Storefront
25
+ never sees the underlying items until the token is presented.
26
+ - Slack notification for high-value abandonments — configurable
27
+ threshold and webhook via the `abandonment` plugin option.
28
+ - Admin API:
29
+ - `GET /ees/abandoned-carts` — paginated list with status,
30
+ value and email filters.
31
+ - `GET /ees/abandoned-carts/summary` — totals, recovery rate,
32
+ recovered vs. lost value in the window.
33
+ - `GET /ees/abandoned-carts/:id` — detail incl. parsed items.
34
+ - `POST /ees/abandoned-carts/:id/status` — mark recovered /
35
+ dismissed / re-open.
36
+ - `GET /ees/abandoned-carts/export.csv` — CSV export.
37
+
38
+ **Co-view product recommendations.**
39
+ - New `ProductCoView` aggregate table. Scanner walks recent
40
+ `product_view` events per session, extracts every ordered pair,
41
+ and increments a per-triple counter. Bounded to 20 events per
42
+ session so runaway bot sessions can't skew the table.
43
+ - Denormalised — both `(A, B)` and `(B, A)` stored — so read-side
44
+ lookups are one indexed scan.
45
+ - Runs every 6 hours on the worker; also exposed as
46
+ `GET /ees/recommendations/aggregate-now` for admins to kick a
47
+ fresh run after a data backfill.
48
+ - Public read endpoints (safe from the storefront):
49
+ - `GET /ees/recommendations/also-viewed?productId=…` — the
50
+ "customers who viewed X also viewed…" rail on a product page.
51
+ - `GET /ees/recommendations/personal?visitorId=…` — personalised
52
+ recs for a returning visitor, from their last 10 product views
53
+ over 30 days. Excludes seeds.
54
+ - `GET /ees/recommendations/trending?hours=24` — most-viewed
55
+ products in the window. Reflects real interest, not search-
56
+ console clicks.
57
+
58
+ **Site search analytics.**
59
+ - Reads back over `visitor_event` where the storefront has fired
60
+ `hulo.search(query, resultsCount)`. Zero new schema.
61
+ - `GET /ees/search-analytics/top` — top queries by volume with
62
+ average results count.
63
+ - `GET /ees/search-analytics/no-results` — top zero-result queries.
64
+ Direct catalogue-gap intel.
65
+ - `GET /ees/search-analytics/conversion` — of sessions that
66
+ searched, what fraction went on to fire `add_to_cart`.
67
+
68
+ **Journey drawer buffs.**
69
+ - Rage-click + dead-click aggregation, keyed on URL. Store-wide
70
+ hot-spot lists for pages where visitors are stuck or frustrated.
71
+ - Per-visitor session summary with a heuristic `intent` label
72
+ (`purchase` / `abandon` / `frustrate` / `consider` / `browse` /
73
+ `bounce`) computed from event history — one glance per session
74
+ in the Journey drawer instead of scrolling event rows.
75
+
76
+ **Storefront helper events (documented in the README).**
77
+ - `hulo.cartSnapshot({ currency, totalMinor, itemCount, items, email })`
78
+ - `hulo.productView(productId, productVariantId?)`
79
+ - `hulo.search(query, resultsCount)`
80
+ - `hulo.rageClick(url, selector?)` / `hulo.deadClick(url, selector)`
81
+ - `hulo.checkoutCompleted()`
82
+ - All flow into the existing `POST /ees/track` endpoint with a
83
+ standard shape, so admins can also fire them from any language.
84
+
85
+ ### Changed
86
+ - `checkout_completed` is now a first-class recognised event type
87
+ — the abandonment scanner uses it to auto-close matched rows.
88
+
89
+ ## [0.7.0] — 2026-07-04
90
+
91
+ ### Added
92
+ - Boot-time compatibility check via the new SDK helper
93
+ `warnIfIncompatibleVendure()`. Logs a non-fatal warning when the runtime
94
+ `@vendure/core` version is outside the tested range. Silent when inside;
95
+ fail-open on unparseable versions.
96
+
97
+ ### Changed
98
+ - Peer dep on `@vendure/core` tightened to `>=3.5.0 <4.0.0` — Vendure 3.5,
99
+ 3.6 and 3.7 are all covered. Anything under 3.5 has never been tested;
100
+ anything from 4.0 upwards is deferred until the changelog is reviewed.
101
+ - Uses `@huloglobal/vendure-licence-sdk@^0.6.0`.
102
+
103
+ ## [0.6.0] — 2026-06-23
104
+
105
+ ### Added
106
+ - Vendure Admin API GraphQL extensions. Operator endpoints are now
107
+ first-class GraphQL queries alongside the existing REST admin
108
+ endpoints: `huloVisitorSummary`, `huloVisitorSources`,
109
+ `huloVisitorTopPages`, `huloVisitorFunnel`, `huloVisitorJourney`.
110
+ - Storefront path (`POST /ees/track`) stays REST — it's anonymous,
111
+ high-frequency, and can ingest millions of events a day; the
112
+ resolver stack would add pointless overhead per event.
113
+
114
+ ## [0.5.0] — 2026-06-23
115
+
116
+ ### Added
117
+ - Tier-gating on every premium feature via the SDK's `isLicensed()`
118
+ helper. Unlicensed installs get:
119
+ - a hard 100 events / UTC day cap on `POST /ees/track` (returns
120
+ `{skipped: 'free-tier-cap'}` after that);
121
+ - live SSE feed 402;
122
+ - conversion-goal creation 402;
123
+ - CSV export 402.
124
+ - Anti-tamper heartbeat via the SDK.
125
+
126
+ ### Changed
127
+ - Relicensed the GitHub source to AGPL-3.0. Published npm builds remain
128
+ under the commercial licence documented at
129
+ <https://huloglobal.com/legal/terms/>.
130
+ - npm builds now include Sigstore provenance attestations.
131
+
132
+ ## [0.4.3] — 2026-06-21
133
+
134
+ ### Fixed
135
+ - Dropped the conflicting `display: block` on mobile tables that broke
136
+ the row / cell alignment.
137
+
138
+ ## [0.4.2] — 2026-06-21
139
+
140
+ ### Changed
141
+ - 44px minimum tap targets on every interactive element in the admin UI.
142
+
143
+ ## [0.4.1] — 2026-06-21
9
144
 
10
145
  ### Changed
11
146
  - Comprehensive README refresh — documents the full v0.4 feature set
12
147
  with the storefront snippet, every privacy + security option, and
13
148
  the conversion-goals API.
14
149
 
15
- ## [0.4.0]
150
+ ## [0.4.0] — 2026-06-20
16
151
 
17
152
  ### Added
18
153
  - Signed visitor + session cookies via the licence-sdk `signValue` /
19
154
  `verifySignedValue` helpers — tampered cookies are rejected.
20
155
  - `Secure` cookie flag is set automatically when serving over HTTPS.
21
- - Rate limiter (240/60s default) on `POST /ees/track`.
156
+ - Rate limiter (240 requests / 60s default) on `POST /ees/track`.
22
157
  - `corsAllowedOrigins` option restricts CORS reflection to the
23
158
  configured list (legacy wildcard preserved when empty).
24
159
  - Security headers on every response.
25
160
  - Opt-in retention sweeper via `options.retention`.
26
161
 
27
- ## [0.3.3]
162
+ ## [0.3.3] — 2026-06-20
28
163
 
29
164
  ### Changed
30
- - Mobile-friendly admin UI — summary cards reflow, tables scroll
31
- inside their card, profile drawer goes full-width.
165
+ - Mobile-friendly admin UI — summary cards reflow, tables scroll inside
166
+ their card, profile drawer goes full-width.
32
167
 
33
- ## [0.3.2]
168
+ ## [0.3.2] — 2026-06-20
34
169
 
35
170
  ### Changed
36
171
  - Republish targeting `@huloglobal/vendure-licence-sdk@^0.2.0`.
37
172
 
38
- ## [0.3.1]
173
+ ## [0.3.1] — 2026-06-20
39
174
 
40
175
  ### Added
41
176
  - `UpdateChecker` integration — `/ees/visitors/status` endpoint returns
42
177
  version + update info; admin banner appears on new releases.
43
178
 
44
- ## [0.3.0]
179
+ ## [0.3.0] — 2026-06-20
45
180
 
46
181
  ### Added
47
182
  - **Conversion goals** — new `ConversionGoal` entity with a URL-glob
@@ -62,7 +197,7 @@ project adheres to [semantic versioning](https://semver.org/spec/v2.0.0.html).
62
197
  `consent:true` or cookie `ees_consent=1`
63
198
  - **CSV export** — `GET /ees/visitors/export.csv?days=N` (max 90).
64
199
 
65
- ## [0.2.0]
200
+ ## [0.2.0] — 2026-06-19
66
201
 
67
202
  ### Added
68
203
  - **UTM attribution** — `utmSource` / `utmMedium` / `utmCampaign` /
@@ -78,7 +213,7 @@ project adheres to [semantic versioning](https://semver.org/spec/v2.0.0.html).
78
213
  - **Custom event recipes** — README section with copy-paste storefront
79
214
  snippets for add-to-cart, search, quote-request, newsletter signup.
80
215
 
81
- ## [0.1.0] — Unreleased
216
+ ## [0.1.0] — 2026-06-19
82
217
 
83
218
  ### Added
84
219
  - `VisitorAnalyticsPlugin` — ingest endpoint + admin dashboards.
@@ -92,5 +227,20 @@ project adheres to [semantic versioning](https://semver.org/spec/v2.0.0.html).
92
227
  profile + journey.
93
228
  - Admin UI: summary tiles, funnel bars, top + exit page tables,
94
229
  recent visitors table with clickable profile drawer.
95
- - Licence verification via `@huloglobal/vendure-licence-sdk` with revocation
96
- polling.
230
+ - Licence verification via `@huloglobal/vendure-licence-sdk` with
231
+ revocation polling.
232
+
233
+ [0.8.0]: https://github.com/exceeded/vendure-plugin-visitor-analytics/releases/tag/v0.8.0
234
+ [0.7.0]: https://github.com/exceeded/vendure-plugin-visitor-analytics/releases/tag/v0.7.0
235
+ [0.6.0]: https://github.com/exceeded/vendure-plugin-visitor-analytics/releases/tag/v0.6.0
236
+ [0.5.0]: https://github.com/exceeded/vendure-plugin-visitor-analytics/releases/tag/v0.5.0
237
+ [0.4.3]: https://github.com/exceeded/vendure-plugin-visitor-analytics/releases/tag/v0.4.3
238
+ [0.4.2]: https://github.com/exceeded/vendure-plugin-visitor-analytics/releases/tag/v0.4.2
239
+ [0.4.1]: https://github.com/exceeded/vendure-plugin-visitor-analytics/releases/tag/v0.4.1
240
+ [0.4.0]: https://github.com/exceeded/vendure-plugin-visitor-analytics/releases/tag/v0.4.0
241
+ [0.3.3]: https://github.com/exceeded/vendure-plugin-visitor-analytics/releases/tag/v0.3.3
242
+ [0.3.2]: https://github.com/exceeded/vendure-plugin-visitor-analytics/releases/tag/v0.3.2
243
+ [0.3.1]: https://github.com/exceeded/vendure-plugin-visitor-analytics/releases/tag/v0.3.1
244
+ [0.3.0]: https://github.com/exceeded/vendure-plugin-visitor-analytics/releases/tag/v0.3.0
245
+ [0.2.0]: https://github.com/exceeded/vendure-plugin-visitor-analytics/releases/tag/v0.2.0
246
+ [0.1.0]: https://github.com/exceeded/vendure-plugin-visitor-analytics/releases/tag/v0.1.0
@@ -0,0 +1,78 @@
1
+ import { RequestContext } from '@vendure/core';
2
+ import { Request, Response } from 'express';
3
+ import { AbandonedCartService } from './abandoned-cart.service';
4
+ /**
5
+ * Admin API for the Abandoned Cart feature.
6
+ *
7
+ * Endpoints:
8
+ * GET /ees/abandoned-carts — paginated list with filters
9
+ * GET /ees/abandoned-carts/summary — totals + top-value + recovery rate
10
+ * GET /ees/abandoned-carts/:id — detail incl. parsed items
11
+ * POST /ees/abandoned-carts/:id/recovery-link — mint/reissue signed URL
12
+ * POST /ees/abandoned-carts/:id/status — mark recovered/dismissed
13
+ * GET /ees/abandoned-carts/export.csv — CSV export
14
+ *
15
+ * Storefront-side (unauthenticated):
16
+ * GET /ees/recover-cart?t=... — resolve a token → items
17
+ */
18
+ export declare class AbandonedCartController {
19
+ private readonly service;
20
+ constructor(service: AbandonedCartService);
21
+ list(ctx: RequestContext, takeRaw?: string, skipRaw?: string, status?: string, minValueRaw?: string, email?: string): Promise<{
22
+ items: any;
23
+ total: number;
24
+ take: number;
25
+ skip: number;
26
+ }>;
27
+ summary(ctx: RequestContext, daysRaw?: string): Promise<{
28
+ windowDays: number;
29
+ total: number;
30
+ openCount: number;
31
+ recoveredCount: number;
32
+ convertedCount: number;
33
+ expiredCount: number;
34
+ dismissedCount: number;
35
+ recoveryRatePct: number;
36
+ totalValueMinor: number;
37
+ recoveredValueMinor: number;
38
+ avgValueMinor: number;
39
+ }>;
40
+ exportCsv(ctx: RequestContext, res: Response, daysRaw?: string): Promise<void>;
41
+ detail(ctx: RequestContext, idRaw: string): Promise<any>;
42
+ issueRecoveryLink(ctx: RequestContext, idRaw: string): Promise<{
43
+ error: string;
44
+ hint: string;
45
+ ok?: undefined;
46
+ url?: undefined;
47
+ } | {
48
+ ok: boolean;
49
+ url: string;
50
+ error?: undefined;
51
+ hint?: undefined;
52
+ }>;
53
+ setStatus(ctx: RequestContext, idRaw: string, body: {
54
+ status?: string;
55
+ }): Promise<{
56
+ error: string;
57
+ ok?: undefined;
58
+ } | {
59
+ ok: boolean;
60
+ error?: undefined;
61
+ }>;
62
+ /**
63
+ * Public storefront endpoint — decodes a recovery token into a set
64
+ * of cart items the storefront can restore. Rate-limited by the
65
+ * plugin's usual ingest limiter (same origin as the tracker).
66
+ */
67
+ recover(req: Request, token?: string): Promise<{
68
+ error: string;
69
+ } | {
70
+ id: number;
71
+ currency: string;
72
+ items: any[];
73
+ email: string | null;
74
+ ok: boolean;
75
+ error?: undefined;
76
+ }>;
77
+ }
78
+ //# sourceMappingURL=abandoned-cart.controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abandoned-cart.controller.d.ts","sourceRoot":"","sources":["../src/abandoned-cart.controller.ts"],"names":[],"mappings":"AACA,OAAO,EAAO,cAAc,EAAqB,MAAM,eAAe,CAAC;AACvE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAEhE;;;;;;;;;;;;;GAaG;AACH,qBACa,uBAAuB;IACpB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,oBAAoB;IAIpD,IAAI,CACC,GAAG,EAAE,cAAc,EACX,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,EACb,WAAW,CAAC,EAAE,MAAM,EACvB,KAAK,CAAC,EAAE,MAAM;;;;;;IAuC5B,OAAO,CAAQ,GAAG,EAAE,cAAc,EAAiB,OAAO,CAAC,EAAE,MAAM;;;;;;;;;;;;;IAuCnE,SAAS,CAAQ,GAAG,EAAE,cAAc,EAAS,GAAG,EAAE,QAAQ,EAAiB,OAAO,CAAC,EAAE,MAAM;IA+B3F,MAAM,CAAQ,GAAG,EAAE,cAAc,EAAe,KAAK,EAAE,MAAM;IAgB7D,iBAAiB,CAAQ,GAAG,EAAE,cAAc,EAAe,KAAK,EAAE,MAAM;;;;;;;;;;;IASxE,SAAS,CACJ,GAAG,EAAE,cAAc,EACb,KAAK,EAAE,MAAM,EAClB,IAAI,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;;;;;;;IAWrC;;;;OAIG;IAEG,OAAO,CAAQ,GAAG,EAAE,OAAO,EAAc,KAAK,CAAC,EAAE,MAAM;;;;;;;;;;CAOhE"}
@@ -0,0 +1,251 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.AbandonedCartController = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const core_1 = require("@vendure/core");
18
+ const abandoned_cart_service_1 = require("./abandoned-cart.service");
19
+ /**
20
+ * Admin API for the Abandoned Cart feature.
21
+ *
22
+ * Endpoints:
23
+ * GET /ees/abandoned-carts — paginated list with filters
24
+ * GET /ees/abandoned-carts/summary — totals + top-value + recovery rate
25
+ * GET /ees/abandoned-carts/:id — detail incl. parsed items
26
+ * POST /ees/abandoned-carts/:id/recovery-link — mint/reissue signed URL
27
+ * POST /ees/abandoned-carts/:id/status — mark recovered/dismissed
28
+ * GET /ees/abandoned-carts/export.csv — CSV export
29
+ *
30
+ * Storefront-side (unauthenticated):
31
+ * GET /ees/recover-cart?t=... — resolve a token → items
32
+ */
33
+ let AbandonedCartController = class AbandonedCartController {
34
+ constructor(service) {
35
+ this.service = service;
36
+ }
37
+ async list(ctx, takeRaw, skipRaw, status, minValueRaw, email) {
38
+ var _a;
39
+ const take = Math.min(Math.max(1, parseInt(takeRaw || '25', 10) || 25), 200);
40
+ const skip = Math.max(0, parseInt(skipRaw || '0', 10) || 0);
41
+ const where = [];
42
+ const params = [];
43
+ if (status) {
44
+ where.push('status = ?');
45
+ params.push(status);
46
+ }
47
+ if (minValueRaw) {
48
+ const v = parseInt(minValueRaw, 10);
49
+ if (Number.isFinite(v)) {
50
+ where.push('totalMinor >= ?');
51
+ params.push(v);
52
+ }
53
+ }
54
+ if (email) {
55
+ where.push('LOWER(email) LIKE ?');
56
+ params.push(`%${email.toLowerCase()}%`);
57
+ }
58
+ const clause = where.length ? `WHERE ${where.join(' AND ')}` : '';
59
+ const conn = this.service.connection.rawConnection;
60
+ const [rows, totalRow] = await Promise.all([
61
+ conn.query(`SELECT id, sessionId, visitorId, customerId, currency, totalMinor, itemCount,
62
+ email, status, abandonedAt, recoveredAt, notificationSent,
63
+ utmSource, utmMedium, countryCode
64
+ FROM abandoned_cart
65
+ ${clause}
66
+ ORDER BY abandonedAt DESC
67
+ LIMIT ? OFFSET ?`, [...params, take, skip]),
68
+ conn.query(`SELECT COUNT(*) AS c FROM abandoned_cart ${clause}`, params),
69
+ ]);
70
+ return {
71
+ items: rows,
72
+ total: Number(((_a = totalRow === null || totalRow === void 0 ? void 0 : totalRow[0]) === null || _a === void 0 ? void 0 : _a.c) || 0),
73
+ take, skip,
74
+ };
75
+ }
76
+ async summary(ctx, daysRaw) {
77
+ const days = Math.min(Math.max(1, parseInt(daysRaw || '30', 10) || 30), 365);
78
+ const since = new Date(Date.now() - days * 86400000);
79
+ const conn = this.service.connection.rawConnection;
80
+ const rows = await conn.query(`SELECT
81
+ COUNT(*) AS total,
82
+ SUM(status = 'abandoned') AS openCount,
83
+ SUM(status = 'recovered') AS recoveredCount,
84
+ SUM(status = 'converted') AS convertedCount,
85
+ SUM(status = 'expired') AS expiredCount,
86
+ SUM(status = 'dismissed') AS dismissedCount,
87
+ SUM(totalMinor) AS totalValueMinor,
88
+ SUM(CASE WHEN status IN ('recovered','converted') THEN totalMinor ELSE 0 END) AS recoveredValueMinor,
89
+ AVG(totalMinor) AS avgValueMinor
90
+ FROM abandoned_cart
91
+ WHERE abandonedAt >= ?`, [since]);
92
+ const s = (rows === null || rows === void 0 ? void 0 : rows[0]) || {};
93
+ const total = Number(s.total || 0);
94
+ const rec = Number(s.recoveredCount || 0) + Number(s.convertedCount || 0);
95
+ return {
96
+ windowDays: days,
97
+ total,
98
+ openCount: Number(s.openCount || 0),
99
+ recoveredCount: Number(s.recoveredCount || 0),
100
+ convertedCount: Number(s.convertedCount || 0),
101
+ expiredCount: Number(s.expiredCount || 0),
102
+ dismissedCount: Number(s.dismissedCount || 0),
103
+ recoveryRatePct: total ? Math.round((rec / total) * 1000) / 10 : 0,
104
+ totalValueMinor: Number(s.totalValueMinor || 0),
105
+ recoveredValueMinor: Number(s.recoveredValueMinor || 0),
106
+ avgValueMinor: Math.round(Number(s.avgValueMinor || 0)),
107
+ };
108
+ }
109
+ async exportCsv(ctx, res, daysRaw) {
110
+ const days = Math.min(Math.max(1, parseInt(daysRaw || '30', 10) || 30), 365);
111
+ const since = new Date(Date.now() - days * 86400000);
112
+ const conn = this.service.connection.rawConnection;
113
+ const rows = await conn.query(`SELECT id, sessionId, visitorId, customerId, currency, totalMinor, itemCount,
114
+ email, status, abandonedAt, recoveredAt, utmSource, utmMedium, utmCampaign, countryCode
115
+ FROM abandoned_cart
116
+ WHERE abandonedAt >= ?
117
+ ORDER BY abandonedAt DESC
118
+ LIMIT 50000`, [since]);
119
+ res.setHeader('content-type', 'text/csv; charset=utf-8');
120
+ res.setHeader('content-disposition', `attachment; filename="abandoned-carts-${new Date().toISOString().slice(0, 10)}.csv"`);
121
+ const esc = (v) => {
122
+ const s = v == null ? '' : String(v);
123
+ return /[,"\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
124
+ };
125
+ const cols = ['id', 'sessionId', 'visitorId', 'customerId', 'currency', 'totalMinor', 'itemCount',
126
+ 'email', 'status', 'abandonedAt', 'recoveredAt', 'utmSource', 'utmMedium', 'utmCampaign', 'countryCode'];
127
+ res.write(cols.join(',') + '\n');
128
+ for (const r of rows) {
129
+ res.write(cols.map(c => esc(r[c])).join(',') + '\n');
130
+ }
131
+ res.end();
132
+ }
133
+ async detail(ctx, idRaw) {
134
+ const id = parseInt(idRaw, 10);
135
+ const conn = this.service.connection.rawConnection;
136
+ const rows = await conn.query(`SELECT * FROM abandoned_cart WHERE id = ? LIMIT 1`, [id]);
137
+ if (!(rows === null || rows === void 0 ? void 0 : rows.length))
138
+ return { error: 'not-found' };
139
+ const r = rows[0];
140
+ let items = [];
141
+ try {
142
+ items = JSON.parse(r.itemsJson || '[]');
143
+ }
144
+ catch { }
145
+ return { ...r, items };
146
+ }
147
+ async issueRecoveryLink(ctx, idRaw) {
148
+ const id = parseInt(idRaw, 10);
149
+ const url = await this.service.issueRecoveryLink(id);
150
+ if (!url)
151
+ return { error: 'recovery-disabled-or-not-found', hint: 'Set abandonment.recoveryLinkSecret in plugin options' };
152
+ return { ok: true, url };
153
+ }
154
+ async setStatus(ctx, idRaw, body) {
155
+ const id = parseInt(idRaw, 10);
156
+ const status = String((body === null || body === void 0 ? void 0 : body.status) || '');
157
+ if (!['recovered', 'dismissed', 'abandoned'].includes(status)) {
158
+ return { error: 'invalid-status' };
159
+ }
160
+ const ok = await this.service.markStatus(id, status);
161
+ return ok ? { ok: true } : { error: 'not-found' };
162
+ }
163
+ /**
164
+ * Public storefront endpoint — decodes a recovery token into a set
165
+ * of cart items the storefront can restore. Rate-limited by the
166
+ * plugin's usual ingest limiter (same origin as the tracker).
167
+ */
168
+ async recover(req, token) {
169
+ const t = String(token || '').trim();
170
+ if (!t)
171
+ return { error: 'missing-token' };
172
+ const result = await this.service.findByRecoveryToken(t);
173
+ if (!result)
174
+ return { error: 'expired-or-invalid' };
175
+ return { ok: true, ...result };
176
+ }
177
+ };
178
+ exports.AbandonedCartController = AbandonedCartController;
179
+ __decorate([
180
+ (0, common_1.Get)('abandoned-carts'),
181
+ (0, core_1.Allow)(core_1.Permission.ReadCustomer),
182
+ __param(0, (0, core_1.Ctx)()),
183
+ __param(1, (0, common_1.Query)('take')),
184
+ __param(2, (0, common_1.Query)('skip')),
185
+ __param(3, (0, common_1.Query)('status')),
186
+ __param(4, (0, common_1.Query)('minValue')),
187
+ __param(5, (0, common_1.Query)('email')),
188
+ __metadata("design:type", Function),
189
+ __metadata("design:paramtypes", [core_1.RequestContext, String, String, String, String, String]),
190
+ __metadata("design:returntype", Promise)
191
+ ], AbandonedCartController.prototype, "list", null);
192
+ __decorate([
193
+ (0, common_1.Get)('abandoned-carts/summary'),
194
+ (0, core_1.Allow)(core_1.Permission.ReadCustomer),
195
+ __param(0, (0, core_1.Ctx)()),
196
+ __param(1, (0, common_1.Query)('days')),
197
+ __metadata("design:type", Function),
198
+ __metadata("design:paramtypes", [core_1.RequestContext, String]),
199
+ __metadata("design:returntype", Promise)
200
+ ], AbandonedCartController.prototype, "summary", null);
201
+ __decorate([
202
+ (0, common_1.Get)('abandoned-carts/export.csv'),
203
+ (0, core_1.Allow)(core_1.Permission.ReadCustomer),
204
+ __param(0, (0, core_1.Ctx)()),
205
+ __param(1, (0, common_1.Res)()),
206
+ __param(2, (0, common_1.Query)('days')),
207
+ __metadata("design:type", Function),
208
+ __metadata("design:paramtypes", [core_1.RequestContext, Object, String]),
209
+ __metadata("design:returntype", Promise)
210
+ ], AbandonedCartController.prototype, "exportCsv", null);
211
+ __decorate([
212
+ (0, common_1.Get)('abandoned-carts/:id'),
213
+ (0, core_1.Allow)(core_1.Permission.ReadCustomer),
214
+ __param(0, (0, core_1.Ctx)()),
215
+ __param(1, (0, common_1.Param)('id')),
216
+ __metadata("design:type", Function),
217
+ __metadata("design:paramtypes", [core_1.RequestContext, String]),
218
+ __metadata("design:returntype", Promise)
219
+ ], AbandonedCartController.prototype, "detail", null);
220
+ __decorate([
221
+ (0, common_1.Post)('abandoned-carts/:id/recovery-link'),
222
+ (0, core_1.Allow)(core_1.Permission.UpdateCustomer),
223
+ __param(0, (0, core_1.Ctx)()),
224
+ __param(1, (0, common_1.Param)('id')),
225
+ __metadata("design:type", Function),
226
+ __metadata("design:paramtypes", [core_1.RequestContext, String]),
227
+ __metadata("design:returntype", Promise)
228
+ ], AbandonedCartController.prototype, "issueRecoveryLink", null);
229
+ __decorate([
230
+ (0, common_1.Post)('abandoned-carts/:id/status'),
231
+ (0, core_1.Allow)(core_1.Permission.UpdateCustomer),
232
+ __param(0, (0, core_1.Ctx)()),
233
+ __param(1, (0, common_1.Param)('id')),
234
+ __param(2, (0, common_1.Body)()),
235
+ __metadata("design:type", Function),
236
+ __metadata("design:paramtypes", [core_1.RequestContext, String, Object]),
237
+ __metadata("design:returntype", Promise)
238
+ ], AbandonedCartController.prototype, "setStatus", null);
239
+ __decorate([
240
+ (0, common_1.Get)('recover-cart'),
241
+ __param(0, (0, common_1.Req)()),
242
+ __param(1, (0, common_1.Query)('t')),
243
+ __metadata("design:type", Function),
244
+ __metadata("design:paramtypes", [Object, String]),
245
+ __metadata("design:returntype", Promise)
246
+ ], AbandonedCartController.prototype, "recover", null);
247
+ exports.AbandonedCartController = AbandonedCartController = __decorate([
248
+ (0, common_1.Controller)('ees'),
249
+ __metadata("design:paramtypes", [abandoned_cart_service_1.AbandonedCartService])
250
+ ], AbandonedCartController);
251
+ //# sourceMappingURL=abandoned-cart.controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abandoned-cart.controller.js","sourceRoot":"","sources":["../src/abandoned-cart.controller.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ export type AbandonedCartStatus = 'abandoned' | 'recovered' | 'converted' | 'expired' | 'dismissed';
2
+ /**
3
+ * One row per abandoned cart, keyed on the visitor session that produced
4
+ * it. Rebuilt fresh from `cart_snapshot` events by the periodic scanner,
5
+ * so a session can be updated in-place until it either converts (order
6
+ * placed) or expires (recovery window elapses without any action).
7
+ */
8
+ export declare class AbandonedCart {
9
+ id: number;
10
+ visitorId: string;
11
+ sessionId: string;
12
+ /** Set when the visitor is signed in. Guest carts stay NULL and are
13
+ * matched to a customer only if they later sign in (via the same
14
+ * visitorId) or check out with a linkable email. */
15
+ customerId: number | null;
16
+ channelId: number;
17
+ currency: string;
18
+ /** Total value in the smallest currency unit (pence/cents). Storing
19
+ * minor units avoids float drift and matches Vendure's convention. */
20
+ totalMinor: number;
21
+ itemCount: number;
22
+ /** JSON array of cart items:
23
+ * [{ variantId, name, qty, unitPriceMinor, productId?, sku? }, …]
24
+ * Held as text because the recovery link needs to reconstruct the
25
+ * cart exactly — we don't want to join back to a live catalog that
26
+ * may have changed price by the time recovery is attempted. */
27
+ itemsJson: string;
28
+ /** Plain email if the storefront passed one (checkout step 1 usually
29
+ * captures this). NULL for cold browsers. */
30
+ email: string | null;
31
+ /** SHA-256 of the lowercased email — safe to keep even under
32
+ * aggressive privacy defaults, and used to link recurring sessions
33
+ * from the same buyer without exposing the raw address. */
34
+ emailHash: string | null;
35
+ firstSnapshotAt: Date;
36
+ lastSnapshotAt: Date;
37
+ /** Moment the scanner promoted the session from "active" to
38
+ * "abandoned" — i.e. no checkout_completed and no fresh
39
+ * cart_snapshot within the abandonment window. */
40
+ abandonedAt: Date;
41
+ recoveredAt: Date | null;
42
+ /** Signed opaque token that recovers the cart on the storefront.
43
+ * Regenerated by the admin/recover endpoint; time-bounded. */
44
+ recoveryToken: string | null;
45
+ recoveryTokenExpiresAt: Date | null;
46
+ status: AbandonedCartStatus;
47
+ recoveryOrderId: number | null;
48
+ lastKnownUrl: string | null;
49
+ lastKnownReferrer: string | null;
50
+ utmSource: string | null;
51
+ utmMedium: string | null;
52
+ utmCampaign: string | null;
53
+ countryCode: string | null;
54
+ /** True once we've fired the Slack / email notification for this
55
+ * abandonment. Prevents duplicate notifications when the scanner
56
+ * re-runs. */
57
+ notificationSent: boolean;
58
+ createdAt: Date;
59
+ updatedAt: Date;
60
+ }
61
+ //# sourceMappingURL=abandoned-cart.entity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abandoned-cart.entity.d.ts","sourceRoot":"","sources":["../src/abandoned-cart.entity.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,mBAAmB,GACzB,WAAW,GACX,WAAW,GACX,WAAW,GACX,SAAS,GACT,WAAW,CAAC;AAElB;;;;;GAKG;AACH,qBAMa,aAAa;IAEtB,EAAE,EAAG,MAAM,CAAC;IAGZ,SAAS,EAAG,MAAM,CAAC;IAGnB,SAAS,EAAG,MAAM,CAAC;IAEnB;;yDAEqD;IAErD,UAAU,EAAG,MAAM,GAAG,IAAI,CAAC;IAG3B,SAAS,EAAG,MAAM,CAAC;IAGnB,QAAQ,EAAG,MAAM,CAAC;IAElB;2EACuE;IAEvE,UAAU,EAAG,MAAM,CAAC;IAGpB,SAAS,EAAG,MAAM,CAAC;IAEnB;;;;oEAIgE;IAEhE,SAAS,EAAG,MAAM,CAAC;IAEnB;kDAC8C;IAE9C,KAAK,EAAG,MAAM,GAAG,IAAI,CAAC;IAEtB;;gEAE4D;IAE5D,SAAS,EAAG,MAAM,GAAG,IAAI,CAAC;IAG1B,eAAe,EAAG,IAAI,CAAC;IAGvB,cAAc,EAAG,IAAI,CAAC;IAEtB;;uDAEmD;IAEnD,WAAW,EAAG,IAAI,CAAC;IAGnB,WAAW,EAAG,IAAI,GAAG,IAAI,CAAC;IAE1B;mEAC+D;IAE/D,aAAa,EAAG,MAAM,GAAG,IAAI,CAAC;IAG9B,sBAAsB,EAAG,IAAI,GAAG,IAAI,CAAC;IAGrC,MAAM,EAAG,mBAAmB,CAAC;IAG7B,eAAe,EAAG,MAAM,GAAG,IAAI,CAAC;IAGhC,YAAY,EAAG,MAAM,GAAG,IAAI,CAAC;IAG7B,iBAAiB,EAAG,MAAM,GAAG,IAAI,CAAC;IAGlC,SAAS,EAAG,MAAM,GAAG,IAAI,CAAC;IAG1B,SAAS,EAAG,MAAM,GAAG,IAAI,CAAC;IAG1B,WAAW,EAAG,MAAM,GAAG,IAAI,CAAC;IAG5B,WAAW,EAAG,MAAM,GAAG,IAAI,CAAC;IAE5B;;mBAEe;IAEf,gBAAgB,EAAG,OAAO,CAAC;IAG3B,SAAS,EAAG,IAAI,CAAC;IAGjB,SAAS,EAAG,IAAI,CAAC;CACpB"}