@pvh-afl/core 1.2.36 → 1.2.38

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 (48) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +2 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/integrations/engagement/index.d.ts +3 -0
  6. package/dist/integrations/engagement/index.d.ts.map +1 -0
  7. package/dist/integrations/engagement/index.js +6 -0
  8. package/dist/integrations/engagement/index.js.map +1 -0
  9. package/dist/integrations/engagement/netcore-email.service.d.ts +32 -0
  10. package/dist/integrations/engagement/netcore-email.service.d.ts.map +1 -0
  11. package/dist/integrations/engagement/netcore-email.service.js +184 -0
  12. package/dist/integrations/engagement/netcore-email.service.js.map +1 -0
  13. package/dist/integrations/engagement/netcore-email.types.d.ts +74 -0
  14. package/dist/integrations/engagement/netcore-email.types.d.ts.map +1 -0
  15. package/dist/integrations/engagement/netcore-email.types.js +3 -0
  16. package/dist/integrations/engagement/netcore-email.types.js.map +1 -0
  17. package/dist/integrations/index.d.ts +1 -0
  18. package/dist/integrations/index.d.ts.map +1 -1
  19. package/dist/integrations/index.js +2 -0
  20. package/dist/integrations/index.js.map +1 -1
  21. package/dist/integrations/integrations.module.d.ts.map +1 -1
  22. package/dist/integrations/integrations.module.js +5 -0
  23. package/dist/integrations/integrations.module.js.map +1 -1
  24. package/dist/notifications/index.d.ts +6 -0
  25. package/dist/notifications/index.d.ts.map +1 -0
  26. package/dist/notifications/index.js +9 -0
  27. package/dist/notifications/index.js.map +1 -0
  28. package/dist/notifications/notifications.module.d.ts +9 -0
  29. package/dist/notifications/notifications.module.d.ts.map +1 -0
  30. package/dist/notifications/notifications.module.js +25 -0
  31. package/dist/notifications/notifications.module.js.map +1 -0
  32. package/dist/notifications/order-email.mapper.d.ts +15 -0
  33. package/dist/notifications/order-email.mapper.d.ts.map +1 -0
  34. package/dist/notifications/order-email.mapper.js +131 -0
  35. package/dist/notifications/order-email.mapper.js.map +1 -0
  36. package/dist/notifications/order-email.service.d.ts +30 -0
  37. package/dist/notifications/order-email.service.d.ts.map +1 -0
  38. package/dist/notifications/order-email.service.js +151 -0
  39. package/dist/notifications/order-email.service.js.map +1 -0
  40. package/dist/notifications/order-notification.types.d.ts +41 -0
  41. package/dist/notifications/order-notification.types.d.ts.map +1 -0
  42. package/dist/notifications/order-notification.types.js +20 -0
  43. package/dist/notifications/order-notification.types.js.map +1 -0
  44. package/dist/notifications/order.source.d.ts +47 -0
  45. package/dist/notifications/order.source.d.ts.map +1 -0
  46. package/dist/notifications/order.source.js +165 -0
  47. package/dist/notifications/order.source.js.map +1 -0
  48. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -12,4 +12,5 @@ export * from './integrations';
12
12
  export * from './queue';
13
13
  export * from './messaging';
14
14
  export * from './theme';
15
+ export * from './notifications';
15
16
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,cAAc,QAAQ,CAAC;AAGvB,cAAc,SAAS,CAAC;AAGxB,cAAc,UAAU,CAAC;AAGzB,cAAc,UAAU,CAAC;AAGzB,cAAc,WAAW,CAAC;AAG1B,cAAc,gBAAgB,CAAC;AAG/B,cAAc,SAAS,CAAC;AAGxB,cAAc,aAAa,CAAC;AAG5B,cAAc,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,cAAc,QAAQ,CAAC;AAGvB,cAAc,SAAS,CAAC;AAGxB,cAAc,UAAU,CAAC;AAGzB,cAAc,UAAU,CAAC;AAGzB,cAAc,WAAW,CAAC;AAG1B,cAAc,gBAAgB,CAAC;AAG/B,cAAc,SAAS,CAAC;AAGxB,cAAc,aAAa,CAAC;AAG5B,cAAc,SAAS,CAAC;AAGxB,cAAc,iBAAiB,CAAC"}
package/dist/index.js CHANGED
@@ -24,4 +24,6 @@ tslib_1.__exportStar(require("./queue"), exports);
24
24
  tslib_1.__exportStar(require("./messaging"), exports);
25
25
  // Theme (placeholder)
26
26
  tslib_1.__exportStar(require("./theme"), exports);
27
+ // Order notifications (shared across brands)
28
+ tslib_1.__exportStar(require("./notifications"), exports);
27
29
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;AAEH,OAAO;AACP,iDAAuB;AAEvB,QAAQ;AACR,kDAAwB;AAExB,SAAS;AACT,mDAAyB;AAEzB,mBAAmB;AACnB,mDAAyB;AAEzB,2EAA2E;AAC3E,oDAA0B;AAE1B,eAAe;AACf,yDAA+B;AAE/B,sBAAsB;AACtB,kDAAwB;AAExB,0CAA0C;AAC1C,sDAA4B;AAE5B,sBAAsB;AACtB,kDAAwB"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;AAEH,OAAO;AACP,iDAAuB;AAEvB,QAAQ;AACR,kDAAwB;AAExB,SAAS;AACT,mDAAyB;AAEzB,mBAAmB;AACnB,mDAAyB;AAEzB,2EAA2E;AAC3E,oDAA0B;AAE1B,eAAe;AACf,yDAA+B;AAE/B,sBAAsB;AACtB,kDAAwB;AAExB,0CAA0C;AAC1C,sDAA4B;AAE5B,sBAAsB;AACtB,kDAAwB;AAExB,6CAA6C;AAC7C,0DAAgC"}
@@ -0,0 +1,3 @@
1
+ export * from './netcore-email.types';
2
+ export * from './netcore-email.service';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/integrations/engagement/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC"}
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ tslib_1.__exportStar(require("./netcore-email.types"), exports);
5
+ tslib_1.__exportStar(require("./netcore-email.service"), exports);
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/integrations/engagement/index.ts"],"names":[],"mappings":";;;AAAA,gEAAsC;AACtC,kEAAwC"}
@@ -0,0 +1,32 @@
1
+ import { IntegrationResponse } from '../integration.types';
2
+ import { IEmailService, NetcoreConfig, NetcoreSendRequest, NetcoreSendResult } from './netcore-email.types';
3
+ /**
4
+ * Netcore (ex-Pepipost) Email API service.
5
+ *
6
+ * Sends transactional email. Auth is an `api_key` header, so this does not
7
+ * extend BaseIntegrationService — that helper forces `Authorization: Bearer`.
8
+ *
9
+ * A send is accepted with 202 and a `message_id`; acceptance means Netcore
10
+ * queued the mail, NOT that it reached the inbox. Delivery outcome arrives on
11
+ * Netcore's own webhooks/reports.
12
+ *
13
+ * @see https://cpaasdocs.netcore.ai/docs/pepipost-api/c2d895762f832-send-an-email
14
+ */
15
+ export declare class NetcoreEmailService implements IEmailService {
16
+ private config;
17
+ private brand;
18
+ private readonly defaultTimeout;
19
+ configure(brand: string, config: NetcoreConfig): void;
20
+ isConfigured(): boolean;
21
+ /**
22
+ * Send one email.
23
+ *
24
+ * Requires `templateId` or `content` — Netcore has nothing to render
25
+ * otherwise, and rejects the request.
26
+ */
27
+ send(request: NetcoreSendRequest): Promise<IntegrationResponse<NetcoreSendResult>>;
28
+ private parseBody;
29
+ private notConfiguredError;
30
+ private normalizeError;
31
+ }
32
+ //# sourceMappingURL=netcore-email.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"netcore-email.service.d.ts","sourceRoot":"","sources":["../../../src/integrations/engagement/netcore-email.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAoB,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EACL,aAAa,EACb,aAAa,EACb,kBAAkB,EAClB,iBAAiB,EAClB,MAAM,uBAAuB,CAAC;AAa/B;;;;;;;;;;;GAWG;AACH,qBACa,mBAAoB,YAAW,aAAa;IACvD,OAAO,CAAC,MAAM,CAA8B;IAC5C,OAAO,CAAC,KAAK,CAAM;IACnB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IAExC,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,IAAI;IAgBrD,YAAY,IAAI,OAAO;IAIvB;;;;;OAKG;IACG,IAAI,CACR,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,mBAAmB,CAAC,iBAAiB,CAAC,CAAC;IAiHlD,OAAO,CAAC,SAAS;IAQjB,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,cAAc;CAiBvB"}
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NetcoreEmailService = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const common_1 = require("@nestjs/common");
6
+ const logger_1 = require("../../common/logger");
7
+ const DEFAULT_BASE_URL = 'https://emailapi.netcorecloud.net';
8
+ const SEND_PATH = '/v5.1/mail/send';
9
+ /**
10
+ * Netcore (ex-Pepipost) Email API service.
11
+ *
12
+ * Sends transactional email. Auth is an `api_key` header, so this does not
13
+ * extend BaseIntegrationService — that helper forces `Authorization: Bearer`.
14
+ *
15
+ * A send is accepted with 202 and a `message_id`; acceptance means Netcore
16
+ * queued the mail, NOT that it reached the inbox. Delivery outcome arrives on
17
+ * Netcore's own webhooks/reports.
18
+ *
19
+ * @see https://cpaasdocs.netcore.ai/docs/pepipost-api/c2d895762f832-send-an-email
20
+ */
21
+ let NetcoreEmailService = class NetcoreEmailService {
22
+ config = null;
23
+ brand = '';
24
+ defaultTimeout = 10000;
25
+ configure(brand, config) {
26
+ this.brand = brand;
27
+ // Spread first, then apply defaults with `??`. A caller that passes the key
28
+ // explicitly as undefined (a brand config omitting `baseUrl`) would
29
+ // otherwise overwrite the default and produce `undefined/v5.1/mail/send`.
30
+ this.config = {
31
+ ...config,
32
+ baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,
33
+ timeout: config.timeout ?? this.defaultTimeout,
34
+ };
35
+ logger_1.logger.info('Netcore email service configured', {
36
+ brand,
37
+ fromEmail: config.fromEmail,
38
+ });
39
+ }
40
+ isConfigured() {
41
+ return this.config !== null;
42
+ }
43
+ /**
44
+ * Send one email.
45
+ *
46
+ * Requires `templateId` or `content` — Netcore has nothing to render
47
+ * otherwise, and rejects the request.
48
+ */
49
+ async send(request) {
50
+ if (!this.config) {
51
+ return this.notConfiguredError();
52
+ }
53
+ const recipients = request.personalizations?.some((p) => p.to?.length);
54
+ if (!recipients) {
55
+ return {
56
+ success: false,
57
+ error: {
58
+ code: 'MISSING_RECIPIENT',
59
+ message: 'Netcore send requires at least one recipient',
60
+ },
61
+ };
62
+ }
63
+ if (!request.templateId && !request.content?.length) {
64
+ return {
65
+ success: false,
66
+ error: {
67
+ code: 'MISSING_BODY',
68
+ message: 'Netcore send requires either templateId or content',
69
+ },
70
+ };
71
+ }
72
+ const body = {
73
+ from: request.from ?? {
74
+ email: this.config.fromEmail,
75
+ name: this.config.fromName,
76
+ },
77
+ ...(request.replyTo || this.config.replyTo
78
+ ? { reply_to: request.replyTo ?? this.config.replyTo }
79
+ : {}),
80
+ subject: request.subject,
81
+ ...(request.templateId ? { template_id: request.templateId } : {}),
82
+ ...(request.content?.length ? { content: request.content } : {}),
83
+ ...(request.tags?.length ? { tags: request.tags } : {}),
84
+ personalizations: request.personalizations.map((p) => ({
85
+ to: p.to,
86
+ ...(p.cc?.length ? { cc: p.cc } : {}),
87
+ ...(p.bcc?.length ? { bcc: p.bcc } : {}),
88
+ ...(p.attributes ? { attributes: p.attributes } : {}),
89
+ })),
90
+ ...(request.settings ? { settings: request.settings } : {}),
91
+ ...(request.schedule ? { schedule: request.schedule } : {}),
92
+ };
93
+ try {
94
+ const controller = new AbortController();
95
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout ?? this.defaultTimeout);
96
+ const response = await fetch(`${this.config.baseUrl}${SEND_PATH}`, {
97
+ method: 'POST',
98
+ headers: {
99
+ 'Content-Type': 'application/json',
100
+ Accept: 'application/json',
101
+ api_key: this.config.apiKey,
102
+ },
103
+ body: JSON.stringify(body),
104
+ signal: controller.signal,
105
+ });
106
+ clearTimeout(timeoutId);
107
+ const raw = await response.text();
108
+ if (!response.ok) {
109
+ return { success: false, error: this.normalizeError(response.status, raw) };
110
+ }
111
+ const parsed = this.parseBody(raw);
112
+ // A 2xx can still carry status:"error" — treat only an explicit success as sent.
113
+ if (parsed?.status && parsed.status !== 'success') {
114
+ return {
115
+ success: false,
116
+ error: {
117
+ code: 'SEND_REJECTED',
118
+ message: parsed.message ?? 'Netcore rejected the send',
119
+ details: parsed,
120
+ },
121
+ };
122
+ }
123
+ const messageId = parsed?.data?.message_id;
124
+ if (!messageId) {
125
+ logger_1.logger.warn('Netcore accepted send without a message_id', {
126
+ brand: this.brand,
127
+ });
128
+ }
129
+ return { success: true, data: { messageId: messageId ?? '' } };
130
+ }
131
+ catch (error) {
132
+ logger_1.logger.error('Netcore send failed', {
133
+ brand: this.brand,
134
+ error: error instanceof Error ? error.message : 'Unknown error',
135
+ });
136
+ return {
137
+ success: false,
138
+ error: {
139
+ code: 'REQUEST_FAILED',
140
+ message: error instanceof Error ? error.message : 'Request failed',
141
+ details: error,
142
+ },
143
+ };
144
+ }
145
+ }
146
+ parseBody(raw) {
147
+ try {
148
+ return raw ? JSON.parse(raw) : null;
149
+ }
150
+ catch {
151
+ return null;
152
+ }
153
+ }
154
+ notConfiguredError() {
155
+ return {
156
+ success: false,
157
+ error: {
158
+ code: 'NOT_CONFIGURED',
159
+ message: 'Netcore email service is not configured',
160
+ },
161
+ };
162
+ }
163
+ normalizeError(status, body) {
164
+ const errorMap = {
165
+ 400: 'BAD_REQUEST',
166
+ 401: 'UNAUTHORIZED',
167
+ 403: 'FORBIDDEN',
168
+ 404: 'NOT_FOUND',
169
+ 429: 'RATE_LIMITED',
170
+ 500: 'INTERNAL_ERROR',
171
+ 502: 'BAD_GATEWAY',
172
+ 503: 'SERVICE_UNAVAILABLE',
173
+ };
174
+ return {
175
+ code: errorMap[status] ?? 'UNKNOWN_ERROR',
176
+ message: body || `HTTP ${status}`,
177
+ };
178
+ }
179
+ };
180
+ exports.NetcoreEmailService = NetcoreEmailService;
181
+ exports.NetcoreEmailService = NetcoreEmailService = tslib_1.__decorate([
182
+ (0, common_1.Injectable)()
183
+ ], NetcoreEmailService);
184
+ //# sourceMappingURL=netcore-email.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"netcore-email.service.js","sourceRoot":"","sources":["../../../src/integrations/engagement/netcore-email.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ import { IntegrationResponse } from '../integration.types';
2
+ /**
3
+ * Netcore (ex-Pepipost) Email API types.
4
+ *
5
+ * @see https://cpaasdocs.netcore.ai/docs/pepipost-api/c2d895762f832-send-an-email
6
+ */
7
+ export interface NetcoreConfig {
8
+ /** API host (default: https://emailapi.netcorecloud.net). EU accounts use https://apieu.netcorecloud.net */
9
+ baseUrl?: string;
10
+ /** Email API key, from Settings -> Integrations in the Netcore account */
11
+ apiKey: string;
12
+ /** Default sender. The domain must be active in the Netcore account or the send is rejected. */
13
+ fromEmail: string;
14
+ fromName?: string;
15
+ replyTo?: string;
16
+ /** Request timeout in milliseconds (default: 10000) */
17
+ timeout?: number;
18
+ }
19
+ export interface NetcoreRecipient {
20
+ email: string;
21
+ name?: string;
22
+ }
23
+ export interface NetcorePersonalization {
24
+ to: NetcoreRecipient[];
25
+ cc?: NetcoreRecipient[];
26
+ bcc?: NetcoreRecipient[];
27
+ /**
28
+ * Values substituted into `[%KEY%]` placeholders in the subject and template.
29
+ * Flat key/value only — Netcore cannot iterate, so a repeating block (order
30
+ * line items) must be pre-rendered into a single attribute by the caller.
31
+ */
32
+ attributes?: Record<string, string>;
33
+ }
34
+ export interface NetcoreContent {
35
+ type: 'html' | 'amp';
36
+ value: string;
37
+ }
38
+ export interface NetcoreSettings {
39
+ open_track?: boolean;
40
+ click_track?: boolean;
41
+ unsubscribe_track?: boolean;
42
+ ip_pool?: string;
43
+ }
44
+ /**
45
+ * One send request.
46
+ *
47
+ * Supply `templateId` to use a Netcore-hosted template, or `content` to send
48
+ * HTML inline. The API documents `content` as required, so a caller passing
49
+ * only `templateId` should verify its account accepts that before relying on it.
50
+ */
51
+ export interface NetcoreSendRequest {
52
+ subject: string;
53
+ personalizations: NetcorePersonalization[];
54
+ templateId?: number;
55
+ content?: NetcoreContent[];
56
+ /** Groups sends for per-segment delivery reporting in the Netcore dashboard. */
57
+ tags?: string[];
58
+ /** Overrides the configured default sender. */
59
+ from?: NetcoreRecipient;
60
+ replyTo?: string;
61
+ settings?: NetcoreSettings;
62
+ /** Unix epoch seconds; Netcore rejects times beyond 72 hours out. */
63
+ schedule?: number;
64
+ }
65
+ export interface NetcoreSendResult {
66
+ messageId: string;
67
+ }
68
+ /** Contract for the email channel. WhatsApp and SMS have their own shapes. */
69
+ export interface IEmailService {
70
+ configure(brand: string, config: NetcoreConfig): void;
71
+ isConfigured(): boolean;
72
+ send(request: NetcoreSendRequest): Promise<IntegrationResponse<NetcoreSendResult>>;
73
+ }
74
+ //# sourceMappingURL=netcore-email.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"netcore-email.types.d.ts","sourceRoot":"","sources":["../../../src/integrations/engagement/netcore-email.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAE3D;;;;GAIG;AAEH,MAAM,WAAW,aAAa;IAC5B,4GAA4G;IAC5G,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;IACf,gGAAgG;IAChG,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,gBAAgB,EAAE,CAAC;IACvB,EAAE,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACxB,GAAG,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACzB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,sBAAsB,EAAE,CAAC;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,cAAc,EAAE,CAAC;IAC3B,gFAAgF;IAChF,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,+CAA+C;IAC/C,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,8EAA8E;AAC9E,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,IAAI,CAAC;IACtD,YAAY,IAAI,OAAO,CAAC;IACxB,IAAI,CACF,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,mBAAmB,CAAC,iBAAiB,CAAC,CAAC,CAAC;CACpD"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=netcore-email.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"netcore-email.types.js","sourceRoot":"","sources":["../../../src/integrations/engagement/netcore-email.types.ts"],"names":[],"mappings":""}
@@ -8,6 +8,7 @@ export * from './search';
8
8
  export * from './reviews';
9
9
  export * from './swish';
10
10
  export * from './storage';
11
+ export * from './engagement';
11
12
  export * from './integration.factory';
12
13
  export * from './integrations.module';
13
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/integrations/index.ts"],"names":[],"mappings":"AACA,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAG3C,cAAc,YAAY,CAAC;AAG3B,cAAc,OAAO,CAAC;AAGtB,cAAc,WAAW,CAAC;AAG1B,cAAc,iBAAiB,CAAC;AAGhC,cAAc,UAAU,CAAC;AAGzB,cAAc,WAAW,CAAC;AAG1B,cAAc,SAAS,CAAC;AAGxB,cAAc,WAAW,CAAC;AAG1B,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/integrations/index.ts"],"names":[],"mappings":"AACA,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAG3C,cAAc,YAAY,CAAC;AAG3B,cAAc,OAAO,CAAC;AAGtB,cAAc,WAAW,CAAC;AAG1B,cAAc,iBAAiB,CAAC;AAGhC,cAAc,UAAU,CAAC;AAGzB,cAAc,WAAW,CAAC;AAG1B,cAAc,SAAS,CAAC;AAGxB,cAAc,WAAW,CAAC;AAG1B,cAAc,cAAc,CAAC;AAG7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC"}
@@ -20,6 +20,8 @@ tslib_1.__exportStar(require("./reviews"), exports);
20
20
  tslib_1.__exportStar(require("./swish"), exports);
21
21
  // Storage integrations (Azure Blob)
22
22
  tslib_1.__exportStar(require("./storage"), exports);
23
+ // Engagement integrations (Netcore email)
24
+ tslib_1.__exportStar(require("./engagement"), exports);
23
25
  // Integration factory and module
24
26
  tslib_1.__exportStar(require("./integration.factory"), exports);
25
27
  tslib_1.__exportStar(require("./integrations.module"), exports);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/integrations/index.ts"],"names":[],"mappings":";;;AAAA,sCAAsC;AACtC,8DAAoC;AACpC,qEAA2C;AAE3C,kCAAkC;AAClC,qDAA2B;AAE3B,2BAA2B;AAC3B,gDAAsB;AAEtB,mCAAmC;AACnC,oDAA0B;AAE1B,oBAAoB;AACpB,0DAAgC;AAEhC,8BAA8B;AAC9B,mDAAyB;AAEzB,kCAAkC;AAClC,oDAA0B;AAE1B,gCAAgC;AAChC,kDAAwB;AAExB,oCAAoC;AACpC,oDAA0B;AAE1B,iCAAiC;AACjC,gEAAsC;AACtC,gEAAsC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/integrations/index.ts"],"names":[],"mappings":";;;AAAA,sCAAsC;AACtC,8DAAoC;AACpC,qEAA2C;AAE3C,kCAAkC;AAClC,qDAA2B;AAE3B,2BAA2B;AAC3B,gDAAsB;AAEtB,mCAAmC;AACnC,oDAA0B;AAE1B,oBAAoB;AACpB,0DAAgC;AAEhC,8BAA8B;AAC9B,mDAAyB;AAEzB,kCAAkC;AAClC,oDAA0B;AAE1B,gCAAgC;AAChC,kDAAwB;AAExB,oCAAoC;AACpC,oDAA0B;AAE1B,0CAA0C;AAC1C,uDAA6B;AAE7B,iCAAiC;AACjC,gEAAsC;AACtC,gEAAsC"}
@@ -1 +1 @@
1
- {"version":3,"file":"integrations.module.d.ts","sourceRoot":"","sources":["../../src/integrations/integrations.module.ts"],"names":[],"mappings":"AA0BA,qBA0Ca,kBAAkB;CAAG"}
1
+ {"version":3,"file":"integrations.module.d.ts","sourceRoot":"","sources":["../../src/integrations/integrations.module.ts"],"names":[],"mappings":"AA4BA,qBA6Ca,kBAAkB;CAAG"}
@@ -24,6 +24,8 @@ const swish_service_1 = require("./swish/swish.service");
24
24
  // Storage
25
25
  const azure_blob_service_1 = require("./storage/azure-blob.service");
26
26
  const shopify_files_service_1 = require("./storage/shopify-files.service");
27
+ // Engagement
28
+ const netcore_email_service_1 = require("./engagement/netcore-email.service");
27
29
  // CacheModule and ConfigModule are @Global() so no import needed
28
30
  let IntegrationsModule = class IntegrationsModule {
29
31
  };
@@ -53,6 +55,8 @@ exports.IntegrationsModule = IntegrationsModule = tslib_1.__decorate([
53
55
  // Storage
54
56
  azure_blob_service_1.AzureBlobService,
55
57
  shopify_files_service_1.ShopifyFilesService,
58
+ // Engagement
59
+ netcore_email_service_1.NetcoreEmailService,
56
60
  ],
57
61
  exports: [
58
62
  integration_factory_1.IntegrationFactory,
@@ -69,6 +73,7 @@ exports.IntegrationsModule = IntegrationsModule = tslib_1.__decorate([
69
73
  swish_service_1.SwishService,
70
74
  azure_blob_service_1.AzureBlobService,
71
75
  shopify_files_service_1.ShopifyFilesService,
76
+ netcore_email_service_1.NetcoreEmailService,
72
77
  ],
73
78
  })
74
79
  ], IntegrationsModule);
@@ -1 +1 @@
1
- {"version":3,"file":"integrations.module.js","sourceRoot":"","sources":["../../src/integrations/integrations.module.ts"],"names":[],"mappings":";;;;AAAA,2CAAwC;AACxC,+DAA2D;AAC3D,yEAAoE;AAEpE,8BAA8B;AAC9B,gEAAoE;AACpE,4EAAuE;AACvE,kFAA6E;AAC7E,iEAA4D;AAC5D,uDAAmD;AACnD,oBAAoB;AACpB,6EAAwE;AACxE,iFAA4E;AAC5E,SAAS;AACT,0DAAsD;AACtD,8EAAyE;AACzE,UAAU;AACV,+DAA2D;AAC3D,WAAW;AACX,yDAAqD;AACrD,UAAU;AACV,qEAAgE;AAChE,2EAAsE;AAEtE,iEAAiE;AA4C1D,IAAM,kBAAkB,GAAxB,MAAM,kBAAkB;CAAG,CAAA;AAArB,gDAAkB;6BAAlB,kBAAkB;IA1C9B,IAAA,eAAM,EAAC;QACN,SAAS,EAAE;YACT,wCAAkB;YAClB,iDAAsB;YACtB,WAAW;YACX,wCAAsB;YACtB,2CAAmB;YACnB,iDAAsB;YACtB,MAAM;YACN,qCAAgB;YAChB,4BAAY;YACZ,UAAU;YACV,6CAAoB;YACpB,iDAAsB;YACtB,SAAS;YACT,4BAAY;YACZ,+CAAqB;YACrB,UAAU;YACV,gCAAc;YACd,WAAW;YACX,4BAAY;YACZ,UAAU;YACV,qCAAgB;YAChB,2CAAmB;SACpB;QACD,OAAO,EAAE;YACP,wCAAkB;YAClB,gEAAgE;YAChE,wCAAsB;YACtB,2CAAmB;YACnB,iDAAsB;YACtB,4BAAY;YACZ,6CAAoB;YACpB,iDAAsB;YACtB,4BAAY;YACZ,+CAAqB;YACrB,gCAAc;YACd,4BAAY;YACZ,qCAAgB;YAChB,2CAAmB;SACpB;KACF,CAAC;GACW,kBAAkB,CAAG"}
1
+ {"version":3,"file":"integrations.module.js","sourceRoot":"","sources":["../../src/integrations/integrations.module.ts"],"names":[],"mappings":";;;;AAAA,2CAAwC;AACxC,+DAA2D;AAC3D,yEAAoE;AAEpE,8BAA8B;AAC9B,gEAAoE;AACpE,4EAAuE;AACvE,kFAA6E;AAC7E,iEAA4D;AAC5D,uDAAmD;AACnD,oBAAoB;AACpB,6EAAwE;AACxE,iFAA4E;AAC5E,SAAS;AACT,0DAAsD;AACtD,8EAAyE;AACzE,UAAU;AACV,+DAA2D;AAC3D,WAAW;AACX,yDAAqD;AACrD,UAAU;AACV,qEAAgE;AAChE,2EAAsE;AACtE,aAAa;AACb,8EAAyE;AAEzE,iEAAiE;AA+C1D,IAAM,kBAAkB,GAAxB,MAAM,kBAAkB;CAAG,CAAA;AAArB,gDAAkB;6BAAlB,kBAAkB;IA7C9B,IAAA,eAAM,EAAC;QACN,SAAS,EAAE;YACT,wCAAkB;YAClB,iDAAsB;YACtB,WAAW;YACX,wCAAsB;YACtB,2CAAmB;YACnB,iDAAsB;YACtB,MAAM;YACN,qCAAgB;YAChB,4BAAY;YACZ,UAAU;YACV,6CAAoB;YACpB,iDAAsB;YACtB,SAAS;YACT,4BAAY;YACZ,+CAAqB;YACrB,UAAU;YACV,gCAAc;YACd,WAAW;YACX,4BAAY;YACZ,UAAU;YACV,qCAAgB;YAChB,2CAAmB;YACnB,aAAa;YACb,2CAAmB;SACpB;QACD,OAAO,EAAE;YACP,wCAAkB;YAClB,gEAAgE;YAChE,wCAAsB;YACtB,2CAAmB;YACnB,iDAAsB;YACtB,4BAAY;YACZ,6CAAoB;YACpB,iDAAsB;YACtB,4BAAY;YACZ,+CAAqB;YACrB,gCAAc;YACd,4BAAY;YACZ,qCAAgB;YAChB,2CAAmB;YACnB,2CAAmB;SACpB;KACF,CAAC;GACW,kBAAkB,CAAG"}
@@ -0,0 +1,6 @@
1
+ export * from './order-notification.types';
2
+ export * from './order.source';
3
+ export * from './order-email.mapper';
4
+ export * from './order-email.service';
5
+ export * from './notifications.module';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/notifications/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAC;AAC3C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC"}
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ tslib_1.__exportStar(require("./order-notification.types"), exports);
5
+ tslib_1.__exportStar(require("./order.source"), exports);
6
+ tslib_1.__exportStar(require("./order-email.mapper"), exports);
7
+ tslib_1.__exportStar(require("./order-email.service"), exports);
8
+ tslib_1.__exportStar(require("./notifications.module"), exports);
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/notifications/index.ts"],"names":[],"mappings":";;;AAAA,qEAA2C;AAC3C,yDAA+B;AAC/B,+DAAqC;AACrC,gEAAsC;AACtC,iEAAuC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Order notifications, shared by every brand.
3
+ *
4
+ * `OrderSource` is channel-neutral, so WhatsApp and SMS senders added later
5
+ * consume the same snapshot rather than re-querying Shopify.
6
+ */
7
+ export declare class NotificationsModule {
8
+ }
9
+ //# sourceMappingURL=notifications.module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifications.module.d.ts","sourceRoot":"","sources":["../../src/notifications/notifications.module.ts"],"names":[],"mappings":"AAKA;;;;;GAKG;AACH,qBAKa,mBAAmB;CAAG"}
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NotificationsModule = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const common_1 = require("@nestjs/common");
6
+ const integrations_module_1 = require("../integrations/integrations.module");
7
+ const order_email_service_1 = require("./order-email.service");
8
+ const order_source_1 = require("./order.source");
9
+ /**
10
+ * Order notifications, shared by every brand.
11
+ *
12
+ * `OrderSource` is channel-neutral, so WhatsApp and SMS senders added later
13
+ * consume the same snapshot rather than re-querying Shopify.
14
+ */
15
+ let NotificationsModule = class NotificationsModule {
16
+ };
17
+ exports.NotificationsModule = NotificationsModule;
18
+ exports.NotificationsModule = NotificationsModule = tslib_1.__decorate([
19
+ (0, common_1.Module)({
20
+ imports: [integrations_module_1.IntegrationsModule],
21
+ providers: [order_source_1.OrderSource, order_email_service_1.OrderEmailService],
22
+ exports: [order_source_1.OrderSource, order_email_service_1.OrderEmailService],
23
+ })
24
+ ], NotificationsModule);
25
+ //# sourceMappingURL=notifications.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifications.module.js","sourceRoot":"","sources":["../../src/notifications/notifications.module.ts"],"names":[],"mappings":";;;;AAAA,2CAAwC;AACxC,6EAAyE;AACzE,+DAA0D;AAC1D,iDAA6C;AAE7C;;;;;GAKG;AAMI,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;CAAG,CAAA;AAAtB,kDAAmB;8BAAnB,mBAAmB;IAL/B,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,wCAAkB,CAAC;QAC7B,SAAS,EAAE,CAAC,0BAAW,EAAE,uCAAiB,CAAC;QAC3C,OAAO,EAAE,CAAC,0BAAW,EAAE,uCAAiB,CAAC;KAC1C,CAAC;GACW,mBAAmB,CAAG"}
@@ -0,0 +1,15 @@
1
+ import { OrderSnapshot } from './order.source';
2
+ import { OrderEmailBranding, OrderEvent } from './order-notification.types';
3
+ /** Money as the templates show it, e.g. `Rs. 2,245.00`. */
4
+ export declare const formatMoney: (amount: number, currency: string) => string;
5
+ export declare const escapeHtml: (value: string) => string;
6
+ export declare const renderItemsHtml: (items: OrderSnapshot["items"], currency: string, branding: OrderEmailBranding) => string;
7
+ export declare const renderAddressHtml: (lines: string[]) => string;
8
+ /**
9
+ * Build the merge attributes for one email.
10
+ *
11
+ * `trackingUrl` overrides the order's own tracking link, so a shipped email
12
+ * points at the parcel that triggered it rather than the first fulfillment.
13
+ */
14
+ export declare const buildAttributes: (event: OrderEvent, order: OrderSnapshot, branding: OrderEmailBranding, trackingUrl?: string) => Record<string, string>;
15
+ //# sourceMappingURL=order-email.mapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"order-email.mapper.d.ts","sourceRoot":"","sources":["../../src/notifications/order-email.mapper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AA+B5E,2DAA2D;AAC3D,eAAO,MAAM,WAAW,GAAI,QAAQ,MAAM,EAAE,UAAU,MAAM,KAAG,MAG9D,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,OAAO,MAAM,KAAG,MAKf,CAAC;AAsD7B,eAAO,MAAM,eAAe,GAC1B,OAAO,aAAa,CAAC,OAAO,CAAC,EAC7B,UAAU,MAAM,EAChB,UAAU,kBAAkB,KAC3B,MAKE,CAAC;AAEN,eAAO,MAAM,iBAAiB,GAAI,OAAO,MAAM,EAAE,KAAG,MACd,CAAC;AAEvC;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAC1B,OAAO,UAAU,EACjB,OAAO,aAAa,EACpB,UAAU,kBAAkB,EAC5B,cAAc,MAAM,KACnB,MAAM,CAAC,MAAM,EAAE,MAAM,CAwCvB,CAAC"}
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildAttributes = exports.renderAddressHtml = exports.renderItemsHtml = exports.escapeHtml = exports.formatMoney = void 0;
4
+ /**
5
+ * Turns an order into the flat `[%KEY%]` values Netcore substitutes.
6
+ *
7
+ * Netcore merge cannot iterate, so the line items and the address blocks are
8
+ * rendered to HTML here and passed as single attributes. That makes this the
9
+ * one part of the email the Netcore panel cannot restyle — its fonts, colours
10
+ * and labels come from `integrations.engagement.options` in the brand config,
11
+ * so they at least live beside the rest of the email settings rather than here.
12
+ */
13
+ /** Symbol shown for a currency; anything unmapped falls back to its ISO code. */
14
+ const CURRENCY_PREFIX = { INR: 'Rs.' };
15
+ const formatters = new Map();
16
+ /** Indian grouping (1,23,456.00) for INR; Western grouping elsewhere. */
17
+ const numberFormat = (currency) => {
18
+ const locale = currency === 'INR' ? 'en-IN' : 'en-US';
19
+ let formatter = formatters.get(locale);
20
+ if (!formatter) {
21
+ formatter = new Intl.NumberFormat(locale, {
22
+ minimumFractionDigits: 2,
23
+ maximumFractionDigits: 2,
24
+ });
25
+ formatters.set(locale, formatter);
26
+ }
27
+ return formatter;
28
+ };
29
+ /** Money as the templates show it, e.g. `Rs. 2,245.00`. */
30
+ const formatMoney = (amount, currency) => {
31
+ const value = Number.isFinite(amount) ? amount : 0;
32
+ return `${CURRENCY_PREFIX[currency] ?? currency} ${numberFormat(currency).format(value)}`;
33
+ };
34
+ exports.formatMoney = formatMoney;
35
+ const escapeHtml = (value) => value
36
+ .replace(/&/g, '&amp;')
37
+ .replace(/</g, '&lt;')
38
+ .replace(/>/g, '&gt;')
39
+ .replace(/"/g, '&quot;');
40
+ exports.escapeHtml = escapeHtml;
41
+ /** Gateway names arrive lower-cased; short ones are acronyms (UPI, COD, EMI). */
42
+ const prettyGateway = (name) => {
43
+ const trimmed = name.trim();
44
+ if (!trimmed)
45
+ return '';
46
+ return trimmed.length <= 4
47
+ ? trimmed.toUpperCase()
48
+ : trimmed.charAt(0).toUpperCase() + trimmed.slice(1);
49
+ };
50
+ const specRow = (label, value, { fontFamily, textColor }) => {
51
+ const cell = `font-family:${fontFamily}; font-size:15px; line-height:26px; color:${textColor};`;
52
+ return `<tr>
53
+ <td width="110" style="${cell}">${(0, exports.escapeHtml)(label)}</td>
54
+ <td width="16" style="${cell}">:</td>
55
+ <td style="${cell}">${(0, exports.escapeHtml)(value)}</td>
56
+ </tr>`;
57
+ };
58
+ /** One item block: bold title with its price, then the spec rows beneath. */
59
+ const renderItem = (item, currency, branding) => {
60
+ const { fontFamily, textColor, labels } = branding;
61
+ const heading = `font-family:${fontFamily}; font-size:17px; line-height:24px; font-weight:bold; color:${textColor};`;
62
+ const specs = [
63
+ item.size ? specRow(labels.size, item.size, branding) : '',
64
+ item.colour ? specRow(labels.colour, item.colour, branding) : '',
65
+ specRow(labels.quantity, String(item.quantity), branding),
66
+ item.itemNumber ? specRow(labels.itemNumber, item.itemNumber, branding) : '',
67
+ ]
68
+ .filter(Boolean)
69
+ .join('\n ');
70
+ return `<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">
71
+ <tr>
72
+ <td style="${heading} padding-right:12px;">${(0, exports.escapeHtml)(item.title)}</td>
73
+ <td align="right" valign="top" style="${heading} white-space:nowrap;">${(0, exports.formatMoney)(item.amount, currency)}</td>
74
+ </tr>
75
+ </table>
76
+ <div style="height:16px; line-height:16px; font-size:0;">&nbsp;</div>
77
+ <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">
78
+ ${specs}
79
+ </table>`;
80
+ };
81
+ const renderItemsHtml = (items, currency, branding) => items
82
+ .map((item) => renderItem(item, currency, branding))
83
+ .join('\n <div style="height:22px; line-height:22px; font-size:0;">&nbsp;</div>\n ');
84
+ exports.renderItemsHtml = renderItemsHtml;
85
+ const renderAddressHtml = (lines) => lines.map(exports.escapeHtml).join('<br />');
86
+ exports.renderAddressHtml = renderAddressHtml;
87
+ /**
88
+ * Build the merge attributes for one email.
89
+ *
90
+ * `trackingUrl` overrides the order's own tracking link, so a shipped email
91
+ * points at the parcel that triggered it rather than the first fulfillment.
92
+ */
93
+ const buildAttributes = (event, order, branding, trackingUrl) => {
94
+ const money = (amount) => (0, exports.formatMoney)(amount, order.currency);
95
+ const attributes = {
96
+ LOGO_URL: branding.logoUrl,
97
+ CUSTOMER_NAME: order.customerName || 'there',
98
+ ORDER_NUMBER: order.orderNumber,
99
+ ITEMS_HTML: (0, exports.renderItemsHtml)(order.items, order.currency, branding),
100
+ // Whole row, or nothing — Netcore has no conditionals, so an empty string
101
+ // is the only way to drop the line on orders without a discount. Carries no
102
+ // styling: the totals table sets font and colour, so this inherits them.
103
+ DISCOUNT_ROW: order.totalDiscount > 0
104
+ ? `<tr><td align="left">${(0, exports.escapeHtml)(branding.discountLabel)}</td>` +
105
+ `<td align="right" style="font-weight:bold;">- ${money(order.totalDiscount)}</td></tr>`
106
+ : '',
107
+ GST_AMOUNT: money(order.totalTax),
108
+ SHIPPING_AMOUNT: money(order.totalShipping),
109
+ TOTAL_AMOUNT: money(order.totalPrice),
110
+ BILLING_ADDRESS_HTML: (0, exports.renderAddressHtml)(order.billingAddress),
111
+ SHIPPING_ADDRESS_HTML: (0, exports.renderAddressHtml)(order.shippingAddress),
112
+ PAYMENT_METHOD: order.paymentMethod
113
+ .split(',')
114
+ .map(prettyGateway)
115
+ .filter(Boolean)
116
+ .join(', '),
117
+ SHIPPING_METHOD: order.shippingMethod,
118
+ SUPPORT_PHONE: branding.supportPhone,
119
+ SUPPORT_EMAIL: branding.supportEmail,
120
+ };
121
+ if (event === 'order_shipped') {
122
+ // A carrier Shopify doesn't recognise yields a tracking number with no URL,
123
+ // which would render the button pointing nowhere. Fall back to the order
124
+ // status page so it always goes somewhere useful.
125
+ attributes.TRACKING_URL =
126
+ trackingUrl ?? order.trackingUrl ?? order.orderStatusUrl ?? '';
127
+ }
128
+ return attributes;
129
+ };
130
+ exports.buildAttributes = buildAttributes;
131
+ //# sourceMappingURL=order-email.mapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"order-email.mapper.js","sourceRoot":"","sources":["../../src/notifications/order-email.mapper.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import { ConfigService } from '../config/config.service';
2
+ import { NetcoreEmailService } from '../integrations/engagement/netcore-email.service';
3
+ import { OrderSource } from './order.source';
4
+ import { OrderEmailResult, OrderEvent } from './order-notification.types';
5
+ /**
6
+ * Sends one transactional order email for any brand.
7
+ *
8
+ * Everything brand-specific arrives through `integrations.engagement` in the
9
+ * brand config: sender, support details, per-event template ids and subjects.
10
+ * The email body is the provider-hosted template, so design and copy changes
11
+ * are made in the provider panel without a deploy — this only supplies the
12
+ * merge values.
13
+ */
14
+ export declare class OrderEmailService {
15
+ private readonly configService;
16
+ private readonly netcore;
17
+ private readonly source;
18
+ constructor(configService: ConfigService, netcore: NetcoreEmailService, source: OrderSource);
19
+ send(brand: string, event: OrderEvent, orderId: string | number, trackingUrl?: string): Promise<OrderEmailResult>;
20
+ /**
21
+ * Presentation values come from the brand config only — no defaults live here,
22
+ * because a fallback would be one brand's colours quietly applied to another.
23
+ * A missing key renders visibly unstyled and is logged, which is preferable to
24
+ * an email that looks deliberate but belongs to a different brand.
25
+ */
26
+ private buildBranding;
27
+ /** Returns the engagement options, or null when the brand has no email setup. */
28
+ private configureProvider;
29
+ }
30
+ //# sourceMappingURL=order-email.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"order-email.service.d.ts","sourceRoot":"","sources":["../../src/notifications/order-email.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,kDAAkD,CAAC;AAEvF,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAEL,gBAAgB,EAChB,UAAU,EAGX,MAAM,4BAA4B,CAAC;AAmBpC;;;;;;;;GAQG;AACH,qBACa,iBAAiB;IAE1B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAFN,aAAa,EAAE,aAAa,EAC5B,OAAO,EAAE,mBAAmB,EAC5B,MAAM,EAAE,WAAW;IAGhC,IAAI,CACR,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,UAAU,EACjB,OAAO,EAAE,MAAM,GAAG,MAAM,EACxB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,gBAAgB,CAAC;IAgE5B;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IAgCrB,iFAAiF;IACjF,OAAO,CAAC,iBAAiB;CA8B1B"}
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OrderEmailService = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const common_1 = require("@nestjs/common");
6
+ const logger_1 = require("../common/logger");
7
+ const config_service_1 = require("../config/config.service");
8
+ const netcore_email_service_1 = require("../integrations/engagement/netcore-email.service");
9
+ const order_email_mapper_1 = require("./order-email.mapper");
10
+ const order_source_1 = require("./order.source");
11
+ /**
12
+ * Sends one transactional order email for any brand.
13
+ *
14
+ * Everything brand-specific arrives through `integrations.engagement` in the
15
+ * brand config: sender, support details, per-event template ids and subjects.
16
+ * The email body is the provider-hosted template, so design and copy changes
17
+ * are made in the provider panel without a deploy — this only supplies the
18
+ * merge values.
19
+ */
20
+ let OrderEmailService = class OrderEmailService {
21
+ configService;
22
+ netcore;
23
+ source;
24
+ constructor(configService, netcore, source) {
25
+ this.configService = configService;
26
+ this.netcore = netcore;
27
+ this.source = source;
28
+ }
29
+ async send(brand, event, orderId, trackingUrl) {
30
+ const options = this.configureProvider(brand);
31
+ if (!options)
32
+ return 'skipped';
33
+ const eventConfig = options.events?.[event];
34
+ if (!eventConfig?.templateId) {
35
+ logger_1.logger.warn('No template configured for order event - skipping email', {
36
+ brand,
37
+ event,
38
+ });
39
+ return 'skipped';
40
+ }
41
+ const order = await this.source.load(brand, orderId);
42
+ if (!order)
43
+ return 'failed';
44
+ if (!order.email) {
45
+ logger_1.logger.warn('Order has no email address - skipping email', {
46
+ brand,
47
+ event,
48
+ orderId,
49
+ });
50
+ return 'skipped';
51
+ }
52
+ const branding = this.buildBranding(brand, options);
53
+ const result = await this.netcore.send({
54
+ subject: eventConfig.subject,
55
+ templateId: eventConfig.templateId,
56
+ // The provider-hosted template is what renders. `content` is only here
57
+ // because the API rejects a send without it, and anything sent in it is
58
+ // prepended to the template — hence an invisible comment.
59
+ content: [{ type: 'html', value: '<!-- -->' }],
60
+ tags: [eventConfig.tag ?? event],
61
+ personalizations: [
62
+ {
63
+ to: [{ email: order.email, name: order.customerName || undefined }],
64
+ attributes: (0, order_email_mapper_1.buildAttributes)(event, order, branding, trackingUrl),
65
+ },
66
+ ],
67
+ });
68
+ if (!result.success) {
69
+ logger_1.logger.error('Failed to send order email', {
70
+ brand,
71
+ event,
72
+ orderId,
73
+ errorCode: result.error?.code,
74
+ errorMessage: result.error?.message,
75
+ });
76
+ // A rejected payload will be rejected identically next time.
77
+ return result.error?.code === 'BAD_REQUEST' ? 'skipped' : 'failed';
78
+ }
79
+ logger_1.logger.info('Order email sent', {
80
+ brand,
81
+ event,
82
+ orderId,
83
+ messageId: result.data?.messageId,
84
+ });
85
+ return 'sent';
86
+ }
87
+ /**
88
+ * Presentation values come from the brand config only — no defaults live here,
89
+ * because a fallback would be one brand's colours quietly applied to another.
90
+ * A missing key renders visibly unstyled and is logged, which is preferable to
91
+ * an email that looks deliberate but belongs to a different brand.
92
+ */
93
+ buildBranding(brand, options) {
94
+ const missing = ['fontFamily', 'textColor', 'discountLabel', 'itemLabels'].filter((key) => !options[key]);
95
+ if (missing.length) {
96
+ logger_1.logger.warn('Brand engagement config is missing presentation values', {
97
+ brand,
98
+ missing,
99
+ });
100
+ }
101
+ const labels = options.itemLabels;
102
+ return {
103
+ logoUrl: options.logoUrl ?? '',
104
+ supportPhone: options.supportPhone ?? '',
105
+ supportEmail: options.supportEmail ?? '',
106
+ fontFamily: options.fontFamily ?? '',
107
+ textColor: options.textColor ?? '',
108
+ discountLabel: options.discountLabel ?? '',
109
+ labels: {
110
+ size: labels?.size ?? '',
111
+ colour: labels?.colour ?? '',
112
+ quantity: labels?.quantity ?? '',
113
+ itemNumber: labels?.itemNumber ?? '',
114
+ },
115
+ };
116
+ }
117
+ /** Returns the engagement options, or null when the brand has no email setup. */
118
+ configureProvider(brand) {
119
+ const engagement = this.configService.getBrand(brand)?.integrations
120
+ ?.engagement;
121
+ if (!engagement?.apiKey) {
122
+ logger_1.logger.warn('Engagement integration not configured - skipping email', {
123
+ brand,
124
+ });
125
+ return null;
126
+ }
127
+ const options = (engagement.options ?? {});
128
+ if (!options.fromEmail) {
129
+ logger_1.logger.warn('Engagement integration missing fromEmail', { brand });
130
+ return null;
131
+ }
132
+ if (!this.netcore.isConfigured()) {
133
+ this.netcore.configure(brand, {
134
+ baseUrl: engagement.baseUrl,
135
+ apiKey: engagement.apiKey,
136
+ fromEmail: options.fromEmail,
137
+ fromName: options.fromName,
138
+ replyTo: options.replyTo,
139
+ });
140
+ }
141
+ return options;
142
+ }
143
+ };
144
+ exports.OrderEmailService = OrderEmailService;
145
+ exports.OrderEmailService = OrderEmailService = tslib_1.__decorate([
146
+ (0, common_1.Injectable)(),
147
+ tslib_1.__metadata("design:paramtypes", [config_service_1.ConfigService,
148
+ netcore_email_service_1.NetcoreEmailService,
149
+ order_source_1.OrderSource])
150
+ ], OrderEmailService);
151
+ //# sourceMappingURL=order-email.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"order-email.service.js","sourceRoot":"","sources":["../../src/notifications/order-email.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Order notification types, shared by every brand and channel.
3
+ *
4
+ * The event list is channel-neutral: email, WhatsApp and SMS all react to the
5
+ * same order lifecycle. Only the per-channel send settings differ.
6
+ */
7
+ export declare const ORDER_EVENTS: readonly ["order_confirmed", "order_shipped", "order_delivered", "order_cancelled", "return_initiated", "return_complete", "refund_initiated", "refund_complete"];
8
+ export type OrderEvent = (typeof ORDER_EVENTS)[number];
9
+ /**
10
+ * Per-event email settings, read from the brand's
11
+ * `integrations.engagement.options.events` config.
12
+ */
13
+ export interface OrderEventEmailConfig {
14
+ /** Id of the provider-hosted template. */
15
+ templateId: number;
16
+ /** Subject line; may carry `[%KEY%]` merge placeholders. */
17
+ subject: string;
18
+ /** Provider tag, for per-event delivery reporting. Defaults to the event name. */
19
+ tag?: string;
20
+ }
21
+ /** Row labels in the line-item block. */
22
+ export interface OrderItemLabels {
23
+ size: string;
24
+ colour: string;
25
+ quantity: string;
26
+ itemNumber: string;
27
+ }
28
+ /** Brand presentation values for the parts rendered in code. */
29
+ export interface OrderEmailBranding {
30
+ logoUrl: string;
31
+ supportPhone: string;
32
+ supportEmail: string;
33
+ /** Must match the provider-hosted template, or the item block looks pasted in. */
34
+ fontFamily: string;
35
+ textColor: string;
36
+ labels: OrderItemLabels;
37
+ discountLabel: string;
38
+ }
39
+ /** `skipped` means nothing a retry can fix; `failed` is transient. */
40
+ export type OrderEmailResult = 'sent' | 'skipped' | 'failed';
41
+ //# sourceMappingURL=order-notification.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"order-notification.types.d.ts","sourceRoot":"","sources":["../../src/notifications/order-notification.types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,YAAY,mKASf,CAAC;AAEX,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvD;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAC;IAChB,kFAAkF;IAClF,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,yCAAyC;AACzC,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,gEAAgE;AAChE,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,kFAAkF;IAClF,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,eAAe,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,sEAAsE;AACtE,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC"}
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ /**
3
+ * Order notification types, shared by every brand and channel.
4
+ *
5
+ * The event list is channel-neutral: email, WhatsApp and SMS all react to the
6
+ * same order lifecycle. Only the per-channel send settings differ.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.ORDER_EVENTS = void 0;
10
+ exports.ORDER_EVENTS = [
11
+ 'order_confirmed',
12
+ 'order_shipped',
13
+ 'order_delivered',
14
+ 'order_cancelled',
15
+ 'return_initiated',
16
+ 'return_complete',
17
+ 'refund_initiated',
18
+ 'refund_complete',
19
+ ];
20
+ //# sourceMappingURL=order-notification.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"order-notification.types.js","sourceRoot":"","sources":["../../src/notifications/order-notification.types.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEU,QAAA,YAAY,GAAG;IAC1B,iBAAiB;IACjB,eAAe;IACf,iBAAiB;IACjB,iBAAiB;IACjB,kBAAkB;IAClB,iBAAiB;IACjB,kBAAkB;IAClB,iBAAiB;CACT,CAAC"}
@@ -0,0 +1,47 @@
1
+ import { ShopifyAdminService } from '../integrations/commerce/shopify-admin.service';
2
+ /** The order, flattened to what a notification needs. Formatting is the caller's job. */
3
+ export interface OrderSnapshot {
4
+ orderNumber: string;
5
+ email: string | null;
6
+ /**
7
+ * Raw Shopify phone, unnormalised — WhatsApp needs E.164 and Indian SMS/DLT
8
+ * may want another form, so each channel converts rather than this guessing.
9
+ */
10
+ phone: string | null;
11
+ /** ISO code from the order, e.g. INR. Drives the money prefix. */
12
+ currency: string;
13
+ customerName: string;
14
+ totalPrice: number;
15
+ totalTax: number;
16
+ /** Order-level discount. Line items keep their list price, so this is the only place it shows. */
17
+ totalDiscount: number;
18
+ totalShipping: number;
19
+ billingAddress: string[];
20
+ shippingAddress: string[];
21
+ paymentMethod: string;
22
+ shippingMethod: string;
23
+ trackingUrl: string | null;
24
+ /** Shopify's order-status page — the fallback when a parcel has no tracking link. */
25
+ orderStatusUrl: string | null;
26
+ items: Array<{
27
+ title: string;
28
+ quantity: number;
29
+ amount: number;
30
+ size: string | null;
31
+ colour: string | null;
32
+ itemNumber: string | null;
33
+ }>;
34
+ }
35
+ export declare class OrderSource {
36
+ private readonly shopifyAdminService;
37
+ constructor(shopifyAdminService: ShopifyAdminService);
38
+ /**
39
+ * Fetch one order. `orderId` may be a numeric Shopify id or a full GID.
40
+ * Returns null when the order is gone or the query errored — the caller
41
+ * decides whether that is worth retrying.
42
+ */
43
+ load(brand: string, orderId: string | number): Promise<OrderSnapshot | null>;
44
+ private delay;
45
+ private toSnapshot;
46
+ }
47
+ //# sourceMappingURL=order.source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"order.source.d.ts","sourceRoot":"","sources":["../../src/notifications/order.source.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,gDAAgD,CAAC;AAkErF,yFAAyF;AACzF,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB;;;OAGG;IACH,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,kEAAkE;IAClE,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,kGAAkG;IAClG,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,qFAAqF;IACrF,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,KAAK,EAAE,KAAK,CAAC;QACX,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,CAAC,CAAC;CACJ;AAwED,qBACa,WAAW;IACV,OAAO,CAAC,QAAQ,CAAC,mBAAmB;gBAAnB,mBAAmB,EAAE,mBAAmB;IAErE;;;;OAIG;IACG,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IA4ClF,OAAO,CAAC,KAAK;IAIb,OAAO,CAAC,UAAU;CA0CnB"}
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OrderSource = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const common_1 = require("@nestjs/common");
6
+ const logger_1 = require("../common/logger");
7
+ const shopify_admin_service_1 = require("../integrations/commerce/shopify-admin.service");
8
+ const ORDER_QUERY = `
9
+ query OrderEmailSnapshot($id: ID!) {
10
+ order(id: $id) {
11
+ id
12
+ name
13
+ email
14
+ phone
15
+ statusPageUrl
16
+ totalPriceSet { shopMoney { amount currencyCode } }
17
+ totalTaxSet { shopMoney { amount } }
18
+ totalDiscountsSet { shopMoney { amount } }
19
+ totalShippingPriceSet { shopMoney { amount } }
20
+ customer { firstName lastName email phone }
21
+ billingAddress { address1 address2 city province zip country }
22
+ shippingAddress { address1 address2 city province zip country phone }
23
+ shippingLine { title }
24
+ paymentGatewayNames
25
+ lineItems(first: 50) {
26
+ edges {
27
+ node {
28
+ title
29
+ quantity
30
+ sku
31
+ variantTitle
32
+ originalTotalSet { shopMoney { amount } }
33
+ variant {
34
+ barcode
35
+ sku
36
+ selectedOptions { name value }
37
+ }
38
+ }
39
+ }
40
+ }
41
+ fulfillments(first: 10) {
42
+ trackingInfo { url number }
43
+ }
44
+ }
45
+ }
46
+ `;
47
+ const toNumber = (value) => {
48
+ const n = parseFloat(value ?? '0');
49
+ return Number.isFinite(n) ? n : 0;
50
+ };
51
+ const formatAddress = (address) => {
52
+ if (!address)
53
+ return [];
54
+ const cityLine = [address.city, address.zip].filter(Boolean).join(' ');
55
+ return [address.address1, address.address2, address.province, cityLine]
56
+ .map((part) => (part ?? '').trim())
57
+ .filter((part) => part.length > 0);
58
+ };
59
+ /** Reads a variant option by name, e.g. "Size" / "Colour". */
60
+ const option = (node, ...names) => {
61
+ const wanted = names.map((n) => n.toLowerCase());
62
+ const match = node.variant?.selectedOptions?.find((o) => wanted.includes((o.name ?? '').toLowerCase()));
63
+ return match?.value?.trim() || null;
64
+ };
65
+ /**
66
+ * Backoff before re-reading an order that came back empty. `orders/create` can
67
+ * arrive before the Admin API will return the order, and an abandoned message
68
+ * is redelivered with no backoff — so without this the whole delivery budget
69
+ * burns in seconds. Total wait stays well inside the queue's lock duration.
70
+ */
71
+ const NOT_READABLE_RETRY_MS = [2000, 5000, 10000];
72
+ let OrderSource = class OrderSource {
73
+ shopifyAdminService;
74
+ constructor(shopifyAdminService) {
75
+ this.shopifyAdminService = shopifyAdminService;
76
+ }
77
+ /**
78
+ * Fetch one order. `orderId` may be a numeric Shopify id or a full GID.
79
+ * Returns null when the order is gone or the query errored — the caller
80
+ * decides whether that is worth retrying.
81
+ */
82
+ async load(brand, orderId) {
83
+ const gid = String(orderId).startsWith('gid://')
84
+ ? String(orderId)
85
+ : `gid://shopify/Order/${orderId}`;
86
+ for (let attempt = 0;; attempt++) {
87
+ const response = await this.shopifyAdminService.adminGraphQL(brand, ORDER_QUERY, { id: gid });
88
+ if (response.errors?.length) {
89
+ logger_1.logger.error('Order email snapshot query failed', {
90
+ brand,
91
+ orderId: gid,
92
+ error: response.errors[0]?.message,
93
+ });
94
+ return null;
95
+ }
96
+ const order = response.data?.order;
97
+ if (order) {
98
+ return this.toSnapshot(order);
99
+ }
100
+ const wait = NOT_READABLE_RETRY_MS[attempt];
101
+ if (wait === undefined) {
102
+ logger_1.logger.warn('Order email snapshot not found', {
103
+ brand,
104
+ orderId: gid,
105
+ attempts: attempt + 1,
106
+ });
107
+ return null;
108
+ }
109
+ logger_1.logger.info('Order not readable yet - retrying', {
110
+ brand,
111
+ orderId: gid,
112
+ attempt: attempt + 1,
113
+ waitMs: wait,
114
+ });
115
+ await this.delay(wait);
116
+ }
117
+ }
118
+ delay(ms) {
119
+ return new Promise((resolve) => setTimeout(resolve, ms));
120
+ }
121
+ toSnapshot(order) {
122
+ const firstName = order.customer?.firstName?.trim() ?? '';
123
+ const lastName = order.customer?.lastName?.trim() ?? '';
124
+ const trackingUrl = order.fulfillments
125
+ ?.flatMap((f) => f.trackingInfo ?? [])
126
+ .map((t) => t.url)
127
+ .find((url) => Boolean(url)) ?? null;
128
+ return {
129
+ // `name` is the customer-facing number (e.g. "#31083220934").
130
+ orderNumber: (order.name ?? '').replace(/^#/, ''),
131
+ email: order.email ?? order.customer?.email ?? null,
132
+ phone: order.phone ??
133
+ order.customer?.phone ??
134
+ order.shippingAddress?.phone ??
135
+ null,
136
+ currency: order.totalPriceSet?.shopMoney?.currencyCode ?? 'INR',
137
+ customerName: [firstName, lastName].filter(Boolean).join(' '),
138
+ totalPrice: toNumber(order.totalPriceSet?.shopMoney?.amount),
139
+ totalTax: toNumber(order.totalTaxSet?.shopMoney?.amount),
140
+ totalDiscount: toNumber(order.totalDiscountsSet?.shopMoney?.amount),
141
+ totalShipping: toNumber(order.totalShippingPriceSet?.shopMoney?.amount),
142
+ billingAddress: formatAddress(order.billingAddress),
143
+ shippingAddress: formatAddress(order.shippingAddress),
144
+ paymentMethod: (order.paymentGatewayNames ?? []).join(', '),
145
+ shippingMethod: order.shippingLine?.title?.trim() ?? '',
146
+ trackingUrl,
147
+ orderStatusUrl: order.statusPageUrl ?? null,
148
+ items: (order.lineItems?.edges ?? []).map(({ node }) => ({
149
+ title: node.title ?? '',
150
+ quantity: node.quantity ?? 1,
151
+ amount: toNumber(node.originalTotalSet?.shopMoney?.amount),
152
+ size: option(node, 'Size'),
153
+ colour: option(node, 'Colour', 'Color'),
154
+ // The design's "Item nr." is a 13-digit EAN, i.e. the variant barcode.
155
+ itemNumber: node.variant?.barcode ?? node.variant?.sku ?? node.sku ?? null,
156
+ })),
157
+ };
158
+ }
159
+ };
160
+ exports.OrderSource = OrderSource;
161
+ exports.OrderSource = OrderSource = tslib_1.__decorate([
162
+ (0, common_1.Injectable)(),
163
+ tslib_1.__metadata("design:paramtypes", [shopify_admin_service_1.ShopifyAdminService])
164
+ ], OrderSource);
165
+ //# sourceMappingURL=order.source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"order.source.js","sourceRoot":"","sources":["../../src/notifications/order.source.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pvh-afl/core",
3
- "version": "1.2.36",
3
+ "version": "1.2.38",
4
4
  "description": "AFL Core - Shared services for multi-brand commerce platform",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",