Klarna clean up

This commit is contained in:
Sebastian Rindom committed 2020-07-11 16:32:05 +02:00
commit 419d5db340
26 files changed
+295 -103

No files matched your search

@@ -5,6 +5,7 @@ export default async (req, res) => {
try { try {
const cartService = req.scope.resolve("cartService") const cartService = req.scope.resolve("cartService")
const klarnaProviderService = req.scope.resolve("pp_klarna") const klarnaProviderService = req.scope.resolve("pp_klarna")
const shippingProfileService = req.scope.resolve("shippingProfileService")
const cart = await cartService.retrieve(merchant_data) const cart = await cartService.retrieve(merchant_data)
@@ -23,6 +24,12 @@ export default async (req, res) => {
await cartService.updateBillingAddress(cart._id, updatedAddress) await cartService.updateBillingAddress(cart._id, updatedAddress)
await cartService.updateEmail(cart._id, shipping_address.email) await cartService.updateEmail(cart._id, shipping_address.email)
const shippingOptions = await shippingProfileService.fetchCartOptions(cart)
if (shippingOptions.length === 1) {
const option = shippingOptions[0]
await cartService.addShippingMethod(cart._id, option._id, option.data)
}
// Fetch and return updated Klarna order // Fetch and return updated Klarna order
const updatedCart = await cartService.retrieve(cart._id) const updatedCart = await cartService.retrieve(cart._id)
const order = await klarnaProviderService.cartToKlarnaOrder(updatedCart) const order = await klarnaProviderService.cartToKlarnaOrder(updatedCart)
@@ -16,7 +16,7 @@ export default async (req, res) => {
try { try {
const order = await orderService.retrieveByCartId(cartId) const order = await orderService.retrieveByCartId(cartId)
await klarnaProviderService.acknowledgeOrder(klarnaOrder.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 === MeudsaError.Types.NOT_FOUND) {
let cart = await cartService.retrieve(cartId) let cart = await cartService.retrieve(cartId)
@@ -24,7 +24,7 @@ export default async (req, res) => {
cart = await cartService.setPaymentMethod(cart._id, method) cart = await cartService.setPaymentMethod(cart._id, method)
const order = await orderService.createFromCart(cart) const order = await orderService.createFromCart(cart)
await klarnaProviderService.acknowledgeOrder(klarnaOrder.id, order._id) await klarnaProviderService.acknowledgeOrder(klarnaOrder.order_id, order._id)
} }
} }
@@ -10,14 +10,11 @@ export default async (req, res) => {
const cart = await cartService.retrieve(merchant_data) const cart = await cartService.retrieve(merchant_data)
const shippingOptions = await shippingProfileService.fetchCartOptions(cart) const shippingOptions = await shippingProfileService.fetchCartOptions(cart)
const option = shippingOptions.find(({ _id }) => _id === selected_shipping_option.id) const option = shippingOptions.find(({ _id }) => _id.equals(selected_shipping_option.id))
if (option) { if (option) {
await cartService.addShippingMethod(cart._id, option._id, option.data) const newCart = await cartService.addShippingMethod(cart._id, option._id, option.data)
const order = await klarnaProviderService.cartToKlarnaOrder(newCart)
// Fetch and return updated Klarna order
const updatedCart = await cartService.retrieve(cart._id)
const order = klarnaProviderService.cartToKlarnaOrder(updatedCart)
res.json(order) res.json(order)
} else { } else {
res.sendStatus(400) res.sendStatus(400)
@@ -5,7 +5,10 @@ import { PaymentService } from "medusa-interfaces"
class KlarnaProviderService extends PaymentService { class KlarnaProviderService extends PaymentService {
static identifier = "klarna" static identifier = "klarna"
constructor({ shippingProfileService, totalsService, regionService }, options) { constructor(
{ shippingProfileService, totalsService, regionService },
options
) {
super() super()
this.options_ = options this.options_ = options
@@ -22,7 +25,8 @@ class KlarnaProviderService extends PaymentService {
this.klarnaOrderManagementUrl_ = "/ordermanagement/v1/orders" this.klarnaOrderManagementUrl_ = "/ordermanagement/v1/orders"
this.backendUrl_ = process.env.BACKEND_URL || "https://58721b1f44d9.ngrok.io" this.backendUrl_ =
process.env.BACKEND_URL || "https://2fe4e28015f5.ngrok.io"
this.totalsService_ = totalsService this.totalsService_ = totalsService
@@ -98,6 +102,7 @@ class KlarnaProviderService extends PaymentService {
order_lines.push({ order_lines.push({
name: `${shippingMethod.name}`, name: `${shippingMethod.name}`,
quantity: 1, quantity: 1,
type: "shipping_fee",
unit_price: price * (1 + taxRate) * 100, unit_price: price * (1 + taxRate) * 100,
tax_rate: taxRate * 10000, tax_rate: taxRate * 10000,
total_amount: price * (1 + taxRate) * 100, total_amount: price * (1 + taxRate) * 100,
@@ -141,8 +146,8 @@ class KlarnaProviderService extends PaymentService {
order.purchase_country = "SE" order.purchase_country = "SE"
} }
order.order_amount = await this.totalsService_.getTotal(cart) * 100 order.order_amount = (await this.totalsService_.getTotal(cart)) * 100
order.order_tax_amount = await this.totalsService_.getTaxTotal(cart) * 100 order.order_tax_amount = (await this.totalsService_.getTaxTotal(cart)) * 100
// TODO: Check if currency matches ISO // TODO: Check if currency matches ISO
order.purchase_currency = currency_code order.purchase_currency = currency_code
@@ -155,22 +160,14 @@ class KlarnaProviderService extends PaymentService {
address_update: `${this.backendUrl_}/klarna/address`, address_update: `${this.backendUrl_}/klarna/address`,
} }
if (cart.shipping_address && cart.shipping_address.first_name) {
const shippingOptions = await this.shippingProfileService_.fetchCartOptions(cart) const shippingOptions = await this.shippingProfileService_.fetchCartOptions(
cart
)
// If the cart does not have shipping methods yet, preselect one from // If the cart does not have shipping methods yet, preselect one from
// shipping_options and set the selected shipping method // shipping_options and set the selected shipping method
if (!cart.shipping_methods.length) { if (cart.shipping_methods.length) {
const shipping_method = shippingOptions[0]
order.selected_shipping_option = {
id: shipping_method._id,
name: shipping_method.name,
price: shipping_method.price * (1 + tax_rate) * 100,
tax_amount: shipping_method.price * tax_rate * 100,
// Medusa tax rate of e.g. 0.25 (25%) needs to be 2500 in Klarna
tax_rate: tax_rate * 10000,
}
} else {
const shipping_method = cart.shipping_methods[0] const shipping_method = cart.shipping_methods[0]
order.selected_shipping_option = { order.selected_shipping_option = {
id: shipping_method._id, id: shipping_method._id,
@@ -189,10 +186,9 @@ class KlarnaProviderService extends PaymentService {
price: so.price * (1 + tax_rate) * 100, price: so.price * (1 + tax_rate) * 100,
tax_amount: so.price * tax_rate * 100, tax_amount: so.price * tax_rate * 100,
tax_rate: tax_rate * 10000, tax_rate: tax_rate * 10000,
preselected: preselected: shippingOptions.length === 1
cart.shipping_methods[0] &&
`${cart.shipping_methods[0].provider_id}` === `${so.provider_id}`,
})) }))
}
return order return order
} }
@@ -204,11 +200,15 @@ class KlarnaProviderService extends PaymentService {
*/ */
async getStatus(paymentData) { async getStatus(paymentData) {
try { try {
const { id } = paymentData const { order_id } = paymentData
const order = await this.klarna_.get(`${this.klarnaOrderUrl_}/${id}`) const { data: order } = await this.klarna_.get(
// TODO: Klarna docs does not provide a list of statues, so we need to `${this.klarnaOrderUrl_}/${order_id}`
// play around our selves to figure it out )
let status = "initial" let status = "initial"
if (order.status === "checkout_complete") {
status = "authorized"
}
return status return status
} catch (error) { } catch (error) {
throw error throw error
@@ -224,7 +224,8 @@ class KlarnaProviderService extends PaymentService {
async createPayment(cart) { async createPayment(cart) {
try { try {
const order = await this.cartToKlarnaOrder(cart) const order = await this.cartToKlarnaOrder(cart)
return this.klarna_.post(this.klarnaOrderUrl_, order) return this.klarna_
.post(this.klarnaOrderUrl_, order)
.then(({ data }) => data) .then(({ data }) => data)
} catch (error) { } catch (error) {
throw error throw error
@@ -236,10 +237,10 @@ class KlarnaProviderService extends PaymentService {
* @param {string} cart - the cart to retrieve order for * @param {string} cart - the cart to retrieve order for
* @returns {Object} Klarna order * @returns {Object} Klarna order
*/ */
async retrievePayment(cart) { async retrievePayment(paymentData) {
try { try {
const { data } = cart.payment_method return this.klarna_.get(`${this.klarnaOrderUrl_}/${paymentData.order_id}`)
return this.klarna_.get(`${this.klarnaOrderUrl_}/${data.id}`) .then(({ data }) => data)
} catch (error) { } catch (error) {
throw error throw error
} }
@@ -306,8 +307,9 @@ class KlarnaProviderService extends PaymentService {
*/ */
async updatePayment(paymentData, cart) { async updatePayment(paymentData, cart) {
try { try {
const order = await this.cartToKlarnaOrder(cart) const order = await this.cartToKlarnaOrder(cart, true)
return this.klarna_.post(`${this.klarnaOrderUrl_}/${paymentData.order_id}`, order) return this.klarna_
.post(`${this.klarnaOrderUrl_}/${paymentData.order_id}`, order)
.then(({ data }) => data) .then(({ data }) => data)
} catch (error) { } catch (error) {
throw error throw error
@@ -321,17 +323,19 @@ class KlarnaProviderService extends PaymentService {
*/ */
async capturePayment(paymentData) { async capturePayment(paymentData) {
try { try {
const { id } = paymentData const { order_id } = paymentData
const orderData = await this.klarna_.get(`${this.klarnaOrderUrl_}/${id}`) const orderData = await this.klarna_.get(
`${this.klarnaOrderUrl_}/${order_id}`
)
const { order_amount } = orderData.order const { order_amount } = orderData.order
await this.klarna_.post( await this.klarna_.post(
`${this.klarnaOrderManagementUrl_}/${id}/captures`, `${this.klarnaOrderManagementUrl_}/${order_id}/captures`,
{ {
captured_amount: order_amount, captured_amount: order_amount,
} }
) )
return id return order_id
} catch (error) { } catch (error) {
throw error throw error
} }
@@ -344,14 +348,14 @@ class KlarnaProviderService extends PaymentService {
*/ */
async refundPayment(paymentData, amount) { async refundPayment(paymentData, amount) {
try { try {
const { id } = paymentData const { order_id } = paymentData
await this.klarna_.post( await this.klarna_.post(
`${this.klarnaOrderManagementUrl_}/${id}/refunds`, `${this.klarnaOrderManagementUrl_}/${order_id}/refunds`,
{ {
refunded_amount: amount, refunded_amount: amount,
} }
) )
return id return order_id
} catch (error) { } catch (error) {
throw error throw error
} }
@@ -364,9 +368,9 @@ class KlarnaProviderService extends PaymentService {
*/ */
async cancelPayment(paymentData) { async cancelPayment(paymentData) {
try { try {
const { id } = paymentData const { order_id } = paymentData
await this.klarna_.post(`${this.klarnaOrderUrl_}/${id}/cancel`) await this.klarna_.post(`${this.klarnaOrderUrl_}/${order_id}/cancel`)
return id return order_id
} catch (error) { } catch (error) {
throw error throw error
} }
@@ -105,7 +105,7 @@ class StripeProviderService extends PaymentService {
amount: amount * 100, // Stripe amount is in cents amount: amount * 100, // Stripe amount is in cents
currency: currency_code, currency: currency_code,
capture_method: "manual", capture_method: "manual",
metadata: { cart_id: cart._id }, metadata: { cart_id: `${cart._id}` },
}) })
return paymentIntent return paymentIntent
@@ -113,12 +113,11 @@ class StripeProviderService extends PaymentService {
/** /**
* Retrieves Stripe PaymentIntent. * Retrieves Stripe PaymentIntent.
* @param {string} cart - the cart to retrieve payment intent for * @param {object} data - the data of the payment to retrieve
* @returns {Object} Stripe PaymentIntent * @returns {Object} Stripe PaymentIntent
*/ */
async retrievePayment(cart) { async retrievePayment(data) {
try { try {
const { data } = cart.payment_method
return this.stripe_.paymentIntents.retrieve(data.id) return this.stripe_.paymentIntents.retrieve(data.id)
} catch (error) { } catch (error) {
throw error throw error
@@ -14,7 +14,6 @@ class EconomicService extends BaseService {
* customer_number_dk: 012 * customer_number_dk: 012
* customer_number_eu: 345 * customer_number_eu: 345
* customer_number_world: 678, * customer_number_world: 678,
* vat_number: 42,
* unit_number: 42, * unit_number: 42,
* payment_terms_number: 42, * payment_terms_number: 42,
* shipping_product_number: 42, * shipping_product_number: 42,
@@ -10,7 +10,7 @@ class SendGridService extends BaseService {
* from: Medusa <hello@medusa.example>, * from: Medusa <hello@medusa.example>,
* order_placed_template: 01234, * order_placed_template: 01234,
* order_updated_template: 56789, * order_updated_template: 56789,
* order_updated_cancelled_template: 4242, * order_cancelled_template: 4242,
* user_password_reset_template: 0000, * user_password_reset_template: 0000,
* customer_password_reset_template: 1111, * customer_password_reset_template: 1111,
* } * }
@@ -42,6 +42,9 @@ class SendGridService extends BaseService {
case "order.cancelled": case "order.cancelled":
templateId = this.options_.order_cancelled_template templateId = this.options_.order_cancelled_template
break break
case "order.completed":
templateId = this.options_.order_completed_template
break
case "user.password_reset": case "user.password_reset":
templateId = this.options_.user_password_reset_template templateId = this.options_.user_password_reset_template
break break
@@ -55,7 +58,7 @@ class SendGridService extends BaseService {
try { try {
return SendGrid.send({ return SendGrid.send({
template_id: templateId, template_id: templateId,
from: options.from, from: this.options_.from,
to: order.email, to: order.email,
dynamic_template_data: order, dynamic_template_data: order,
}) })
@@ -12,6 +12,10 @@ class OrderSubscriber {
await this.sendgridService_.transactionalEmail("order.cancelled", order) await this.sendgridService_.transactionalEmail("order.cancelled", order)
}) })
this.eventBus_.subscribe("order.completed", async (order) => {
await this.sendgridService_.transactionalEmail("order.completed", order)
})
this.eventBus_.subscribe("order.updated", async (order) => { this.eventBus_.subscribe("order.updated", async (order) => {
await this.sendgridService_.transactionalEmail("order.updated", order) await this.sendgridService_.transactionalEmail("order.updated", order)
}) })
@@ -16,7 +16,7 @@ export default () => {
break break
case MedusaError.Types.DB_ERROR: case MedusaError.Types.DB_ERROR:
statusCode = 500 statusCode = 500
logger.error(err) console.log(err)
break break
default: default:
break break
@@ -0,0 +1,12 @@
export default async (req, res) => {
const { id } = req.params
try {
const orderService = req.scope.resolve("orderService")
const order = await orderService.completeOrder(id)
res.json({ order })
} catch (error) {
console.log(error)
throw error
}
}
@@ -3,7 +3,9 @@ export default async (req, res) => {
try { try {
const orderService = req.scope.resolve("orderService") const orderService = req.scope.resolve("orderService")
const order = await orderService.retrieve(id)
let order = await orderService.retrieve(id)
order = await orderService.decorate(order, [], ["region"])
res.json({ order }) res.json({ order })
} catch (error) { } catch (error) {
@@ -11,6 +11,10 @@ export default app => {
route.post("/", middlewares.wrap(require("./create-order").default)) route.post("/", middlewares.wrap(require("./create-order").default))
route.post("/:id", middlewares.wrap(require("./update-order").default)) route.post("/:id", middlewares.wrap(require("./update-order").default))
route.post(
"/:id/complete",
middlewares.wrap(require("./complete-order").default)
)
route.post( route.post(
"/:id/capture", "/:id/capture",
@@ -0,0 +1,16 @@
import { Validator, MedusaError } from "medusa-core-utils"
export default async (req, res) => {
const { id, code } = req.params
try {
const cartService = req.scope.resolve("cartService")
let cart = await cartService.removeDiscount(id, code)
cart = await cartService.decorate(cart, [], ["region"])
res.status(200).json({ cart })
} catch (err) {
throw err
}
}
@@ -25,6 +25,11 @@ export default app => {
middlewares.wrap(require("./delete-line-item").default) middlewares.wrap(require("./delete-line-item").default)
) )
route.delete(
"/:id/discounts/:code",
middlewares.wrap(require("./delete-discount").default)
)
// Payment sessions // Payment sessions
route.post( route.post(
"/:id/payment-sessions", "/:id/payment-sessions",
@@ -51,7 +51,7 @@ export default async (req, res) => {
if (value.discounts && value.discounts.length) { if (value.discounts && value.discounts.length) {
await Promise.all( await Promise.all(
value.discounts.map(async ({ code }) => value.discounts.map(async ({ code }) =>
cartService.applyPromoCode(id, code) cartService.applyDiscount(id, code)
) )
) )
} }
@@ -4,7 +4,6 @@ export default async (req, res) => {
const { id, line_id } = req.params const { id, line_id } = req.params
const schema = Validator.object().keys({ const schema = Validator.object().keys({
variant_id: Validator.objectId().required(),
quantity: Validator.number().required(), quantity: Validator.number().required(),
}) })
@@ -16,15 +15,23 @@ export default async (req, res) => {
try { try {
const lineItemService = req.scope.resolve("lineItemService") const lineItemService = req.scope.resolve("lineItemService")
const cartService = req.scope.resolve("cartService") const cartService = req.scope.resolve("cartService")
let cart = await cartService.retrieve(id)
let cart
if (value.quantity === 0) {
cart = await cartService.removeLineItem(id, line_id)
} else {
cart = await cartService.retrieve(id)
const existing = cart.items.find(i => i._id.equals(line_id))
const lineItem = await lineItemService.generate( const lineItem = await lineItemService.generate(
value.variant_id, existing.content.variant._id,
value.quantity, cart.region_id,
cart.region_id value.quantity
) )
cart = await cartService.updateLineItem(cart._id, line_id, lineItem) cart = await cartService.updateLineItem(cart._id, line_id, lineItem)
}
cart = await cartService.decorate(cart, [], ["region"]) cart = await cartService.decorate(cart, [], ["region"])
res.status(200).json({ cart }) res.status(200).json({ cart })
@@ -5,6 +5,7 @@ export default async (req, res) => {
const schema = Validator.object().keys({ const schema = Validator.object().keys({
provider_id: Validator.string().required(), provider_id: Validator.string().required(),
data: Validator.object().optional(),
}) })
const { value, error } = schema.validate(req.body) const { value, error } = schema.validate(req.body)
@@ -15,13 +16,7 @@ export default async (req, res) => {
try { try {
const cartService = req.scope.resolve("cartService") const cartService = req.scope.resolve("cartService")
const session = await cartService.retrievePaymentSession( let cart = await cartService.setPaymentMethod(id, value)
id,
value.provider_id
)
await cartService.setPaymentMethod(id, session)
let cart = await cartService.retrieve(id)
cart = await cartService.decorate(cart, [], ["region"]) cart = await cartService.decorate(cart, [], ["region"])
res.status(200).json({ cart }) res.status(200).json({ cart })
@@ -16,6 +16,8 @@ export default async (req, res) => {
const cart = await cartService.retrieve(value.cartId) const cart = await cartService.retrieve(value.cartId)
let order = await orderService.createFromCart(cart) let order = await orderService.createFromCart(cart)
order = await orderService.retrieveByCartId(value.cartId)
order = await orderService.decorate(order, [ order = await orderService.decorate(order, [
"status", "status",
"fulfillment_status", "fulfillment_status",
@@ -4,7 +4,9 @@ export default async (req, res) => {
try { try {
const orderService = req.scope.resolve("orderService") const orderService = req.scope.resolve("orderService")
let order = await orderService.retrieve(id) let order = await orderService.retrieve(id)
order = await orderService.decorate(order, [ order = await orderService.decorate(
order,
[
"status", "status",
"fulfillment_status", "fulfillment_status",
"payment_status", "payment_status",
@@ -18,7 +20,9 @@ export default async (req, res) => {
"payment_method", "payment_method",
"shipping_methods", "shipping_methods",
"metadata", "metadata",
]) ],
["region"]
)
res.json({ order }) res.json({ order })
} catch (error) { } catch (error) {
+4
View File
@@ -4,6 +4,7 @@ import mongooseLoader from "./mongoose"
import apiLoader from "./api" import apiLoader from "./api"
import modelsLoader from "./models" import modelsLoader from "./models"
import servicesLoader from "./services" import servicesLoader from "./services"
import subscribersLoader from "./subscribers"
import passportLoader from "./passport" import passportLoader from "./passport"
import pluginsLoader from "./plugins" import pluginsLoader from "./plugins"
import defaultsLoader from "./defaults" import defaultsLoader from "./defaults"
@@ -37,6 +38,9 @@ export default async ({ directory: rootDirectory, expressApp }) => {
await servicesLoader({ container }) await servicesLoader({ container })
Logger.info("Services initialized") Logger.info("Services initialized")
await subscribersLoader({ container })
Logger.info("Subscribers initialized")
const dbConnection = await mongooseLoader({ container }) const dbConnection = await mongooseLoader({ container })
Logger.info("MongoDB Intialized") Logger.info("MongoDB Intialized")
@@ -0,0 +1,22 @@
import glob from "glob"
import path from "path"
import { asFunction } from "awilix"
/**
* Registers all subscribers in the subscribers directory
*/
export default ({ container }) => {
const isTest = process.env.NODE_ENV === "test"
const corePath = isTest
? "../subscribers/__mocks__/*.js"
: "../subscribers/*.js"
const coreFull = path.join(__dirname, corePath)
const core = glob.sync(coreFull, { cwd: __dirname })
core.forEach(fn => {
const loaded = require(fn).default
container.build(asFunction(cradle => new loaded(cradle)).singleton())
})
}
+1
View File
@@ -22,6 +22,7 @@ class OrderModel extends BaseModel {
billing_address: { type: AddressSchema, required: true }, billing_address: { type: AddressSchema, required: true },
shipping_address: { type: AddressSchema, required: true }, shipping_address: { type: AddressSchema, required: true },
items: { type: [LineItemSchema], required: true }, items: { type: [LineItemSchema], required: true },
currency_code: { type: String, required: true },
region_id: { type: String, required: true }, region_id: { type: String, required: true },
discounts: { type: [DiscountSchema], default: [] }, discounts: { type: [DiscountSchema], default: [] },
customer_id: { type: String }, customer_id: { type: String },
+17 -17
View File
@@ -303,7 +303,7 @@ class CartService extends BaseService {
const validatedLineItem = this.lineItemService_.validate(lineItem) const validatedLineItem = this.lineItemService_.validate(lineItem)
const cart = await this.retrieve(cartId) const cart = await this.retrieve(cartId)
const currentItem = cart.items.find(line => const currentItem = cart.items.find(line =>
_.isEqual(line.content, validatedLineItem.content) this.lineItemService_.isEqual(line, validatedLineItem)
) )
// If content matches one of the line items currently in the cart we can // If content matches one of the line items currently in the cart we can
@@ -393,7 +393,7 @@ class CartService extends BaseService {
} }
// Ensure that the line item exists in the cart // Ensure that the line item exists in the cart
const lineItemExists = cart.items.find(i => i._id === lineItemId) const lineItemExists = cart.items.find(i => i._id.equals(lineItemId))
if (!lineItemExists) { if (!lineItemExists) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.INVALID_DATA, MedusaError.Types.INVALID_DATA,
@@ -519,6 +519,8 @@ class CartService extends BaseService {
) )
} }
address.country_code = address.country_code.toUpperCase()
return this.cartModel_ return this.cartModel_
.updateOne( .updateOne(
{ {
@@ -551,6 +553,8 @@ class CartService extends BaseService {
) )
} }
address.country_code = address.country_code.toUpperCase()
const region = await this.regionService_.retrieve(cart.region_id) const region = await this.regionService_.retrieve(cart.region_id)
if (!region.countries.includes(address.country_code.toUpperCase())) { if (!region.countries.includes(address.country_code.toUpperCase())) {
throw new MedusaError( throw new MedusaError(
@@ -681,6 +685,16 @@ class CartService extends BaseService {
} }
} }
async removeDiscount(cartId, discountCode) {
const cart = await this.retrieve(cartId)
return this.cartModel_.updateOne(
{ _id: cart._id },
{
$pull: { discounts: { code: discountCode } },
}
)
}
/** /**
* A payment method represents a way for the customer to pay. The payment * A payment method represents a way for the customer to pay. The payment
* method will typically come from one of the payment sessions. * method will typically come from one of the payment sessions.
@@ -736,20 +750,6 @@ class CartService extends BaseService {
) )
} }
// The provider service will be able to perform operations on the
// session we are trying to set as the payment method.
const provider = this.paymentProviderService_.retrieveProvider(
paymentMethod.provider_id
)
const status = await provider.getStatus(paymentMethod.data)
if (!(status === "authorized" || status === "succeeded")) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
`The payment method was not authorized`
)
}
// At this point we can register the payment method. // At this point we can register the payment method.
return this.cartModel_ return this.cartModel_
.updateOne( .updateOne(
@@ -883,7 +883,7 @@ class CartService extends BaseService {
// that has the same profile as the selected shipping method. // that has the same profile as the selected shipping method.
let exists = false let exists = false
const newMethods = shipping_methods.map(sm => { const newMethods = shipping_methods.map(sm => {
if (sm.profile_id === option.profile_id) { if (option.profile_id.equals(sm.profile_id)) {
exists = true exists = true
return option return option
} }
+22
View File
@@ -117,6 +117,28 @@ class LineItemService extends BaseService {
}, },
} }
} }
isEqual(line, match) {
if (Array.isArray(line.content)) {
if (
Array.isArray(match.content) &&
match.content.length === line.content.length
) {
return line.content.every(
(c, index) =>
c.variant._id.equals(match[index].variant._id) &&
c.quantity === match[index].quantity
)
}
} else if (!Array.isArray(match.content)) {
return (
line.content.variant._id.equals(match.content.variant._id) &&
line.content.quantity === match.content.quantity
)
}
return false
}
} }
export default LineItemService export default LineItemService
+80 -14
View File
@@ -7,6 +7,7 @@ class OrderService extends BaseService {
PLACED: "order.placed", PLACED: "order.placed",
UPDATED: "order.updated", UPDATED: "order.updated",
CANCELLED: "order.cancelled", CANCELLED: "order.cancelled",
COMPLETED: "order.completed",
} }
constructor({ constructor({
@@ -16,6 +17,7 @@ class OrderService extends BaseService {
fulfillmentProviderService, fulfillmentProviderService,
lineItemService, lineItemService,
totalsService, totalsService,
regionService,
eventBusService, eventBusService,
}) { }) {
super() super()
@@ -38,6 +40,9 @@ class OrderService extends BaseService {
/** @private @const {TotalsService} */ /** @private @const {TotalsService} */
this.totalsService_ = totalsService this.totalsService_ = totalsService
/** @private @const {RegionService} */
this.regionService_ = regionService
/** @private @const {EventBus} */ /** @private @const {EventBus} */
this.eventBus_ = eventBusService this.eventBus_ = eventBusService
} }
@@ -160,7 +165,7 @@ class OrderService extends BaseService {
*/ */
async retrieveByCartId(cartId) { async retrieveByCartId(cartId) {
const order = await this.orderModel_ const order = await this.orderModel_
.findOne({ metadata: { cart_id: cartId } }) .findOne({ cart_id: cartId })
.catch(err => { .catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message) throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
}) })
@@ -200,6 +205,37 @@ class OrderService extends BaseService {
return this.orderModel_.find(selector) return this.orderModel_.find(selector)
} }
/**
* @param {string} orderId - id of the order to complete
* @return {Promise} the result of the find operation
*/
async completeOrder(orderId) {
const order = await this.retrieve(orderId)
this.orderModel_
.updateOne(
{ _id: order._id },
{
$set: { status: "completed" },
}
)
.then(async result => {
const completeOrderJob = await this.eventBus_.emit(
OrderService.Events.COMPLETED,
result
)
return completeOrderJob
.finished()
.then(async () => {
return this.retrieve(order._id)
})
.catch(error => {
console.log(error)
throw error
})
})
}
/** /**
* Creates an order from a cart * Creates an order from a cart
* @param {object} order - the order to create * @param {object} order - the order to create
@@ -231,21 +267,41 @@ class OrderService extends BaseService {
const { payment_method } = cart const { payment_method } = cart
const paymentProvider = await this.paymentProviderService_.retrieveProvider( let paymentSession = cart.payment_sessions.find(
payment_method.provider_id ps => ps.provider_id === payment_method.provider_id
) )
const paymentStatus = await paymentProvider.getStatus(payment_method.data)
// Throw if payment method does not exist
if (!paymentSession) {
throw new MedusaError(
MedusaError.Types.INVALID_ARGUMENT,
"Cart does not have an authorized payment session"
)
}
const region = await this.regionService_.retrieve(cart.region_id)
const paymentProvider = this.paymentProviderService_.retrieveProvider(
paymentSession.provider_id
)
const paymentStatus = await paymentProvider.getStatus(paymentSession.data)
// If payment status is not authorized, we throw // If payment status is not authorized, we throw
if (paymentStatus !== "authorized") { if (paymentStatus !== "authorized" && paymentStatus !== "succeeded") {
throw new MedusaError( throw new MedusaError(
MedusaError.types.INVALID_ARGUMENT, MedusaError.Types.INVALID_ARGUMENT,
"Payment method is not authorized" "Payment method is not authorized"
) )
} }
const paymentData = await paymentProvider.retrievePayment(
paymentSession.data
)
const o = { const o = {
payment_method: cart.payment_method, payment_method: {
provider_id: paymentSession.provider_id,
data: paymentData,
},
shipping_methods: cart.shipping_methods, shipping_methods: cart.shipping_methods,
items: cart.items, items: cart.items,
shipping_address: cart.shipping_address, shipping_address: cart.shipping_address,
@@ -254,13 +310,15 @@ class OrderService extends BaseService {
email: cart.email, email: cart.email,
customer_id: cart.customer_id, customer_id: cart.customer_id,
cart_id: cart._id, cart_id: cart._id,
currency_code: region.currency_code,
} }
const orderDocument = await this.orderModel_.create(o) const orderDocument = await this.orderModel_.create([o], {
// Commit transaction session: dbSession,
await dbSession.commitTransaction() })
// Emit and return // Emit and return
this.eventBus_emit(OrderService.Events.PLACED, orderDocument) this.eventBus_.emit(OrderService.Events.PLACED, orderDocument)
return orderDocument return orderDocument
}) })
} }
@@ -594,9 +652,17 @@ class OrderService extends BaseService {
* @return {Order} return the decorated order. * @return {Order} return the decorated order.
*/ */
async decorate(order, fields, expandFields = []) { async decorate(order, fields, expandFields = []) {
const requiredFields = ["_id", "metadata"] const o = order.toObject()
const decorated = _.pick(order, fields.concat(requiredFields)) o.shipping_total = await this.totalsService_.getShippingTotal(order)
return decorated o.discount_total = await this.totalsService_.getDiscountTotal(order)
o.tax_total = await this.totalsService_.getTaxTotal(order)
o.subtotal = await this.totalsService_.getSubtotal(order)
o.total = await this.totalsService_.getTotal(order)
o.created = order._id.getTimestamp()
if (expandFields.includes("region")) {
o.region = await this.regionService_.retrieve(order.region_id)
}
return o
} }
/** /**
+17
View File
@@ -0,0 +1,17 @@
class OrderSubscriber {
constructor({ paymentProviderService, eventBusService }) {
this.paymentProviderService_ = paymentProviderService
this.eventBus_ = eventBusService
this.eventBus_.subscribe("order.completed", async order => {
const paymentProvider = this.paymentProviderService_.retrieveProvider(
order.payment_method.provider_id
)
await paymentProvider.capturePayment(order._id)
})
}
}
export default OrderSubscriber