diff --git a/packages/medusa-payment-stripe/src/api/routes/hooks/stripe.js b/packages/medusa-payment-stripe/src/api/routes/hooks/stripe.js index 9584b26a78..03e9e6b0d4 100644 --- a/packages/medusa-payment-stripe/src/api/routes/hooks/stripe.js +++ b/packages/medusa-payment-stripe/src/api/routes/hooks/stripe.js @@ -12,19 +12,31 @@ export default async (req, res) => { const paymentIntent = event.data.object + const orderService = req.scope.resolve("orderService") + // handle payment intent events switch (event.type) { case "payment_intent.succeeded": + const cartId = paymentIntent.metadata.cart_id + const order = await orderService.retrieveByCartId(cartId) + + await orderService.update(order._id, { + payment_status: "captured", + }) break - case "payment_intent.canceled": - break - case "payment_intent.created": + case "payment_intent.cancelled": + const cartId = paymentIntent.metadata.cart_id + const order = await orderService.retrieveByCartId(cartId) + + await orderService.update(order._id, { + status: "cancelled", + }) break case "payment_intent.payment_failed": + // TODO: Not implemented yet break case "payment_intent.amount_capturable_updated": - break - case "payment_intent.processing": + // TODO: Not implemented yet break default: res.status(400) diff --git a/packages/medusa-payment-stripe/src/services/stripe-provider.js b/packages/medusa-payment-stripe/src/services/stripe-provider.js index 87001ddccc..412c6bcc38 100644 --- a/packages/medusa-payment-stripe/src/services/stripe-provider.js +++ b/packages/medusa-payment-stripe/src/services/stripe-provider.js @@ -104,6 +104,8 @@ class StripeProviderService extends PaymentService { customer: stripeCustomerId, amount: amount * 100, // Stripe amount is in cents currency: currency_code, + capture_method: "manual", + metadata: { cart_id: cart._id }, }) return paymentIntent diff --git a/packages/medusa-payment-stripe/src/subscribers/cart.js b/packages/medusa-payment-stripe/src/subscribers/cart.js index e5a0af6c1e..ef3b65fa5d 100644 --- a/packages/medusa-payment-stripe/src/subscribers/cart.js +++ b/packages/medusa-payment-stripe/src/subscribers/cart.js @@ -10,13 +10,14 @@ class CartSubscriber { this.stripeProviderService_ = stripeProviderService this.eventBus_ = eventBusService - this.eventBus_.subscribe("cart.created", (data) => { - console.log(data) - }) - this.eventBus_.subscribe("cart.customer_updated", async (cart) => { await this.onCustomerUpdated(cart) }) + + this.eventBus_.subscribe("order.completed", async (order) => { + const paymentData = order.payment_method.data + await this.stripeProviderService_.capturePayment(paymentData) + }) } async onCustomerUpdated(cart) { diff --git a/packages/medusa-plugin-economic/src/api/index.js b/packages/medusa-plugin-economic/src/api/index.js index e69de29bb2..c861a5b001 100644 --- a/packages/medusa-plugin-economic/src/api/index.js +++ b/packages/medusa-plugin-economic/src/api/index.js @@ -0,0 +1,10 @@ +import { Router } from "express" +import routes from "./routes" + +export default (container) => { + const app = Router() + + routes(app) + + return app +} diff --git a/packages/medusa-plugin-economic/src/api/middlewares/await-middleware.js b/packages/medusa-plugin-economic/src/api/middlewares/await-middleware.js new file mode 100644 index 0000000000..1c3692b377 --- /dev/null +++ b/packages/medusa-plugin-economic/src/api/middlewares/await-middleware.js @@ -0,0 +1 @@ +export default (fn) => (...args) => fn(...args).catch(args[2]) diff --git a/packages/medusa-plugin-economic/src/api/middlewares/index.js b/packages/medusa-plugin-economic/src/api/middlewares/index.js new file mode 100644 index 0000000000..c784e319a9 --- /dev/null +++ b/packages/medusa-plugin-economic/src/api/middlewares/index.js @@ -0,0 +1,5 @@ +import { default as wrap } from "./await-middleware" + +export default { + wrap, +} diff --git a/packages/medusa-plugin-economic/src/api/routes/book-invoice.js b/packages/medusa-plugin-economic/src/api/routes/book-invoice.js new file mode 100644 index 0000000000..2f635d4aaf --- /dev/null +++ b/packages/medusa-plugin-economic/src/api/routes/book-invoice.js @@ -0,0 +1,18 @@ +export default async (req, res) => { + const schema = Validator.object().keys({ + orderId: Validator.string().required(), + }) + + const { value, error } = schema.validate(req.body) + if (error) { + throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details) + } + + try { + const economicService = req.scope.resolve("economicService") + await economicService.bookEconomicInvoice(value.orderId) + res.sendStatus(200) + } catch (error) { + throw error + } +} diff --git a/packages/medusa-plugin-economic/src/api/routes/create-draft-invoice.js b/packages/medusa-plugin-economic/src/api/routes/create-draft-invoice.js new file mode 100644 index 0000000000..0374ca12f3 --- /dev/null +++ b/packages/medusa-plugin-economic/src/api/routes/create-draft-invoice.js @@ -0,0 +1,18 @@ +export default async (req, res) => { + const schema = Validator.object().keys({ + orderId: Validator.string().required(), + }) + + const { value, error } = schema.validate(req.body) + if (error) { + throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details) + } + + try { + const economicService = req.scope.resolve("economicService") + await economicService.draftEconomicInvoice(value.orderId) + res.sendStatus(200) + } catch (error) { + throw error + } +} diff --git a/packages/medusa-plugin-economic/src/api/routes/index.js b/packages/medusa-plugin-economic/src/api/routes/index.js new file mode 100644 index 0000000000..fbe8682565 --- /dev/null +++ b/packages/medusa-plugin-economic/src/api/routes/index.js @@ -0,0 +1,20 @@ +import { Router } from "express" +import middlewares from "../../middlewares" + +const route = Router() + +export default (app) => { + app.use("/economic", route) + + route.post( + "/draft-invoice", + middlewares.wrap(require("./create-draft-invoice").default) + ) + + route.post( + "/book-invoice", + middlewares.wrap(require("./book-invoice").default) + ) + + return app +} diff --git a/packages/medusa-plugin-economic/src/services/economic.js b/packages/medusa-plugin-economic/src/services/economic.js index 46d3024c8c..b7390d369d 100644 --- a/packages/medusa-plugin-economic/src/services/economic.js +++ b/packages/medusa-plugin-economic/src/services/economic.js @@ -188,6 +188,9 @@ class EconomicService extends BaseService { "economicDraftId", draftInvoice.draftInvoiceNumber ) + + const invoiceOrder = await this.orderService_.retrieve(order._id) + return invoiceOrder } catch (error) { throw error } @@ -211,7 +214,7 @@ class EconomicService extends BaseService { }, } - await this.economic_.post( + return this.economic_.post( `${ECONOMIC_BASE_URL}/invoices/booked`, bookInvoiceRequest ) diff --git a/packages/medusa-plugin-economic/src/subscribers/order.js b/packages/medusa-plugin-economic/src/subscribers/order.js index 038254f0dc..40c81f41a8 100644 --- a/packages/medusa-plugin-economic/src/subscribers/order.js +++ b/packages/medusa-plugin-economic/src/subscribers/order.js @@ -7,6 +7,10 @@ class OrderSubscriber { this.eventBus_.subscribe("order.placed", async (order) => { await this.economicService_.draftEconomicInvoice(order) }) + + this.eventBus_.subscribe("order.completed", async (order) => { + await this.economicService_.bookEconomicInvoice(order._id) + }) } } 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 bbba9da8fa..b4d3a5ea7e 100644 --- a/packages/medusa/src/api/routes/store/orders/create-order.js +++ b/packages/medusa/src/api/routes/store/orders/create-order.js @@ -15,7 +15,7 @@ export default async (req, res) => { const orderService = req.scope.resolve("orderService") const cart = await cartService.retrieve(value.cartId) - let order = await orderService.create(cart) + let order = await orderService.createFromCart(cart) order = await orderService.decorate(order, [ "status", "fulfillment_status", diff --git a/packages/medusa/src/models/__mocks__/order.js b/packages/medusa/src/models/__mocks__/order.js index e3c198ed5f..67530bf4e4 100644 --- a/packages/medusa/src/models/__mocks__/order.js +++ b/packages/medusa/src/models/__mocks__/order.js @@ -63,6 +63,9 @@ export const orders = { fulfillment_status: "not_fulfilled", payment_status: "awaiting", status: "pending", + metadata: { + cart_id: IdMap.getId("test-cart"), + }, }, processedOrder: { _id: IdMap.getId("processed-order"), @@ -237,6 +240,9 @@ export const OrderModelMock = { orders.orderToRefund.payment_status = "captured" return Promise.resolve(orders.orderToRefund) } + if (query.metadata.cart_id === IdMap.getId("test-cart")) { + return Promise.resolve(orders.testOrder) + } return Promise.resolve(undefined) }), } diff --git a/packages/medusa/src/services/__tests__/order.js b/packages/medusa/src/services/__tests__/order.js index c760eefe58..8bee6106ab 100644 --- a/packages/medusa/src/services/__tests__/order.js +++ b/packages/medusa/src/services/__tests__/order.js @@ -1,5 +1,6 @@ import { IdMap } from "medusa-test-utils" import { OrderModelMock, orders } from "../../models/__mocks__/order" +import { carts } from "../../models/__mocks__/cart" import OrderService from "../order" import { PaymentProviderServiceMock } from "../__mocks__/payment-provider" import { FulfillmentProviderServiceMock } from "../__mocks__/fulfillment-provider" @@ -30,6 +31,27 @@ describe("OrderService", () => { }) }) + describe("createFromCart", () => { + const orderService = new OrderService({ + orderModel: OrderModelMock, + eventBusService: EventBusServiceMock, + }) + + beforeEach(async () => { + jest.clearAllMocks() + }) + + it("calls order model functions", async () => { + await orderService.createFromCart(carts.completeCart) + + expect(OrderModelMock.create).toHaveBeenCalledTimes(1) + expect(OrderModelMock.create).toHaveBeenCalledWith({ + ...carts.completeCart, + metadata: { cart_id: carts.completeCart._id }, + }) + }) + }) + describe("retrieve", () => { let result const orderService = new OrderService({ @@ -53,6 +75,29 @@ describe("OrderService", () => { }) }) + describe("retrieveByCartId", () => { + let result + const orderService = new OrderService({ + orderModel: OrderModelMock, + }) + + beforeAll(async () => { + jest.clearAllMocks() + result = await orderService.retrieveByCartId(IdMap.getId("test-cart")) + }) + + it("calls order model functions", async () => { + expect(OrderModelMock.findOne).toHaveBeenCalledTimes(1) + expect(OrderModelMock.findOne).toHaveBeenCalledWith({ + metadata: { cart_id: IdMap.getId("test-cart") }, + }) + }) + + it("returns correct order", async () => { + expect(result._id).toEqual(IdMap.getId("test-order")) + }) + }) + describe("update", () => { const orderService = new OrderService({ orderModel: OrderModelMock, diff --git a/packages/medusa/src/services/order.js b/packages/medusa/src/services/order.js index 603ad97ff8..f58da00647 100644 --- a/packages/medusa/src/services/order.js +++ b/packages/medusa/src/services/order.js @@ -153,6 +153,27 @@ class OrderService extends BaseService { return order } + /** + * Gets an order by cart id. + * @param {string} cartId - cart id to find order + * @return {Promise} the order document + */ + async retrieveByCartId(cartId) { + const order = await this.orderModel_ + .findOne({ metadata: { cart_id: cartId } }) + .catch(err => { + throw new MedusaError(MedusaError.Types.DB_ERROR, err.message) + }) + + if (!order) { + throw new MedusaError( + MedusaError.Types.NOT_FOUND, + `Order with cart id ${cartId} was not found` + ) + } + return order + } + /** * @param {Object} selector - the query object for find * @return {Promise} the result of the find operation @@ -161,6 +182,24 @@ class OrderService extends BaseService { return this.orderModel_.find(selector) } + /** + * Creates an order from a cart + * @param {object} order - the order to create + * @return {Promise} resolves to the creation result. + */ + async createFromCart(cart) { + return this.orderModel_ + .create({ ...cart, metadata: { cart_id: cart._id } }) + .then(result => { + // Notify subscribers + this.eventBus_.emit(OrderService.Events.PLACED, result) + return result + }) + .catch(err => { + throw new MedusaError(MedusaError.Types.DB_ERROR, err.message) + }) + } + /** * Creates an order * @param {object} order - the order to create