diff --git a/packages/medusa-payment-klarna/.prettierrc b/packages/medusa-payment-klarna/.prettierrc index 70175ce150..48e90e8d40 100644 --- a/packages/medusa-payment-klarna/.prettierrc +++ b/packages/medusa-payment-klarna/.prettierrc @@ -4,4 +4,4 @@ "singleQuote": false, "tabWidth": 2, "trailingComma": "es5" -} \ No newline at end of file +} diff --git a/packages/medusa-payment-klarna/src/api/routes/hooks/address.js b/packages/medusa-payment-klarna/src/api/routes/hooks/address.js index fdf1c2c87b..4b9fc24762 100644 --- a/packages/medusa-payment-klarna/src/api/routes/hooks/address.js +++ b/packages/medusa-payment-klarna/src/api/routes/hooks/address.js @@ -5,6 +5,7 @@ export default async (req, res) => { try { const cartService = req.scope.resolve("cartService") const klarnaProviderService = req.scope.resolve("pp_klarna") + const shippingProfileService = req.scope.resolve("shippingProfileService") const cart = await cartService.retrieve(merchant_data) @@ -23,6 +24,12 @@ export default async (req, res) => { await cartService.updateBillingAddress(cart._id, updatedAddress) await cartService.updateEmail(cart._id, shipping_address.email) + const shippingOptions = await shippingProfileService.fetchCartOptions(cart) + if (shippingOptions.length === 1) { + const option = shippingOptions[0] + await cartService.addShippingMethod(cart._id, option._id, option.data) + } + // Fetch and return updated Klarna order const updatedCart = await cartService.retrieve(cart._id) const order = await klarnaProviderService.cartToKlarnaOrder(updatedCart) diff --git a/packages/medusa-payment-klarna/src/api/routes/hooks/push.js b/packages/medusa-payment-klarna/src/api/routes/hooks/push.js index e652ba8e66..397cd5ff14 100644 --- a/packages/medusa-payment-klarna/src/api/routes/hooks/push.js +++ b/packages/medusa-payment-klarna/src/api/routes/hooks/push.js @@ -16,7 +16,7 @@ export default async (req, res) => { try { const order = await orderService.retrieveByCartId(cartId) - await klarnaProviderService.acknowledgeOrder(klarnaOrder.id, order._id) + await klarnaProviderService.acknowledgeOrder(klarnaOrder.order_id, order._id) } catch (err) { if (err.type === MeudsaError.Types.NOT_FOUND) { let cart = await cartService.retrieve(cartId) @@ -24,7 +24,7 @@ export default async (req, res) => { cart = await cartService.setPaymentMethod(cart._id, method) const order = await orderService.createFromCart(cart) - await klarnaProviderService.acknowledgeOrder(klarnaOrder.id, order._id) + await klarnaProviderService.acknowledgeOrder(klarnaOrder.order_id, order._id) } } diff --git a/packages/medusa-payment-klarna/src/api/routes/hooks/shipping.js b/packages/medusa-payment-klarna/src/api/routes/hooks/shipping.js index 4e41d230b6..6e91ab6422 100644 --- a/packages/medusa-payment-klarna/src/api/routes/hooks/shipping.js +++ b/packages/medusa-payment-klarna/src/api/routes/hooks/shipping.js @@ -10,14 +10,11 @@ export default async (req, res) => { const cart = await cartService.retrieve(merchant_data) const shippingOptions = await shippingProfileService.fetchCartOptions(cart) - const option = shippingOptions.find(({ _id }) => _id === selected_shipping_option.id) + const option = shippingOptions.find(({ _id }) => _id.equals(selected_shipping_option.id)) if (option) { - await cartService.addShippingMethod(cart._id, option._id, option.data) - - // Fetch and return updated Klarna order - const updatedCart = await cartService.retrieve(cart._id) - const order = klarnaProviderService.cartToKlarnaOrder(updatedCart) + const newCart = await cartService.addShippingMethod(cart._id, option._id, option.data) + const order = await klarnaProviderService.cartToKlarnaOrder(newCart) res.json(order) } else { res.sendStatus(400) diff --git a/packages/medusa-payment-klarna/src/services/klarna-provider.js b/packages/medusa-payment-klarna/src/services/klarna-provider.js index 3dadbc7a10..a03620c44f 100644 --- a/packages/medusa-payment-klarna/src/services/klarna-provider.js +++ b/packages/medusa-payment-klarna/src/services/klarna-provider.js @@ -5,7 +5,10 @@ import { PaymentService } from "medusa-interfaces" class KlarnaProviderService extends PaymentService { static identifier = "klarna" - constructor({ shippingProfileService, totalsService, regionService }, options) { + constructor( + { shippingProfileService, totalsService, regionService }, + options + ) { super() this.options_ = options @@ -22,7 +25,8 @@ class KlarnaProviderService extends PaymentService { this.klarnaOrderManagementUrl_ = "/ordermanagement/v1/orders" - this.backendUrl_ = process.env.BACKEND_URL || "https://58721b1f44d9.ngrok.io" + this.backendUrl_ = + process.env.BACKEND_URL || "https://2fe4e28015f5.ngrok.io" this.totalsService_ = totalsService @@ -77,7 +81,7 @@ class KlarnaProviderService extends PaymentService { const quantity = item.content.quantity const unit_price = item.content.unit_price * 100 * (taxRate + 1) const total_discount_amount = itemDiscount * (taxRate + 1) * 100 - const total_amount = unit_price * quantity - total_discount_amount + const total_amount = unit_price * quantity - total_discount_amount const total_tax_amount = total_amount * (taxRate / (1 + taxRate)) order_lines.push({ @@ -98,6 +102,7 @@ class KlarnaProviderService extends PaymentService { order_lines.push({ name: `${shippingMethod.name}`, quantity: 1, + type: "shipping_fee", unit_price: price * (1 + taxRate) * 100, tax_rate: taxRate * 10000, total_amount: price * (1 + taxRate) * 100, @@ -141,8 +146,8 @@ class KlarnaProviderService extends PaymentService { order.purchase_country = "SE" } - order.order_amount = await this.totalsService_.getTotal(cart) * 100 - order.order_tax_amount = await this.totalsService_.getTaxTotal(cart) * 100 + order.order_amount = (await this.totalsService_.getTotal(cart)) * 100 + order.order_tax_amount = (await this.totalsService_.getTaxTotal(cart)) * 100 // TODO: Check if currency matches ISO order.purchase_currency = currency_code @@ -155,45 +160,36 @@ class KlarnaProviderService extends PaymentService { address_update: `${this.backendUrl_}/klarna/address`, } + if (cart.shipping_address && cart.shipping_address.first_name) { + const shippingOptions = await this.shippingProfileService_.fetchCartOptions( + cart + ) - const shippingOptions = await this.shippingProfileService_.fetchCartOptions(cart) + // If the cart does not have shipping methods yet, preselect one from + // shipping_options and set the selected shipping method + if (cart.shipping_methods.length) { + const shipping_method = cart.shipping_methods[0] + order.selected_shipping_option = { + id: shipping_method._id, + name: shipping_method.name, + price: shipping_method.price * (1 + tax_rate) * 100, + tax_amount: shipping_method.price * tax_rate * 100, + tax_rate: tax_rate * 10000, + } + } - // If the cart does not have shipping methods yet, preselect one from - // shipping_options and set the selected shipping method - if (!cart.shipping_methods.length) { - const shipping_method = shippingOptions[0] - order.selected_shipping_option = { - id: shipping_method._id, - name: shipping_method.name, - price: shipping_method.price * (1 + tax_rate) * 100, - tax_amount: shipping_method.price * tax_rate * 100, - // Medusa tax rate of e.g. 0.25 (25%) needs to be 2500 in Klarna + // If the cart does have shipping methods, set the selected shipping method + + order.shipping_options = shippingOptions.map((so) => ({ + id: so._id, + name: so.name, + price: so.price * (1 + tax_rate) * 100, + tax_amount: so.price * tax_rate * 100, tax_rate: tax_rate * 10000, - } - } else { - const shipping_method = cart.shipping_methods[0] - order.selected_shipping_option = { - id: shipping_method._id, - name: shipping_method.name, - price: shipping_method.price * (1 + tax_rate) * 100, - tax_amount: shipping_method.price * tax_rate * 100, - tax_rate: tax_rate * 10000, - } + preselected: shippingOptions.length === 1 + })) } - // If the cart does have shipping methods, set the selected shipping method - - order.shipping_options = shippingOptions.map((so) => ({ - id: so._id, - name: so.name, - price: so.price * (1 + tax_rate) * 100, - tax_amount: so.price * tax_rate * 100, - tax_rate: tax_rate * 10000, - preselected: - cart.shipping_methods[0] && - `${cart.shipping_methods[0].provider_id}` === `${so.provider_id}`, - })) - return order } @@ -204,11 +200,15 @@ class KlarnaProviderService extends PaymentService { */ async getStatus(paymentData) { try { - const { id } = paymentData - const order = await this.klarna_.get(`${this.klarnaOrderUrl_}/${id}`) - // TODO: Klarna docs does not provide a list of statues, so we need to - // play around our selves to figure it out + const { order_id } = paymentData + const { data: order } = await this.klarna_.get( + `${this.klarnaOrderUrl_}/${order_id}` + ) + let status = "initial" + if (order.status === "checkout_complete") { + status = "authorized" + } return status } catch (error) { throw error @@ -224,7 +224,8 @@ class KlarnaProviderService extends PaymentService { async createPayment(cart) { try { const order = await this.cartToKlarnaOrder(cart) - return this.klarna_.post(this.klarnaOrderUrl_, order) + return this.klarna_ + .post(this.klarnaOrderUrl_, order) .then(({ data }) => data) } catch (error) { throw error @@ -236,10 +237,10 @@ class KlarnaProviderService extends PaymentService { * @param {string} cart - the cart to retrieve order for * @returns {Object} Klarna order */ - async retrievePayment(cart) { + async retrievePayment(paymentData) { try { - const { data } = cart.payment_method - return this.klarna_.get(`${this.klarnaOrderUrl_}/${data.id}`) + return this.klarna_.get(`${this.klarnaOrderUrl_}/${paymentData.order_id}`) + .then(({ data }) => data) } catch (error) { throw error } @@ -306,8 +307,9 @@ class KlarnaProviderService extends PaymentService { */ async updatePayment(paymentData, cart) { try { - const order = await this.cartToKlarnaOrder(cart) - return this.klarna_.post(`${this.klarnaOrderUrl_}/${paymentData.order_id}`, order) + const order = await this.cartToKlarnaOrder(cart, true) + return this.klarna_ + .post(`${this.klarnaOrderUrl_}/${paymentData.order_id}`, order) .then(({ data }) => data) } catch (error) { throw error @@ -321,17 +323,19 @@ class KlarnaProviderService extends PaymentService { */ async capturePayment(paymentData) { try { - const { id } = paymentData - const orderData = await this.klarna_.get(`${this.klarnaOrderUrl_}/${id}`) + const { order_id } = paymentData + const orderData = await this.klarna_.get( + `${this.klarnaOrderUrl_}/${order_id}` + ) const { order_amount } = orderData.order await this.klarna_.post( - `${this.klarnaOrderManagementUrl_}/${id}/captures`, + `${this.klarnaOrderManagementUrl_}/${order_id}/captures`, { captured_amount: order_amount, } ) - return id + return order_id } catch (error) { throw error } @@ -344,14 +348,14 @@ class KlarnaProviderService extends PaymentService { */ async refundPayment(paymentData, amount) { try { - const { id } = paymentData + const { order_id } = paymentData await this.klarna_.post( - `${this.klarnaOrderManagementUrl_}/${id}/refunds`, + `${this.klarnaOrderManagementUrl_}/${order_id}/refunds`, { refunded_amount: amount, } ) - return id + return order_id } catch (error) { throw error } @@ -364,9 +368,9 @@ class KlarnaProviderService extends PaymentService { */ async cancelPayment(paymentData) { try { - const { id } = paymentData - await this.klarna_.post(`${this.klarnaOrderUrl_}/${id}/cancel`) - return id + const { order_id } = paymentData + await this.klarna_.post(`${this.klarnaOrderUrl_}/${order_id}/cancel`) + return order_id } catch (error) { throw error } diff --git a/packages/medusa-payment-stripe/src/services/stripe-provider.js b/packages/medusa-payment-stripe/src/services/stripe-provider.js index c3be378d4c..752e3d3681 100644 --- a/packages/medusa-payment-stripe/src/services/stripe-provider.js +++ b/packages/medusa-payment-stripe/src/services/stripe-provider.js @@ -105,7 +105,7 @@ class StripeProviderService extends PaymentService { amount: amount * 100, // Stripe amount is in cents currency: currency_code, capture_method: "manual", - metadata: { cart_id: cart._id }, + metadata: { cart_id: `${cart._id}` }, }) return paymentIntent @@ -113,12 +113,11 @@ class StripeProviderService extends PaymentService { /** * Retrieves Stripe PaymentIntent. - * @param {string} cart - the cart to retrieve payment intent for + * @param {object} data - the data of the payment to retrieve * @returns {Object} Stripe PaymentIntent */ - async retrievePayment(cart) { + async retrievePayment(data) { try { - const { data } = cart.payment_method return this.stripe_.paymentIntents.retrieve(data.id) } catch (error) { throw error diff --git a/packages/medusa-plugin-economic/src/services/economic.js b/packages/medusa-plugin-economic/src/services/economic.js index b7390d369d..2e3c0fac51 100644 --- a/packages/medusa-plugin-economic/src/services/economic.js +++ b/packages/medusa-plugin-economic/src/services/economic.js @@ -14,7 +14,6 @@ class EconomicService extends BaseService { * customer_number_dk: 012 * customer_number_eu: 345 * customer_number_world: 678, - * vat_number: 42, * unit_number: 42, * payment_terms_number: 42, * shipping_product_number: 42, diff --git a/packages/medusa-plugin-sendgrid/src/services/sendgrid.js b/packages/medusa-plugin-sendgrid/src/services/sendgrid.js index 4d150ba5c1..4241597917 100644 --- a/packages/medusa-plugin-sendgrid/src/services/sendgrid.js +++ b/packages/medusa-plugin-sendgrid/src/services/sendgrid.js @@ -10,7 +10,7 @@ class SendGridService extends BaseService { * from: Medusa , * order_placed_template: 01234, * order_updated_template: 56789, - * order_updated_cancelled_template: 4242, + * order_cancelled_template: 4242, * user_password_reset_template: 0000, * customer_password_reset_template: 1111, * } @@ -42,6 +42,9 @@ class SendGridService extends BaseService { case "order.cancelled": templateId = this.options_.order_cancelled_template break + case "order.completed": + templateId = this.options_.order_completed_template + break case "user.password_reset": templateId = this.options_.user_password_reset_template break @@ -55,7 +58,7 @@ class SendGridService extends BaseService { try { return SendGrid.send({ template_id: templateId, - from: options.from, + from: this.options_.from, to: order.email, dynamic_template_data: order, }) diff --git a/packages/medusa-plugin-sendgrid/src/subscribers/order.js b/packages/medusa-plugin-sendgrid/src/subscribers/order.js index e6226cc787..af82482b43 100644 --- a/packages/medusa-plugin-sendgrid/src/subscribers/order.js +++ b/packages/medusa-plugin-sendgrid/src/subscribers/order.js @@ -12,6 +12,10 @@ class OrderSubscriber { await this.sendgridService_.transactionalEmail("order.cancelled", order) }) + this.eventBus_.subscribe("order.completed", async (order) => { + await this.sendgridService_.transactionalEmail("order.completed", order) + }) + this.eventBus_.subscribe("order.updated", async (order) => { await this.sendgridService_.transactionalEmail("order.updated", order) }) diff --git a/packages/medusa/src/api/middlewares/error-handler.js b/packages/medusa/src/api/middlewares/error-handler.js index 0aafd74533..212d49aaea 100644 --- a/packages/medusa/src/api/middlewares/error-handler.js +++ b/packages/medusa/src/api/middlewares/error-handler.js @@ -16,7 +16,7 @@ export default () => { break case MedusaError.Types.DB_ERROR: statusCode = 500 - logger.error(err) + console.log(err) break default: break diff --git a/packages/medusa/src/api/routes/admin/orders/complete-order.js b/packages/medusa/src/api/routes/admin/orders/complete-order.js new file mode 100644 index 0000000000..c2ca4282a2 --- /dev/null +++ b/packages/medusa/src/api/routes/admin/orders/complete-order.js @@ -0,0 +1,12 @@ +export default async (req, res) => { + const { id } = req.params + + try { + const orderService = req.scope.resolve("orderService") + const order = await orderService.completeOrder(id) + res.json({ order }) + } catch (error) { + console.log(error) + throw error + } +} diff --git a/packages/medusa/src/api/routes/admin/orders/get-order.js b/packages/medusa/src/api/routes/admin/orders/get-order.js index 0eba81a217..d3912b871e 100644 --- a/packages/medusa/src/api/routes/admin/orders/get-order.js +++ b/packages/medusa/src/api/routes/admin/orders/get-order.js @@ -3,7 +3,9 @@ export default async (req, res) => { try { const orderService = req.scope.resolve("orderService") - const order = await orderService.retrieve(id) + + let order = await orderService.retrieve(id) + order = await orderService.decorate(order, [], ["region"]) res.json({ order }) } catch (error) { diff --git a/packages/medusa/src/api/routes/admin/orders/index.js b/packages/medusa/src/api/routes/admin/orders/index.js index 195674feb3..7f71c698ab 100644 --- a/packages/medusa/src/api/routes/admin/orders/index.js +++ b/packages/medusa/src/api/routes/admin/orders/index.js @@ -11,6 +11,10 @@ export default app => { route.post("/", middlewares.wrap(require("./create-order").default)) route.post("/:id", middlewares.wrap(require("./update-order").default)) + route.post( + "/:id/complete", + middlewares.wrap(require("./complete-order").default) + ) route.post( "/:id/capture", diff --git a/packages/medusa/src/api/routes/store/carts/delete-discount.js b/packages/medusa/src/api/routes/store/carts/delete-discount.js new file mode 100644 index 0000000000..b404af55b4 --- /dev/null +++ b/packages/medusa/src/api/routes/store/carts/delete-discount.js @@ -0,0 +1,16 @@ +import { Validator, MedusaError } from "medusa-core-utils" + +export default async (req, res) => { + const { id, code } = req.params + + try { + const cartService = req.scope.resolve("cartService") + + let cart = await cartService.removeDiscount(id, code) + cart = await cartService.decorate(cart, [], ["region"]) + + res.status(200).json({ cart }) + } catch (err) { + throw err + } +} diff --git a/packages/medusa/src/api/routes/store/carts/index.js b/packages/medusa/src/api/routes/store/carts/index.js index b76417379b..a561399b58 100644 --- a/packages/medusa/src/api/routes/store/carts/index.js +++ b/packages/medusa/src/api/routes/store/carts/index.js @@ -25,6 +25,11 @@ export default app => { middlewares.wrap(require("./delete-line-item").default) ) + route.delete( + "/:id/discounts/:code", + middlewares.wrap(require("./delete-discount").default) + ) + // Payment sessions route.post( "/:id/payment-sessions", diff --git a/packages/medusa/src/api/routes/store/carts/update-cart.js b/packages/medusa/src/api/routes/store/carts/update-cart.js index dc06df901a..84ab93b0fe 100644 --- a/packages/medusa/src/api/routes/store/carts/update-cart.js +++ b/packages/medusa/src/api/routes/store/carts/update-cart.js @@ -51,7 +51,7 @@ export default async (req, res) => { if (value.discounts && value.discounts.length) { await Promise.all( value.discounts.map(async ({ code }) => - cartService.applyPromoCode(id, code) + cartService.applyDiscount(id, code) ) ) } diff --git a/packages/medusa/src/api/routes/store/carts/update-line-item.js b/packages/medusa/src/api/routes/store/carts/update-line-item.js index c30578b3bb..4c500064ae 100644 --- a/packages/medusa/src/api/routes/store/carts/update-line-item.js +++ b/packages/medusa/src/api/routes/store/carts/update-line-item.js @@ -4,7 +4,6 @@ export default async (req, res) => { const { id, line_id } = req.params const schema = Validator.object().keys({ - variant_id: Validator.objectId().required(), quantity: Validator.number().required(), }) @@ -16,15 +15,23 @@ export default async (req, res) => { try { const lineItemService = req.scope.resolve("lineItemService") const cartService = req.scope.resolve("cartService") - let cart = await cartService.retrieve(id) - const lineItem = await lineItemService.generate( - value.variant_id, - value.quantity, - cart.region_id - ) + let cart + if (value.quantity === 0) { + cart = await cartService.removeLineItem(id, line_id) + } else { + cart = await cartService.retrieve(id) + + const existing = cart.items.find(i => i._id.equals(line_id)) + const lineItem = await lineItemService.generate( + existing.content.variant._id, + cart.region_id, + value.quantity + ) + + cart = await cartService.updateLineItem(cart._id, line_id, lineItem) + } - cart = await cartService.updateLineItem(cart._id, line_id, lineItem) cart = await cartService.decorate(cart, [], ["region"]) res.status(200).json({ cart }) diff --git a/packages/medusa/src/api/routes/store/carts/update-payment-method.js b/packages/medusa/src/api/routes/store/carts/update-payment-method.js index e5a6c13ab5..258fa7ee52 100644 --- a/packages/medusa/src/api/routes/store/carts/update-payment-method.js +++ b/packages/medusa/src/api/routes/store/carts/update-payment-method.js @@ -5,6 +5,7 @@ export default async (req, res) => { const schema = Validator.object().keys({ provider_id: Validator.string().required(), + data: Validator.object().optional(), }) const { value, error } = schema.validate(req.body) @@ -15,13 +16,7 @@ export default async (req, res) => { try { const cartService = req.scope.resolve("cartService") - const session = await cartService.retrievePaymentSession( - id, - value.provider_id - ) - await cartService.setPaymentMethod(id, session) - - let cart = await cartService.retrieve(id) + let cart = await cartService.setPaymentMethod(id, value) cart = await cartService.decorate(cart, [], ["region"]) res.status(200).json({ cart }) diff --git a/packages/medusa/src/api/routes/store/orders/create-order.js b/packages/medusa/src/api/routes/store/orders/create-order.js index b4d3a5ea7e..fa80df1c66 100644 --- a/packages/medusa/src/api/routes/store/orders/create-order.js +++ b/packages/medusa/src/api/routes/store/orders/create-order.js @@ -16,6 +16,8 @@ export default async (req, res) => { const cart = await cartService.retrieve(value.cartId) let order = await orderService.createFromCart(cart) + + order = await orderService.retrieveByCartId(value.cartId) order = await orderService.decorate(order, [ "status", "fulfillment_status", diff --git a/packages/medusa/src/api/routes/store/orders/get-order.js b/packages/medusa/src/api/routes/store/orders/get-order.js index 6a4b06202e..3c0de24093 100644 --- a/packages/medusa/src/api/routes/store/orders/get-order.js +++ b/packages/medusa/src/api/routes/store/orders/get-order.js @@ -4,21 +4,25 @@ export default async (req, res) => { try { const orderService = req.scope.resolve("orderService") let order = await orderService.retrieve(id) - order = await orderService.decorate(order, [ - "status", - "fulfillment_status", - "payment_status", - "email", - "billing_address", - "shipping_address", - "items", - "region", - "discounts", - "customer_id", - "payment_method", - "shipping_methods", - "metadata", - ]) + order = await orderService.decorate( + order, + [ + "status", + "fulfillment_status", + "payment_status", + "email", + "billing_address", + "shipping_address", + "items", + "region", + "discounts", + "customer_id", + "payment_method", + "shipping_methods", + "metadata", + ], + ["region"] + ) res.json({ order }) } catch (error) { diff --git a/packages/medusa/src/loaders/index.js b/packages/medusa/src/loaders/index.js index e00ffbdce1..a39d783abd 100644 --- a/packages/medusa/src/loaders/index.js +++ b/packages/medusa/src/loaders/index.js @@ -4,6 +4,7 @@ import mongooseLoader from "./mongoose" import apiLoader from "./api" import modelsLoader from "./models" import servicesLoader from "./services" +import subscribersLoader from "./subscribers" import passportLoader from "./passport" import pluginsLoader from "./plugins" import defaultsLoader from "./defaults" @@ -37,6 +38,9 @@ export default async ({ directory: rootDirectory, expressApp }) => { await servicesLoader({ container }) Logger.info("Services initialized") + await subscribersLoader({ container }) + Logger.info("Subscribers initialized") + const dbConnection = await mongooseLoader({ container }) Logger.info("MongoDB Intialized") diff --git a/packages/medusa/src/loaders/subscribers.js b/packages/medusa/src/loaders/subscribers.js new file mode 100644 index 0000000000..2340c03ca3 --- /dev/null +++ b/packages/medusa/src/loaders/subscribers.js @@ -0,0 +1,22 @@ +import glob from "glob" +import path from "path" +import { asFunction } from "awilix" + +/** + * Registers all subscribers in the subscribers directory + */ +export default ({ container }) => { + const isTest = process.env.NODE_ENV === "test" + + const corePath = isTest + ? "../subscribers/__mocks__/*.js" + : "../subscribers/*.js" + const coreFull = path.join(__dirname, corePath) + + const core = glob.sync(coreFull, { cwd: __dirname }) + core.forEach(fn => { + const loaded = require(fn).default + + container.build(asFunction(cradle => new loaded(cradle)).singleton()) + }) +} diff --git a/packages/medusa/src/models/order.js b/packages/medusa/src/models/order.js index f6e93eed1d..5aa8f2d49f 100644 --- a/packages/medusa/src/models/order.js +++ b/packages/medusa/src/models/order.js @@ -22,6 +22,7 @@ class OrderModel extends BaseModel { billing_address: { type: AddressSchema, required: true }, shipping_address: { type: AddressSchema, required: true }, items: { type: [LineItemSchema], required: true }, + currency_code: { type: String, required: true }, region_id: { type: String, required: true }, discounts: { type: [DiscountSchema], default: [] }, customer_id: { type: String }, diff --git a/packages/medusa/src/services/cart.js b/packages/medusa/src/services/cart.js index d33c0ec021..f54317d92b 100644 --- a/packages/medusa/src/services/cart.js +++ b/packages/medusa/src/services/cart.js @@ -303,7 +303,7 @@ class CartService extends BaseService { const validatedLineItem = this.lineItemService_.validate(lineItem) const cart = await this.retrieve(cartId) const currentItem = cart.items.find(line => - _.isEqual(line.content, validatedLineItem.content) + this.lineItemService_.isEqual(line, validatedLineItem) ) // If content matches one of the line items currently in the cart we can @@ -393,7 +393,7 @@ class CartService extends BaseService { } // Ensure that the line item exists in the cart - const lineItemExists = cart.items.find(i => i._id === lineItemId) + const lineItemExists = cart.items.find(i => i._id.equals(lineItemId)) if (!lineItemExists) { throw new MedusaError( MedusaError.Types.INVALID_DATA, @@ -519,6 +519,8 @@ class CartService extends BaseService { ) } + address.country_code = address.country_code.toUpperCase() + return this.cartModel_ .updateOne( { @@ -551,6 +553,8 @@ class CartService extends BaseService { ) } + address.country_code = address.country_code.toUpperCase() + const region = await this.regionService_.retrieve(cart.region_id) if (!region.countries.includes(address.country_code.toUpperCase())) { throw new MedusaError( @@ -681,6 +685,16 @@ class CartService extends BaseService { } } + async removeDiscount(cartId, discountCode) { + const cart = await this.retrieve(cartId) + return this.cartModel_.updateOne( + { _id: cart._id }, + { + $pull: { discounts: { code: discountCode } }, + } + ) + } + /** * A payment method represents a way for the customer to pay. The payment * method will typically come from one of the payment sessions. @@ -736,20 +750,6 @@ class CartService extends BaseService { ) } - // The provider service will be able to perform operations on the - // session we are trying to set as the payment method. - const provider = this.paymentProviderService_.retrieveProvider( - paymentMethod.provider_id - ) - - const status = await provider.getStatus(paymentMethod.data) - if (!(status === "authorized" || status === "succeeded")) { - throw new MedusaError( - MedusaError.Types.NOT_ALLOWED, - `The payment method was not authorized` - ) - } - // At this point we can register the payment method. return this.cartModel_ .updateOne( @@ -883,7 +883,7 @@ class CartService extends BaseService { // that has the same profile as the selected shipping method. let exists = false const newMethods = shipping_methods.map(sm => { - if (sm.profile_id === option.profile_id) { + if (option.profile_id.equals(sm.profile_id)) { exists = true return option } diff --git a/packages/medusa/src/services/line-item.js b/packages/medusa/src/services/line-item.js index 22ea8c321e..2ce35a09a8 100644 --- a/packages/medusa/src/services/line-item.js +++ b/packages/medusa/src/services/line-item.js @@ -117,6 +117,28 @@ class LineItemService extends BaseService { }, } } + + isEqual(line, match) { + if (Array.isArray(line.content)) { + if ( + Array.isArray(match.content) && + match.content.length === line.content.length + ) { + return line.content.every( + (c, index) => + c.variant._id.equals(match[index].variant._id) && + c.quantity === match[index].quantity + ) + } + } else if (!Array.isArray(match.content)) { + return ( + line.content.variant._id.equals(match.content.variant._id) && + line.content.quantity === match.content.quantity + ) + } + + return false + } } export default LineItemService diff --git a/packages/medusa/src/services/order.js b/packages/medusa/src/services/order.js index b7b4123e81..c3f12ac529 100644 --- a/packages/medusa/src/services/order.js +++ b/packages/medusa/src/services/order.js @@ -7,6 +7,7 @@ class OrderService extends BaseService { PLACED: "order.placed", UPDATED: "order.updated", CANCELLED: "order.cancelled", + COMPLETED: "order.completed", } constructor({ @@ -16,6 +17,7 @@ class OrderService extends BaseService { fulfillmentProviderService, lineItemService, totalsService, + regionService, eventBusService, }) { super() @@ -38,6 +40,9 @@ class OrderService extends BaseService { /** @private @const {TotalsService} */ this.totalsService_ = totalsService + /** @private @const {RegionService} */ + this.regionService_ = regionService + /** @private @const {EventBus} */ this.eventBus_ = eventBusService } @@ -160,7 +165,7 @@ class OrderService extends BaseService { */ async retrieveByCartId(cartId) { const order = await this.orderModel_ - .findOne({ metadata: { cart_id: cartId } }) + .findOne({ cart_id: cartId }) .catch(err => { throw new MedusaError(MedusaError.Types.DB_ERROR, err.message) }) @@ -200,6 +205,37 @@ class OrderService extends BaseService { return this.orderModel_.find(selector) } + /** + * @param {string} orderId - id of the order to complete + * @return {Promise} the result of the find operation + */ + async completeOrder(orderId) { + const order = await this.retrieve(orderId) + this.orderModel_ + .updateOne( + { _id: order._id }, + { + $set: { status: "completed" }, + } + ) + .then(async result => { + const completeOrderJob = await this.eventBus_.emit( + OrderService.Events.COMPLETED, + result + ) + + return completeOrderJob + .finished() + .then(async () => { + return this.retrieve(order._id) + }) + .catch(error => { + console.log(error) + throw error + }) + }) + } + /** * Creates an order from a cart * @param {object} order - the order to create @@ -231,21 +267,41 @@ class OrderService extends BaseService { const { payment_method } = cart - const paymentProvider = await this.paymentProviderService_.retrieveProvider( - payment_method.provider_id + let paymentSession = cart.payment_sessions.find( + ps => ps.provider_id === payment_method.provider_id ) - const paymentStatus = await paymentProvider.getStatus(payment_method.data) + + // Throw if payment method does not exist + if (!paymentSession) { + throw new MedusaError( + MedusaError.Types.INVALID_ARGUMENT, + "Cart does not have an authorized payment session" + ) + } + + const region = await this.regionService_.retrieve(cart.region_id) + const paymentProvider = this.paymentProviderService_.retrieveProvider( + paymentSession.provider_id + ) + const paymentStatus = await paymentProvider.getStatus(paymentSession.data) // If payment status is not authorized, we throw - if (paymentStatus !== "authorized") { + if (paymentStatus !== "authorized" && paymentStatus !== "succeeded") { throw new MedusaError( - MedusaError.types.INVALID_ARGUMENT, + MedusaError.Types.INVALID_ARGUMENT, "Payment method is not authorized" ) } + const paymentData = await paymentProvider.retrievePayment( + paymentSession.data + ) + const o = { - payment_method: cart.payment_method, + payment_method: { + provider_id: paymentSession.provider_id, + data: paymentData, + }, shipping_methods: cart.shipping_methods, items: cart.items, shipping_address: cart.shipping_address, @@ -254,13 +310,15 @@ class OrderService extends BaseService { email: cart.email, customer_id: cart.customer_id, cart_id: cart._id, + currency_code: region.currency_code, } - const orderDocument = await this.orderModel_.create(o) - // Commit transaction - await dbSession.commitTransaction() + const orderDocument = await this.orderModel_.create([o], { + session: dbSession, + }) + // Emit and return - this.eventBus_emit(OrderService.Events.PLACED, orderDocument) + this.eventBus_.emit(OrderService.Events.PLACED, orderDocument) return orderDocument }) } @@ -594,9 +652,17 @@ class OrderService extends BaseService { * @return {Order} return the decorated order. */ async decorate(order, fields, expandFields = []) { - const requiredFields = ["_id", "metadata"] - const decorated = _.pick(order, fields.concat(requiredFields)) - return decorated + const o = order.toObject() + o.shipping_total = await this.totalsService_.getShippingTotal(order) + o.discount_total = await this.totalsService_.getDiscountTotal(order) + o.tax_total = await this.totalsService_.getTaxTotal(order) + o.subtotal = await this.totalsService_.getSubtotal(order) + o.total = await this.totalsService_.getTotal(order) + o.created = order._id.getTimestamp() + if (expandFields.includes("region")) { + o.region = await this.regionService_.retrieve(order.region_id) + } + return o } /** diff --git a/packages/medusa/src/subscribers/order.js b/packages/medusa/src/subscribers/order.js new file mode 100644 index 0000000000..b88c2e410f --- /dev/null +++ b/packages/medusa/src/subscribers/order.js @@ -0,0 +1,17 @@ +class OrderSubscriber { + constructor({ paymentProviderService, eventBusService }) { + this.paymentProviderService_ = paymentProviderService + + this.eventBus_ = eventBusService + + this.eventBus_.subscribe("order.completed", async order => { + const paymentProvider = this.paymentProviderService_.retrieveProvider( + order.payment_method.provider_id + ) + + await paymentProvider.capturePayment(order._id) + }) + } +} + +export default OrderSubscriber