@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.
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/integrations/engagement/index.d.ts +3 -0
- package/dist/integrations/engagement/index.d.ts.map +1 -0
- package/dist/integrations/engagement/index.js +6 -0
- package/dist/integrations/engagement/index.js.map +1 -0
- package/dist/integrations/engagement/netcore-email.service.d.ts +32 -0
- package/dist/integrations/engagement/netcore-email.service.d.ts.map +1 -0
- package/dist/integrations/engagement/netcore-email.service.js +184 -0
- package/dist/integrations/engagement/netcore-email.service.js.map +1 -0
- package/dist/integrations/engagement/netcore-email.types.d.ts +74 -0
- package/dist/integrations/engagement/netcore-email.types.d.ts.map +1 -0
- package/dist/integrations/engagement/netcore-email.types.js +3 -0
- package/dist/integrations/engagement/netcore-email.types.js.map +1 -0
- package/dist/integrations/index.d.ts +1 -0
- package/dist/integrations/index.d.ts.map +1 -1
- package/dist/integrations/index.js +2 -0
- package/dist/integrations/index.js.map +1 -1
- package/dist/integrations/integrations.module.d.ts.map +1 -1
- package/dist/integrations/integrations.module.js +5 -0
- package/dist/integrations/integrations.module.js.map +1 -1
- package/dist/notifications/index.d.ts +6 -0
- package/dist/notifications/index.d.ts.map +1 -0
- package/dist/notifications/index.js +9 -0
- package/dist/notifications/index.js.map +1 -0
- package/dist/notifications/notifications.module.d.ts +9 -0
- package/dist/notifications/notifications.module.d.ts.map +1 -0
- package/dist/notifications/notifications.module.js +25 -0
- package/dist/notifications/notifications.module.js.map +1 -0
- package/dist/notifications/order-email.mapper.d.ts +15 -0
- package/dist/notifications/order-email.mapper.d.ts.map +1 -0
- package/dist/notifications/order-email.mapper.js +131 -0
- package/dist/notifications/order-email.mapper.js.map +1 -0
- package/dist/notifications/order-email.service.d.ts +30 -0
- package/dist/notifications/order-email.service.d.ts.map +1 -0
- package/dist/notifications/order-email.service.js +151 -0
- package/dist/notifications/order-email.service.js.map +1 -0
- package/dist/notifications/order-notification.types.d.ts +41 -0
- package/dist/notifications/order-notification.types.d.ts.map +1 -0
- package/dist/notifications/order-notification.types.js +20 -0
- package/dist/notifications/order-notification.types.js.map +1 -0
- package/dist/notifications/order.source.d.ts +47 -0
- package/dist/notifications/order.source.d.ts.map +1 -0
- package/dist/notifications/order.source.js +165 -0
- package/dist/notifications/order.source.js.map +1 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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":"
|
|
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;
|
|
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 @@
|
|
|
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, '&')
|
|
37
|
+
.replace(/</g, '<')
|
|
38
|
+
.replace(/>/g, '>')
|
|
39
|
+
.replace(/"/g, '"');
|
|
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;"> </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;"> </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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"}
|