medusa-plugin-sendgrid-typescript
Version:
SendGrid transactional emails typescript
1,066 lines • 45.8 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SendGridService = void 0;
const mail_1 = __importDefault(require("@sendgrid/mail"));
const medusa_core_utils_1 = require("medusa-core-utils");
const utils_1 = require("@medusajs/utils");
const medusa_1 = require("@medusajs/medusa");
class SendGridService extends medusa_1.AbstractNotificationService {
constructor({ storeService, orderService, returnService, swapService, cartService, lineItemService, claimService, fulfillmentService, fulfillmentProviderService, totalsService, productVariantService, giftCardService, logger, }, options) {
// @ts-expect-error prefer-rest-params
super(...arguments);
this.options_ = options;
this.fulfillmentProviderService_ = fulfillmentProviderService;
this.storeService_ = storeService;
this.lineItemService_ = lineItemService;
this.orderService_ = orderService;
this.cartService_ = cartService;
this.claimService_ = claimService;
this.returnService_ = returnService;
this.swapService_ = swapService;
this.fulfillmentService_ = fulfillmentService;
this.totalsService_ = totalsService;
this.productVariantService_ = productVariantService;
this.giftCardService_ = giftCardService;
this.logger_ = logger;
mail_1.default.setApiKey(options.api_key);
}
async fetchAttachments(event, data, attachmentGenerator) {
switch (event) {
case "swap.created":
case "order.return_requested": {
let attachments = [];
if (!data) {
return [];
}
const { shipping_method, shipping_data } = data?.return_request;
if (shipping_method) {
const provider = shipping_method.shipping_option.provider_id;
const lbl = await this.fulfillmentProviderService_.retrieveDocuments(provider, shipping_data, "label");
attachments = attachments.concat(lbl.map((d) => ({
name: "return-label",
base64: d.base_64,
type: d.type,
})));
}
if (attachmentGenerator && attachmentGenerator.createReturnInvoice) {
const base64 = await attachmentGenerator.createReturnInvoice(data?.order, data?.return_request.items);
attachments.push({
name: "invoice",
base64,
type: "application/pdf",
});
}
return attachments;
}
default:
return [];
}
}
async fetchData(event, eventData, attachmentGenerator) {
switch (event) {
case "order.return_requested":
return this.returnRequestedData(eventData);
case "swap.shipment_created":
return this.swapShipmentCreatedData(eventData);
case "claim.shipment_created":
return this.claimShipmentCreatedData(eventData);
case "order.items_returned":
return this.itemsReturnedData(eventData);
case "swap.received":
return this.swapReceivedData(eventData);
case "swap.created":
return this.swapCreatedData(eventData);
case "gift_card.created":
return this.gcCreatedData(eventData);
case "order.gift_card_created":
return this.gcCreatedData(eventData);
case "order.placed":
return this.orderPlacedData(eventData);
case "order.shipment_created":
return this.orderShipmentCreatedData(eventData, attachmentGenerator);
case "order.canceled":
return this.orderCanceledData(eventData);
case "user.password_reset":
return this.userPasswordResetData(eventData);
case "customer.password_reset":
return this.customerPasswordResetData(eventData);
case "restock-notification.restocked":
return await this.restockNotificationData(eventData);
case "order.refund_created":
return this.orderRefundCreatedData(eventData);
default:
return eventData;
}
}
getLocalizedTemplateId(event, locale, subject) {
if (this.options_.localization && this.options_.localization[locale]) {
const map = this.options_.localization[locale];
switch (event) {
case "order.return_requested":
return map.order_return_requested_template?.[subject ? "subject" : "id"];
case "swap.shipment_created":
return map.swap_shipment_created_template?.[subject ? "subject" : "id"];
case "claim.shipment_created":
return map.claim_shipment_created_template?.[subject ? "subject" : "id"];
case "order.items_returned":
return map.order_items_returned_template?.[subject ? "subject" : "id"];
case "swap.received":
return map.swap_received_template?.[subject ? "subject" : "id"];
case "swap.created":
return map.swap_created_template?.[subject ? "subject" : "id"];
case "gift_card.created":
return map.gift_card_created_template?.[subject ? "subject" : "id"];
case "order.gift_card_created":
return map.gift_card_created_template?.[subject ? "subject" : "id"];
case "order.placed":
return map.order_placed_template?.[subject ? "subject" : "id"];
case "order.shipment_created":
return map.order_shipment_created_template?.[subject ? "subject" : "id"];
case "order.canceled":
return map.order_canceled_template?.[subject ? "subject" : "id"];
case "user.password_reset":
return map.user_password_reset_template?.[subject ? "subject" : "id"];
case "customer.password_reset":
return map.customer_password_reset_template?.[subject ? "subject" : "id"];
case "restock-notification.restocked":
return map.medusa_restock_template?.[subject ? "subject" : "id"];
case "order.refund_created":
return map.order_refund_created_template?.[subject ? "subject" : "id"];
default:
return null;
}
}
return null;
}
getTemplateId(event, subject) {
const templates = Object.keys(this.options_.templates ?? {});
const normalizedEvent = event.toLowerCase().replaceAll(".", "_");
const key = templates.find((template) => {
return (normalizedEvent === template ||
`${normalizedEvent}_template` === template);
});
// @ts-expect-error - wrong types in options_
return this.options_.templates[key]?.[subject ? "subject" : "id"];
}
getSubjectVariable(template) {
// i have a template string {} extract variable
const regex = /{([^}]+)}/g;
const matches = template.matchAll(regex);
const variables = [];
for (const match of matches) {
variables.push(match[1]);
}
return variables;
}
async sendNotification(event, eventData, attachmentGenerator) {
const data = await this.fetchData(event, eventData, attachmentGenerator);
let templateId = this.getTemplateId(event);
let subject = this.getTemplateId(event, true);
if (data.locale) {
const localizedTemplateId = this.getLocalizedTemplateId(event, data.locale);
const localizedSubject = this.getLocalizedTemplateId(event, data.locale, true);
templateId = localizedTemplateId || templateId;
subject = localizedSubject || subject;
}
if (!templateId) {
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Sendgrid service: No template was set for event: ${event}`);
}
if (subject) {
let variables = this.getSubjectVariable(subject);
if (variables.length > 0) {
variables.forEach((variable) => {
const isNested = variable.includes(".");
if (isNested) {
const [first, ...rest] = variable.split(".");
if (!data[first]) {
this.logger_.warn(`Sendgrid service: No data was set for variable: ${variable}`);
}
if (data[first]) {
let nested = data[first];
rest.forEach((r) => {
if (!nested[r]) {
this.logger_.warn(`Sendgrid service: No data was set for variable: ${variable}`);
}
if (nested[r]) {
nested = nested[r];
}
});
subject = subject.replace(`{${variable}}`, nested);
}
}
if (!data[variable]) {
this.logger_.warn(`Sendgrid service: No data was set for variable: ${variable}`);
}
if (data[variable]) {
subject = subject.replace(`{${variable}}`, data[variable]);
}
});
}
data.subject = subject;
}
const attachments = await this.fetchAttachments(event, data, attachmentGenerator);
const toBcc = () => {
if (this.options_.orderPlacedBcc && event === "order.placed") {
if (this.options_?.noSendCountries && this?.options_?.noSendCountries.includes(data.order?.shipping_address?.country_code)) {
return undefined;
}
return this.options_.orderPlacedBcc;
}
return undefined;
};
const sendOptions = {
templateId: templateId,
from: this.options_.from,
to: data.email,
bcc: this.options_.orderPlacedBcc && event === "order.placed" ? this.options_.orderPlacedBcc : undefined,
dynamicTemplateData: data,
};
const dataToDb = {
template_id: templateId,
from: this.options_.from,
to: data.email,
bcc: this.options_.orderPlacedBcc && event === "order.placed" ? this.options_.orderPlacedBcc : undefined,
dynamic_template_data: data,
has_attachments: attachments?.length > 0,
};
if (attachments?.length) {
dataToDb.has_attachments = true;
sendOptions.attachments = attachments.map((a) => {
return {
content: a.base64,
filename: a.name,
type: a.type,
disposition: "attachment",
contentId: a.name,
};
});
}
let status;
await mail_1.default.send(sendOptions)
.then(() => {
status = "sent";
})
.catch((error) => {
status = "failed";
this.logger_.error(error);
});
return { to: data.email, status, data: dataToDb };
}
async resendNotification(notification, config, attachmentGenerator) {
// @ts-ignore - wrong types in SendGrid
const sendOptions = {
...notification.data,
to: config.to || notification.to,
};
const attachs = await this.fetchAttachments(notification.event_name, notification?.data?.dynamic_template_data, attachmentGenerator);
sendOptions.attachments = attachs.map((a) => {
return {
content: a.base64,
filename: a.name,
type: a.type,
disposition: "attachment",
contentId: a.name,
};
});
const status = await mail_1.default.send(sendOptions)
.then(() => "sent")
.catch(() => "failed");
return { to: sendOptions.to, status, data: sendOptions };
}
async sendEmail(options) {
const email = await mail_1.default.send(options);
return email;
}
async orderShipmentCreatedData({ id, fulfillment_id }, attachmentGenerator) {
if (!id || typeof id !== "string") {
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Sendgrid service: No order_id was set for event: order.shipment_created
`);
}
const order = await this.orderService_.retrieve(id, {
select: [
"shipping_total",
"discount_total",
"tax_total",
"refunded_total",
"gift_card_total",
"subtotal",
"total",
"refundable_amount",
],
relations: [
"customer",
"billing_address",
"shipping_address",
"discounts",
"discounts.rule",
"shipping_methods",
"shipping_methods.shipping_option",
"payments",
"fulfillments",
"returns",
"gift_cards",
"gift_card_transactions",
],
});
if (!fulfillment_id) {
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Sendgrid service: No fulfillment_id was set for event: order.shipment_created`);
}
const shipment = await this.fulfillmentService_.retrieve(fulfillment_id, {
relations: ["items", "tracking_links"],
});
const locale = await this.extractLocale(order);
return {
locale,
order,
date: shipment.shipped_at.toDateString(),
email: order.email,
fulfillment: shipment,
tracking_links: shipment.tracking_links,
tracking_number: shipment.tracking_numbers.join(", "),
};
}
async orderCanceledData({ id }) {
if (!id || typeof id !== "string") {
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Sendgrid service: No order_id was set for event: order.shipment_created
`);
}
const order = await this.orderService_.retrieve(id, {
select: [
"shipping_total",
"discount_total",
"tax_total",
"refunded_total",
"gift_card_total",
"subtotal",
"total",
],
relations: [
"customer",
"billing_address",
"shipping_address",
"discounts",
"discounts.rule",
"shipping_methods",
"shipping_methods.shipping_option",
"payments",
"fulfillments",
"returns",
"gift_cards",
"gift_card_transactions",
],
});
const { subtotal, tax_total, discount_total, shipping_total, gift_card_total, total, } = order;
const taxRate = (order?.tax_rate || 0) / 100;
const currencyCode = order.currency_code.toUpperCase();
const items = this.processItems_(order.items, taxRate, currencyCode);
let discounts = [];
if (order.discounts) {
discounts = order.discounts.map((discount) => {
return {
is_giftcard: false,
code: discount.code,
descriptor: `${discount.rule.value}${discount.rule.type === "percentage" ? "%" : ` ${currencyCode}`}`,
};
});
}
let giftCards = [];
if (order.gift_cards) {
giftCards = order.gift_cards.map((gc) => {
return {
is_giftcard: true,
code: gc.code,
descriptor: `${gc.value} ${currencyCode}`,
};
});
discounts.concat(giftCards);
}
const locale = await this.extractLocale(order);
return {
...order,
locale,
has_discounts: order.discounts.length,
has_gift_cards: order.gift_cards.length,
date: order?.created_at?.toDateString(),
items,
discounts,
subtotal: `${this.humanPrice_(subtotal * (1 + taxRate), currencyCode)} ${currencyCode}`,
gift_card_total: `${this.humanPrice_(gift_card_total * (1 + taxRate), currencyCode)} ${currencyCode}`,
tax_total: `${this.humanPrice_(tax_total, currencyCode)} ${currencyCode}`,
discount_total: `${this.humanPrice_(discount_total * (1 + taxRate), currencyCode)} ${currencyCode}`,
shipping_total: `${this.humanPrice_(shipping_total * (1 + taxRate), currencyCode)} ${currencyCode}`,
total: `${this.humanPrice_(total, currencyCode)} ${currencyCode}`,
};
}
async orderPlacedData({ id }) {
if (!id || typeof id !== "string") {
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Sendgrid service: No order_id was set for event: order.shipment_created
`);
}
const order = await this.orderService_.retrieve(id, {
select: [
"shipping_total",
"discount_total",
"tax_total",
"refunded_total",
"gift_card_total",
"subtotal",
"total",
],
relations: [
"customer",
"billing_address",
"shipping_address",
"discounts",
"discounts.rule",
"shipping_methods",
"shipping_methods.shipping_option",
"payments",
"fulfillments",
"returns",
"gift_cards",
"gift_card_transactions",
],
});
const { tax_total, shipping_total, gift_card_total, total } = order;
const currencyCode = order.currency_code.toUpperCase();
const promises = [];
order.items.forEach((item) => {
promises.push(this.totalsService_.getLineItemTotals(item, order, {
include_tax: true,
use_tax_lines: true,
}));
});
const totals = await Promise.all(promises);
const items = [];
for (let i = 0; i < order.items.length; i++) {
const item = order.items[i];
items.push({
...item,
totals: totals[i],
thumbnail: this.normalizeThumbUrl_(item.thumbnail),
discounted_price: `${this.humanPrice_(totals[i].total / item.quantity, currencyCode)} ${currencyCode}`,
price: `${this.humanPrice_(totals[i].original_total / item.quantity, currencyCode)} ${currencyCode}`,
});
}
let discounts = [];
if (order.discounts) {
discounts = order.discounts.map((discount) => {
return {
is_giftcard: false,
code: discount.code,
descriptor: `${discount.rule.value}${discount.rule.type === "percentage" ? "%" : ` ${currencyCode}`}`,
};
});
}
let giftCards = [];
if (order.gift_cards) {
giftCards = order.gift_cards.map((gc) => {
return {
is_giftcard: true,
code: gc.code,
descriptor: `${gc.value} ${currencyCode}`,
};
});
discounts.concat(giftCards);
}
const locale = await this.extractLocale(order);
// Includes taxes in discount amount
const discountTotal = items.reduce((acc, i) => {
return acc + i.totals.original_total - i.totals.total;
}, 0);
const discounted_subtotal = items.reduce((acc, i) => {
return acc + i.totals.total;
}, 0);
const subtotal = items.reduce((acc, i) => {
return acc + i.totals.original_total;
}, 0);
const subtotal_ex_tax = items.reduce((total, i) => {
return total + i.totals.subtotal;
}, 0);
return {
...order,
locale,
has_discounts: order.discounts.length,
has_gift_cards: order.gift_cards.length,
date: order.created_at.toDateString(),
items,
discounts,
subtotal_ex_tax: `${this.humanPrice_(subtotal_ex_tax, currencyCode)} ${currencyCode}`,
subtotal: `${this.humanPrice_(subtotal, currencyCode)} ${currencyCode}`,
gift_card_total: `${this.humanPrice_(gift_card_total, currencyCode)} ${currencyCode}`,
tax_total: `${this.humanPrice_(tax_total, currencyCode)} ${currencyCode}`,
discount_total: `${this.humanPrice_(discountTotal, currencyCode)} ${currencyCode}`,
shipping_total: `${this.humanPrice_(shipping_total, currencyCode)} ${currencyCode}`,
total: `${this.humanPrice_(total, currencyCode)} ${currencyCode}`,
};
}
async gcCreatedData({ id }) {
if (!id || typeof id !== "string") {
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Sendgrid service: No order_id was set for event: order.shipment_created
`);
}
const giftCard = await this.giftCardService_.retrieve(id, {
relations: ["region", "order"],
});
const taxRate = giftCard.region.tax_rate / 100;
const locale = giftCard.order
? await this.extractLocale(giftCard.order)
: null;
const email = giftCard.order
? giftCard.order.email
: giftCard.metadata.email;
return {
...giftCard,
locale,
email,
display_value: `${this.humanPrice_(giftCard.value * 1 + taxRate, giftCard.region.currency_code)} ${giftCard.region.currency_code}`,
message: giftCard.metadata?.message || giftCard.metadata?.personal_message,
};
}
async returnRequestedData({ id, return_id }) {
// Fetch the return request
if (!return_id || typeof return_id !== "string") {
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Sendgrid service: No return_id was set for event: order.return_requested`);
}
const returnRequest = await this.returnService_.retrieve(return_id, {
relations: [
"items.item.tax_lines",
"items.item.variant.product.profiles",
"shipping_method",
"shipping_method.tax_lines",
"shipping_method.shipping_option",
],
});
const items = await this.lineItemService_.list({
id: returnRequest.items.map(({ item_id }) => item_id),
}, {
relations: ["tax_lines", "variant", "variant.product.profiles"],
});
if (!id || typeof id !== "string") {
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Sendgrid service: No order_id was set for event: order.shipment_created
`);
}
// Fetch the order
const order = await this.orderService_.retrieve(id, {
select: ["total"],
relations: [
"items",
"items.variant",
"items.tax_lines",
"discounts",
"discounts.rule",
"shipping_address",
"returns",
],
});
const currencyCode = order.currency_code.toUpperCase();
const promises = [];
returnRequest.items.forEach((item) => {
const found = items.find((oi) => oi.id === item.item_id);
if (!found) {
return promises.push(new Promise((resolve) => resolve({
total: 0,
tax_lines: [],
unit_price: 0,
subtotal: 0,
tax_total: 0,
discount_total: 0,
quantity: 0,
original_total: 0,
original_tax_total: 0,
raw_discount_total: 0,
})));
}
return promises.push(this.totalsService_.getLineItemTotals(found, order, {
include_tax: true,
use_tax_lines: true,
}));
});
const totals = await Promise.all(promises);
const returnItems = [];
// Calculate which items are in the return
for (let i = 0; i < returnRequest.items.length; i++) {
const item = returnRequest.items[i];
const found = items.find((oi) => oi.id === item.item_id);
if (!found) {
continue;
}
returnItems.push({
...item,
totals: totals[i],
price: `${this.humanPrice_(totals[i].total / item.quantity, currencyCode)} ${currencyCode}`,
tax_lines: found.tax_lines,
thumbnail: this.normalizeThumbUrl_(found.thumbnail),
});
}
// Get total of the returned products
const item_subtotal = returnItems.reduce((acc, next) => acc + next.totals.total, 0);
// If the return has a shipping method get the price and any attachments
let shippingTotal = 0;
if (returnRequest.shipping_method) {
const base = returnRequest.shipping_method.price;
shippingTotal =
base +
returnRequest.shipping_method.tax_lines.reduce((acc, next) => {
return Math.round(acc + base * (next.rate / 100));
}, 0);
}
const locale = await this.extractLocale(order);
return {
locale,
has_shipping: !!returnRequest.shipping_method,
email: order.email,
items: returnItems,
subtotal: `${this.humanPrice_(item_subtotal, currencyCode)} ${currencyCode}`,
shipping_total: `${this.humanPrice_(shippingTotal, currencyCode)} ${currencyCode}`,
refund_amount: `${this.humanPrice_(returnRequest.refund_amount, currencyCode)} ${currencyCode}`,
return_request: {
...returnRequest,
refund_amount: `${this.humanPrice_(returnRequest.refund_amount, currencyCode)} ${currencyCode}`,
},
order,
date: returnRequest.updated_at.toDateString(),
};
}
async swapReceivedData({ id }) {
if (!id || typeof id !== "string") {
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Sendgrid service: No order_id was set for event: order.shipment_created
`);
}
const store = await this.storeService_.retrieve();
const swap = await this.swapService_.retrieve(id, {
relations: [
"additional_items",
"additional_items.tax_lines",
"additional_items.variant",
"return_order",
"return_order.items",
"return_order.items.item",
"return_order.items.item.variant",
"return_order.shipping_method",
"return_order.shipping_method.shipping_option",
],
});
const returnRequest = swap.return_order;
const items = await this.lineItemService_.list({
id: returnRequest.items.map(({ item_id }) => item_id),
}, {
relations: ["tax_lines"],
});
returnRequest.items = returnRequest.items.map((item) => {
const found = items.find((i) => i.id === item.item_id);
if (!found) {
return item;
}
return {
...item,
item: found,
};
});
const swapLink = store?.swap_link_template?.replace(/\{cart_id\}/, swap.cart_id);
if (!swapLink) {
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Sendgrid service: No swap_link_template was set for event: swap.received`);
}
const order = await this.orderService_.retrieve(swap.order_id, {
select: ["total"],
relations: [
"items",
"items.variant",
"discounts",
"discounts.rule",
"shipping_address",
"swaps",
"swaps.additional_items",
"swaps.additional_items.tax_lines",
"swaps.additional_items.variant",
],
});
const cart = await this.cartService_.retrieve(swap.cart_id, {
relations: ["items.variant.product.profiles"],
select: [
"total",
"tax_total",
"discount_total",
"shipping_total",
"subtotal",
],
});
const currencyCode = order.currency_code.toUpperCase();
const decoratedItems = await Promise.all(cart.items.map(async (i) => {
const totals = await this.totalsService_.getLineItemTotals(i, cart, {
include_tax: true,
});
return {
...i,
totals,
price: this.humanPrice_(totals.subtotal + totals.tax_total, currencyCode),
};
}));
const returnTotal = decoratedItems.reduce((acc, next) => {
if (next.is_return) {
return acc + -1 * (next.totals.subtotal + next.totals.tax_total);
}
return acc;
}, 0);
const additionalTotal = decoratedItems.reduce((acc, next) => {
if (!next.is_return) {
return acc + next.totals.subtotal + next.totals.tax_total;
}
return acc;
}, 0);
const refundAmount = swap.return_order.refund_amount;
const locale = await this.extractLocale(order);
return {
locale,
swap,
order,
return_request: returnRequest,
date: swap.updated_at.toDateString(),
swap_link: swapLink,
email: order.email,
items: decoratedItems.filter((di) => !di.is_return),
return_items: decoratedItems.filter((di) => di.is_return),
return_total: `${this.humanPrice_(returnTotal, currencyCode)} ${currencyCode}`,
tax_total: `${this.humanPrice_(cart.total, currencyCode)} ${currencyCode}`,
refund_amount: `${this.humanPrice_(refundAmount, currencyCode)} ${currencyCode}`,
additional_total: `${this.humanPrice_(additionalTotal, currencyCode)} ${currencyCode}`,
};
}
async swapCreatedData({ id }) {
if (!id || typeof id !== "string") {
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Sendgrid service: No order_id was set for event: order.shipment_created
`);
}
const store = await this.storeService_.retrieve();
const swap = await this.swapService_.retrieve(id, {
relations: [
"additional_items.variant.product.profiles",
"additional_items.tax_lines",
"return_order",
"return_order.items",
"return_order.items.item",
"return_order.shipping_method",
"return_order.shipping_method.shipping_option",
],
});
const returnRequest = swap.return_order;
const items = await this.lineItemService_.list({
id: returnRequest.items.map(({ item_id }) => item_id),
}, {
relations: ["tax_lines", "variant.product.profiles"],
});
returnRequest.items = returnRequest.items.map((item) => {
const found = items.find((i) => i.id === item.item_id);
return {
...item,
item: found,
};
});
if (!store) {
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Sendgrid service: No store was set for event: swap.created`);
}
const swapLink = store?.swap_link_template?.replace(/\{cart_id\}/, swap.cart_id);
const order = await this.orderService_.retrieve(swap.order_id, {
select: ["total"],
relations: [
"items.variant.product.profiles",
"items.tax_lines",
"discounts",
"discounts.rule",
"shipping_address",
"swaps",
"swaps.additional_items",
"swaps.additional_items.tax_lines",
"swaps.additional_items.variant",
],
});
const cart = await this.cartService_.retrieve(swap.cart_id, {
select: [
"total",
"tax_total",
"discount_total",
"shipping_total",
"subtotal",
],
relations: ["items.variant.product.profiles"],
});
const currencyCode = order.currency_code.toUpperCase();
const decoratedItems = await Promise.all(cart.items.map(async (i) => {
const totals = await this.totalsService_.getLineItemTotals(i, cart, {
include_tax: true,
});
return {
...i,
totals,
tax_lines: totals.tax_lines,
price: `${this.humanPrice_(totals.original_total / i.quantity, currencyCode)} ${currencyCode}`,
discounted_price: `${this.humanPrice_(totals.total / i.quantity, currencyCode)} ${currencyCode}`,
};
}));
const returnTotal = decoratedItems.reduce((acc, next) => {
const { total } = next.totals;
if (next.is_return && next.variant_id) {
return acc + -1 * total;
}
return acc;
}, 0);
const additionalTotal = decoratedItems.reduce((acc, next) => {
const { total } = next.totals;
if (!next.is_return) {
return acc + total;
}
return acc;
}, 0);
const refundAmount = swap.return_order.refund_amount;
const locale = await this.extractLocale(order);
return {
locale,
swap,
order,
return_request: returnRequest,
date: swap.updated_at.toDateString(),
swap_link: swapLink,
email: order.email,
items: decoratedItems.filter((di) => !di.is_return),
return_items: decoratedItems.filter((di) => di.is_return),
return_total: `${this.humanPrice_(returnTotal, currencyCode)} ${currencyCode}`,
refund_amount: `${this.humanPrice_(refundAmount, currencyCode)} ${currencyCode}`,
additional_total: `${this.humanPrice_(additionalTotal, currencyCode)} ${currencyCode}`,
};
}
async itemsReturnedData(data) {
return this.returnRequestedData(data);
}
async swapShipmentCreatedData({ id, fulfillment_id }) {
if (!id || typeof id !== "string") {
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Sendgrid service: No order_id was set for event: order.shipment_created
`);
}
const swap = await this.swapService_.retrieve(id, {
relations: [
"shipping_address",
"shipping_methods",
"shipping_methods.shipping_option",
"shipping_methods.tax_lines",
"additional_items.variant.product.profiles",
"additional_items.tax_lines",
"return_order",
"return_order.items",
],
});
const order = await this.orderService_.retrieve(swap.order_id, {
relations: [
"region",
"items",
"items.tax_lines",
"items.variant.product.profiles",
"discounts",
"discounts.rule",
"swaps",
"swaps.additional_items.variant.product.profiles",
"swaps.additional_items.tax_lines",
],
});
const cart = await this.cartService_.retrieve(swap.cart_id, {
select: [
"total",
"tax_total",
"discount_total",
"shipping_total",
"subtotal",
],
relations: ["items.variant.product.profiles"],
});
const returnRequest = swap.return_order;
const items = await this.lineItemService_.list({
id: returnRequest.items.map(({ item_id }) => item_id),
}, {
relations: ["tax_lines", "variant.product.profiles"],
});
const taxRate = (order.tax_rate || 0) / 100;
const currencyCode = order.currency_code.toUpperCase();
const returnItems = await Promise.all(swap.return_order.items.map(async (i) => {
const found = items.find((oi) => oi.id === i.item_id);
const totals = await this.totalsService_.getLineItemTotals(i.item, cart, {
include_tax: true,
});
return {
...found,
thumbnail: this.normalizeThumbUrl_(found?.thumbnail),
price: `${this.humanPrice_(totals.original_total / i.quantity, currencyCode)} ${currencyCode}`,
discounted_price: `${this.humanPrice_(totals.total / i.quantity, currencyCode)} ${currencyCode}`,
quantity: i.quantity,
};
}));
const returnTotal = await this.totalsService_.getRefundTotal(order,
// @ts-expect-error - wrong types in
returnItems);
const constructedOrder = {
...order,
shipping_methods: swap.shipping_methods,
items: swap.additional_items,
};
// @ts-expect-error - wrong types in
const additionalTotal = await this.totalsService_.getTotal(constructedOrder);
const refundAmount = swap.return_order.refund_amount;
if (!fulfillment_id) {
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Sendgrid service: No fulfillment_id was set for event: swap.shipment_created`);
}
const shipment = await this.fulfillmentService_.retrieve(fulfillment_id, {
relations: ["tracking_links"],
});
const locale = await this.extractLocale(order);
return {
locale,
swap,
order,
items: await Promise.all(swap.additional_items.map(async (i) => {
const totals = await this.totalsService_.getLineItemTotals(i, cart, {
include_tax: true,
});
return {
...i,
thumbnail: this.normalizeThumbUrl_(i.thumbnail),
price: `${this.humanPrice_(totals.original_total / i.quantity, currencyCode)} ${currencyCode}`,
discounted_price: `${this.humanPrice_(totals.total / i.quantity, currencyCode)} ${currencyCode}`,
quantity: i.quantity,
};
})),
date: swap.updated_at.toDateString(),
email: order.email,
tax_amount: `${this.humanPrice_(cart.tax_total, currencyCode)} ${currencyCode}`,
paid_total: `${this.humanPrice_(swap.difference_due, currencyCode)} ${currencyCode}`,
return_total: `${this.humanPrice_(returnTotal, currencyCode)} ${currencyCode}`,
refund_amount: `${this.humanPrice_(refundAmount, currencyCode)} ${currencyCode}`,
additional_total: `${this.humanPrice_(additionalTotal, currencyCode)} ${currencyCode}`,
fulfillment: shipment,
tracking_links: shipment.tracking_links,
tracking_number: shipment.tracking_numbers.join(", "),
};
}
async claimShipmentCreatedData({ id, fulfillment_id }) {
if (!id || typeof id !== "string") {
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Sendgrid service: No order_id was set for event: order.shipment_created
`);
}
const claim = await this.claimService_.retrieve(id, {
relations: [
"order.items.variant.product.profiles",
"order.shipping_address",
],
});
if (!fulfillment_id) {
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Sendgrid service: No fulfillment_id was set for event: claim.shipment_created`);
}
const shipment = await this.fulfillmentService_.retrieve(fulfillment_id, {
relations: ["tracking_links"],
});
const locale = await this.extractLocale(claim.order);
return {
locale,
email: claim.order.email,
claim,
order: claim.order,
fulfillment: shipment,
tracking_links: shipment.tracking_links,
tracking_number: shipment.tracking_numbers.join(", "),
};
}
async restockNotificationData({ variant_id, emails }) {
if (!variant_id || typeof variant_id !== "string") {
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Sendgrid service: No variant_id was set for event: restock-notification.restocked`);
}
const variant = await this.productVariantService_.retrieve(variant_id, {
relations: ["product"],
});
let thumb = null;
if (variant.product.thumbnail) {
thumb = this.normalizeThumbUrl_(variant.product.thumbnail);
}
return {
product: {
...variant.product,
thumbnail: thumb,
},
variant,
variant_id,
emails,
};
}
userPasswordResetData(data) {
return data;
}
customerPasswordResetData(data) {
return data;
}
async orderRefundCreatedData({ id, refund_id }) {
if (!id || typeof id !== "string") {
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Sendgrid service: No order_id was set for event: order.shipment_created
`);
}
const order = await this.orderService_.retrieveWithTotals(id, {
relations: ["refunds", "items"],
});
const refund = order.refunds.find((refund) => refund.id === refund_id);
return {
order,
refund,
refund_amount: `${this.humanPrice_(refund?.amount, order.currency_code)} ${order.currency_code}`,
email: order.email,
};
}
processItems_(items, taxRate, currencyCode) {
return items.map((i) => {
return {
...i,
thumbnail: this.normalizeThumbUrl_(i.thumbnail),
price: `${this.humanPrice_(i.unit_price * (1 + taxRate), currencyCode)} ${currencyCode}`,
};
});
}
humanPrice_(amount, currency) {
if (!amount) {
return "0.00";
}
const normalized = (0, medusa_core_utils_1.humanizeAmount)(amount, currency);
return normalized.toFixed(medusa_core_utils_1.zeroDecimalCurrencies.includes(currency.toLowerCase()) ? 0 : 2);
}
normalizeThumbUrl_(url) {
if (!url) {
return '';
}
if (url.startsWith("http")) {
return url;
}
else if (url.startsWith("//")) {
return `https:${url}`;
}
return url;
}
async extractLocale(fromOrder) {
if (fromOrder.cart_id) {
try {
const cart = await this.cartService_.retrieve(fromOrder.cart_id, {
select: ["id", "context"],
});
if (cart.context && cart.context.locale) {
return cart.context.locale;
}
}
catch (err) {
console.log(err);
console.warn("Failed to gather context for order");
return null;
}
}
return null;
}
}
exports.SendGridService = SendGridService;
SendGridService.identifier = "sendgrid";
exports.default = SendGridService;
//# sourceMappingURL=sendgrid.js.map