Adds account support

This commit is contained in:
Sebastian Rindom
2020-07-12 12:11:31 +02:00
parent 3eb2e9fe49
commit 60b8bca753
18 changed files with 200 additions and 44 deletions
@@ -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 })
}
}
+4 -2
View File
@@ -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: [] },
+21 -1
View File
@@ -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
+41 -7
View File
@@ -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
}
+2 -2
View File
@@ -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]
})
}
+16 -1
View File
@@ -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)
})
}
}