@pvh-afl/core 1.2.35 → 1.2.37
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/cache/cache.service.d.ts +2 -2
- package/dist/cache/cache.service.js +25 -25
- package/dist/cache/cache.service.js.map +1 -1
- 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 +46 -0
- package/dist/notifications/order.source.d.ts.map +1 -0
- package/dist/notifications/order.source.js +139 -0
- package/dist/notifications/order.source.js.map +1 -0
- package/package.json +1 -1
|
@@ -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":";;;AAGA;;;;;;;;GAQG;AAEH,iFAAiF;AACjF,MAAM,eAAe,GAA2B,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;AAE/D,MAAM,UAAU,GAAG,IAAI,GAAG,EAA6B,CAAC;AAExD,yEAAyE;AACzE,MAAM,YAAY,GAAG,CAAC,QAAgB,EAAqB,EAAE;IAC3D,MAAM,MAAM,GAAG,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;IACtD,IAAI,SAAS,GAAG,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACvC,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,SAAS,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE;YACxC,qBAAqB,EAAE,CAAC;YACxB,qBAAqB,EAAE,CAAC;SACzB,CAAC,CAAC;QACH,UAAU,CAAC,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACpC,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC,CAAC;AAEF,2DAA2D;AACpD,MAAM,WAAW,GAAG,CAAC,MAAc,EAAE,QAAgB,EAAU,EAAE;IACtE,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACnD,OAAO,GAAG,eAAe,CAAC,QAAQ,CAAC,IAAI,QAAQ,IAAI,YAAY,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;AAC5F,CAAC,CAAC;AAHW,QAAA,WAAW,eAGtB;AAEK,MAAM,UAAU,GAAG,CAAC,KAAa,EAAU,EAAE,CAClD,KAAK;KACF,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;KACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;KACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;KACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AALhB,QAAA,UAAU,cAKM;AAE7B,iFAAiF;AACjF,MAAM,aAAa,GAAG,CAAC,IAAY,EAAU,EAAE;IAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC;IACxB,OAAO,OAAO,CAAC,MAAM,IAAI,CAAC;QACxB,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE;QACvB,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACzD,CAAC,CAAC;AAEF,MAAM,OAAO,GAAG,CACd,KAAa,EACb,KAAa,EACb,EAAE,UAAU,EAAE,SAAS,EAAsB,EACrC,EAAE;IACV,MAAM,IAAI,GAAG,eAAe,UAAU,6CAA6C,SAAS,GAAG,CAAC;IAChG,OAAO;+BACsB,IAAI,KAAK,IAAA,kBAAU,EAAC,KAAK,CAAC;8BAC3B,IAAI;mBACf,IAAI,KAAK,IAAA,kBAAU,EAAC,KAAK,CAAC;UACnC,CAAC;AACX,CAAC,CAAC;AAEF,6EAA6E;AAC7E,MAAM,UAAU,GAAG,CACjB,IAAoC,EACpC,QAAgB,EAChB,QAA4B,EACpB,EAAE;IACV,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAC;IACnD,MAAM,OAAO,GAAG,eAAe,UAAU,+DAA+D,SAAS,GAAG,CAAC;IAErH,MAAM,KAAK,GAAG;QACZ,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE;QAC1D,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE;QAChE,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;QACzD,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE;KAC7E;SACE,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,QAAQ,CAAC,CAAC;IAElB,OAAO;;mBAEU,OAAO,yBAAyB,IAAA,kBAAU,EAAC,IAAI,CAAC,KAAK,CAAC;8CAC3B,OAAO,yBAAyB,IAAA,mBAAW,EAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC;;;;;MAK1G,KAAK;WACA,CAAC;AACZ,CAAC,CAAC;AAEK,MAAM,eAAe,GAAG,CAC7B,KAA6B,EAC7B,QAAgB,EAChB,QAA4B,EACpB,EAAE,CACV,KAAK;KACF,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;KACnD,IAAI,CACH,+EAA+E,CAChF,CAAC;AATO,QAAA,eAAe,mBAStB;AAEC,MAAM,iBAAiB,GAAG,CAAC,KAAe,EAAU,EAAE,CAC3D,KAAK,CAAC,GAAG,CAAC,kBAAU,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAD1B,QAAA,iBAAiB,qBACS;AAEvC;;;;;GAKG;AACI,MAAM,eAAe,GAAG,CAC7B,KAAiB,EACjB,KAAoB,EACpB,QAA4B,EAC5B,WAAoB,EACI,EAAE;IAC1B,MAAM,KAAK,GAAG,CAAC,MAAc,EAAU,EAAE,CAAC,IAAA,mBAAW,EAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IAE9E,MAAM,UAAU,GAA2B;QACzC,QAAQ,EAAE,QAAQ,CAAC,OAAO;QAC1B,aAAa,EAAE,KAAK,CAAC,YAAY,IAAI,OAAO;QAC5C,YAAY,EAAE,KAAK,CAAC,WAAW;QAC/B,UAAU,EAAE,IAAA,uBAAe,EAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,QAAQ,EAAE,QAAQ,CAAC;QAClE,0EAA0E;QAC1E,4EAA4E;QAC5E,yEAAyE;QACzE,YAAY,EACV,KAAK,CAAC,aAAa,GAAG,CAAC;YACrB,CAAC,CAAC,wBAAwB,IAAA,kBAAU,EAAC,QAAQ,CAAC,aAAa,CAAC,OAAO;gBACjE,iDAAiD,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,YAAY;YACzF,CAAC,CAAC,EAAE;QACR,UAAU,EAAE,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC;QACjC,eAAe,EAAE,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC;QAC3C,YAAY,EAAE,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC;QACrC,oBAAoB,EAAE,IAAA,yBAAiB,EAAC,KAAK,CAAC,cAAc,CAAC;QAC7D,qBAAqB,EAAE,IAAA,yBAAiB,EAAC,KAAK,CAAC,eAAe,CAAC;QAC/D,cAAc,EAAE,KAAK,CAAC,aAAa;aAChC,KAAK,CAAC,GAAG,CAAC;aACV,GAAG,CAAC,aAAa,CAAC;aAClB,MAAM,CAAC,OAAO,CAAC;aACf,IAAI,CAAC,IAAI,CAAC;QACb,eAAe,EAAE,KAAK,CAAC,cAAc;QACrC,aAAa,EAAE,QAAQ,CAAC,YAAY;QACpC,aAAa,EAAE,QAAQ,CAAC,YAAY;KACrC,CAAC;IAEF,IAAI,KAAK,KAAK,eAAe,EAAE,CAAC;QAC9B,4EAA4E;QAC5E,yEAAyE;QACzE,kDAAkD;QAClD,UAAU,CAAC,YAAY;YACrB,WAAW,IAAI,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,cAAc,IAAI,EAAE,CAAC;IACnE,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC,CAAC;AA7CW,QAAA,eAAe,mBA6C1B"}
|
|
@@ -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,46 @@
|
|
|
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 toSnapshot;
|
|
45
|
+
}
|
|
46
|
+
//# 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;AAgED,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;IA2BlF,OAAO,CAAC,UAAU;CA0CnB"}
|