Adds account support
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
})
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export default async (req, res) => {
|
||||
req.session.jwt = {}
|
||||
res.json({})
|
||||
}
|
||||
@@ -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 })
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: [] },
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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]
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user