@vymalo/medusa-printful
Version:
Connect your MedusaJS store to Printful
294 lines • 11.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("@medusajs/utils");
const core_1 = require("../../../core");
const lodash_1 = require("lodash");
const utils_2 = require("../../../utils");
const core_flows_1 = require("@medusajs/core-flows");
const ColorKey = 'Color'.toLowerCase();
const SizeKey = 'Size'.toLowerCase();
class PrintFulService {
logger_;
fulfillmentService;
productModuleService;
options;
constructor({ logger, fulfillment, product }, options) {
this.logger_ = logger;
this.productModuleService = product;
this.fulfillmentService = fulfillment;
this.options = options;
}
get confirmOrder() {
return this.options.confirmOrder;
}
async getShippingRates(data) {
try {
const { data: { result }, } = await core_1.ShippingRateApiService.calculateShippingRates({
body: data,
});
return result;
}
catch (e) {
this.logger_.error('Could not get shipping rates from Printful', e);
return null;
}
}
async createOrder(order) {
const { data: { id: printfulShippingId }, } = await this.fulfillmentService.retrieveShippingOption(order.shipping_methods[0].shipping_option_id);
const { shipping_address, email, items } = order;
const orderObj = {
external_id: order.id,
shipping: printfulShippingId,
recipient: {
name: shipping_address.first_name + ' ' + shipping_address.last_name,
address1: shipping_address.address_1,
address2: shipping_address.address_2 ?? '',
city: shipping_address.city,
state_code: shipping_address.province,
country_code: shipping_address.country_code,
zip: shipping_address.postal_code,
email,
phone: shipping_address.phone ?? '',
},
items: items.map((item) => {
return {
name: item.title,
external_id: item.variant_id,
variant_id: item.metadata.printful_catalog_variant_id,
sync_variant_id: item.metadata.printful_id,
quantity: item.quantity,
price: `${(item.unit_price / 100).toFixed(2)}`.replace('.', '.'),
retail_price: `${(item.unit_price / 100).toFixed(2)}`.replace('.', '.'),
};
}),
packing_slip: (0, lodash_1.merge)({}, {
message: order.email,
logo_url: this.options.logo_url,
}, await this.getPackingSlip(order)),
};
const { data: { result }, } = await core_1.OrdersApiService.createOrder({
query: {
confirm: false,
update_existing: true,
},
body: orderObj,
});
this.logger_.info(`Order created with Printful: ${result.id}`);
return result;
}
async confirmOrderById(orderId) {
if (!this.confirmOrder) {
this.logger_.info('Order confirmation is disabled');
const { data: { result }, } = await core_1.OrdersApiService.getOrderById({
path: {
id: orderId,
},
});
return result;
}
this.logger_.info(`Confirming order with Printful: ${orderId}`);
const { data: { result }, } = await core_1.OrdersApiService.confirmOrderById({
path: {
id: orderId,
},
});
return result;
}
async cancelOrder(orderId) {
const { data: { result }, } = await core_1.OrdersApiService.cancelOrderById({
path: {
id: orderId,
},
});
return result;
}
async deleteMedusaProduct(productOrProductId, container) {
try {
await (0, core_flows_1.deleteProductsWorkflow)(container).run({
input: {
ids: [productOrProductId],
},
});
}
catch (e) {
this.logger_.error('Could not delete product in Medusa', e);
}
}
async getSyncProduct(id) {
const { data: { result }, } = await core_1.ProductsApiService.getSyncProductById({
path: {
id,
},
});
return result;
}
async createOrUpdateProduct(productId, container) {
const [{ sync_product, sync_variants }, product] = await Promise.all([
this.getSyncProduct(productId),
this.getProductByPrintfulProductId(productId),
]);
await this.syncProduct(sync_product, sync_variants, product, container);
}
async deleteProduct(productId, container) {
const product = await this.getProductByPrintfulProductId(productId);
if (!product) {
return;
}
await this.deleteMedusaProduct(product.id, container);
}
async syncProducts(container) {
const { data: { result }, } = await core_1.ProductsApiService.getSyncProducts({
query: {
status: 'synced',
},
});
await Promise.all(result.map(({ id: productId }) => this.createOrUpdateProduct(productId, container)));
}
buildProductId(productId) {
return `printful_product_${productId}`;
}
async getProductByPrintfulProductId(productId) {
const [products, count] = await this.productModuleService.listAndCountProducts({
id: this.buildProductId(productId),
}, {
take: 1,
relations: ['tags', 'options', 'variants'],
});
if (count === 0) {
return null;
}
return products[0];
}
async getPackingSlip(order) {
return {};
}
async syncProduct(syncProduct, syncVariants, product, container) {
const { allFiles, allColors, allSizes } = (0, utils_2.multiMap)(syncVariants, {
allFiles: (variant) => variant.files,
allSizes: (variant) => [variant.size],
allColors: (variant) => [variant.color],
}, {
uniqByConfig: {
allFiles: (file) => file.preview_url,
allSizes: (size) => size,
allColors: (color) => color,
},
filterConfig: {
allFiles: (file) => file.type === 'preview' &&
file.preview_url !== undefined &&
file.preview_url !== null &&
file.preview_url !== '',
allSizes: (size) => size !== null && size !== '',
allColors: (color) => color !== null && color !== '',
},
});
const images = allFiles.map((file) => ({
url: file.preview_url,
metadata: {
printful_id: file.id,
},
}));
const rawOptions = [
{ title: ColorKey, values: allColors },
{ title: SizeKey, values: allSizes },
];
const options = (0, lodash_1.map)((0, lodash_1.groupBy)(rawOptions, (option) => option.title), (values) => values.map(({ values, ...rest }) => ({
...rest,
values: (0, lodash_1.uniq)(values),
}))).flat();
const variants = syncVariants.map((variant) => ({
title: variant.name,
sku: variant.sku,
options: {
[ColorKey]: variant.color,
[SizeKey]: variant.size,
},
metadata: {
printful_variant: variant,
},
manage_inventory: false,
allow_backorder: true,
prices: [
{
amount: variant.retail_price,
currency_code: variant.currency.toLowerCase(),
},
],
}));
const printfulTag = await this.getPrintfulTag();
if (!product) {
const createProductDTO = {
id: this.buildProductId(syncProduct.id),
title: syncProduct.name,
external_id: `${syncProduct.id}`,
handle: (0, lodash_1.kebabCase)(syncProduct.name),
thumbnail: syncProduct.thumbnail_url,
images: images,
options: options,
status: 'draft',
variants: variants,
metadata: {
is_printful: true,
},
tag_ids: [printfulTag.id],
};
const { result } = await (0, core_flows_1.createProductsWorkflow)(container).run({
input: {
products: [createProductDTO],
},
});
return result[0];
}
else {
await Promise.all([
(0, core_flows_1.deleteProductOptionsWorkflow)(container).run({
input: {
ids: product.options.map((i) => i.id),
},
}),
(0, core_flows_1.deleteProductVariantsWorkflow)(container).run({
input: {
ids: product.variants.map((i) => i.id),
},
}),
]);
const { result } = await (0, core_flows_1.updateProductsWorkflow)(container).run({
input: {
products: [
{
id: this.buildProductId(syncProduct.id),
title: syncProduct.name,
external_id: `${syncProduct.id}`,
handle: (0, lodash_1.kebabCase)(syncProduct.name),
thumbnail: syncProduct.thumbnail_url,
images: (0, lodash_1.uniq)(images.concat(product?.images || [])),
options: options,
status: product.status,
variants: variants,
metadata: {
...product.metadata,
is_printful: true,
},
tag_ids: (0, lodash_1.uniq)([
...(product.tags || []).map((i) => i.id),
printfulTag.id,
]),
},
],
},
});
return result[0];
}
}
async getPrintfulTag() {
const result = await this.productModuleService.listProductTags({
value: 'printful',
});
if (result.length === 0) {
throw new Error('Printful tag not found');
}
return result[0];
}
}
exports.default = PrintFulService;
//# sourceMappingURL=printful-service.js.map