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 397cd5ff14..99cfd64da1 100644 --- a/packages/medusa-payment-klarna/src/api/routes/hooks/push.js +++ b/packages/medusa-payment-klarna/src/api/routes/hooks/push.js @@ -19,10 +19,7 @@ export default async (req, res) => { await klarnaProviderService.acknowledgeOrder(klarnaOrder.order_id, order._id) } catch (err) { if (err.type === MeudsaError.Types.NOT_FOUND) { - let cart = await cartService.retrieve(cartId) - const method = cart.payment_sessions.find(p => p.provider_id === "klarna") - cart = await cartService.setPaymentMethod(cart._id, method) - + const cart = await cartService.retrieve(cartId) const order = await orderService.createFromCart(cart) await klarnaProviderService.acknowledgeOrder(klarnaOrder.order_id, order._id) } @@ -30,6 +27,7 @@ export default async (req, res) => { res.sendStatus(200) } catch (error) { + console.log(error) throw error } } diff --git a/packages/medusa-payment-klarna/src/services/klarna-provider.js b/packages/medusa-payment-klarna/src/services/klarna-provider.js index a03620c44f..752692337a 100644 --- a/packages/medusa-payment-klarna/src/services/klarna-provider.js +++ b/packages/medusa-payment-klarna/src/services/klarna-provider.js @@ -78,7 +78,7 @@ class KlarnaProviderService extends PaymentService { 0 // Withdraw discount from the total item amount - const quantity = item.content.quantity + const quantity = item.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 @@ -155,7 +155,7 @@ class KlarnaProviderService extends PaymentService { terms: this.options_.merchant_urls.terms, checkout: this.options_.merchant_urls.checkout, confirmation: this.options_.merchant_urls.confirmation, - push: `${this.backendUrl_}/klarna/push`, + push: `${this.backendUrl_}/klarna/push?klarna_order_id={checkout.order_id}`, shipping_option_update: `${this.backendUrl_}/klarna/shipping`, address_update: `${this.backendUrl_}/klarna/address`, } @@ -274,6 +274,7 @@ class KlarnaProviderService extends PaymentService { ) return klarnaOrderId } catch (error) { + throw error } } 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 03e9e6b0d4..407d1b5b3d 100644 --- a/packages/medusa-payment-stripe/src/api/routes/hooks/stripe.js +++ b/packages/medusa-payment-stripe/src/api/routes/hooks/stripe.js @@ -13,21 +13,17 @@ export default async (req, res) => { const paymentIntent = event.data.object const orderService = req.scope.resolve("orderService") + const cartId = paymentIntent.metadata.cart_id + const order = await orderService.retrieveByCartId(cartId) // 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.cancelled": - const cartId = paymentIntent.metadata.cart_id - const order = await orderService.retrieveByCartId(cartId) - await orderService.update(order._id, { status: "cancelled", }) diff --git a/packages/medusa/src/api/routes/store/auth/create-session.js b/packages/medusa/src/api/routes/store/auth/create-session.js index 23d7630baa..38d3481250 100644 --- a/packages/medusa/src/api/routes/store/auth/create-session.js +++ b/packages/medusa/src/api/routes/store/auth/create-session.js @@ -15,6 +15,8 @@ export default async (req, res) => { } const authService = req.scope.resolve("authService") + const customerService = req.scope.resolve("customerService") + const result = await authService.authenticateCustomer( value.email, value.password @@ -26,12 +28,21 @@ export default async (req, res) => { // Add JWT to cookie req.session.jwt = jwt.sign( - { customer_id: result.user._id }, + { customer_id: result.customer._id }, config.jwtSecret, { expiresIn: "30d", } ) - res.json({ customer: result.customer }) + const data = await customerService.decorate(result.customer, [ + "_id", + "email", + "orders", + "shipping_addresses", + "first_name", + "last_name", + ]) + + res.json({ customer: data }) } diff --git a/packages/medusa/src/api/routes/store/auth/delete-session.js b/packages/medusa/src/api/routes/store/auth/delete-session.js new file mode 100644 index 0000000000..8d5404913a --- /dev/null +++ b/packages/medusa/src/api/routes/store/auth/delete-session.js @@ -0,0 +1,4 @@ +export default async (req, res) => { + req.session.jwt = {} + res.json({}) +} diff --git a/packages/medusa/src/api/routes/store/auth/exists.js b/packages/medusa/src/api/routes/store/auth/exists.js new file mode 100644 index 0000000000..deb2bbac2d --- /dev/null +++ b/packages/medusa/src/api/routes/store/auth/exists.js @@ -0,0 +1,13 @@ +import { Validator, MedusaError } from "medusa-core-utils" + +export default async (req, res) => { + const { email } = req.params + + try { + const customerService = req.scope.resolve("customerService") + const customer = await customerService.retrieveByEmail(email) + res.status(200).json({ exists: !!customer.password_hash }) + } catch (err) { + res.status(200).json({ exists: false }) + } +} diff --git a/packages/medusa/src/api/routes/store/auth/get-session.js b/packages/medusa/src/api/routes/store/auth/get-session.js new file mode 100644 index 0000000000..a997789f6e --- /dev/null +++ b/packages/medusa/src/api/routes/store/auth/get-session.js @@ -0,0 +1,26 @@ +export default async (req, res) => { + try { + if (req.user && req.user.customer_id) { + const customerService = req.scope.resolve("customerService") + const customer = await customerService.retrieve(req.user.customer_id) + + const data = await customerService.decorate( + customer, + [ + "_id", + "email", + "orders", + "shipping_addresses", + "first_name", + "last_name", + ], + ["orders"] + ) + res.json({ customer: data }) + } + + res.sendStatus(401) + } catch (err) { + throw err + } +} diff --git a/packages/medusa/src/api/routes/store/auth/index.js b/packages/medusa/src/api/routes/store/auth/index.js index 965b49c109..bcc2f8334a 100644 --- a/packages/medusa/src/api/routes/store/auth/index.js +++ b/packages/medusa/src/api/routes/store/auth/index.js @@ -5,6 +5,15 @@ const route = Router() export default app => { app.use("/auth", route) + + route.get( + "/", + middlewares.authenticate(), + middlewares.wrap(require("./get-session").default) + ) + route.get("/:email", middlewares.wrap(require("./exists").default)) + route.delete("/", middlewares.wrap(require("./delete-session").default)) route.post("/", middlewares.wrap(require("./create-session").default)) + return app } diff --git a/packages/medusa/src/api/routes/store/customers/create-customer.js b/packages/medusa/src/api/routes/store/customers/create-customer.js index 1d4f0378f6..ad3321bd4d 100644 --- a/packages/medusa/src/api/routes/store/customers/create-customer.js +++ b/packages/medusa/src/api/routes/store/customers/create-customer.js @@ -1,4 +1,6 @@ +import jwt from "jsonwebtoken" import { Validator, MedusaError } from "medusa-core-utils" +import config from "../../../../config" export default async (req, res) => { const schema = Validator.object().keys({ @@ -17,7 +19,24 @@ export default async (req, res) => { try { const customerService = req.scope.resolve("customerService") const customer = await customerService.create(value) - const data = await customerService.decorate(customer, ["_id", "email"]) + + // Add JWT to cookie + req.session.jwt = jwt.sign( + { customer_id: customer._id }, + config.jwtSecret, + { + expiresIn: "30d", + } + ) + + const data = await customerService.decorate(customer, [ + "_id", + "email", + "orders", + "shipping_addresses", + "first_name", + "last_name", + ]) res.status(201).json({ customer: data }) } catch (err) { throw err diff --git a/packages/medusa/src/api/routes/store/customers/index.js b/packages/medusa/src/api/routes/store/customers/index.js index d6ca168dbd..854b37ed8b 100644 --- a/packages/medusa/src/api/routes/store/customers/index.js +++ b/packages/medusa/src/api/routes/store/customers/index.js @@ -6,9 +6,6 @@ const route = Router() export default app => { app.use("/customers", route) - route.get("/", middlewares.wrap(require("./list-customers").default)) - route.get("/:id", middlewares.wrap(require("./get-customer").default)) - route.post("/", middlewares.wrap(require("./create-customer").default)) route.post( @@ -26,6 +23,7 @@ export default app => { route.param("id", middlewares.wrap(require("./authorize-customer").default)) + route.get("/:id", middlewares.wrap(require("./get-customer").default)) route.post("/:id", middlewares.wrap(require("./update-customer").default)) route.post( "/:id/password", diff --git a/packages/medusa/src/api/routes/store/customers/list-customers.js b/packages/medusa/src/api/routes/store/customers/list-customers.js deleted file mode 100644 index c64cc20aa9..0000000000 --- a/packages/medusa/src/api/routes/store/customers/list-customers.js +++ /dev/null @@ -1,12 +0,0 @@ -export default async (req, res) => { - const selector = {} - - try { - const customerService = req.scope.resolve("customerService") - const customers = await customerService.list(selector) - - res.json({ customers }) - } catch (error) { - throw error - } -} diff --git a/packages/medusa/src/api/routes/store/index.js b/packages/medusa/src/api/routes/store/index.js index 918522f5bf..c6429b697e 100644 --- a/packages/medusa/src/api/routes/store/index.js +++ b/packages/medusa/src/api/routes/store/index.js @@ -1,6 +1,7 @@ import { Router } from "express" import cors from "cors" +import authRoutes from "./auth" import productRoutes from "./products" import cartRoutes from "./carts" import orderRoutes from "./orders" @@ -21,6 +22,7 @@ export default (app, container, config) => { }) ) + authRoutes(route) customerRoutes(route) productRoutes(route) orderRoutes(route) 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 fa80df1c66..2c09bf7548 100644 --- a/packages/medusa/src/api/routes/store/orders/create-order.js +++ b/packages/medusa/src/api/routes/store/orders/create-order.js @@ -36,6 +36,26 @@ export default async (req, res) => { res.status(200).json({ order }) } catch (err) { - throw err + // If something fails it might be because the order has already been created + // if it has we find it from the cart id + const orderService = req.scope.resolve("orderService") + let order = await orderService.retrieveByCartId(value.cartId) + 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", + ]) + + res.status(200).json({ order }) } } diff --git a/packages/medusa/src/models/customer.js b/packages/medusa/src/models/customer.js index 2988f7e88d..6324b6208e 100644 --- a/packages/medusa/src/models/customer.js +++ b/packages/medusa/src/models/customer.js @@ -11,9 +11,11 @@ class CustomerModel extends BaseModel { static schema = { email: { type: String, required: true, unique: true }, - first_name: { type: String, required: true }, - last_name: { type: String, required: true }, + first_name: { type: String }, + last_name: { type: String }, billing_address: { type: AddressSchema }, + payment_methods: { type: [mongoose.Schema.Types.Mixed], default: [] }, + shipping_addresses: { type: [AddressSchema], default: [] }, password_hash: { type: String }, has_account: { type: Boolean, default: false }, orders: { type: [String], default: [] }, diff --git a/packages/medusa/src/services/cart.js b/packages/medusa/src/services/cart.js index f54317d92b..bf61fc7400 100644 --- a/packages/medusa/src/services/cart.js +++ b/packages/medusa/src/services/cart.js @@ -22,6 +22,7 @@ class CartService extends BaseService { lineItemService, shippingOptionService, shippingProfileService, + customerService, discountService, totalsService, }) { @@ -51,6 +52,9 @@ class CartService extends BaseService { /** @private @const {ShippingProfileService} */ this.shippingProfileService_ = shippingProfileService + /** @private @const {CustomerService} */ + this.customerService_ = customerService + /** @private @const {ShippingOptionService} */ this.shippingOptionService_ = shippingOptionService @@ -487,13 +491,24 @@ class CartService extends BaseService { ) } + let customer = await this.customerService_ + .retrieveByEmail(value) + .catch(err => undefined) + + if (!customer) { + customer = await this.customerService_.create({ email }) + } + return this.cartModel_ .updateOne( { _id: cart._id, }, { - $set: { email: value }, + $set: { + email: value, + customer_id: customer._id, + }, } ) .then(result => { @@ -998,6 +1013,11 @@ class CartService extends BaseService { }) } + async delete(cartId) { + const cart = await this.retrieve(cartId) + return this.cartModel_.deleteOne({ _id: cart._id }) + } + /** * Dedicated method to set metadata for a cart. * To ensure that plugins does not overwrite each diff --git a/packages/medusa/src/services/customer.js b/packages/medusa/src/services/customer.js index 7a54540585..4de18adedd 100644 --- a/packages/medusa/src/services/customer.js +++ b/packages/medusa/src/services/customer.js @@ -13,7 +13,7 @@ class CustomerService extends BaseService { PASSWORD_RESET: "customer.password_reset", } - constructor({ customerModel, eventBusService }) { + constructor({ customerModel, orderService, eventBusService }) { super() /** @private @const {CustomerModel} */ @@ -21,6 +21,9 @@ class CustomerService extends BaseService { /** @private @const {EventBus} */ this.eventBus_ = eventBusService + + /** @private @const {EventBus} */ + this.orderService_ = orderService } /** @@ -171,16 +174,32 @@ class CustomerService extends BaseService { this.validateBillingAddress_(billing_address) } - if (password) { + const existing = await this.retrieveByEmail(email).catch(err => undefined) + + if (existing && password && !existing.has_account) { const hashedPassword = await bcrypt.hash(password, 10) customer.password_hash = hashedPassword customer.has_account = true delete customer.password - } - return this.customerModel_.create(customer).catch(err => { - throw new MedusaError(MedusaError.Types.DB_ERROR, err.message) - }) + return this.customerModel_.updateOne( + { _id: existing._id }, + { + $set: customer, + } + ) + } else { + if (password) { + const hashedPassword = await bcrypt.hash(password, 10) + customer.password_hash = hashedPassword + customer.has_account = true + delete customer.password + } + + return this.customerModel_.create(customer).catch(err => { + throw new MedusaError(MedusaError.Types.DB_ERROR, err.message) + }) + } } /** @@ -228,6 +247,14 @@ class CustomerService extends BaseService { }) } + async addOrder(customerId, orderId) { + const customer = await this.retrieve(customerId) + return this.customerModel_.updateOne( + { _id: customer._id }, + { $addToSet: { orders: orderId } } + ) + } + /** * Deletes a customer from a given customer id. * @param {string} customerId - the id of the customer to delete. Must be @@ -255,9 +282,16 @@ class CustomerService extends BaseService { * @param {string[]} expandFields - fields to expand. * @return {Customer} return the decorated customer. */ - decorate(customer, fields, expandFields = []) { + async decorate(customer, fields, expandFields = []) { const requiredFields = ["_id", "metadata"] const decorated = _.pick(customer, fields.concat(requiredFields)) + + if (expandFields.includes("orders")) { + decorated.orders = await Promise.all( + customer.orders.map(async o => this.orderService_.retrieve(o)) + ) + } + return decorated } diff --git a/packages/medusa/src/services/order.js b/packages/medusa/src/services/order.js index cd5e6ed308..34a133a3a9 100644 --- a/packages/medusa/src/services/order.js +++ b/packages/medusa/src/services/order.js @@ -317,8 +317,8 @@ class OrderService extends BaseService { }) // Emit and return - this.eventBus_.emit(OrderService.Events.PLACED, orderDocument) - return orderDocument + this.eventBus_.emit(OrderService.Events.PLACED, orderDocument[0]) + return orderDocument[0] }) } diff --git a/packages/medusa/src/subscribers/order.js b/packages/medusa/src/subscribers/order.js index b88c2e410f..6e7afb0ec7 100644 --- a/packages/medusa/src/subscribers/order.js +++ b/packages/medusa/src/subscribers/order.js @@ -1,6 +1,13 @@ class OrderSubscriber { - constructor({ paymentProviderService, eventBusService }) { + constructor({ + paymentProviderService, + cartService, + customerService, + eventBusService, + }) { this.paymentProviderService_ = paymentProviderService + this.customerService_ = customerService + this.cartService_ = cartService this.eventBus_ = eventBusService @@ -11,6 +18,14 @@ class OrderSubscriber { await paymentProvider.capturePayment(order._id) }) + + this.eventBus_.subscribe("order.placed", async order => { + await this.customerService_.addOrder(order.customer_id, order._id) + }) + + this.eventBus_.subscribe("order.placed", async order => { + await this.cartService_.delete(order.cart_id) + }) } }