medusa-plugin-ses 2.0.3 → 2.0.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.
@@ -1,1290 +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
-
263
- const attachments = await this.fetchAttachments(
264
- event,
265
- data,
266
- attachmentGenerator
267
- )
268
-
269
- if (attachments?.length) {
270
- sendOptions.has_attachments = true
271
- sendOptions.attachments = attachments.map((a) => {
272
- return {
273
- content: a.base64,
274
- filename: a.name,
275
- encoding: 'base64',
276
- contentType: a.type
277
- }
278
- })
279
- }
280
-
281
- //const status = await this.transporter_.sendMail(sendOptions).then(() => "sent").catch(() => "failed")
282
- let status
283
- await this.transporter_.sendMail(sendOptions)
284
- .then(() => { status = "sent" })
285
- .catch((error) => { status = "failed"; console.log(error) })
286
-
287
- // We don't want heavy docs stored in DB
288
- delete sendOptions.attachments
289
-
290
- return { to: data.email, status, data: sendOptions }
291
- }
292
-
293
- async resendNotification(notification, config, attachmentGenerator) {
294
- const sendOptions = {
295
- ...notification.data,
296
- to: config.to || notification.to,
297
- }
298
-
299
- const attachs = await this.fetchAttachments(
300
- notification.event_name,
301
- notification.data.dynamic_template_data,
302
- attachmentGenerator
303
- )
304
-
305
- sendOptions.attachments = attachs.map((a) => {
306
- return {
307
- content: a.base64,
308
- filename: a.name,
309
- encoding: 'base64',
310
- contentType: a.type
311
- }
312
- })
313
-
314
- //const status = await this.transporter_.sendMail(sendOptions).then(() => "sent").catch(() => "failed")
315
- let status
316
- await this.transporter_.sendMail(sendOptions)
317
- .then(() => { status = "sent" })
318
- .catch((error) => { status = "failed"; console.log(error) })
319
-
320
- return { to: sendOptions.to, status, data: sendOptions }
321
- }
322
-
323
- /**
324
- * Sends an email using SES.
325
- * @param {string} template_id - id of template to use
326
- * @param {string} from - sender of email
327
- * @param {string} to - receiver of email
328
- * @param {Object} data - data to send in mail (match with template)
329
- * @return {Promise} result of the send operation
330
- */
331
- async sendEmail(template_id, from, to, data) {
332
- // This function is used by the /ses/send API endpoint included in this plugin.
333
- // It is disabled by default.
334
- // This endpoint may be useful for testing purposes and for use by related applications.
335
- // There is NO SECURITY on the endpoint by default.
336
- // Most people will NOT need to enable it.
337
- // If you are certain that you want to enable it and that you know what you are doing,
338
- // set the environment variable SES_ENABLE_ENDPOINT to "42" (a string, not an int).
339
- // The unsual setting is meant to prevent enabling by accident or without thought.
340
- if (this.options_.enable_endpoint !== '42') { return false }
341
- const { subject, html, text } = await this.compileTemplate(template_id, data)
342
- if (!subject || (!html && !text)) { return false }
343
- try {
344
- return this.transporter_.sendMail({
345
- from: from,
346
- to: to,
347
- subject,
348
- html,
349
- text
350
- })
351
- } catch (error) {
352
- throw error
353
- }
354
- }
355
-
356
- async orderShipmentCreatedData({ id, fulfillment_id }, attachmentGenerator) {
357
- const order = await this.orderService_.retrieve(id, {
358
- select: [
359
- "shipping_total",
360
- "discount_total",
361
- "tax_total",
362
- "refunded_total",
363
- "gift_card_total",
364
- "subtotal",
365
- "total",
366
- "refundable_amount",
367
- ],
368
- relations: [
369
- "customer",
370
- "billing_address",
371
- "shipping_address",
372
- "discounts",
373
- "discounts.rule",
374
- "shipping_methods",
375
- "shipping_methods.shipping_option",
376
- "payments",
377
- "fulfillments",
378
- "returns",
379
- "gift_cards",
380
- "gift_card_transactions",
381
- ],
382
- })
383
-
384
- const shipment = await this.fulfillmentService_.retrieve(fulfillment_id, {
385
- relations: ["items", "tracking_links"],
386
- })
387
-
388
- const locale = await this.extractLocale(order)
389
-
390
- return {
391
- locale,
392
- order,
393
- date: shipment.shipped_at.toDateString(),
394
- email: order.email,
395
- fulfillment: shipment,
396
- tracking_links: shipment.tracking_links,
397
- tracking_number: shipment.tracking_numbers.join(", "),
398
- }
399
- }
400
-
401
- async orderCanceledData({ id }) {
402
- const order = await this.orderService_.retrieve(id, {
403
- select: [
404
- "shipping_total",
405
- "discount_total",
406
- "tax_total",
407
- "refunded_total",
408
- "gift_card_total",
409
- "subtotal",
410
- "total",
411
- ],
412
- relations: [
413
- "customer",
414
- "billing_address",
415
- "shipping_address",
416
- "discounts",
417
- "discounts.rule",
418
- "shipping_methods",
419
- "shipping_methods.shipping_option",
420
- "payments",
421
- "fulfillments",
422
- "returns",
423
- "gift_cards",
424
- "gift_card_transactions",
425
- ],
426
- })
427
-
428
- const {
429
- subtotal,
430
- tax_total,
431
- discount_total,
432
- shipping_total,
433
- gift_card_total,
434
- total,
435
- } = order
436
-
437
- const taxRate = order.tax_rate / 100
438
- const currencyCode = order.currency_code.toUpperCase()
439
-
440
- const items = this.processItems_(order.items, taxRate, currencyCode)
441
-
442
- let discounts = []
443
- if (order.discounts) {
444
- discounts = order.discounts.map((discount) => {
445
- return {
446
- is_giftcard: false,
447
- code: discount.code,
448
- descriptor: `${discount.rule.value}${
449
- discount.rule.type === "percentage" ? "%" : ` ${currencyCode}`
450
- }`,
451
- }
452
- })
453
- }
454
-
455
- let giftCards = []
456
- if (order.gift_cards) {
457
- giftCards = order.gift_cards.map((gc) => {
458
- return {
459
- is_giftcard: true,
460
- code: gc.code,
461
- descriptor: `${gc.value} ${currencyCode}`,
462
- }
463
- })
464
-
465
- discounts.concat(giftCards)
466
- }
467
-
468
- const locale = await this.extractLocale(order)
469
-
470
- return {
471
- ...order,
472
- locale,
473
- has_discounts: order.discounts.length,
474
- has_gift_cards: order.gift_cards.length,
475
- date: order.created_at.toDateString(),
476
- items,
477
- discounts,
478
- subtotal: `${this.humanPrice_(
479
- subtotal * (1 + taxRate),
480
- currencyCode
481
- )} ${currencyCode}`,
482
- gift_card_total: `${this.humanPrice_(
483
- gift_card_total * (1 + taxRate),
484
- currencyCode
485
- )} ${currencyCode}`,
486
- tax_total: `${this.humanPrice_(tax_total, currencyCode)} ${currencyCode}`,
487
- discount_total: `${this.humanPrice_(
488
- discount_total * (1 + taxRate),
489
- currencyCode
490
- )} ${currencyCode}`,
491
- shipping_total: `${this.humanPrice_(
492
- shipping_total * (1 + taxRate),
493
- currencyCode
494
- )} ${currencyCode}`,
495
- total: `${this.humanPrice_(total, currencyCode)} ${currencyCode}`,
496
- }
497
- }
498
-
499
- async orderPlacedData({ id }) {
500
- const order = await this.orderService_.retrieve(id, {
501
- select: [
502
- "shipping_total",
503
- "discount_total",
504
- "tax_total",
505
- "refunded_total",
506
- "gift_card_total",
507
- "subtotal",
508
- "total",
509
- ],
510
- relations: [
511
- "customer",
512
- "billing_address",
513
- "shipping_address",
514
- "discounts",
515
- "discounts.rule",
516
- "shipping_methods",
517
- "shipping_methods.shipping_option",
518
- "payments",
519
- "fulfillments",
520
- "returns",
521
- "gift_cards",
522
- "gift_card_transactions",
523
- ],
524
- })
525
-
526
- const { tax_total, shipping_total, gift_card_total, total } = order
527
-
528
- const currencyCode = order.currency_code.toUpperCase()
529
-
530
- const items = await Promise.all(
531
- order.items.map(async (i) => {
532
- i.totals = await this.totalsService_.getLineItemTotals(i, order, {
533
- include_tax: true,
534
- use_tax_lines: true,
535
- })
536
- i.thumbnail = this.normalizeThumbUrl_(i.thumbnail)
537
- i.discounted_price = `${this.humanPrice_(
538
- i.totals.total / i.quantity,
539
- currencyCode
540
- )} ${currencyCode}`
541
- i.price = `${this.humanPrice_(
542
- i.totals.original_total / i.quantity,
543
- currencyCode
544
- )} ${currencyCode}`
545
- return i
546
- })
547
- )
548
-
549
- let discounts = []
550
- if (order.discounts) {
551
- discounts = order.discounts.map((discount) => {
552
- return {
553
- is_giftcard: false,
554
- code: discount.code,
555
- descriptor: `${discount.rule.value}${
556
- discount.rule.type === "percentage" ? "%" : ` ${currencyCode}`
557
- }`,
558
- }
559
- })
560
- }
561
-
562
- let giftCards = []
563
- if (order.gift_cards) {
564
- giftCards = order.gift_cards.map((gc) => {
565
- return {
566
- is_giftcard: true,
567
- code: gc.code,
568
- descriptor: `${gc.value} ${currencyCode}`,
569
- }
570
- })
571
-
572
- discounts.concat(giftCards)
573
- }
574
-
575
- const locale = await this.extractLocale(order)
576
-
577
- // Includes taxes in discount amount
578
- const discountTotal = items.reduce((acc, i) => {
579
- return acc + i.totals.original_total - i.totals.total
580
- }, 0)
581
-
582
- const discounted_subtotal = items.reduce((acc, i) => {
583
- return acc + i.totals.total
584
- }, 0)
585
- const subtotal = items.reduce((acc, i) => {
586
- return acc + i.totals.original_total
587
- }, 0)
588
-
589
- const subtotal_ex_tax = items.reduce((total, i) => {
590
- return total + i.totals.subtotal
591
- }, 0)
592
-
593
- return {
594
- ...order,
595
- locale,
596
- has_discounts: order.discounts.length,
597
- has_gift_cards: order.gift_cards.length,
598
- date: order.created_at.toDateString(),
599
- items,
600
- discounts,
601
- subtotal_ex_tax: `${this.humanPrice_(
602
- subtotal_ex_tax,
603
- currencyCode
604
- )} ${currencyCode}`,
605
- subtotal: `${this.humanPrice_(subtotal, currencyCode)} ${currencyCode}`,
606
- gift_card_total: `${this.humanPrice_(
607
- gift_card_total,
608
- currencyCode
609
- )} ${currencyCode}`,
610
- tax_total: `${this.humanPrice_(tax_total, currencyCode)} ${currencyCode}`,
611
- discount_total: `${this.humanPrice_(
612
- discountTotal,
613
- currencyCode
614
- )} ${currencyCode}`,
615
- shipping_total: `${this.humanPrice_(
616
- shipping_total,
617
- currencyCode
618
- )} ${currencyCode}`,
619
- total: `${this.humanPrice_(total, currencyCode)} ${currencyCode}`,
620
- }
621
- }
622
-
623
- async gcCreatedData({ id }) {
624
- const giftCard = await this.giftCardService_.retrieve(id, {
625
- relations: ["region", "order"],
626
- })
627
-
628
- if (!giftCard.order) {
629
- return
630
- }
631
-
632
- const taxRate = giftCard.region.tax_rate / 100
633
-
634
- const locale = await this.extractLocale(order)
635
-
636
- return {
637
- ...giftCard,
638
- locale,
639
- email: giftCard.order.email,
640
- display_value: giftCard.value * (1 + taxRate),
641
- }
642
- }
643
-
644
- async returnRequestedData({ id, return_id }) {
645
- // Fetch the return request
646
- const returnRequest = await this.returnService_.retrieve(return_id, {
647
- relations: [
648
- "items",
649
- "items.item",
650
- "items.item.tax_lines",
651
- "items.item.variant",
652
- "items.item.variant.product",
653
- "shipping_method",
654
- "shipping_method.tax_lines",
655
- "shipping_method.shipping_option",
656
- ],
657
- })
658
-
659
- const items = await this.lineItemService_.list(
660
- {
661
- id: returnRequest.items.map(({ item_id }) => item_id),
662
- },
663
- { relations: ["tax_lines"] }
664
- )
665
-
666
- // Fetch the order
667
- const order = await this.orderService_.retrieve(id, {
668
- select: ["total"],
669
- relations: [
670
- "items",
671
- "items.tax_lines",
672
- "discounts",
673
- "discounts.rule",
674
- "shipping_address",
675
- "returns",
676
- ],
677
- })
678
-
679
- const currencyCode = order.currency_code.toUpperCase()
680
-
681
- // Calculate which items are in the return
682
- const returnItems = await Promise.all(
683
- returnRequest.items.map(async (i) => {
684
- const found = items.find((oi) => oi.id === i.item_id)
685
- found.quantity = i.quantity
686
- found.thumbnail = this.normalizeThumbUrl_(found.thumbnail)
687
- found.totals = await this.totalsService_.getLineItemTotals(
688
- found,
689
- order,
690
- {
691
- include_tax: true,
692
- use_tax_lines: true,
693
- }
694
- )
695
- found.price = `${this.humanPrice_(
696
- found.totals.total,
697
- currencyCode
698
- )} ${currencyCode}`
699
- found.tax_lines = found.totals.tax_lines
700
- return found
701
- })
702
- )
703
-
704
- // Get total of the returned products
705
- const item_subtotal = returnItems.reduce(
706
- (acc, next) => acc + next.totals.total,
707
- 0
708
- )
709
-
710
- // If the return has a shipping method get the price and any attachments
711
- let shippingTotal = 0
712
- if (returnRequest.shipping_method) {
713
- const base = returnRequest.shipping_method.price
714
- shippingTotal =
715
- base +
716
- returnRequest.shipping_method.tax_lines.reduce((acc, next) => {
717
- return Math.round(acc + base * (next.rate / 100))
718
- }, 0)
719
- }
720
-
721
- const locale = await this.extractLocale(order)
722
-
723
- return {
724
- locale,
725
- has_shipping: !!returnRequest.shipping_method,
726
- email: order.email,
727
- items: returnItems,
728
- subtotal: `${this.humanPrice_(
729
- item_subtotal,
730
- currencyCode
731
- )} ${currencyCode}`,
732
- shipping_total: `${this.humanPrice_(
733
- shippingTotal,
734
- currencyCode
735
- )} ${currencyCode}`,
736
- refund_amount: `${this.humanPrice_(
737
- returnRequest.refund_amount,
738
- currencyCode
739
- )} ${currencyCode}`,
740
- return_request: {
741
- ...returnRequest,
742
- refund_amount: `${this.humanPrice_(
743
- returnRequest.refund_amount,
744
- currencyCode
745
- )} ${currencyCode}`,
746
- },
747
- order,
748
- date: returnRequest.updated_at.toDateString(),
749
- }
750
- }
751
-
752
- async swapReceivedData({ id }) {
753
- const store = await this.storeService_.retrieve()
754
- const swap = await this.swapService_.retrieve(id, {
755
- relations: [
756
- "additional_items",
757
- "additional_items.tax_lines",
758
- "return_order",
759
- "return_order.items",
760
- "return_order.items.item",
761
- "return_order.shipping_method",
762
- "return_order.shipping_method.shipping_option",
763
- ],
764
- })
765
-
766
- const returnRequest = swap.return_order
767
-
768
- const items = await this.lineItemService_.list(
769
- {
770
- id: returnRequest.items.map(({ item_id }) => item_id),
771
- },
772
- {
773
- relations: ["tax_lines"],
774
- }
775
- )
776
-
777
- returnRequest.items = returnRequest.items.map((item) => {
778
- const found = items.find((i) => i.id === item.item_id)
779
- return {
780
- ...item,
781
- item: found,
782
- }
783
- })
784
-
785
- const swapLink = store.swap_link_template.replace(
786
- /\{cart_id\}/,
787
- swap.cart_id
788
- )
789
-
790
- const order = await this.orderService_.retrieve(swap.order_id, {
791
- select: ["total"],
792
- relations: [
793
- "items",
794
- "discounts",
795
- "discounts.rule",
796
- "shipping_address",
797
- "swaps",
798
- "swaps.additional_items",
799
- "swaps.additional_items.tax_lines",
800
- ],
801
- })
802
-
803
- const cart = await this.cartService_.retrieve(swap.cart_id, {
804
- select: [
805
- "total",
806
- "tax_total",
807
- "discount_total",
808
- "shipping_total",
809
- "subtotal",
810
- ],
811
- })
812
- const currencyCode = order.currency_code.toUpperCase()
813
-
814
- const decoratedItems = await Promise.all(
815
- cart.items.map(async (i) => {
816
- const totals = await this.totalsService_.getLineItemTotals(i, cart, {
817
- include_tax: true,
818
- })
819
-
820
- return {
821
- ...i,
822
- totals,
823
- price: this.humanPrice_(
824
- totals.subtotal + totals.tax_total,
825
- currencyCode
826
- ),
827
- }
828
- })
829
- )
830
-
831
- const returnTotal = decoratedItems.reduce((acc, next) => {
832
- if (next.is_return) {
833
- return acc + -1 * (next.totals.subtotal + next.totals.tax_total)
834
- }
835
- return acc
836
- }, 0)
837
-
838
- const additionalTotal = decoratedItems.reduce((acc, next) => {
839
- if (!next.is_return) {
840
- return acc + next.totals.subtotal + next.totals.tax_total
841
- }
842
- return acc
843
- }, 0)
844
-
845
- const refundAmount = swap.return_order.refund_amount
846
-
847
- const locale = await this.extractLocale(order)
848
-
849
- return {
850
- locale,
851
- swap,
852
- order,
853
- return_request: returnRequest,
854
- date: swap.updated_at.toDateString(),
855
- swap_link: swapLink,
856
- email: order.email,
857
- items: decoratedItems.filter((di) => !di.is_return),
858
- return_items: decoratedItems.filter((di) => di.is_return),
859
- return_total: `${this.humanPrice_(
860
- returnTotal,
861
- currencyCode
862
- )} ${currencyCode}`,
863
- tax_total: `${this.humanPrice_(
864
- cart.total,
865
- currencyCode
866
- )} ${currencyCode}`,
867
- refund_amount: `${this.humanPrice_(
868
- refundAmount,
869
- currencyCode
870
- )} ${currencyCode}`,
871
- additional_total: `${this.humanPrice_(
872
- additionalTotal,
873
- currencyCode
874
- )} ${currencyCode}`,
875
- }
876
- }
877
-
878
- async swapCreatedData({ id }) {
879
- const store = await this.storeService_.retrieve()
880
- const swap = await this.swapService_.retrieve(id, {
881
- relations: [
882
- "additional_items",
883
- "additional_items.tax_lines",
884
- "return_order",
885
- "return_order.items",
886
- "return_order.items.item",
887
- "return_order.shipping_method",
888
- "return_order.shipping_method.shipping_option",
889
- ],
890
- })
891
-
892
- const returnRequest = swap.return_order
893
-
894
- const items = await this.lineItemService_.list(
895
- {
896
- id: returnRequest.items.map(({ item_id }) => item_id),
897
- },
898
- {
899
- relations: ["tax_lines"],
900
- }
901
- )
902
-
903
- returnRequest.items = returnRequest.items.map((item) => {
904
- const found = items.find((i) => i.id === item.item_id)
905
- return {
906
- ...item,
907
- item: found,
908
- }
909
- })
910
-
911
- const swapLink = store.swap_link_template.replace(
912
- /\{cart_id\}/,
913
- swap.cart_id
914
- )
915
-
916
- const order = await this.orderService_.retrieve(swap.order_id, {
917
- select: ["total"],
918
- relations: [
919
- "items",
920
- "items.tax_lines",
921
- "discounts",
922
- "discounts.rule",
923
- "shipping_address",
924
- "swaps",
925
- "swaps.additional_items",
926
- "swaps.additional_items.tax_lines",
927
- ],
928
- })
929
-
930
- const cart = await this.cartService_.retrieve(swap.cart_id, {
931
- select: [
932
- "total",
933
- "tax_total",
934
- "discount_total",
935
- "shipping_total",
936
- "subtotal",
937
- ],
938
- })
939
- const currencyCode = order.currency_code.toUpperCase()
940
-
941
- const decoratedItems = await Promise.all(
942
- cart.items.map(async (i) => {
943
- const totals = await this.totalsService_.getLineItemTotals(i, cart, {
944
- include_tax: true,
945
- })
946
-
947
- return {
948
- ...i,
949
- totals,
950
- tax_lines: totals.tax_lines,
951
- price: `${this.humanPrice_(
952
- totals.original_total / i.quantity,
953
- currencyCode
954
- )} ${currencyCode}`,
955
- discounted_price: `${this.humanPrice_(
956
- totals.total / i.quantity,
957
- currencyCode
958
- )} ${currencyCode}`,
959
- }
960
- })
961
- )
962
-
963
- const returnTotal = decoratedItems.reduce((acc, next) => {
964
- const { total } = next.totals
965
- if (next.is_return && next.variant_id) {
966
- return acc + -1 * total
967
- }
968
- return acc
969
- }, 0)
970
-
971
- const additionalTotal = decoratedItems.reduce((acc, next) => {
972
- const { total } = next.totals
973
- if (!next.is_return) {
974
- return acc + total
975
- }
976
- return acc
977
- }, 0)
978
-
979
- const refundAmount = swap.return_order.refund_amount
980
-
981
- const locale = await this.extractLocale(order)
982
-
983
- return {
984
- locale,
985
- swap,
986
- order,
987
- return_request: returnRequest,
988
- date: swap.updated_at.toDateString(),
989
- swap_link: swapLink,
990
- email: order.email,
991
- items: decoratedItems.filter((di) => !di.is_return),
992
- return_items: decoratedItems.filter((di) => di.is_return),
993
- return_total: `${this.humanPrice_(
994
- returnTotal,
995
- currencyCode
996
- )} ${currencyCode}`,
997
- refund_amount: `${this.humanPrice_(
998
- refundAmount,
999
- currencyCode
1000
- )} ${currencyCode}`,
1001
- additional_total: `${this.humanPrice_(
1002
- additionalTotal,
1003
- currencyCode
1004
- )} ${currencyCode}`,
1005
- }
1006
- }
1007
-
1008
- async itemsReturnedData(data) {
1009
- return this.returnRequestedData(data)
1010
- }
1011
-
1012
- async swapShipmentCreatedData({ id, fulfillment_id }) {
1013
- const swap = await this.swapService_.retrieve(id, {
1014
- relations: [
1015
- "shipping_address",
1016
- "shipping_methods",
1017
- "shipping_methods.tax_lines",
1018
- "additional_items",
1019
- "additional_items.tax_lines",
1020
- "return_order",
1021
- "return_order.items",
1022
- ],
1023
- })
1024
-
1025
- const order = await this.orderService_.retrieve(swap.order_id, {
1026
- relations: [
1027
- "region",
1028
- "items",
1029
- "items.tax_lines",
1030
- "discounts",
1031
- "discounts.rule",
1032
- "swaps",
1033
- "swaps.additional_items",
1034
- "swaps.additional_items.tax_lines",
1035
- ],
1036
- })
1037
-
1038
- const cart = await this.cartService_.retrieve(swap.cart_id, {
1039
- select: [
1040
- "total",
1041
- "tax_total",
1042
- "discount_total",
1043
- "shipping_total",
1044
- "subtotal",
1045
- ],
1046
- })
1047
-
1048
- const returnRequest = swap.return_order
1049
- const items = await this.lineItemService_.list(
1050
- {
1051
- id: returnRequest.items.map(({ item_id }) => item_id),
1052
- },
1053
- {
1054
- relations: ["tax_lines"],
1055
- }
1056
- )
1057
-
1058
- const taxRate = order.tax_rate / 100
1059
- const currencyCode = order.currency_code.toUpperCase()
1060
-
1061
- const returnItems = await Promise.all(
1062
- swap.return_order.items.map(async (i) => {
1063
- const found = items.find((oi) => oi.id === i.item_id)
1064
- const totals = await this.totalsService_.getLineItemTotals(i, cart, {
1065
- include_tax: true,
1066
- })
1067
-
1068
- return {
1069
- ...found,
1070
- thumbnail: this.normalizeThumbUrl_(found.thumbnail),
1071
- price: `${this.humanPrice_(
1072
- totals.original_total / i.quantity,
1073
- currencyCode
1074
- )} ${currencyCode}`,
1075
- discounted_price: `${this.humanPrice_(
1076
- totals.total / i.quantity,
1077
- currencyCode
1078
- )} ${currencyCode}`,
1079
- quantity: i.quantity,
1080
- }
1081
- })
1082
- )
1083
-
1084
- const returnTotal = await this.totalsService_.getRefundTotal(
1085
- order,
1086
- returnItems
1087
- )
1088
-
1089
- const constructedOrder = {
1090
- ...order,
1091
- shipping_methods: swap.shipping_methods,
1092
- items: swap.additional_items,
1093
- }
1094
-
1095
- const additionalTotal = await this.totalsService_.getTotal(constructedOrder)
1096
-
1097
- const refundAmount = swap.return_order.refund_amount
1098
-
1099
- const shipment = await this.fulfillmentService_.retrieve(fulfillment_id, {
1100
- relations: ["tracking_links"],
1101
- })
1102
-
1103
- const locale = await this.extractLocale(order)
1104
-
1105
- return {
1106
- locale,
1107
- swap,
1108
- order,
1109
- items: await Promise.all(
1110
- swap.additional_items.map(async (i) => {
1111
- const totals = await this.totalsService_.getLineItemTotals(i, cart, {
1112
- include_tax: true,
1113
- })
1114
-
1115
- return {
1116
- ...i,
1117
- thumbnail: this.normalizeThumbUrl_(i.thumbnail),
1118
- price: `${this.humanPrice_(
1119
- totals.original_total / i.quantity,
1120
- currencyCode
1121
- )} ${currencyCode}`,
1122
- discounted_price: `${this.humanPrice_(
1123
- totals.total / i.quantity,
1124
- currencyCode
1125
- )} ${currencyCode}`,
1126
- quantity: i.quantity,
1127
- }
1128
- })
1129
- ),
1130
- date: swap.updated_at.toDateString(),
1131
- email: order.email,
1132
- tax_amount: `${this.humanPrice_(
1133
- cart.tax_total,
1134
- currencyCode
1135
- )} ${currencyCode}`,
1136
- paid_total: `${this.humanPrice_(
1137
- swap.difference_due,
1138
- currencyCode
1139
- )} ${currencyCode}`,
1140
- return_total: `${this.humanPrice_(
1141
- returnTotal,
1142
- currencyCode
1143
- )} ${currencyCode}`,
1144
- refund_amount: `${this.humanPrice_(
1145
- refundAmount,
1146
- currencyCode
1147
- )} ${currencyCode}`,
1148
- additional_total: `${this.humanPrice_(
1149
- additionalTotal,
1150
- currencyCode
1151
- )} ${currencyCode}`,
1152
- fulfillment: shipment,
1153
- tracking_links: shipment.tracking_links,
1154
- tracking_number: shipment.tracking_numbers.join(", "),
1155
- }
1156
- }
1157
-
1158
- async claimShipmentCreatedData({ id, fulfillment_id }) {
1159
- const claim = await this.claimService_.retrieve(id, {
1160
- relations: ["order", "order.items", "order.shipping_address"],
1161
- })
1162
-
1163
- const shipment = await this.fulfillmentService_.retrieve(fulfillment_id, {
1164
- relations: ["tracking_links"],
1165
- })
1166
-
1167
- const locale = await this.extractLocale(claim.order)
1168
-
1169
- return {
1170
- locale,
1171
- email: claim.order.email,
1172
- claim,
1173
- order: claim.order,
1174
- fulfillment: shipment,
1175
- tracking_links: shipment.tracking_links,
1176
- tracking_number: shipment.tracking_numbers.join(", "),
1177
- }
1178
- }
1179
-
1180
- async restockNotificationData({ variant_id, emails }) {
1181
- const variant = await this.productVariantService_.retrieve(variant_id, {
1182
- relations: ["product"],
1183
- })
1184
-
1185
- let thumb
1186
- if (variant.product.thumbnail) {
1187
- thumb = this.normalizeThumbUrl_(variant.product.thumbnail)
1188
- }
1189
-
1190
- return {
1191
- product: {
1192
- ...variant.product,
1193
- thumbnail: thumb,
1194
- },
1195
- variant,
1196
- variant_id,
1197
- emails,
1198
- }
1199
- }
1200
-
1201
- userPasswordResetData(data) {
1202
- return data
1203
- }
1204
-
1205
- customerPasswordResetData(data) {
1206
- return data
1207
- }
1208
-
1209
- async orderRefundCreatedData({ id, refund_id }) {
1210
- const order = await this.orderService_.retrieveWithTotals(id, {
1211
- select: [
1212
- "total",
1213
- ],
1214
- relations: [
1215
- "refunds",
1216
- "items",
1217
- ]
1218
- })
1219
-
1220
- const refund = order.refunds.find((refund) => refund.id === refund_id)
1221
-
1222
- return {
1223
- order,
1224
- refund,
1225
- refund_amount: `${this.humanPrice_(
1226
- refund.amount,
1227
- order.currency_code
1228
- )} ${order.currency_code}`,
1229
- email: order.email
1230
- }
1231
- }
1232
-
1233
- processItems_(items, taxRate, currencyCode) {
1234
- return items.map((i) => {
1235
- return {
1236
- ...i,
1237
- thumbnail: this.normalizeThumbUrl_(i.thumbnail),
1238
- price: `${this.humanPrice_(
1239
- i.unit_price * (1 + taxRate),
1240
- currencyCode
1241
- )} ${currencyCode}`,
1242
- }
1243
- })
1244
- }
1245
-
1246
- humanPrice_(amount, currency) {
1247
- if (!amount) {
1248
- return "0.00"
1249
- }
1250
-
1251
- const normalized = humanizeAmount(amount, currency)
1252
- return normalized.toFixed(
1253
- zeroDecimalCurrencies.includes(currency.toLowerCase()) ? 0 : 2
1254
- )
1255
- }
1256
-
1257
- normalizeThumbUrl_(url) {
1258
- if (!url) {
1259
- return null
1260
- }
1261
-
1262
- if (url.startsWith("http")) {
1263
- return url
1264
- } else if (url.startsWith("//")) {
1265
- return `https:${url}`
1266
- }
1267
- return url
1268
- }
1269
-
1270
- async extractLocale(fromOrder) {
1271
- if (fromOrder.cart_id) {
1272
- try {
1273
- const cart = await this.cartService_.retrieve(fromOrder.cart_id, {
1274
- select: ["id", "context"],
1275
- })
1276
-
1277
- if (cart.context && cart.context.locale) {
1278
- return cart.context.locale
1279
- }
1280
- } catch (err) {
1281
- console.log(err)
1282
- console.warn("Failed to gather context for order")
1283
- return null
1284
- }
1285
- }
1286
- return null
1287
- }
1288
- }
1289
-
1290
- export default SESService