UNPKG

@vymalo/medusa-printful

Version:

Connect your MedusaJS store to Printful

294 lines 11.1 kB
"use strict"; 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