@jarwizz/create-jarshop 0.1.3 → 0.1.5
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/template/src/email-smoke.ts +1 -0
- package/dist/template/src/migrations/1787800000000-jarshop-order-email-event-key.ts +60 -0
- package/dist/template/src/migrations/1787900000000-jarshop-email-brand-configuration.ts +32 -0
- package/dist/template/src/plugins/jarshop-checkout/confirmation.ts +13 -0
- package/dist/template/src/plugins/jarshop-checkout/dashboard/index.tsx +213 -32
- package/dist/template/src/plugins/jarshop-checkout/dashboard/order-email-delivery-status.ts +17 -0
- package/dist/template/src/plugins/jarshop-checkout/index.ts +49 -1
- package/dist/template/src/plugins/jarshop-checkout/jarshop-checkout.plugin.ts +160 -12
- package/dist/template/src/plugins/jarshop-checkout/order-email-configuration.entity.ts +61 -0
- package/dist/template/src/plugins/jarshop-checkout/order-email-configuration.ts +190 -0
- package/dist/template/src/plugins/jarshop-checkout/order-email-delivery-core.ts +321 -13
- package/dist/template/src/plugins/jarshop-checkout/order-email-delivery-status.ts +3 -1
- package/dist/template/src/plugins/jarshop-checkout/order-email-delivery.entity.ts +15 -4
- package/dist/template/src/plugins/jarshop-checkout/order-email-lifecycle.ts +335 -0
- package/dist/template/src/plugins/jarshop-checkout/order-email-outbox.ts +308 -15
- package/dist/template/src/plugins/jarshop-checkout/order-email-worker.ts +86 -8
- package/dist/template/src/plugins/jarshop-checkout/order-lifecycle-core.ts +131 -0
- package/dist/template/src/plugins/jarshop-checkout/order-lifecycle-process.ts +102 -0
- package/dist/template-manifest.json +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { mkdir, rename, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import type { JarShopEmailBrandConfiguration } from "./order-email-configuration.entity.js";
|
|
4
|
+
import {
|
|
5
|
+
createTrackingUrl,
|
|
6
|
+
resolveShippingEmailConfiguration,
|
|
7
|
+
} from "./order-email-configuration.js";
|
|
8
|
+
import type {
|
|
9
|
+
FulfillmentLifecycleEmailKind,
|
|
10
|
+
FulfillmentLifecycleEmailSnapshot,
|
|
11
|
+
OrderCancellationEmailSnapshot,
|
|
12
|
+
} from "./order-email-outbox.js";
|
|
3
13
|
|
|
4
14
|
const fileTransport = "file";
|
|
5
15
|
const defaultStorefrontUrl = "http://localhost:3001";
|
|
@@ -11,6 +21,7 @@ export type OrderConfirmationEmail = {
|
|
|
11
21
|
recipient: string;
|
|
12
22
|
subject: string;
|
|
13
23
|
text: string;
|
|
24
|
+
html: string;
|
|
14
25
|
};
|
|
15
26
|
|
|
16
27
|
export type OrderEmailTransport = {
|
|
@@ -55,8 +66,21 @@ export type OrderConfirmationEmailInput = {
|
|
|
55
66
|
quantity: number;
|
|
56
67
|
totalWithTax: number;
|
|
57
68
|
}>;
|
|
69
|
+
brandConfiguration?: JarShopEmailBrandConfiguration;
|
|
58
70
|
};
|
|
59
71
|
|
|
72
|
+
export type OrderLifecycleEmailInput =
|
|
73
|
+
| {
|
|
74
|
+
kind: FulfillmentLifecycleEmailKind;
|
|
75
|
+
snapshot: FulfillmentLifecycleEmailSnapshot;
|
|
76
|
+
brandConfiguration: JarShopEmailBrandConfiguration;
|
|
77
|
+
}
|
|
78
|
+
| {
|
|
79
|
+
kind: "order-cancelled";
|
|
80
|
+
snapshot: OrderCancellationEmailSnapshot;
|
|
81
|
+
brandConfiguration: JarShopEmailBrandConfiguration;
|
|
82
|
+
};
|
|
83
|
+
|
|
60
84
|
export const orderEmailDeliveryPolicy = {
|
|
61
85
|
maximumAttempts: 5,
|
|
62
86
|
leaseDurationMs: 2 * 60 * 1_000,
|
|
@@ -182,6 +206,7 @@ export function createSmtpOrderEmailTransport(
|
|
|
182
206
|
to: string;
|
|
183
207
|
subject: string;
|
|
184
208
|
text: string;
|
|
209
|
+
html: string;
|
|
185
210
|
}) => Promise<unknown>,
|
|
186
211
|
): OrderEmailTransport {
|
|
187
212
|
return {
|
|
@@ -192,6 +217,7 @@ export function createSmtpOrderEmailTransport(
|
|
|
192
217
|
to: email.recipient,
|
|
193
218
|
subject: email.subject,
|
|
194
219
|
text: email.text,
|
|
220
|
+
html: email.html,
|
|
195
221
|
});
|
|
196
222
|
},
|
|
197
223
|
};
|
|
@@ -210,22 +236,104 @@ export function renderOrderConfirmationEmail(
|
|
|
210
236
|
const lines = input.lines.map(
|
|
211
237
|
(line) => `- ${line.quantity}× ${line.name}: ${money(line.totalWithTax)}`,
|
|
212
238
|
);
|
|
239
|
+
const brand = input.brandConfiguration ?? defaultBrandConfiguration;
|
|
213
240
|
|
|
241
|
+
const text = [
|
|
242
|
+
`${copy.greeting} #${input.orderCode}.`,
|
|
243
|
+
"",
|
|
244
|
+
copy.items,
|
|
245
|
+
...lines,
|
|
246
|
+
"",
|
|
247
|
+
`${copy.shipping}: ${input.shippingMethod || copy.notAvailable} (${money(input.shippingWithTax)})`,
|
|
248
|
+
`${copy.payment}: ${input.paymentMethod || copy.notAvailable}`,
|
|
249
|
+
`${copy.total}: ${money(input.totalWithTax)}`,
|
|
250
|
+
"",
|
|
251
|
+
`${copy.confirmation}: ${input.confirmationUrl}`,
|
|
252
|
+
"",
|
|
253
|
+
...renderTextBrandFooter(brand),
|
|
254
|
+
].join("\n");
|
|
214
255
|
return {
|
|
215
256
|
recipient: input.recipient,
|
|
216
257
|
subject: `${copy.subject} #${input.orderCode}`,
|
|
217
|
-
text
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
258
|
+
text,
|
|
259
|
+
html: renderBrandedEmailHtml({
|
|
260
|
+
brand,
|
|
261
|
+
languageCode,
|
|
262
|
+
heading: `${copy.subject} #${input.orderCode}`,
|
|
263
|
+
intro: `${copy.greeting} #${input.orderCode}.`,
|
|
264
|
+
lines: input.lines,
|
|
265
|
+
currencyCode: input.currencyCode,
|
|
266
|
+
details: [
|
|
267
|
+
[copy.shipping, input.shippingMethod || copy.notAvailable],
|
|
268
|
+
[copy.payment, input.paymentMethod || copy.notAvailable],
|
|
269
|
+
[copy.total, money(input.totalWithTax)],
|
|
270
|
+
],
|
|
271
|
+
action: { label: copy.confirmation, url: input.confirmationUrl },
|
|
272
|
+
}),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function renderOrderLifecycleEmail(
|
|
277
|
+
input: OrderLifecycleEmailInput,
|
|
278
|
+
): OrderConfirmationEmail {
|
|
279
|
+
const { snapshot } = input;
|
|
280
|
+
const languageCode = toOrderEmailLanguageCode(snapshot.languageCode);
|
|
281
|
+
const copy = lifecycleEmailCopy[languageCode][input.kind];
|
|
282
|
+
const money = createMoneyFormatter(languageCode, snapshot.currencyCode);
|
|
283
|
+
const methodConfiguration =
|
|
284
|
+
input.kind === "order-cancelled"
|
|
285
|
+
? undefined
|
|
286
|
+
: resolveShippingEmailConfiguration(
|
|
287
|
+
input.brandConfiguration,
|
|
288
|
+
input.snapshot.shippingMethodCode,
|
|
289
|
+
);
|
|
290
|
+
const trackingUrl =
|
|
291
|
+
input.kind === "shipment-shipped"
|
|
292
|
+
? createTrackingUrl(
|
|
293
|
+
methodConfiguration?.trackingUrlTemplate ?? "",
|
|
294
|
+
input.snapshot.trackingCode,
|
|
295
|
+
)
|
|
296
|
+
: undefined;
|
|
297
|
+
const extraText =
|
|
298
|
+
input.kind === "pickup-ready"
|
|
299
|
+
? methodConfiguration?.pickupDetails.trim()
|
|
300
|
+
: undefined;
|
|
301
|
+
const lines = snapshot.lines.map(
|
|
302
|
+
(line) => `- ${line.quantity}× ${line.name}: ${money(line.totalWithTax)}`,
|
|
303
|
+
);
|
|
304
|
+
const text = [
|
|
305
|
+
`${copy.intro} #${snapshot.orderCode}.`,
|
|
306
|
+
"",
|
|
307
|
+
copy.items,
|
|
308
|
+
...lines,
|
|
309
|
+
"",
|
|
310
|
+
`${copy.total}: ${money(snapshot.totalWithTax)}`,
|
|
311
|
+
...(extraText ? ["", `${copy.details}: ${extraText}`] : []),
|
|
312
|
+
...(trackingUrl ? ["", `${copy.tracking}: ${trackingUrl}`] : []),
|
|
313
|
+
"",
|
|
314
|
+
...renderTextBrandFooter(input.brandConfiguration),
|
|
315
|
+
].join("\n");
|
|
316
|
+
return {
|
|
317
|
+
recipient: snapshot.recipient,
|
|
318
|
+
subject: `${copy.subject} #${snapshot.orderCode}`,
|
|
319
|
+
text,
|
|
320
|
+
html: renderBrandedEmailHtml({
|
|
321
|
+
brand: input.brandConfiguration,
|
|
322
|
+
languageCode,
|
|
323
|
+
heading: `${copy.subject} #${snapshot.orderCode}`,
|
|
324
|
+
intro: `${copy.intro} #${snapshot.orderCode}.`,
|
|
325
|
+
lines: snapshot.lines,
|
|
326
|
+
currencyCode: snapshot.currencyCode,
|
|
327
|
+
details: [
|
|
328
|
+
[copy.total, money(snapshot.totalWithTax)],
|
|
329
|
+
...(extraText
|
|
330
|
+
? ([[copy.details, extraText]] as [string, string][])
|
|
331
|
+
: []),
|
|
332
|
+
],
|
|
333
|
+
...(trackingUrl
|
|
334
|
+
? { action: { label: copy.tracking, url: trackingUrl } }
|
|
335
|
+
: {}),
|
|
336
|
+
}),
|
|
229
337
|
};
|
|
230
338
|
}
|
|
231
339
|
|
|
@@ -264,7 +372,10 @@ export function sanitizeFileTransportEmail(
|
|
|
264
372
|
"To: [redacted]",
|
|
265
373
|
`Subject: ${email.subject}`,
|
|
266
374
|
"",
|
|
267
|
-
email.text
|
|
375
|
+
redactEmailTokens(email.text),
|
|
376
|
+
"",
|
|
377
|
+
"HTML:",
|
|
378
|
+
redactEmailTokens(email.html),
|
|
268
379
|
].join("\n");
|
|
269
380
|
}
|
|
270
381
|
|
|
@@ -335,3 +446,200 @@ const emailCopy = {
|
|
|
335
446
|
notAvailable: "not available",
|
|
336
447
|
},
|
|
337
448
|
} as const;
|
|
449
|
+
|
|
450
|
+
const lifecycleEmailCopy = {
|
|
451
|
+
sk: {
|
|
452
|
+
"pickup-ready": {
|
|
453
|
+
subject: "Objednávka je pripravená na vyzdvihnutie",
|
|
454
|
+
intro: "Vaša objednávka je pripravená na vyzdvihnutie",
|
|
455
|
+
items: "Pripravené položky:",
|
|
456
|
+
total: "Aktuálna suma objednávky",
|
|
457
|
+
details: "Miesto a pokyny na vyzdvihnutie",
|
|
458
|
+
tracking: "Sledovanie zásielky",
|
|
459
|
+
},
|
|
460
|
+
"pickup-picked-up": {
|
|
461
|
+
subject: "Objednávka bola vyzdvihnutá",
|
|
462
|
+
intro: "Vaša objednávka bola vyzdvihnutá",
|
|
463
|
+
items: "Vyzdvihnuté položky:",
|
|
464
|
+
total: "Celkom",
|
|
465
|
+
details: "Podrobnosti",
|
|
466
|
+
tracking: "Sledovanie zásielky",
|
|
467
|
+
},
|
|
468
|
+
"shipment-shipped": {
|
|
469
|
+
subject: "Objednávka bola odoslaná",
|
|
470
|
+
intro: "Časť vašej objednávky bola odoslaná",
|
|
471
|
+
items: "Odoslané položky:",
|
|
472
|
+
total: "Aktuálna suma objednávky",
|
|
473
|
+
details: "Podrobnosti",
|
|
474
|
+
tracking: "Sledovať zásielku",
|
|
475
|
+
},
|
|
476
|
+
"shipment-delivered": {
|
|
477
|
+
subject: "Objednávka bola doručená",
|
|
478
|
+
intro: "Časť vašej objednávky bola doručená",
|
|
479
|
+
items: "Doručené položky:",
|
|
480
|
+
total: "Aktuálna suma objednávky",
|
|
481
|
+
details: "Podrobnosti",
|
|
482
|
+
tracking: "Sledovanie zásielky",
|
|
483
|
+
},
|
|
484
|
+
"order-cancelled": {
|
|
485
|
+
subject: "Objednávka bola zrušená",
|
|
486
|
+
intro: "V objednávke bola vykonaná storno operácia",
|
|
487
|
+
items: "Zrušené položky:",
|
|
488
|
+
total: "Nová suma objednávky",
|
|
489
|
+
details: "Podrobnosti",
|
|
490
|
+
tracking: "Sledovanie zásielky",
|
|
491
|
+
},
|
|
492
|
+
},
|
|
493
|
+
en: {
|
|
494
|
+
"pickup-ready": {
|
|
495
|
+
subject: "Order ready for pickup",
|
|
496
|
+
intro: "Your order is ready for pickup",
|
|
497
|
+
items: "Items ready for pickup:",
|
|
498
|
+
total: "Current order total",
|
|
499
|
+
details: "Pickup location and instructions",
|
|
500
|
+
tracking: "Track shipment",
|
|
501
|
+
},
|
|
502
|
+
"pickup-picked-up": {
|
|
503
|
+
subject: "Order picked up",
|
|
504
|
+
intro: "Your order has been picked up",
|
|
505
|
+
items: "Picked-up items:",
|
|
506
|
+
total: "Total",
|
|
507
|
+
details: "Details",
|
|
508
|
+
tracking: "Track shipment",
|
|
509
|
+
},
|
|
510
|
+
"shipment-shipped": {
|
|
511
|
+
subject: "Order shipped",
|
|
512
|
+
intro: "Part of your order has been shipped",
|
|
513
|
+
items: "Shipped items:",
|
|
514
|
+
total: "Current order total",
|
|
515
|
+
details: "Details",
|
|
516
|
+
tracking: "Track shipment",
|
|
517
|
+
},
|
|
518
|
+
"shipment-delivered": {
|
|
519
|
+
subject: "Order delivered",
|
|
520
|
+
intro: "Part of your order has been delivered",
|
|
521
|
+
items: "Delivered items:",
|
|
522
|
+
total: "Current order total",
|
|
523
|
+
details: "Details",
|
|
524
|
+
tracking: "Track shipment",
|
|
525
|
+
},
|
|
526
|
+
"order-cancelled": {
|
|
527
|
+
subject: "Order cancelled",
|
|
528
|
+
intro: "A cancellation was applied to your order",
|
|
529
|
+
items: "Cancelled items:",
|
|
530
|
+
total: "New order total",
|
|
531
|
+
details: "Details",
|
|
532
|
+
tracking: "Track shipment",
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
} as const;
|
|
536
|
+
|
|
537
|
+
const defaultBrandConfiguration = {
|
|
538
|
+
brandName: "JarShop",
|
|
539
|
+
logoUrl: null,
|
|
540
|
+
primaryColor: "#111827",
|
|
541
|
+
websiteUrl: "https://jarwizz.com",
|
|
542
|
+
supportEmail: "support@jarwizz.com",
|
|
543
|
+
companyName: "JarShop",
|
|
544
|
+
companyAddress: "",
|
|
545
|
+
} as const;
|
|
546
|
+
|
|
547
|
+
function createMoneyFormatter(
|
|
548
|
+
languageCode: OrderEmailLanguageCode,
|
|
549
|
+
currencyCode: string,
|
|
550
|
+
) {
|
|
551
|
+
const formatter = new Intl.NumberFormat(
|
|
552
|
+
languageCode === "sk" ? "sk-SK" : "en-GB",
|
|
553
|
+
{ style: "currency", currency: currencyCode },
|
|
554
|
+
);
|
|
555
|
+
return (minorUnits: number) => formatter.format(minorUnits / 100);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function renderBrandedEmailHtml(input: {
|
|
559
|
+
brand: Pick<
|
|
560
|
+
JarShopEmailBrandConfiguration,
|
|
561
|
+
| "brandName"
|
|
562
|
+
| "logoUrl"
|
|
563
|
+
| "primaryColor"
|
|
564
|
+
| "websiteUrl"
|
|
565
|
+
| "supportEmail"
|
|
566
|
+
| "companyName"
|
|
567
|
+
| "companyAddress"
|
|
568
|
+
>;
|
|
569
|
+
languageCode: OrderEmailLanguageCode;
|
|
570
|
+
heading: string;
|
|
571
|
+
intro: string;
|
|
572
|
+
lines: ReadonlyArray<{
|
|
573
|
+
name: string;
|
|
574
|
+
quantity: number;
|
|
575
|
+
totalWithTax: number;
|
|
576
|
+
}>;
|
|
577
|
+
currencyCode: string;
|
|
578
|
+
details: readonly (readonly [string, string])[];
|
|
579
|
+
action?: { label: string; url: string };
|
|
580
|
+
}): string {
|
|
581
|
+
const money = new Intl.NumberFormat(
|
|
582
|
+
input.languageCode === "sk" ? "sk-SK" : "en-GB",
|
|
583
|
+
{
|
|
584
|
+
style: "currency",
|
|
585
|
+
currency: input.currencyCode,
|
|
586
|
+
},
|
|
587
|
+
);
|
|
588
|
+
const logo = input.brand.logoUrl
|
|
589
|
+
? `<img src="${escapeHtml(input.brand.logoUrl)}" alt="${escapeHtml(input.brand.brandName)}" style="max-height:56px;max-width:180px">`
|
|
590
|
+
: `<strong style="font-size:24px">${escapeHtml(input.brand.brandName)}</strong>`;
|
|
591
|
+
const itemRows = input.lines
|
|
592
|
+
.map(
|
|
593
|
+
(line) =>
|
|
594
|
+
`<tr><td style="padding:8px;border-bottom:1px solid #e5e7eb">${escapeHtml(`${line.quantity}× ${line.name}`)}</td><td style="padding:8px;border-bottom:1px solid #e5e7eb;text-align:right">${escapeHtml(money.format(line.totalWithTax / 100))}</td></tr>`,
|
|
595
|
+
)
|
|
596
|
+
.join("");
|
|
597
|
+
const details = input.details
|
|
598
|
+
.map(
|
|
599
|
+
([label, value]) =>
|
|
600
|
+
`<p><strong>${escapeHtml(label)}:</strong> ${escapeHtml(value)}</p>`,
|
|
601
|
+
)
|
|
602
|
+
.join("");
|
|
603
|
+
const action = input.action
|
|
604
|
+
? `<p style="margin:24px 0"><a href="${escapeHtml(input.action.url)}" style="background:${escapeHtml(input.brand.primaryColor)};color:#fff;padding:12px 18px;text-decoration:none;border-radius:4px">${escapeHtml(input.action.label)}</a></p>`
|
|
605
|
+
: "";
|
|
606
|
+
return `<!doctype html><html><body style="margin:0;background:#f3f4f6;font-family:Arial,sans-serif;color:#111827"><main style="max-width:640px;margin:0 auto;background:#fff;padding:32px"><header><a href="${escapeHtml(input.brand.websiteUrl)}" style="color:${escapeHtml(input.brand.primaryColor)}">${logo}</a></header><h1 style="color:${escapeHtml(input.brand.primaryColor)}">${escapeHtml(input.heading)}</h1><p>${escapeHtml(input.intro)}</p><table style="width:100%;border-collapse:collapse">${itemRows}</table>${details}${action}<footer style="margin-top:32px;padding-top:16px;border-top:1px solid #e5e7eb;font-size:12px;color:#6b7280"><p>${escapeHtml(input.brand.companyName)}<br>${escapeHtml(input.brand.companyAddress)}</p><p><a href="mailto:${escapeHtml(input.brand.supportEmail)}">${escapeHtml(input.brand.supportEmail)}</a></p></footer></main></body></html>`;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function renderTextBrandFooter(
|
|
610
|
+
brand: Pick<
|
|
611
|
+
JarShopEmailBrandConfiguration,
|
|
612
|
+
| "brandName"
|
|
613
|
+
| "websiteUrl"
|
|
614
|
+
| "supportEmail"
|
|
615
|
+
| "companyName"
|
|
616
|
+
| "companyAddress"
|
|
617
|
+
>,
|
|
618
|
+
): string[] {
|
|
619
|
+
return [
|
|
620
|
+
brand.brandName,
|
|
621
|
+
brand.companyName,
|
|
622
|
+
brand.companyAddress,
|
|
623
|
+
brand.websiteUrl,
|
|
624
|
+
brand.supportEmail,
|
|
625
|
+
].filter((value, index, values) => value && values.indexOf(value) === index);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function escapeHtml(value: string): string {
|
|
629
|
+
return value.replace(/[&<>"']/gu, (character) => htmlEscapes[character]);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function redactEmailTokens(value: string): string {
|
|
633
|
+
return value.replace(
|
|
634
|
+
/([?&](?:token|credential)=)[^\s"'&<]+/gu,
|
|
635
|
+
"$1[redacted]",
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const htmlEscapes: Record<string, string> = {
|
|
640
|
+
"&": "&",
|
|
641
|
+
"<": "<",
|
|
642
|
+
">": ">",
|
|
643
|
+
'"': """,
|
|
644
|
+
"'": "'",
|
|
645
|
+
};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import type { OrderEmailDeliveryKind } from "./order-email-delivery.entity.js";
|
|
2
|
+
|
|
1
3
|
export type OrderEmailDeliveryStatusRecord = {
|
|
2
4
|
id: number;
|
|
3
5
|
orderId: number;
|
|
4
|
-
kind:
|
|
6
|
+
kind: OrderEmailDeliveryKind;
|
|
5
7
|
status: "pending" | "processing" | "sent" | "failed";
|
|
6
8
|
attemptCount: number;
|
|
7
9
|
nextAttemptAt: Date;
|
|
@@ -11,8 +11,16 @@ import {
|
|
|
11
11
|
export type OrderEmailDeliveryStatus =
|
|
12
12
|
"pending" | "processing" | "sent" | "failed";
|
|
13
13
|
|
|
14
|
+
export type OrderEmailDeliveryKind =
|
|
15
|
+
| "order-confirmation"
|
|
16
|
+
| "pickup-ready"
|
|
17
|
+
| "pickup-picked-up"
|
|
18
|
+
| "shipment-shipped"
|
|
19
|
+
| "shipment-delivered"
|
|
20
|
+
| "order-cancelled";
|
|
21
|
+
|
|
14
22
|
@Entity({ name: "jarshop_order_email_delivery" })
|
|
15
|
-
@Unique("
|
|
23
|
+
@Unique("UQ_jarshop_order_email_delivery_event_key", ["eventKey"])
|
|
16
24
|
@Index("IDX_jarshop_order_email_delivery_pending", ["status", "nextAttemptAt"])
|
|
17
25
|
export class JarShopOrderEmailDelivery {
|
|
18
26
|
@PrimaryGeneratedColumn()
|
|
@@ -21,11 +29,14 @@ export class JarShopOrderEmailDelivery {
|
|
|
21
29
|
@Column({ type: "int" })
|
|
22
30
|
orderId!: number;
|
|
23
31
|
|
|
24
|
-
@Column({ type: "varchar", length:
|
|
25
|
-
|
|
32
|
+
@Column({ type: "varchar", length: 255 })
|
|
33
|
+
eventKey!: string;
|
|
34
|
+
|
|
35
|
+
@Column({ type: "varchar", length: 36, nullable: true })
|
|
36
|
+
checkoutAttemptId!: string | null;
|
|
26
37
|
|
|
27
38
|
@Column({ type: "varchar", length: 64 })
|
|
28
|
-
kind!:
|
|
39
|
+
kind!: OrderEmailDeliveryKind;
|
|
29
40
|
|
|
30
41
|
@Column({ type: "varchar", length: 10 })
|
|
31
42
|
languageCode!: string;
|