Robustness

This commit is contained in:
Sebastian Rindom
2020-07-14 17:23:43 +02:00
parent 44c7177e43
commit 312e405588
21 changed files with 152 additions and 74 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ Joi.address = () => {
country_code: Joi.string().required(), country_code: Joi.string().required(),
province: Joi.string().allow(""), province: Joi.string().allow(""),
postal_code: Joi.string().required(), postal_code: Joi.string().required(),
phone: Joi.string().required(), phone: Joi.string(),
metadata: Joi.object(), metadata: Joi.object(),
}) })
} }
@@ -16,8 +16,9 @@ export default async (req, res) => {
address_1: shipping_address.street_address, address_1: shipping_address.street_address,
address_2: shipping_address.street_address2, address_2: shipping_address.street_address2,
city: shipping_address.city, city: shipping_address.city,
country_code: shipping_address.country, country_code: shipping_address.country.toUpperCase(),
postal_code: shipping_address.postal_code, postal_code: shipping_address.postal_code,
phone: shipping_address.phone
} }
await cartService.updateShippingAddress(cart._id, updatedAddress) await cartService.updateShippingAddress(cart._id, updatedAddress)
@@ -1,4 +1,5 @@
import { Router } from "express" import { Router } from "express"
import bodyParser from "body-parser"
import middlewares from "../../middlewares" import middlewares from "../../middlewares"
const route = Router() const route = Router()
@@ -6,6 +7,7 @@ const route = Router()
export default (app) => { export default (app) => {
app.use("/klarna", route) app.use("/klarna", route)
route.use(bodyParser.json())
route.post("/address", middlewares.wrap(require("./address").default)) route.post("/address", middlewares.wrap(require("./address").default))
route.post("/shipping", middlewares.wrap(require("./shipping").default)) route.post("/shipping", middlewares.wrap(require("./shipping").default))
route.post("/push", middlewares.wrap(require("./push").default)) route.post("/push", middlewares.wrap(require("./push").default))
@@ -10,15 +10,14 @@ export default async (req, res) => {
const klarnaOrder = await klarnaProviderService.retrieveCompletedOrder( const klarnaOrder = await klarnaProviderService.retrieveCompletedOrder(
klarna_order_id klarna_order_id
) ).then(({ data }) => data)
const cartId = klarnaOrder.merchant_data const cartId = klarnaOrder.merchant_data
try { try {
const order = await orderService.retrieveByCartId(cartId) const order = await orderService.retrieveByCartId(cartId)
await klarnaProviderService.acknowledgeOrder(klarnaOrder.order_id, order._id) await klarnaProviderService.acknowledgeOrder(klarnaOrder.order_id, order._id)
} catch (err) { } catch (err) {
if (err.type === MeudsaError.Types.NOT_FOUND) { if (err.type === MedusaError.Types.NOT_FOUND) {
const cart = await cartService.retrieve(cartId) const cart = await cartService.retrieve(cartId)
const order = await orderService.createFromCart(cart) const order = await orderService.createFromCart(cart)
await klarnaProviderService.acknowledgeOrder(klarnaOrder.order_id, order._id) await klarnaProviderService.acknowledgeOrder(klarnaOrder.order_id, order._id)
@@ -26,7 +26,7 @@ class KlarnaProviderService extends PaymentService {
this.klarnaOrderManagementUrl_ = "/ordermanagement/v1/orders" this.klarnaOrderManagementUrl_ = "/ordermanagement/v1/orders"
this.backendUrl_ = this.backendUrl_ =
process.env.BACKEND_URL || "https://2fe4e28015f5.ngrok.io" process.env.BACKEND_URL || "https://7e9a5bc2a2eb.ngrok.io"
this.totalsService_ = totalsService this.totalsService_ = totalsService
@@ -155,7 +155,7 @@ class KlarnaProviderService extends PaymentService {
terms: this.options_.merchant_urls.terms, terms: this.options_.merchant_urls.terms,
checkout: this.options_.merchant_urls.checkout, checkout: this.options_.merchant_urls.checkout,
confirmation: this.options_.merchant_urls.confirmation, confirmation: this.options_.merchant_urls.confirmation,
push: `${this.backendUrl_}/klarna/push?klarna_order_id={checkout.order_id}`, push: `${this.backendUrl_}/klarna/push?klarna_order_id={checkout.order.id}`,
shipping_option_update: `${this.backendUrl_}/klarna/shipping`, shipping_option_update: `${this.backendUrl_}/klarna/shipping`,
address_update: `${this.backendUrl_}/klarna/address`, address_update: `${this.backendUrl_}/klarna/address`,
} }
@@ -266,12 +266,20 @@ class KlarnaProviderService extends PaymentService {
* @param {string} klarnaOrderId - id of the order to acknowledge * @param {string} klarnaOrderId - id of the order to acknowledge
* @returns {string} id of acknowledged order * @returns {string} id of acknowledged order
*/ */
async acknowledgeOrder(klarnaOrderId) { async acknowledgeOrder(klarnaOrderId, orderId) {
try { try {
await this.klarna_.post( await this.klarna_.post(
`${this.klarnaOrderManagementUrl_}/${klarnaOrderId}/acknowledge`, `${this.klarnaOrderManagementUrl_}/${klarnaOrderId}/acknowledge`
{}
) )
await this.klarna_.patch(
`${this.klarnaOrderManagementUrl_}/${klarnaOrderId}/merchant-references`,
{
merchant_reference1: orderId
}
)
return klarnaOrderId return klarnaOrderId
} catch (error) { } catch (error) {
@@ -5,10 +5,10 @@ import middlewares from "../../middlewares"
const route = Router() const route = Router()
export default (app) => { export default (app) => {
app.use("/hooks", route) app.use("/stripe", route)
route.post( route.post(
"/stripe", "/hooks",
// stripe constructEvent fails without body-parser // stripe constructEvent fails without body-parser
bodyParser.raw({ type: "application/json" }), bodyParser.raw({ type: "application/json" }),
middlewares.wrap(require("./stripe").default) middlewares.wrap(require("./stripe").default)
@@ -12,27 +12,37 @@ export default async (req, res) => {
const paymentIntent = event.data.object const paymentIntent = event.data.object
const cartService = req.scope.resolve("cartService")
const orderService = req.scope.resolve("orderService") const orderService = req.scope.resolve("orderService")
const cartId = paymentIntent.metadata.cart_id const cartId = paymentIntent.metadata.cart_id
const order = await orderService.retrieveByCartId(cartId) const order = await orderService.retrieveByCartId(cartId)
.catch(() => undefined)
// handle payment intent events // handle payment intent events
switch (event.type) { switch (event.type) {
case "payment_intent.succeeded": case "payment_intent.succeeded":
if (order) {
await orderService.update(order._id, { await orderService.update(order._id, {
payment_status: "captured", payment_status: "captured",
}) })
}
break break
case "payment_intent.cancelled": case "payment_intent.cancelled":
if (order) {
await orderService.update(order._id, { await orderService.update(order._id, {
status: "cancelled", status: "cancelled",
}) })
}
break break
case "payment_intent.payment_failed": case "payment_intent.payment_failed":
// TODO: Not implemented yet // TODO: Not implemented yet
break break
case "payment_intent.amount_capturable_updated": case "payment_intent.amount_capturable_updated":
// TODO: Not implemented yet if (!order) {
const cart = await cartService.retrieve(cartId)
await orderService.createFromCart(cart)
}
break break
default: default:
res.status(400) res.status(400)
@@ -158,6 +158,15 @@ class StripeProviderService extends PaymentService {
} }
} }
async deletePayment(data) {
try {
const { id } = data
return this.stripe_.paymentIntents.cancel(id)
} catch (error) {
throw error
}
}
/** /**
* Updates customer of Stripe PaymentIntent. * Updates customer of Stripe PaymentIntent.
* @param {string} cart - the cart to update payment intent for * @param {string} cart - the cart to update payment intent for
@@ -60,7 +60,7 @@ class CartSubscriber {
} }
if (stripeCustomer.id !== paymentIntent.customer) { if (stripeCustomer.id !== paymentIntent.customer) {
await this.stripeProviderService_.cancelPayment(paymentIntent.id) await this.stripeProviderService_.cancelPayment(paymentIntent)
const newPaymentIntent = await this.stripeProviderService_.createPayment( const newPaymentIntent = await this.stripeProviderService_.createPayment(
cart cart
) )
@@ -30,7 +30,7 @@ class SendGridService extends BaseService {
* correlate with the structure specificed in the dynamic template * correlate with the structure specificed in the dynamic template
* @returns {Promise} result of the send operation * @returns {Promise} result of the send operation
*/ */
async transactionalEmail(event, order) { async transactionalEmail(event, data) {
let templateId let templateId
switch (event) { switch (event) {
case "order.placed": case "order.placed":
@@ -54,13 +54,12 @@ class SendGridService extends BaseService {
default: default:
return return
} }
try { try {
return SendGrid.send({ return SendGrid.send({
template_id: templateId, template_id: templateId,
from: this.options_.from, from: this.options_.from,
to: order.email, to: data.email,
dynamic_template_data: order, dynamic_template_data: data,
}) })
} catch (error) { } catch (error) {
throw error throw error
@@ -0,0 +1,14 @@
export default async (req, res) => {
const { id, provider_id } = req.params
try {
const cartService = req.scope.resolve("cartService")
let cart = await cartService.deletePaymentSession(id, provider_id)
cart = await cartService.decorate(cart, [], ["region"])
res.status(200).json({ cart })
} catch (err) {
throw err
}
}
@@ -35,6 +35,11 @@ export default app => {
"/:id/payment-sessions", "/:id/payment-sessions",
middlewares.wrap(require("./create-payment-sessions").default) middlewares.wrap(require("./create-payment-sessions").default)
) )
route.delete(
"/:id/payment-sessions/:provider_id",
middlewares.wrap(require("./delete-payment-session").default)
)
route.post( route.post(
"/:id/payment-method", "/:id/payment-method",
middlewares.wrap(require("./update-payment-method").default) middlewares.wrap(require("./update-payment-method").default)
@@ -25,10 +25,6 @@ export default app => {
route.get("/:id", middlewares.wrap(require("./get-customer").default)) route.get("/:id", middlewares.wrap(require("./get-customer").default))
route.post("/:id", middlewares.wrap(require("./update-customer").default)) route.post("/:id", middlewares.wrap(require("./update-customer").default))
route.post(
"/:id/password",
middlewares.wrap(require("./update-password").default)
)
route.post( route.post(
"/:id/addresses", "/:id/addresses",
@@ -20,13 +20,14 @@ export default async (req, res) => {
const customer = await customerService.retrieveByEmail(value.email) const customer = await customerService.retrieveByEmail(value.email)
const decodedToken = await jwt.verify(value.token, customer.password_hash) const decodedToken = await jwt.verify(value.token, customer.password_hash)
if (!decodedToken || decodedToken.customer_id !== customer._id) { if (!decodedToken || !customer._id.equals(decodedToken.customer_id)) {
res.status(401).send("Invalid or expired password reset token") res.status(401).send("Invalid or expired password reset token")
return
} }
await customerService.update(customer._id, { password: value.password }) const updated = await customerService.update(customer._id, {
password: value.password,
const updated = await customerService.retrieve(customer._id) })
const data = await customerService.decorate(customer) const data = await customerService.decorate(customer)
res.status(200).json({ customer: data }) res.status(200).json({ customer: data })
} catch (error) { } catch (error) {
@@ -16,10 +16,12 @@ export default async (req, res) => {
try { try {
const customerService = req.scope.resolve("customerService") const customerService = req.scope.resolve("customerService")
await customerService.update(id, value) const customer = await customerService.update(id, value)
const data = await customerService.decorate(
const customer = await customerService.retrieve(id) customer,
const data = await customerService.decorate(customer) ["email", "first_name", "last_name", "shipping_addresses"],
["orders"]
)
res.status(200).json({ customer: data }) res.status(200).json({ customer: data })
} catch (err) { } catch (err) {
throw err throw err
@@ -1,26 +0,0 @@
import { Validator, MedusaError } from "medusa-core-utils"
export default async (req, res) => {
const { id } = req.params
const schema = Validator.object().keys({
password: Validator.string().required(),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const customerService = req.scope.resolve("customerService")
await customerService.update(id, value)
const customer = await customerService.retrieve(id)
const data = await customerService.decorate(customer)
res.status(200).json({ customer: data })
} catch (err) {
throw err
}
}
+4
View File
@@ -1,3 +1,4 @@
import bodyParser from "body-parser"
import { getConfigFile } from "medusa-core-utils" import { getConfigFile } from "medusa-core-utils"
import routes from "../api" import routes from "../api"
@@ -5,6 +6,9 @@ import routes from "../api"
export default async ({ app, rootDirectory, container }) => { export default async ({ app, rootDirectory, container }) => {
const { configModule } = getConfigFile(rootDirectory, `medusa-config`) const { configModule } = getConfigFile(rootDirectory, `medusa-config`)
const config = (configModule && configModule.projectConfig) || {} const config = (configModule && configModule.projectConfig) || {}
app.use(bodyParser.json())
app.use("/", routes(container, config)) app.use("/", routes(container, config))
return app return app
} }
-2
View File
@@ -1,5 +1,4 @@
import express from "express" import express from "express"
import bodyParser from "body-parser"
import session from "client-sessions" import session from "client-sessions"
import cookieParser from "cookie-parser" import cookieParser from "cookie-parser"
import morgan from "morgan" import morgan from "morgan"
@@ -14,7 +13,6 @@ export default async ({ app }) => {
}) })
) )
app.use(cookieParser()) app.use(cookieParser())
app.use(bodyParser.json())
app.use( app.use(
session({ session({
cookieName: "session", cookieName: "session",
+57 -8
View File
@@ -539,10 +539,7 @@ class CartService extends BaseService {
const cart = await this.retrieve(cartId) const cart = await this.retrieve(cartId)
const { value, error } = Validator.address().validate(address) const { value, error } = Validator.address().validate(address)
if (error) { if (error) {
throw new MedusaError( throw new MedusaError(MedusaError.Types.INVALID_DATA, error.message)
MedusaError.Types.INVALID_DATA,
"The address is not valid"
)
} }
address.country_code = address.country_code.toUpperCase() address.country_code = address.country_code.toUpperCase()
@@ -573,10 +570,7 @@ class CartService extends BaseService {
const cart = await this.retrieve(cartId) const cart = await this.retrieve(cartId)
const { value, error } = Validator.address().validate(address) const { value, error } = Validator.address().validate(address)
if (error) { if (error) {
throw new MedusaError( throw new MedusaError(MedusaError.Types.INVALID_DATA, error.message)
MedusaError.Types.INVALID_DATA,
"The address is not valid"
)
} }
address.country_code = address.country_code.toUpperCase() address.country_code = address.country_code.toUpperCase()
@@ -867,6 +861,61 @@ class CartService extends BaseService {
}) })
} }
async deletePaymentSession(cartId, providerId) {
const cart = await this.retrieve(cartId)
if (cart.payment_sessions) {
const session = cart.payment_sessions.find(
s => s.provider_id === providerId
)
if (session) {
// Delete the session with the provider
await this.paymentProviderService_.deleteSession(session)
const selector = {
$pull: { payment_sessions: { provider_id: providerId } },
}
if (
cart.payment_method &&
cart.payment_method.provider_id === providerId
) {
selector["$set"] = { payment_method: null }
}
return this.cartModel_
.updateOne({ _id: cart._id }, selector)
.then(result => {
// Notify subscribers
this.eventBus_.emit(CartService.Events.UPDATED, result)
return result
})
}
}
return cart
}
async updatePaymentSession(cartId, providerId, session) {
const cart = await this.retrieve(cartId)
return this.cartModel_
.updateOne(
{
_id: cart._id,
"payment_sessions.provider_id": providerId,
},
{
$set: { "payment_sessions.$": session },
}
)
.then(result => {
// Notify subscribers
this.eventBus_.emit(CartService.Events.UPDATED, result)
return result
})
}
/** /**
* Adds the shipping method to the list of shipping methods associated with * Adds the shipping method to the list of shipping methods associated with
* the cart. Shipping Methods are the ways that an order is shipped, whereas a * the cart. Shipping Methods are the ways that an order is shipped, whereas a
+4 -2
View File
@@ -1,4 +1,4 @@
import mongoose from "mongoose" import jwt from "jsonwebtoken"
import bcrypt from "bcrypt" import bcrypt from "bcrypt"
import _ from "lodash" import _ from "lodash"
import { Validator, MedusaError } from "medusa-core-utils" import { Validator, MedusaError } from "medusa-core-utils"
@@ -102,6 +102,8 @@ class CustomerService extends BaseService {
// Notify subscribers // Notify subscribers
this.eventBus_.emit(CustomerService.Events.PASSWORD_RESET, { this.eventBus_.emit(CustomerService.Events.PASSWORD_RESET, {
email: customer.email, email: customer.email,
first_name: customer.first_name,
last_name: customer.last_name,
token, token,
}) })
return token return token
@@ -328,7 +330,7 @@ class CustomerService extends BaseService {
* @param {string[]} expandFields - fields to expand. * @param {string[]} expandFields - fields to expand.
* @return {Customer} return the decorated customer. * @return {Customer} return the decorated customer.
*/ */
async decorate(customer, fields, expandFields = []) { async decorate(customer, fields = [], expandFields = []) {
const requiredFields = ["_id", "metadata"] const requiredFields = ["_id", "metadata"]
const decorated = _.pick(customer, fields.concat(requiredFields)) const decorated = _.pick(customer, fields.concat(requiredFields))
@@ -32,6 +32,11 @@ class PaymentProviderService {
return provider.updatePayment(paymentSession.data, cart) return provider.updatePayment(paymentSession.data, cart)
} }
deleteSession(paymentSession) {
const provider = this.retrieveProvider(paymentSession.provider_id)
return provider.deletePayment(paymentSession.data)
}
/** /**
* Finds a provider given an id * Finds a provider given an id
* @param {string} providerId - the id of the provider to get * @param {string} providerId - the id of the provider to get