medusa-plugin-ses 2.0.2 → 2.0.4

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.
@@ -1,1291 +0,0 @@
1
- import * as aws from '@aws-sdk/client-ses'
2
- import Handlebars from 'handlebars'
3
- import nodemailer from 'nodemailer'
4
- import path from 'path'
5
- import fs from 'fs'
6
- import { humanizeAmount, zeroDecimalCurrencies } from 'medusa-core-utils'
7
- import { NotificationService } from 'medusa-interfaces'
8
-
9
- class SESService extends NotificationService {
10
- static identifier = "ses"
11
-
12
- /**
13
- * @param {Object} options - options defined in `medusa-config.js`
14
- * e.g.
15
- * {
16
- * access_key_id: process.env.SES_ACCESS_KEY_ID,
17
- * secret_access_key: process.env.SES_SECRET_ACCESS_KEY,
18
- * region: process.env.SES_REGION,
19
- * from: process.env.SES_FROM,
20
- * enable_endpoint: process.env.SES_ENABLE_ENDPOINT,
21
- * template_path: process.env.SES_TEMPLATE_PATH,
22
- * order_placed_template: 'order_placed',
23
- * }
24
- */
25
- constructor({
26
- storeService,
27
- orderService,
28
- returnService,
29
- swapService,
30
- cartService,
31
- lineItemService,
32
- claimService,
33
- fulfillmentService,
34
- fulfillmentProviderService,
35
- totalsService,
36
- productVariantService,
37
- }, options) {
38
-
39
- super()
40
-
41
- this.options_ = options
42
-
43
- this.fulfillmentProviderService_ = fulfillmentProviderService
44
- this.storeService_ = storeService
45
- this.lineItemService_ = lineItemService
46
- this.orderService_ = orderService
47
- this.cartService_ = cartService
48
- this.claimService_ = claimService
49
- this.returnService_ = returnService
50
- this.swapService_ = swapService
51
- this.fulfillmentService_ = fulfillmentService
52
- this.totalsService_ = totalsService
53
- this.productVariantService_ = productVariantService
54
-
55
- const ses = new aws.SES({
56
- region: options.region,
57
- credentials: {
58
- accessKeyId: options.access_key_id,
59
- secretAccessKey: options.secret_access_key,
60
- },
61
- })
62
-
63
- this.transporter_ = nodemailer.createTransport({
64
- SES: { ses, aws }
65
- })
66
- }
67
-
68
- async fetchAttachments(event, data, attachmentGenerator) {
69
- switch (event) {
70
- case "swap.created":
71
- case "order.return_requested": {
72
- let attachments = []
73
- const { shipping_method, shipping_data } = data.return_request
74
- if (shipping_method) {
75
- const provider = shipping_method.shipping_option.provider_id
76
-
77
- const lbl = await this.fulfillmentProviderService_.retrieveDocuments(
78
- provider,
79
- shipping_data,
80
- "label"
81
- )
82
-
83
- attachments = attachments.concat(
84
- lbl.map((d) => ({
85
- name: "return-label",
86
- base64: d.base_64,
87
- type: d.type,
88
- }))
89
- )
90
- }
91
-
92
- if (attachmentGenerator && attachmentGenerator.createReturnInvoice) {
93
- const base64 = await attachmentGenerator.createReturnInvoice(
94
- data.order,
95
- data.return_request.items
96
- )
97
- attachments.push({
98
- name: "invoice",
99
- base64,
100
- type: "application/pdf",
101
- })
102
- }
103
-
104
- return attachments
105
- }
106
- default:
107
- return []
108
- }
109
- }
110
-
111
- async fetchData(event, eventData, attachmentGenerator) {
112
- switch (event) {
113
- case "order.return_requested":
114
- return this.returnRequestedData(eventData, attachmentGenerator)
115
- case "swap.shipment_created":
116
- return this.swapShipmentCreatedData(eventData, attachmentGenerator)
117
- case "claim.shipment_created":
118
- return this.claimShipmentCreatedData(eventData, attachmentGenerator)
119
- case "order.items_returned":
120
- return this.itemsReturnedData(eventData, attachmentGenerator)
121
- case "swap.received":
122
- return this.swapReceivedData(eventData, attachmentGenerator)
123
- case "swap.created":
124
- return this.swapCreatedData(eventData, attachmentGenerator)
125
- case "gift_card.created":
126
- return this.gcCreatedData(eventData, attachmentGenerator)
127
- case "order.gift_card_created":
128
- return this.gcCreatedData(eventData, attachmentGenerator)
129
- case "order.placed":
130
- return this.orderPlacedData(eventData, attachmentGenerator)
131
- case "order.shipment_created":
132
- return this.orderShipmentCreatedData(eventData, attachmentGenerator)
133
- case "order.canceled":
134
- return this.orderCanceledData(eventData, attachmentGenerator)
135
- case "user.password_reset":
136
- return this.userPasswordResetData(eventData, attachmentGenerator)
137
- case "customer.password_reset":
138
- return this.customerPasswordResetData(eventData, attachmentGenerator)
139
- case "restock-notification.restocked":
140
- return await this.restockNotificationData(
141
- eventData,
142
- attachmentGenerator
143
- )
144
- case "order.refund_created":
145
- return this.orderRefundCreatedData(eventData, attachmentGenerator)
146
- default:
147
- return {}
148
- }
149
- }
150
-
151
- getLocalizedTemplateId(event, locale) {
152
- if (this.options_.localization && this.options_.localization[locale]) {
153
- const map = this.options_.localization[locale]
154
- switch (event) {
155
- case "order.return_requested":
156
- return map.order_return_requested_template
157
- case "swap.shipment_created":
158
- return map.swap_shipment_created_template
159
- case "claim.shipment_created":
160
- return map.claim_shipment_created_template
161
- case "order.items_returned":
162
- return map.order_items_returned_template
163
- case "swap.received":
164
- return map.swap_received_template
165
- case "swap.created":
166
- return map.swap_created_template
167
- case "gift_card.created":
168
- return map.gift_card_created_template
169
- case "order.gift_card_created":
170
- return map.gift_card_created_template
171
- case "order.placed":
172
- return map.order_placed_template
173
- case "order.shipment_created":
174
- return map.order_shipped_template
175
- case "order.canceled":
176
- return map.order_canceled_template
177
- case "user.password_reset":
178
- return map.user_password_reset_template
179
- case "customer.password_reset":
180
- return map.customer_password_reset_template
181
- case "restock-notification.restocked":
182
- return map.medusa_restock_template
183
- case "order.refund_created":
184
- return map.order_refund_created_template
185
- default:
186
- return null
187
- }
188
- }
189
- return null
190
- }
191
-
192
- getTemplateId(event) {
193
- switch (event) {
194
- case "order.return_requested":
195
- return this.options_.order_return_requested_template
196
- case "swap.shipment_created":
197
- return this.options_.swap_shipment_created_template
198
- case "claim.shipment_created":
199
- return this.options_.claim_shipment_created_template
200
- case "order.items_returned":
201
- return this.options_.order_items_returned_template
202
- case "swap.received":
203
- return this.options_.swap_received_template
204
- case "swap.created":
205
- return this.options_.swap_created_template
206
- case "gift_card.created":
207
- return this.options_.gift_card_created_template
208
- case "order.gift_card_created":
209
- return this.options_.gift_card_created_template
210
- case "order.placed":
211
- return this.options_.order_placed_template
212
- case "order.shipment_created":
213
- return this.options_.order_shipped_template
214
- case "order.canceled":
215
- return this.options_.order_canceled_template
216
- case "user.password_reset":
217
- return this.options_.user_password_reset_template
218
- case "customer.password_reset":
219
- return this.options_.customer_password_reset_template
220
- case "restock-notification.restocked":
221
- return this.options_.medusa_restock_template
222
- case "order.refund_created":
223
- return this.options_.order_refund_created_template
224
- default:
225
- return null
226
- }
227
- }
228
-
229
- async compileTemplate(templateId, data) {
230
- //const base = path.resolve(__dirname, '../../../', this.options_.template_path, templateId)
231
- const base = path.resolve(this.options_.template_path, templateId)
232
- const subjectTemplate = Handlebars.compile(fs.readFileSync(path.join(base, 'subject.hbs'), "utf8"))
233
- const htmlTemplate = Handlebars.compile(fs.readFileSync(path.join(base, 'html.hbs'), "utf8"))
234
- const textTemplate = Handlebars.compile(fs.readFileSync(path.join(base, 'text.hbs'), "utf8"))
235
- return {
236
- subject: subjectTemplate(data),
237
- html: htmlTemplate(data),
238
- text: textTemplate(data)
239
- }
240
- }
241
-
242
- async sendNotification(event, eventData, attachmentGenerator) {
243
- let templateId = this.getTemplateId(event)
244
- if (!templateId) { return false }
245
- if (data.locale) {
246
- templateId = this.getLocalizedTemplateId(event, data.locale) || templateId
247
- }
248
-
249
- const data = await this.fetchData(event, eventData, attachmentGenerator)
250
- if (!data) { return false }
251
-
252
- const { subject, html, text } = await this.compileTemplate(templateId, data)
253
- if (!subject || (!html && !text)) { return false }
254
-
255
- const sendOptions = {
256
- from: this.options_.from,
257
- to: data.email,
258
- subject,
259
- html,
260
- text
261
- }
262
- console.log(sendOptions)
263
-
264
- const attachments = await this.fetchAttachments(
265
- event,
266
- data,
267
- attachmentGenerator
268
- )
269
-
270
- if (attachments?.length) {
271
- sendOptions.has_attachments = true
272
- sendOptions.attachments = attachments.map((a) => {
273
- return {
274
- content: a.base64,
275
- filename: a.name,
276
- encoding: 'base64',
277
- contentType: a.type
278
- }
279
- })
280
- }
281
-
282
- //const status = await this.transporter_.sendMail(sendOptions).then(() => "sent").catch(() => "failed")
283
- let status
284
- await this.transporter_.sendMail(sendOptions)
285
- .then(() => { status = "sent" })
286
- .catch((error) => { status = "failed"; console.log(error) })
287
-
288
- // We don't want heavy docs stored in DB
289
- delete sendOptions.attachments
290
-
291
- return { to: data.email, status, data: sendOptions }
292
- }
293
-
294
- async resendNotification(notification, config, attachmentGenerator) {
295
- const sendOptions = {
296
- ...notification.data,
297
- to: config.to || notification.to,
298
- }
299
-
300
- const attachs = await this.fetchAttachments(
301
- notification.event_name,
302
- notification.data.dynamic_template_data,
303
- attachmentGenerator
304
- )
305
-
306
- sendOptions.attachments = attachs.map((a) => {
307
- return {
308
- content: a.base64,
309
- filename: a.name,
310
- encoding: 'base64',
311
- contentType: a.type
312
- }
313
- })
314
-
315
- //const status = await this.transporter_.sendMail(sendOptions).then(() => "sent").catch(() => "failed")
316
- let status
317
- await this.transporter_.sendMail(sendOptions)
318
- .then(() => { status = "sent" })
319
- .catch((error) => { status = "failed"; console.log(error) })
320
-
321
- return { to: sendOptions.to, status, data: sendOptions }
322
- }
323
-
324
- /**
325
- * Sends an email using SES.
326
- * @param {string} template_id - id of template to use
327
- * @param {string} from - sender of email
328
- * @param {string} to - receiver of email
329
- * @param {Object} data - data to send in mail (match with template)
330
- * @return {Promise} result of the send operation
331
- */
332
- async sendEmail(template_id, from, to, data) {
333
- // This function is used by the /ses/send API endpoint included in this plugin.
334
- // It is disabled by default.
335
- // This endpoint may be useful for testing purposes and for use by related applications.
336
- // There is NO SECURITY on the endpoint by default.
337
- // Most people will NOT need to enable it.
338
- // If you are certain that you want to enable it and that you know what you are doing,
339
- // set the environment variable SES_ENABLE_ENDPOINT to "42" (a string, not an int).
340
- // The unsual setting is meant to prevent enabling by accident or without thought.
341
- if (this.options_.enable_endpoint !== '42') { return false }
342
- const { subject, html, text } = await this.compileTemplate(template_id, data)
343
- if (!subject || (!html && !text)) { return false }
344
- try {
345
- return this.transporter_.sendMail({
346
- from: from,
347
- to: to,
348
- subject,
349
- html,
350
- text
351
- })
352
- } catch (error) {
353
- throw error
354
- }
355
- }
356
-
357
- async orderShipmentCreatedData({ id, fulfillment_id }, attachmentGenerator) {
358
- const order = await this.orderService_.retrieve(id, {
359
- select: [
360
- "shipping_total",
361
- "discount_total",
362
- "tax_total",
363
- "refunded_total",
364
- "gift_card_total",
365
- "subtotal",
366
- "total",
367
- "refundable_amount",
368
- ],
369
- relations: [
370
- "customer",
371
- "billing_address",
372
- "shipping_address",
373
- "discounts",
374
- "discounts.rule",
375
- "shipping_methods",
376
- "shipping_methods.shipping_option",
377
- "payments",
378
- "fulfillments",
379
- "returns",
380
- "gift_cards",
381
- "gift_card_transactions",
382
- ],
383
- })
384
-
385
- const shipment = await this.fulfillmentService_.retrieve(fulfillment_id, {
386
- relations: ["items", "tracking_links"],
387
- })
388
-
389
- const locale = await this.extractLocale(order)
390
-
391
- return {
392
- locale,
393
- order,
394
- date: shipment.shipped_at.toDateString(),
395
- email: order.email,
396
- fulfillment: shipment,
397
- tracking_links: shipment.tracking_links,
398
- tracking_number: shipment.tracking_numbers.join(", "),
399
- }
400
- }
401
-
402
- async orderCanceledData({ id }) {
403
- const order = await this.orderService_.retrieve(id, {
404
- select: [
405
- "shipping_total",
406
- "discount_total",
407
- "tax_total",
408
- "refunded_total",
409
- "gift_card_total",
410
- "subtotal",
411
- "total",
412
- ],
413
- relations: [
414
- "customer",
415
- "billing_address",
416
- "shipping_address",
417
- "discounts",
418
- "discounts.rule",
419
- "shipping_methods",
420
- "shipping_methods.shipping_option",
421
- "payments",
422
- "fulfillments",
423
- "returns",
424
- "gift_cards",
425
- "gift_card_transactions",
426
- ],
427
- })
428
-
429
- const {
430
- subtotal,
431
- tax_total,
432
- discount_total,
433
- shipping_total,
434
- gift_card_total,
435
- total,
436
- } = order
437
-
438
- const taxRate = order.tax_rate / 100
439
- const currencyCode = order.currency_code.toUpperCase()
440
-
441
- const items = this.processItems_(order.items, taxRate, currencyCode)
442
-
443
- let discounts = []
444
- if (order.discounts) {
445
- discounts = order.discounts.map((discount) => {
446
- return {
447
- is_giftcard: false,
448
- code: discount.code,
449
- descriptor: `${discount.rule.value}${
450
- discount.rule.type === "percentage" ? "%" : ` ${currencyCode}`
451
- }`,
452
- }
453
- })
454
- }
455
-
456
- let giftCards = []
457
- if (order.gift_cards) {
458
- giftCards = order.gift_cards.map((gc) => {
459
- return {
460
- is_giftcard: true,
461
- code: gc.code,
462
- descriptor: `${gc.value} ${currencyCode}`,
463
- }
464
- })
465
-
466
- discounts.concat(giftCards)
467
- }
468
-
469
- const locale = await this.extractLocale(order)
470
-
471
- return {
472
- ...order,
473
- locale,
474
- has_discounts: order.discounts.length,
475
- has_gift_cards: order.gift_cards.length,
476
- date: order.created_at.toDateString(),
477
- items,
478
- discounts,
479
- subtotal: `${this.humanPrice_(
480
- subtotal * (1 + taxRate),
481
- currencyCode
482
- )} ${currencyCode}`,
483
- gift_card_total: `${this.humanPrice_(
484
- gift_card_total * (1 + taxRate),
485
- currencyCode
486
- )} ${currencyCode}`,
487
- tax_total: `${this.humanPrice_(tax_total, currencyCode)} ${currencyCode}`,
488
- discount_total: `${this.humanPrice_(
489
- discount_total * (1 + taxRate),
490
- currencyCode
491
- )} ${currencyCode}`,
492
- shipping_total: `${this.humanPrice_(
493
- shipping_total * (1 + taxRate),
494
- currencyCode
495
- )} ${currencyCode}`,
496
- total: `${this.humanPrice_(total, currencyCode)} ${currencyCode}`,
497
- }
498
- }
499
-
500
- async orderPlacedData({ id }) {
501
- const order = await this.orderService_.retrieve(id, {
502
- select: [
503
- "shipping_total",
504
- "discount_total",
505
- "tax_total",
506
- "refunded_total",
507
- "gift_card_total",
508
- "subtotal",
509
- "total",
510
- ],
511
- relations: [
512
- "customer",
513
- "billing_address",
514
- "shipping_address",
515
- "discounts",
516
- "discounts.rule",
517
- "shipping_methods",
518
- "shipping_methods.shipping_option",
519
- "payments",
520
- "fulfillments",
521
- "returns",
522
- "gift_cards",
523
- "gift_card_transactions",
524
- ],
525
- })
526
-
527
- const { tax_total, shipping_total, gift_card_total, total } = order
528
-
529
- const currencyCode = order.currency_code.toUpperCase()
530
-
531
- const items = await Promise.all(
532
- order.items.map(async (i) => {
533
- i.totals = await this.totalsService_.getLineItemTotals(i, order, {
534
- include_tax: true,
535
- use_tax_lines: true,
536
- })
537
- i.thumbnail = this.normalizeThumbUrl_(i.thumbnail)
538
- i.discounted_price = `${this.humanPrice_(
539
- i.totals.total / i.quantity,
540
- currencyCode
541
- )} ${currencyCode}`
542
- i.price = `${this.humanPrice_(
543
- i.totals.original_total / i.quantity,
544
- currencyCode
545
- )} ${currencyCode}`
546
- return i
547
- })
548
- )
549
-
550
- let discounts = []
551
- if (order.discounts) {
552
- discounts = order.discounts.map((discount) => {
553
- return {
554
- is_giftcard: false,
555
- code: discount.code,
556
- descriptor: `${discount.rule.value}${
557
- discount.rule.type === "percentage" ? "%" : ` ${currencyCode}`
558
- }`,
559
- }
560
- })
561
- }
562
-
563
- let giftCards = []
564
- if (order.gift_cards) {
565
- giftCards = order.gift_cards.map((gc) => {
566
- return {
567
- is_giftcard: true,
568
- code: gc.code,
569
- descriptor: `${gc.value} ${currencyCode}`,
570
- }
571
- })
572
-
573
- discounts.concat(giftCards)
574
- }
575
-
576
- const locale = await this.extractLocale(order)
577
-
578
- // Includes taxes in discount amount
579
- const discountTotal = items.reduce((acc, i) => {
580
- return acc + i.totals.original_total - i.totals.total
581
- }, 0)
582
-
583
- const discounted_subtotal = items.reduce((acc, i) => {
584
- return acc + i.totals.total
585
- }, 0)
586
- const subtotal = items.reduce((acc, i) => {
587
- return acc + i.totals.original_total
588
- }, 0)
589
-
590
- const subtotal_ex_tax = items.reduce((total, i) => {
591
- return total + i.totals.subtotal
592
- }, 0)
593
-
594
- return {
595
- ...order,
596
- locale,
597
- has_discounts: order.discounts.length,
598
- has_gift_cards: order.gift_cards.length,
599
- date: order.created_at.toDateString(),
600
- items,
601
- discounts,
602
- subtotal_ex_tax: `${this.humanPrice_(
603
- subtotal_ex_tax,
604
- currencyCode
605
- )} ${currencyCode}`,
606
- subtotal: `${this.humanPrice_(subtotal, currencyCode)} ${currencyCode}`,
607
- gift_card_total: `${this.humanPrice_(
608
- gift_card_total,
609
- currencyCode
610
- )} ${currencyCode}`,
611
- tax_total: `${this.humanPrice_(tax_total, currencyCode)} ${currencyCode}`,
612
- discount_total: `${this.humanPrice_(
613
- discountTotal,
614
- currencyCode
615
- )} ${currencyCode}`,
616
- shipping_total: `${this.humanPrice_(
617
- shipping_total,
618
- currencyCode
619
- )} ${currencyCode}`,
620
- total: `${this.humanPrice_(total, currencyCode)} ${currencyCode}`,
621
- }
622
- }
623
-
624
- async gcCreatedData({ id }) {
625
- const giftCard = await this.giftCardService_.retrieve(id, {
626
- relations: ["region", "order"],
627
- })
628
-
629
- if (!giftCard.order) {
630
- return
631
- }
632
-
633
- const taxRate = giftCard.region.tax_rate / 100
634
-
635
- const locale = await this.extractLocale(order)
636
-
637
- return {
638
- ...giftCard,
639
- locale,
640
- email: giftCard.order.email,
641
- display_value: giftCard.value * (1 + taxRate),
642
- }
643
- }
644
-
645
- async returnRequestedData({ id, return_id }) {
646
- // Fetch the return request
647
- const returnRequest = await this.returnService_.retrieve(return_id, {
648
- relations: [
649
- "items",
650
- "items.item",
651
- "items.item.tax_lines",
652
- "items.item.variant",
653
- "items.item.variant.product",
654
- "shipping_method",
655
- "shipping_method.tax_lines",
656
- "shipping_method.shipping_option",
657
- ],
658
- })
659
-
660
- const items = await this.lineItemService_.list(
661
- {
662
- id: returnRequest.items.map(({ item_id }) => item_id),
663
- },
664
- { relations: ["tax_lines"] }
665
- )
666
-
667
- // Fetch the order
668
- const order = await this.orderService_.retrieve(id, {
669
- select: ["total"],
670
- relations: [
671
- "items",
672
- "items.tax_lines",
673
- "discounts",
674
- "discounts.rule",
675
- "shipping_address",
676
- "returns",
677
- ],
678
- })
679
-
680
- const currencyCode = order.currency_code.toUpperCase()
681
-
682
- // Calculate which items are in the return
683
- const returnItems = await Promise.all(
684
- returnRequest.items.map(async (i) => {
685
- const found = items.find((oi) => oi.id === i.item_id)
686
- found.quantity = i.quantity
687
- found.thumbnail = this.normalizeThumbUrl_(found.thumbnail)
688
- found.totals = await this.totalsService_.getLineItemTotals(
689
- found,
690
- order,
691
- {
692
- include_tax: true,
693
- use_tax_lines: true,
694
- }
695
- )
696
- found.price = `${this.humanPrice_(
697
- found.totals.total,
698
- currencyCode
699
- )} ${currencyCode}`
700
- found.tax_lines = found.totals.tax_lines
701
- return found
702
- })
703
- )
704
-
705
- // Get total of the returned products
706
- const item_subtotal = returnItems.reduce(
707
- (acc, next) => acc + next.totals.total,
708
- 0
709
- )
710
-
711
- // If the return has a shipping method get the price and any attachments
712
- let shippingTotal = 0
713
- if (returnRequest.shipping_method) {
714
- const base = returnRequest.shipping_method.price
715
- shippingTotal =
716
- base +
717
- returnRequest.shipping_method.tax_lines.reduce((acc, next) => {
718
- return Math.round(acc + base * (next.rate / 100))
719
- }, 0)
720
- }
721
-
722
- const locale = await this.extractLocale(order)
723
-
724
- return {
725
- locale,
726
- has_shipping: !!returnRequest.shipping_method,
727
- email: order.email,
728
- items: returnItems,
729
- subtotal: `${this.humanPrice_(
730
- item_subtotal,
731
- currencyCode
732
- )} ${currencyCode}`,
733
- shipping_total: `${this.humanPrice_(
734
- shippingTotal,
735
- currencyCode
736
- )} ${currencyCode}`,
737
- refund_amount: `${this.humanPrice_(
738
- returnRequest.refund_amount,
739
- currencyCode
740
- )} ${currencyCode}`,
741
- return_request: {
742
- ...returnRequest,
743
- refund_amount: `${this.humanPrice_(
744
- returnRequest.refund_amount,
745
- currencyCode
746
- )} ${currencyCode}`,
747
- },
748
- order,
749
- date: returnRequest.updated_at.toDateString(),
750
- }
751
- }
752
-
753
- async swapReceivedData({ id }) {
754
- const store = await this.storeService_.retrieve()
755
- const swap = await this.swapService_.retrieve(id, {
756
- relations: [
757
- "additional_items",
758
- "additional_items.tax_lines",
759
- "return_order",
760
- "return_order.items",
761
- "return_order.items.item",
762
- "return_order.shipping_method",
763
- "return_order.shipping_method.shipping_option",
764
- ],
765
- })
766
-
767
- const returnRequest = swap.return_order
768
-
769
- const items = await this.lineItemService_.list(
770
- {
771
- id: returnRequest.items.map(({ item_id }) => item_id),
772
- },
773
- {
774
- relations: ["tax_lines"],
775
- }
776
- )
777
-
778
- returnRequest.items = returnRequest.items.map((item) => {
779
- const found = items.find((i) => i.id === item.item_id)
780
- return {
781
- ...item,
782
- item: found,
783
- }
784
- })
785
-
786
- const swapLink = store.swap_link_template.replace(
787
- /\{cart_id\}/,
788
- swap.cart_id
789
- )
790
-
791
- const order = await this.orderService_.retrieve(swap.order_id, {
792
- select: ["total"],
793
- relations: [
794
- "items",
795
- "discounts",
796
- "discounts.rule",
797
- "shipping_address",
798
- "swaps",
799
- "swaps.additional_items",
800
- "swaps.additional_items.tax_lines",
801
- ],
802
- })
803
-
804
- const cart = await this.cartService_.retrieve(swap.cart_id, {
805
- select: [
806
- "total",
807
- "tax_total",
808
- "discount_total",
809
- "shipping_total",
810
- "subtotal",
811
- ],
812
- })
813
- const currencyCode = order.currency_code.toUpperCase()
814
-
815
- const decoratedItems = await Promise.all(
816
- cart.items.map(async (i) => {
817
- const totals = await this.totalsService_.getLineItemTotals(i, cart, {
818
- include_tax: true,
819
- })
820
-
821
- return {
822
- ...i,
823
- totals,
824
- price: this.humanPrice_(
825
- totals.subtotal + totals.tax_total,
826
- currencyCode
827
- ),
828
- }
829
- })
830
- )
831
-
832
- const returnTotal = decoratedItems.reduce((acc, next) => {
833
- if (next.is_return) {
834
- return acc + -1 * (next.totals.subtotal + next.totals.tax_total)
835
- }
836
- return acc
837
- }, 0)
838
-
839
- const additionalTotal = decoratedItems.reduce((acc, next) => {
840
- if (!next.is_return) {
841
- return acc + next.totals.subtotal + next.totals.tax_total
842
- }
843
- return acc
844
- }, 0)
845
-
846
- const refundAmount = swap.return_order.refund_amount
847
-
848
- const locale = await this.extractLocale(order)
849
-
850
- return {
851
- locale,
852
- swap,
853
- order,
854
- return_request: returnRequest,
855
- date: swap.updated_at.toDateString(),
856
- swap_link: swapLink,
857
- email: order.email,
858
- items: decoratedItems.filter((di) => !di.is_return),
859
- return_items: decoratedItems.filter((di) => di.is_return),
860
- return_total: `${this.humanPrice_(
861
- returnTotal,
862
- currencyCode
863
- )} ${currencyCode}`,
864
- tax_total: `${this.humanPrice_(
865
- cart.total,
866
- currencyCode
867
- )} ${currencyCode}`,
868
- refund_amount: `${this.humanPrice_(
869
- refundAmount,
870
- currencyCode
871
- )} ${currencyCode}`,
872
- additional_total: `${this.humanPrice_(
873
- additionalTotal,
874
- currencyCode
875
- )} ${currencyCode}`,
876
- }
877
- }
878
-
879
- async swapCreatedData({ id }) {
880
- const store = await this.storeService_.retrieve()
881
- const swap = await this.swapService_.retrieve(id, {
882
- relations: [
883
- "additional_items",
884
- "additional_items.tax_lines",
885
- "return_order",
886
- "return_order.items",
887
- "return_order.items.item",
888
- "return_order.shipping_method",
889
- "return_order.shipping_method.shipping_option",
890
- ],
891
- })
892
-
893
- const returnRequest = swap.return_order
894
-
895
- const items = await this.lineItemService_.list(
896
- {
897
- id: returnRequest.items.map(({ item_id }) => item_id),
898
- },
899
- {
900
- relations: ["tax_lines"],
901
- }
902
- )
903
-
904
- returnRequest.items = returnRequest.items.map((item) => {
905
- const found = items.find((i) => i.id === item.item_id)
906
- return {
907
- ...item,
908
- item: found,
909
- }
910
- })
911
-
912
- const swapLink = store.swap_link_template.replace(
913
- /\{cart_id\}/,
914
- swap.cart_id
915
- )
916
-
917
- const order = await this.orderService_.retrieve(swap.order_id, {
918
- select: ["total"],
919
- relations: [
920
- "items",
921
- "items.tax_lines",
922
- "discounts",
923
- "discounts.rule",
924
- "shipping_address",
925
- "swaps",
926
- "swaps.additional_items",
927
- "swaps.additional_items.tax_lines",
928
- ],
929
- })
930
-
931
- const cart = await this.cartService_.retrieve(swap.cart_id, {
932
- select: [
933
- "total",
934
- "tax_total",
935
- "discount_total",
936
- "shipping_total",
937
- "subtotal",
938
- ],
939
- })
940
- const currencyCode = order.currency_code.toUpperCase()
941
-
942
- const decoratedItems = await Promise.all(
943
- cart.items.map(async (i) => {
944
- const totals = await this.totalsService_.getLineItemTotals(i, cart, {
945
- include_tax: true,
946
- })
947
-
948
- return {
949
- ...i,
950
- totals,
951
- tax_lines: totals.tax_lines,
952
- price: `${this.humanPrice_(
953
- totals.original_total / i.quantity,
954
- currencyCode
955
- )} ${currencyCode}`,
956
- discounted_price: `${this.humanPrice_(
957
- totals.total / i.quantity,
958
- currencyCode
959
- )} ${currencyCode}`,
960
- }
961
- })
962
- )
963
-
964
- const returnTotal = decoratedItems.reduce((acc, next) => {
965
- const { total } = next.totals
966
- if (next.is_return && next.variant_id) {
967
- return acc + -1 * total
968
- }
969
- return acc
970
- }, 0)
971
-
972
- const additionalTotal = decoratedItems.reduce((acc, next) => {
973
- const { total } = next.totals
974
- if (!next.is_return) {
975
- return acc + total
976
- }
977
- return acc
978
- }, 0)
979
-
980
- const refundAmount = swap.return_order.refund_amount
981
-
982
- const locale = await this.extractLocale(order)
983
-
984
- return {
985
- locale,
986
- swap,
987
- order,
988
- return_request: returnRequest,
989
- date: swap.updated_at.toDateString(),
990
- swap_link: swapLink,
991
- email: order.email,
992
- items: decoratedItems.filter((di) => !di.is_return),
993
- return_items: decoratedItems.filter((di) => di.is_return),
994
- return_total: `${this.humanPrice_(
995
- returnTotal,
996
- currencyCode
997
- )} ${currencyCode}`,
998
- refund_amount: `${this.humanPrice_(
999
- refundAmount,
1000
- currencyCode
1001
- )} ${currencyCode}`,
1002
- additional_total: `${this.humanPrice_(
1003
- additionalTotal,
1004
- currencyCode
1005
- )} ${currencyCode}`,
1006
- }
1007
- }
1008
-
1009
- async itemsReturnedData(data) {
1010
- return this.returnRequestedData(data)
1011
- }
1012
-
1013
- async swapShipmentCreatedData({ id, fulfillment_id }) {
1014
- const swap = await this.swapService_.retrieve(id, {
1015
- relations: [
1016
- "shipping_address",
1017
- "shipping_methods",
1018
- "shipping_methods.tax_lines",
1019
- "additional_items",
1020
- "additional_items.tax_lines",
1021
- "return_order",
1022
- "return_order.items",
1023
- ],
1024
- })
1025
-
1026
- const order = await this.orderService_.retrieve(swap.order_id, {
1027
- relations: [
1028
- "region",
1029
- "items",
1030
- "items.tax_lines",
1031
- "discounts",
1032
- "discounts.rule",
1033
- "swaps",
1034
- "swaps.additional_items",
1035
- "swaps.additional_items.tax_lines",
1036
- ],
1037
- })
1038
-
1039
- const cart = await this.cartService_.retrieve(swap.cart_id, {
1040
- select: [
1041
- "total",
1042
- "tax_total",
1043
- "discount_total",
1044
- "shipping_total",
1045
- "subtotal",
1046
- ],
1047
- })
1048
-
1049
- const returnRequest = swap.return_order
1050
- const items = await this.lineItemService_.list(
1051
- {
1052
- id: returnRequest.items.map(({ item_id }) => item_id),
1053
- },
1054
- {
1055
- relations: ["tax_lines"],
1056
- }
1057
- )
1058
-
1059
- const taxRate = order.tax_rate / 100
1060
- const currencyCode = order.currency_code.toUpperCase()
1061
-
1062
- const returnItems = await Promise.all(
1063
- swap.return_order.items.map(async (i) => {
1064
- const found = items.find((oi) => oi.id === i.item_id)
1065
- const totals = await this.totalsService_.getLineItemTotals(i, cart, {
1066
- include_tax: true,
1067
- })
1068
-
1069
- return {
1070
- ...found,
1071
- thumbnail: this.normalizeThumbUrl_(found.thumbnail),
1072
- price: `${this.humanPrice_(
1073
- totals.original_total / i.quantity,
1074
- currencyCode
1075
- )} ${currencyCode}`,
1076
- discounted_price: `${this.humanPrice_(
1077
- totals.total / i.quantity,
1078
- currencyCode
1079
- )} ${currencyCode}`,
1080
- quantity: i.quantity,
1081
- }
1082
- })
1083
- )
1084
-
1085
- const returnTotal = await this.totalsService_.getRefundTotal(
1086
- order,
1087
- returnItems
1088
- )
1089
-
1090
- const constructedOrder = {
1091
- ...order,
1092
- shipping_methods: swap.shipping_methods,
1093
- items: swap.additional_items,
1094
- }
1095
-
1096
- const additionalTotal = await this.totalsService_.getTotal(constructedOrder)
1097
-
1098
- const refundAmount = swap.return_order.refund_amount
1099
-
1100
- const shipment = await this.fulfillmentService_.retrieve(fulfillment_id, {
1101
- relations: ["tracking_links"],
1102
- })
1103
-
1104
- const locale = await this.extractLocale(order)
1105
-
1106
- return {
1107
- locale,
1108
- swap,
1109
- order,
1110
- items: await Promise.all(
1111
- swap.additional_items.map(async (i) => {
1112
- const totals = await this.totalsService_.getLineItemTotals(i, cart, {
1113
- include_tax: true,
1114
- })
1115
-
1116
- return {
1117
- ...i,
1118
- thumbnail: this.normalizeThumbUrl_(i.thumbnail),
1119
- price: `${this.humanPrice_(
1120
- totals.original_total / i.quantity,
1121
- currencyCode
1122
- )} ${currencyCode}`,
1123
- discounted_price: `${this.humanPrice_(
1124
- totals.total / i.quantity,
1125
- currencyCode
1126
- )} ${currencyCode}`,
1127
- quantity: i.quantity,
1128
- }
1129
- })
1130
- ),
1131
- date: swap.updated_at.toDateString(),
1132
- email: order.email,
1133
- tax_amount: `${this.humanPrice_(
1134
- cart.tax_total,
1135
- currencyCode
1136
- )} ${currencyCode}`,
1137
- paid_total: `${this.humanPrice_(
1138
- swap.difference_due,
1139
- currencyCode
1140
- )} ${currencyCode}`,
1141
- return_total: `${this.humanPrice_(
1142
- returnTotal,
1143
- currencyCode
1144
- )} ${currencyCode}`,
1145
- refund_amount: `${this.humanPrice_(
1146
- refundAmount,
1147
- currencyCode
1148
- )} ${currencyCode}`,
1149
- additional_total: `${this.humanPrice_(
1150
- additionalTotal,
1151
- currencyCode
1152
- )} ${currencyCode}`,
1153
- fulfillment: shipment,
1154
- tracking_links: shipment.tracking_links,
1155
- tracking_number: shipment.tracking_numbers.join(", "),
1156
- }
1157
- }
1158
-
1159
- async claimShipmentCreatedData({ id, fulfillment_id }) {
1160
- const claim = await this.claimService_.retrieve(id, {
1161
- relations: ["order", "order.items", "order.shipping_address"],
1162
- })
1163
-
1164
- const shipment = await this.fulfillmentService_.retrieve(fulfillment_id, {
1165
- relations: ["tracking_links"],
1166
- })
1167
-
1168
- const locale = await this.extractLocale(claim.order)
1169
-
1170
- return {
1171
- locale,
1172
- email: claim.order.email,
1173
- claim,
1174
- order: claim.order,
1175
- fulfillment: shipment,
1176
- tracking_links: shipment.tracking_links,
1177
- tracking_number: shipment.tracking_numbers.join(", "),
1178
- }
1179
- }
1180
-
1181
- async restockNotificationData({ variant_id, emails }) {
1182
- const variant = await this.productVariantService_.retrieve(variant_id, {
1183
- relations: ["product"],
1184
- })
1185
-
1186
- let thumb
1187
- if (variant.product.thumbnail) {
1188
- thumb = this.normalizeThumbUrl_(variant.product.thumbnail)
1189
- }
1190
-
1191
- return {
1192
- product: {
1193
- ...variant.product,
1194
- thumbnail: thumb,
1195
- },
1196
- variant,
1197
- variant_id,
1198
- emails,
1199
- }
1200
- }
1201
-
1202
- userPasswordResetData(data) {
1203
- return data
1204
- }
1205
-
1206
- customerPasswordResetData(data) {
1207
- return data
1208
- }
1209
-
1210
- async orderRefundCreatedData({ id, refund_id }) {
1211
- const order = await this.orderService_.retrieveWithTotals(id, {
1212
- select: [
1213
- "total",
1214
- ],
1215
- relations: [
1216
- "refunds",
1217
- "items",
1218
- ]
1219
- })
1220
-
1221
- const refund = order.refunds.find((refund) => refund.id === refund_id)
1222
-
1223
- return {
1224
- order,
1225
- refund,
1226
- refund_amount: `${this.humanPrice_(
1227
- refund.amount,
1228
- order.currency_code
1229
- )} ${order.currency_code}`,
1230
- email: order.email
1231
- }
1232
- }
1233
-
1234
- processItems_(items, taxRate, currencyCode) {
1235
- return items.map((i) => {
1236
- return {
1237
- ...i,
1238
- thumbnail: this.normalizeThumbUrl_(i.thumbnail),
1239
- price: `${this.humanPrice_(
1240
- i.unit_price * (1 + taxRate),
1241
- currencyCode
1242
- )} ${currencyCode}`,
1243
- }
1244
- })
1245
- }
1246
-
1247
- humanPrice_(amount, currency) {
1248
- if (!amount) {
1249
- return "0.00"
1250
- }
1251
-
1252
- const normalized = humanizeAmount(amount, currency)
1253
- return normalized.toFixed(
1254
- zeroDecimalCurrencies.includes(currency.toLowerCase()) ? 0 : 2
1255
- )
1256
- }
1257
-
1258
- normalizeThumbUrl_(url) {
1259
- if (!url) {
1260
- return null
1261
- }
1262
-
1263
- if (url.startsWith("http")) {
1264
- return url
1265
- } else if (url.startsWith("//")) {
1266
- return `https:${url}`
1267
- }
1268
- return url
1269
- }
1270
-
1271
- async extractLocale(fromOrder) {
1272
- if (fromOrder.cart_id) {
1273
- try {
1274
- const cart = await this.cartService_.retrieve(fromOrder.cart_id, {
1275
- select: ["id", "context"],
1276
- })
1277
-
1278
- if (cart.context && cart.context.locale) {
1279
- return cart.context.locale
1280
- }
1281
- } catch (err) {
1282
- console.log(err)
1283
- console.warn("Failed to gather context for order")
1284
- return null
1285
- }
1286
- }
1287
- return null
1288
- }
1289
- }
1290
-
1291
- export default SESService