Adds dynamic coupon codes and segment plugin
This commit is contained in:
1 parent
ec6a538877
commit
3710f7f80a
41 files changed
+11784
-109
No files matched your search
@@ -37,20 +37,6 @@ class KlarnaProviderService extends PaymentService {
|
|||||||
|
|
||||||
async lineItemsToOrderLines_(cart, taxRate) {
|
async lineItemsToOrderLines_(cart, taxRate) {
|
||||||
let order_lines = []
|
let order_lines = []
|
||||||
// Find the discount, that is not free shipping
|
|
||||||
const discount = cart.discounts.find(
|
|
||||||
({ discount_rule }) => discount_rule.type !== "free_shipping"
|
|
||||||
)
|
|
||||||
|
|
||||||
let itemDiscounts = []
|
|
||||||
if (discount) {
|
|
||||||
// If the discount has an item specific allocation method,
|
|
||||||
// we need to fetch the discount for each item
|
|
||||||
itemDiscounts = await this.totalsService_.getAllocationItemDiscounts(
|
|
||||||
discount,
|
|
||||||
cart
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
cart.items.forEach((item) => {
|
cart.items.forEach((item) => {
|
||||||
// For bundles, we create an order line for each item in the bundle
|
// For bundles, we create an order line for each item in the bundle
|
||||||
@@ -62,8 +48,6 @@ class KlarnaProviderService extends PaymentService {
|
|||||||
order_lines.push({
|
order_lines.push({
|
||||||
name: item.title,
|
name: item.title,
|
||||||
unit_price: c.unit_price,
|
unit_price: c.unit_price,
|
||||||
// Medusa does not allow discount on bundles
|
|
||||||
total_discount_amount: 0,
|
|
||||||
quantity: c.quantity,
|
quantity: c.quantity,
|
||||||
tax_rate: taxRate * 10000,
|
tax_rate: taxRate * 10000,
|
||||||
total_amount,
|
total_amount,
|
||||||
@@ -71,17 +55,10 @@ class KlarnaProviderService extends PaymentService {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// Find the discount for current item and default to 0
|
|
||||||
const itemDiscount =
|
|
||||||
(itemDiscounts &&
|
|
||||||
itemDiscounts.find((el) => el.lineItem._id === item._id)) ||
|
|
||||||
0
|
|
||||||
|
|
||||||
// Withdraw discount from the total item amount
|
// Withdraw discount from the total item amount
|
||||||
const quantity = item.quantity
|
const quantity = item.quantity
|
||||||
const unit_price = item.content.unit_price * 100 * (taxRate + 1)
|
const unit_price = item.content.unit_price * 100 * (taxRate + 1)
|
||||||
const total_discount_amount = itemDiscount * (taxRate + 1) * 100
|
const total_amount = unit_price * quantity
|
||||||
const total_amount = unit_price * quantity - total_discount_amount
|
|
||||||
const total_tax_amount = total_amount * (taxRate / (1 + taxRate))
|
const total_tax_amount = total_amount * (taxRate / (1 + taxRate))
|
||||||
|
|
||||||
order_lines.push({
|
order_lines.push({
|
||||||
@@ -89,7 +66,6 @@ class KlarnaProviderService extends PaymentService {
|
|||||||
tax_rate: taxRate * 10000,
|
tax_rate: taxRate * 10000,
|
||||||
quantity,
|
quantity,
|
||||||
unit_price,
|
unit_price,
|
||||||
total_discount_amount,
|
|
||||||
total_amount,
|
total_amount,
|
||||||
total_tax_amount,
|
total_tax_amount,
|
||||||
})
|
})
|
||||||
@@ -127,6 +103,20 @@ class KlarnaProviderService extends PaymentService {
|
|||||||
|
|
||||||
order.order_lines = await this.lineItemsToOrderLines_(cart, tax_rate)
|
order.order_lines = await this.lineItemsToOrderLines_(cart, tax_rate)
|
||||||
|
|
||||||
|
const discount = (await this.totalsService_.getDiscountTotal(cart)) * 100
|
||||||
|
if (discount) {
|
||||||
|
order.order_lines.push({
|
||||||
|
name: `Discount`,
|
||||||
|
quantity: 1,
|
||||||
|
type: "discount",
|
||||||
|
unit_price: 0,
|
||||||
|
total_discount_amount: discount * (1 + tax_rate),
|
||||||
|
tax_rate: tax_rate * 10000,
|
||||||
|
total_amount: - discount * (1 + tax_rate),
|
||||||
|
total_tax_amount: - discount * tax_rate
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (!_.isEmpty(cart.billing_address)) {
|
if (!_.isEmpty(cart.billing_address)) {
|
||||||
order.billing_address = {
|
order.billing_address = {
|
||||||
email: cart.email,
|
email: cart.email,
|
||||||
@@ -145,7 +135,6 @@ class KlarnaProviderService extends PaymentService {
|
|||||||
// Defaults to Sweden
|
// Defaults to Sweden
|
||||||
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
|
||||||
@@ -179,7 +168,6 @@ class KlarnaProviderService extends PaymentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the cart does have shipping methods, set the selected shipping method
|
// If the cart does have shipping methods, set the selected shipping method
|
||||||
|
|
||||||
order.shipping_options = shippingOptions.map((so) => ({
|
order.shipping_options = shippingOptions.map((so) => ({
|
||||||
id: so._id,
|
id: so._id,
|
||||||
name: so.name,
|
name: so.name,
|
||||||
@@ -190,6 +178,7 @@ class KlarnaProviderService extends PaymentService {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(order)
|
||||||
return order
|
return order
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"plugins": [
|
||||||
|
"@babel/plugin-proposal-class-properties",
|
||||||
|
"@babel/plugin-transform-instanceof",
|
||||||
|
"@babel/plugin-transform-classes"
|
||||||
|
],
|
||||||
|
"presets": ["@babel/preset-env"],
|
||||||
|
"env": {
|
||||||
|
"test": {
|
||||||
|
"plugins": ["@babel/plugin-transform-runtime"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"plugins": ["prettier"],
|
||||||
|
"extends": ["prettier"],
|
||||||
|
"rules": {
|
||||||
|
"prettier/prettier": "error",
|
||||||
|
"semi": "error",
|
||||||
|
"no-unused-expressions": "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/lib
|
||||||
|
node_modules
|
||||||
|
.DS_store
|
||||||
|
.env*
|
||||||
|
/*.js
|
||||||
|
!index.js
|
||||||
|
!jest.config.js
|
||||||
|
|
||||||
|
/dist
|
||||||
|
|
||||||
|
/api
|
||||||
|
/services
|
||||||
|
/models
|
||||||
|
/subscribers
|
||||||
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/lib
|
||||||
|
node_modules
|
||||||
|
.DS_store
|
||||||
|
.env*
|
||||||
|
/*.js
|
||||||
|
!index.js
|
||||||
|
yarn.lock
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": false,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"trailingComma": "es5"
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
testEnvironment: "node",
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "medusa-plugin-discount-generator",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"license": "MIT",
|
||||||
|
"author": "Sebastian Rindom",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/medusajs/medusa",
|
||||||
|
"directory": "packages/medusa-plugin-discount-generator"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/cli": "^7.7.5",
|
||||||
|
"@babel/core": "^7.7.5",
|
||||||
|
"@babel/node": "^7.7.4",
|
||||||
|
"@babel/plugin-proposal-class-properties": "^7.7.4",
|
||||||
|
"@babel/plugin-transform-instanceof": "^7.8.3",
|
||||||
|
"@babel/plugin-transform-runtime": "^7.7.6",
|
||||||
|
"@babel/preset-env": "^7.7.5",
|
||||||
|
"@babel/register": "^7.7.4",
|
||||||
|
"@babel/runtime": "^7.9.6",
|
||||||
|
"client-sessions": "^0.8.0",
|
||||||
|
"cross-env": "^5.2.1",
|
||||||
|
"eslint": "^6.8.0",
|
||||||
|
"jest": "^25.5.2"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "babel src -d .",
|
||||||
|
"prepare": "cross-env NODE_ENV=production npm run build",
|
||||||
|
"watch": "babel -w src --out-dir . --ignore **/__tests__",
|
||||||
|
"test": "jest"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"body-parser": "^1.19.0",
|
||||||
|
"express": "^4.17.1",
|
||||||
|
"medusa-core-utils": "^0.1.27",
|
||||||
|
"randomatic": "^3.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { Router } from "express"
|
||||||
|
import bodyParser from "body-parser"
|
||||||
|
import { Validator, MedusaError } from "medusa-core-utils"
|
||||||
|
|
||||||
|
export default (container) => {
|
||||||
|
const route = Router()
|
||||||
|
|
||||||
|
route.post("/discount-code", bodyParser.json(), async (req, res) => {
|
||||||
|
const schema = Validator.object().keys({
|
||||||
|
discount_code: Validator.string().required(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { value, error } = schema.validate(req.body)
|
||||||
|
if (error) {
|
||||||
|
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||||
|
}
|
||||||
|
|
||||||
|
const discountGenerator = req.scope.resolve("discountGeneratorService")
|
||||||
|
const code = await discountGenerator.generateDiscount(value.discount_code)
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
code
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
return route
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import randomize from "randomatic"
|
||||||
|
import { BaseService } from "medusa-interfaces"
|
||||||
|
|
||||||
|
class DiscountGeneratorService extends BaseService {
|
||||||
|
constructor({ discountService }) {
|
||||||
|
super()
|
||||||
|
|
||||||
|
this.discountService_ = discountService
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateDiscount(dynamicDiscountCode) {
|
||||||
|
const discount = await this.discountService_.retrieveByCode(dynamicDiscountCode)
|
||||||
|
const data = {
|
||||||
|
code: randomize("A0", 10)
|
||||||
|
}
|
||||||
|
return this.discountService_.createDynamicCode(discount._id, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DiscountGeneratorService
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"plugins": [
|
||||||
|
"@babel/plugin-proposal-class-properties",
|
||||||
|
"@babel/plugin-transform-instanceof",
|
||||||
|
"@babel/plugin-transform-classes"
|
||||||
|
],
|
||||||
|
"presets": ["@babel/preset-env"],
|
||||||
|
"env": {
|
||||||
|
"test": {
|
||||||
|
"plugins": ["@babel/plugin-transform-runtime"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"plugins": ["prettier"],
|
||||||
|
"extends": ["prettier"],
|
||||||
|
"rules": {
|
||||||
|
"prettier/prettier": "error",
|
||||||
|
"semi": "error",
|
||||||
|
"no-unused-expressions": "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/lib
|
||||||
|
node_modules
|
||||||
|
.DS_store
|
||||||
|
.env*
|
||||||
|
/*.js
|
||||||
|
!index.js
|
||||||
|
!jest.config.js
|
||||||
|
|
||||||
|
/dist
|
||||||
|
|
||||||
|
/api
|
||||||
|
/services
|
||||||
|
/models
|
||||||
|
/subscribers
|
||||||
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/lib
|
||||||
|
node_modules
|
||||||
|
.DS_store
|
||||||
|
.env*
|
||||||
|
/*.js
|
||||||
|
!index.js
|
||||||
|
yarn.lock
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": false,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"trailingComma": "es5"
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
// noop
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
testEnvironment: "node",
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"name": "medusa-plugin-segment",
|
||||||
|
"version": "0.3.0",
|
||||||
|
"description": "Segment Analytics",
|
||||||
|
"main": "index.js",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/medusajs/medusa",
|
||||||
|
"directory": "packages/medusa-plugin-segment"
|
||||||
|
},
|
||||||
|
"author": "Sebastian Rindom",
|
||||||
|
"license": "AGPL-3.0-or-later",
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/cli": "^7.7.5",
|
||||||
|
"@babel/core": "^7.7.5",
|
||||||
|
"@babel/node": "^7.7.4",
|
||||||
|
"@babel/plugin-proposal-class-properties": "^7.7.4",
|
||||||
|
"@babel/plugin-transform-classes": "^7.9.5",
|
||||||
|
"@babel/plugin-transform-instanceof": "^7.8.3",
|
||||||
|
"@babel/plugin-transform-runtime": "^7.7.6",
|
||||||
|
"@babel/preset-env": "^7.7.5",
|
||||||
|
"@babel/register": "^7.7.4",
|
||||||
|
"@babel/runtime": "^7.9.6",
|
||||||
|
"cross-env": "^5.2.1",
|
||||||
|
"eslint": "^6.8.0",
|
||||||
|
"jest": "^25.5.2"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "babel src -d .",
|
||||||
|
"prepare": "cross-env NODE_ENV=production npm run build",
|
||||||
|
"watch": "babel -w src --out-dir . --ignore **/__tests__",
|
||||||
|
"test": "jest"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"analytics-node": "^3.4.0-beta.1",
|
||||||
|
"axios": "^0.19.2",
|
||||||
|
"body-parser": "^1.19.0",
|
||||||
|
"express": "^4.17.1",
|
||||||
|
"medusa-core-utils": "^0.3.0",
|
||||||
|
"medusa-interfaces": "^0.3.0",
|
||||||
|
"medusa-test-utils": "^0.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
import Analytics from "analytics-node"
|
||||||
|
import axios from "axios"
|
||||||
|
import { BaseService } from "medusa-interfaces"
|
||||||
|
|
||||||
|
class SegmentService extends BaseService {
|
||||||
|
/**
|
||||||
|
* @param {Object} options - options defined in `medusa-config.js`
|
||||||
|
* e.g.
|
||||||
|
* {
|
||||||
|
* write_key: Segment write key given in Segment dashboard
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
constructor({totalsService}, options) {
|
||||||
|
super()
|
||||||
|
|
||||||
|
this.options_ = options
|
||||||
|
this.totalsService_ = totalsService
|
||||||
|
|
||||||
|
this.analytics_ = new Analytics(options.write_key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper around segment's identify call
|
||||||
|
*/
|
||||||
|
identify(data) {
|
||||||
|
return this.analytics_.identify(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
track(data) {
|
||||||
|
return this.analytics_.track(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
async getReportingValue(fromCurrency, value) {
|
||||||
|
const date = "latest"
|
||||||
|
const toCurrency =
|
||||||
|
(this.options_.reporting_currency &&
|
||||||
|
this.options_.reporting_currency.toUpperCase()) ||
|
||||||
|
"EUR"
|
||||||
|
|
||||||
|
if (fromCurrency === toCurrency) return value.toFixed(2)
|
||||||
|
|
||||||
|
const exchangeRate = await axios
|
||||||
|
.get(`https://api.exchangeratesapi.io/${date}?symbols=${fromCurrency}&base=${toCurrency}`)
|
||||||
|
.then(({ data }) => {
|
||||||
|
return data.rates[fromCurrency]
|
||||||
|
})
|
||||||
|
|
||||||
|
return (value / exchangeRate).toFixed(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async buildOrder(order) {
|
||||||
|
console.log("build", order)
|
||||||
|
const subtotal = await this.totalsService_.getSubtotal(order)
|
||||||
|
const total = await this.totalsService_.getTotal(order)
|
||||||
|
const tax = await this.totalsService_.getTaxTotal(order)
|
||||||
|
const discount = await this.totalsService_.getDiscountTotal(order)
|
||||||
|
const shipping = await this.totalsService_.getShippingTotal(order)
|
||||||
|
const revenue = total - tax
|
||||||
|
|
||||||
|
let coupon
|
||||||
|
if (order.discounts && order.discounts.length) {
|
||||||
|
coupon = order.discounts[0].code
|
||||||
|
}
|
||||||
|
|
||||||
|
const orderData = {
|
||||||
|
checkout_id: order.cart_id,
|
||||||
|
order_id: order._id,
|
||||||
|
email: order.email,
|
||||||
|
reporting_total: await this.getReportingValue(order.currency_code, total),
|
||||||
|
reporting_subtotal: await this.getReportingValue(order.currency_code, subtotal),
|
||||||
|
reporting_revenue: await this.getReportingValue(order.currency_code, revenue),
|
||||||
|
reporting_shipping: await this.getReportingValue(order.currency_code, shipping),
|
||||||
|
reporting_tax: await this.getReportingValue(order.currency_code, tax),
|
||||||
|
reporting_discount: await this.getReportingValue(order.currency_code, discount),
|
||||||
|
total,
|
||||||
|
subtotal,
|
||||||
|
revenue,
|
||||||
|
shipping,
|
||||||
|
tax,
|
||||||
|
discount,
|
||||||
|
coupon,
|
||||||
|
currency: order.currency_code,
|
||||||
|
products: await Promise.all(
|
||||||
|
order.items.map(async item => {
|
||||||
|
let name = item.title
|
||||||
|
let variant = item.description
|
||||||
|
|
||||||
|
const unit_price = item.content.unit_price
|
||||||
|
const line_total = unit_price * item.content.quantity * item.quantity
|
||||||
|
const revenue = await this.getReportingValue(order.currency_code, line_total)
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
variant,
|
||||||
|
price: unit_price,
|
||||||
|
reporting_revenue: revenue,
|
||||||
|
product_id: `${item.content.product._id}`,
|
||||||
|
sku: item.content.variant.sku,
|
||||||
|
quantity: item.quantity,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return orderData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SegmentService
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
class OrderSubscriber {
|
||||||
|
constructor({ segmentService, eventBusService }) {
|
||||||
|
eventBusService.subscribe("order.placed", async (order) => {
|
||||||
|
const date = new Date(parseInt(order.created))
|
||||||
|
const orderData = await segmentService.buildOrder(order)
|
||||||
|
const orderEvent = {
|
||||||
|
event: "Order Completed",
|
||||||
|
userId: order.customer_id,
|
||||||
|
properties: orderData,
|
||||||
|
timestamp: date,
|
||||||
|
}
|
||||||
|
|
||||||
|
segmentService.track(orderEvent)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OrderSubscriber
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -37,6 +37,7 @@ describe("POST /admin/discounts", () => {
|
|||||||
value: 10,
|
value: 10,
|
||||||
allocation: "total",
|
allocation: "total",
|
||||||
},
|
},
|
||||||
|
is_dynamic: false
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ describe("POST /admin/discounts", () => {
|
|||||||
value: 10,
|
value: 10,
|
||||||
allocation: "total",
|
allocation: "total",
|
||||||
},
|
},
|
||||||
|
is_dynamic: false,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { MedusaError, Validator } from "medusa-core-utils"
|
|||||||
export default async (req, res) => {
|
export default async (req, res) => {
|
||||||
const schema = Validator.object().keys({
|
const schema = Validator.object().keys({
|
||||||
code: Validator.string().required(),
|
code: Validator.string().required(),
|
||||||
|
is_dynamic: Validator.boolean().default(false),
|
||||||
discount_rule: Validator.object()
|
discount_rule: Validator.object()
|
||||||
.keys({
|
.keys({
|
||||||
description: Validator.string().optional(),
|
description: Validator.string().optional(),
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { MedusaError, Validator } from "medusa-core-utils"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { discount_id } = req.params
|
||||||
|
const schema = Validator.object().keys({
|
||||||
|
code: Validator.string().required(),
|
||||||
|
metadata: Validator.object().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { value, error } = schema.validate(req.body)
|
||||||
|
if (error) {
|
||||||
|
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const discountService = req.scope.resolve("discountService")
|
||||||
|
await discountService.createDynamicCode(discount_id, value)
|
||||||
|
|
||||||
|
const data = await discountService.retrieve(dicsount_id)
|
||||||
|
|
||||||
|
res.status(200).json({ discount: data })
|
||||||
|
} catch (err) {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { MedusaError, Validator } from "medusa-core-utils"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { discount_id, code } = req.params
|
||||||
|
|
||||||
|
const { value, error } = schema.validate(req.body)
|
||||||
|
if (error) {
|
||||||
|
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const discountService = req.scope.resolve("discountService")
|
||||||
|
await discountService.deleteDynamicCode(discount_id, code)
|
||||||
|
|
||||||
|
const data = await discountService.retrieve(dicsount_id)
|
||||||
|
|
||||||
|
res.status(200).json({ discount: data })
|
||||||
|
} catch (err) {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,16 @@ export default app => {
|
|||||||
middlewares.wrap(require("./delete-discount").default)
|
middlewares.wrap(require("./delete-discount").default)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Dynamic codes
|
||||||
|
route.post(
|
||||||
|
"/:discount_id/dynamic-codes",
|
||||||
|
middlewares.wrap(require("./create-dynamic-code").default)
|
||||||
|
)
|
||||||
|
route.delete(
|
||||||
|
"/:discount_id/dynamic-codes/:code",
|
||||||
|
middlewares.wrap(require("./delete-dynamic-code").default)
|
||||||
|
)
|
||||||
|
|
||||||
// Discount valid variants management
|
// Discount valid variants management
|
||||||
route.post(
|
route.post(
|
||||||
"/:discount_id/variants/:variant_id",
|
"/:discount_id/variants/:variant_id",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export default async (req, res) => {
|
|||||||
const { discount_id } = req.params
|
const { discount_id } = req.params
|
||||||
const schema = Validator.object().keys({
|
const schema = Validator.object().keys({
|
||||||
code: Validator.string().required(),
|
code: Validator.string().required(),
|
||||||
|
is_dynamic: Validator.boolean().default(false),
|
||||||
discount_rule: Validator.object()
|
discount_rule: Validator.object()
|
||||||
.keys({
|
.keys({
|
||||||
description: Validator.string().optional(),
|
description: Validator.string().optional(),
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export default async (req, res) => {
|
|||||||
|
|
||||||
res.status(200).json({ cart })
|
res.status(200).json({ cart })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.log(err.response.data)
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -34,7 +34,6 @@ export default async (req, res) => {
|
|||||||
|
|
||||||
res.status(200).json({ order })
|
res.status(200).json({ order })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err)
|
|
||||||
// If something fails it might be because the order has already been created
|
// If something fails it might be because the order has already been created
|
||||||
// if it has we find it from the cart id
|
// if it has we find it from the cart id
|
||||||
const orderService = req.scope.resolve("orderService")
|
const orderService = req.scope.resolve("orderService")
|
||||||
|
|||||||
@@ -1,6 +1,17 @@
|
|||||||
import { IdMap } from "medusa-test-utils"
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
|
||||||
export const discounts = {
|
export const discounts = {
|
||||||
|
dynamic: {
|
||||||
|
_id: IdMap.getId("dynamic"),
|
||||||
|
code: "Something",
|
||||||
|
is_dynamic: true,
|
||||||
|
discount_rule: {
|
||||||
|
type: "percentage",
|
||||||
|
allocation: "total",
|
||||||
|
value: 10,
|
||||||
|
},
|
||||||
|
regions: [IdMap.getId("region-france")],
|
||||||
|
},
|
||||||
total10Percent: {
|
total10Percent: {
|
||||||
_id: IdMap.getId("total10"),
|
_id: IdMap.getId("total10"),
|
||||||
code: "10%OFF",
|
code: "10%OFF",
|
||||||
@@ -117,6 +128,9 @@ export const DiscountModelMock = {
|
|||||||
}),
|
}),
|
||||||
deleteOne: jest.fn().mockReturnValue(Promise.resolve()),
|
deleteOne: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
findOne: jest.fn().mockImplementation(query => {
|
findOne: jest.fn().mockImplementation(query => {
|
||||||
|
if (query._id === IdMap.getId("dynamic")) {
|
||||||
|
return Promise.resolve(discounts.dynamic)
|
||||||
|
}
|
||||||
if (query._id === IdMap.getId("total10")) {
|
if (query._id === IdMap.getId("total10")) {
|
||||||
return Promise.resolve(discounts.total10Percent)
|
return Promise.resolve(discounts.total10Percent)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
|
||||||
|
export const dynamicDiscounts = {
|
||||||
|
dynamicOff: {
|
||||||
|
_id: IdMap.getId("dynamicOff"),
|
||||||
|
discount_id: IdMap.getId("dynamic"),
|
||||||
|
code: "DYNAMICOFF",
|
||||||
|
disabled: false,
|
||||||
|
usage_count: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DynamicDiscountCodeModelMock = {
|
||||||
|
create: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
|
updateOne: jest.fn().mockImplementation((query, update) => {
|
||||||
|
return Promise.resolve()
|
||||||
|
}),
|
||||||
|
deleteOne: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
|
findOne: jest.fn().mockImplementation(query => {
|
||||||
|
if (query.code === "DYNAMICOFF") {
|
||||||
|
return Promise.resolve(dynamicDiscounts.dynamicOff)
|
||||||
|
}
|
||||||
|
return Promise.resolve(undefined)
|
||||||
|
}),
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ class DiscountModel extends BaseModel {
|
|||||||
|
|
||||||
static schema = {
|
static schema = {
|
||||||
code: { type: String, required: true, unique: true },
|
code: { type: String, required: true, unique: true },
|
||||||
|
is_dynamic: { type: Boolean, default: false },
|
||||||
discount_rule: { type: DiscountRule, required: true },
|
discount_rule: { type: DiscountRule, required: true },
|
||||||
usage_count: { type: Number, default: 0 },
|
usage_count: { type: Number, default: 0 },
|
||||||
disabled: { type: Boolean, default: false },
|
disabled: { type: Boolean, default: false },
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import mongoose from "mongoose"
|
||||||
|
import { BaseModel } from "medusa-interfaces"
|
||||||
|
|
||||||
|
class DynamicDiscountCode extends BaseModel {
|
||||||
|
static modelName = "DynamicDiscountCode"
|
||||||
|
|
||||||
|
static schema = {
|
||||||
|
code: { type: String, required: true, unique: true },
|
||||||
|
discount_id: { type: String, required: true },
|
||||||
|
usage_count: { type: Number, default: 0 },
|
||||||
|
disabled: { type: Boolean, default: false },
|
||||||
|
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DynamicDiscountCode
|
||||||
@@ -28,6 +28,7 @@ class OrderModel extends BaseModel {
|
|||||||
customer_id: { type: String },
|
customer_id: { type: String },
|
||||||
payment_method: { type: PaymentMethodSchema, required: true },
|
payment_method: { type: PaymentMethodSchema, required: true },
|
||||||
shipping_methods: { type: [ShippingMethodSchema], required: true },
|
shipping_methods: { type: [ShippingMethodSchema], required: true },
|
||||||
|
created: { type: String, default: Date.now },
|
||||||
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
|
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import DiscountService from "../discount"
|
import DiscountService from "../discount"
|
||||||
import { DiscountModelMock, discounts } from "../../models/__mocks__/discount"
|
import { DiscountModelMock, discounts } from "../../models/__mocks__/discount"
|
||||||
|
import {
|
||||||
|
DynamicDiscountCodeModelMock,
|
||||||
|
dynamicDiscounts,
|
||||||
|
} from "../../models/__mocks__/dynamic-discount-code"
|
||||||
import { IdMap } from "medusa-test-utils"
|
import { IdMap } from "medusa-test-utils"
|
||||||
import { ProductVariantServiceMock } from "../__mocks__/product-variant"
|
import { ProductVariantServiceMock } from "../__mocks__/product-variant"
|
||||||
import { RegionServiceMock } from "../__mocks__/region"
|
import { RegionServiceMock } from "../__mocks__/region"
|
||||||
@@ -61,6 +65,46 @@ describe("DiscountService", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("retrieveByCode", () => {
|
||||||
|
let res
|
||||||
|
const discountService = new DiscountService({
|
||||||
|
discountModel: DiscountModelMock,
|
||||||
|
dynamicDiscountCodeModel: DynamicDiscountCodeModelMock,
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls model layer findOne", async () => {
|
||||||
|
res = await discountService.retrieveByCode("10%off")
|
||||||
|
expect(DiscountModelMock.findOne).toHaveBeenCalledTimes(1)
|
||||||
|
expect(DiscountModelMock.findOne).toHaveBeenCalledWith({
|
||||||
|
code: "10%OFF",
|
||||||
|
})
|
||||||
|
expect(res).toEqual(discounts.total10Percent)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("finds dynamic code", async () => {
|
||||||
|
res = await discountService.retrieveByCode("dynamicoff")
|
||||||
|
expect(DiscountModelMock.findOne).toHaveBeenCalledTimes(2)
|
||||||
|
expect(DiscountModelMock.findOne).toHaveBeenCalledWith({
|
||||||
|
_id: IdMap.getId("dynamic"),
|
||||||
|
})
|
||||||
|
expect(DiscountModelMock.findOne).toHaveBeenCalledWith({
|
||||||
|
code: "DYNAMICOFF",
|
||||||
|
})
|
||||||
|
expect(DynamicDiscountCodeModelMock.findOne).toHaveBeenCalledTimes(1)
|
||||||
|
expect(DynamicDiscountCodeModelMock.findOne).toHaveBeenCalledWith({
|
||||||
|
code: "DYNAMICOFF",
|
||||||
|
})
|
||||||
|
expect(res).toEqual({
|
||||||
|
...discounts.dynamic,
|
||||||
|
code: "DYNAMICOFF",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("update", () => {
|
describe("update", () => {
|
||||||
const discountService = new DiscountService({
|
const discountService = new DiscountService({
|
||||||
discountModel: DiscountModelMock,
|
discountModel: DiscountModelMock,
|
||||||
|
|||||||
@@ -610,6 +610,14 @@ class CartService extends BaseService {
|
|||||||
async applyDiscount(cartId, discountCode) {
|
async applyDiscount(cartId, discountCode) {
|
||||||
const cart = await this.retrieve(cartId)
|
const cart = await this.retrieve(cartId)
|
||||||
const discount = await this.discountService_.retrieveByCode(discountCode)
|
const discount = await this.discountService_.retrieveByCode(discountCode)
|
||||||
|
|
||||||
|
if (discount.disabled) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"The discount code is disabled"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (!discount.regions.includes(cart.region_id)) {
|
if (!discount.regions.includes(cart.region_id)) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
MedusaError.Types.INVALID_DATA,
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import _ from "lodash"
|
|||||||
class DiscountService extends BaseService {
|
class DiscountService extends BaseService {
|
||||||
constructor({
|
constructor({
|
||||||
discountModel,
|
discountModel,
|
||||||
|
dynamicDiscountCodeModel,
|
||||||
totalsService,
|
totalsService,
|
||||||
productVariantService,
|
productVariantService,
|
||||||
regionService,
|
regionService,
|
||||||
@@ -18,6 +19,9 @@ class DiscountService extends BaseService {
|
|||||||
/** @private @const {DiscountModel} */
|
/** @private @const {DiscountModel} */
|
||||||
this.discountModel_ = discountModel
|
this.discountModel_ = discountModel
|
||||||
|
|
||||||
|
/** @private @const {DynamicDiscountCodeModel} */
|
||||||
|
this.dynamicCodeModel_ = dynamicDiscountCodeModel
|
||||||
|
|
||||||
/** @private @const {TotalsService} */
|
/** @private @const {TotalsService} */
|
||||||
this.totalsService_ = totalsService
|
this.totalsService_ = totalsService
|
||||||
|
|
||||||
@@ -144,18 +148,38 @@ class DiscountService extends BaseService {
|
|||||||
*/
|
*/
|
||||||
async retrieveByCode(discountCode) {
|
async retrieveByCode(discountCode) {
|
||||||
discountCode = this.normalizeDiscountCode_(discountCode)
|
discountCode = this.normalizeDiscountCode_(discountCode)
|
||||||
const discount = await this.discountModel_
|
let discount = await this.discountModel_
|
||||||
.findOne({ code: discountCode })
|
.findOne({ code: discountCode })
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!discount) {
|
if (!discount) {
|
||||||
throw new MedusaError(
|
const dynamicCode = await this.dynamicCodeModel_
|
||||||
MedusaError.Types.NOT_FOUND,
|
.findOne({ code: discountCode })
|
||||||
`Discount with code ${discountCode} was not found`
|
.catch(err => {
|
||||||
)
|
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!dynamicCode) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`Discount with code ${discountCode} was not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
discount = await this.retrieve(dynamicCode.discount_id)
|
||||||
|
if (!discount) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`Discount with code ${discountCode} was not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
discount.code = discountCode
|
||||||
|
discount.disabled = dynamicCode.disabled
|
||||||
}
|
}
|
||||||
|
|
||||||
return discount
|
return discount
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,6 +210,49 @@ class DiscountService extends BaseService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a dynamic code for a discount id.
|
||||||
|
* @param {string} discountId - the id of the discount to create a code for
|
||||||
|
* @param {string} code - the code to identify the discount by
|
||||||
|
* @return {Promise} the newly created dynamic code
|
||||||
|
*/
|
||||||
|
async createDynamicCode(discountId, data) {
|
||||||
|
const discount = await this.retrieve(discountId)
|
||||||
|
if (!discount.is_dynamic) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"Discount must be set to dynamic"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const code = this.normalizeDiscountCode_(data.code)
|
||||||
|
return this.dynamicCodeModel_.create({
|
||||||
|
...data,
|
||||||
|
discount_id: discount._id,
|
||||||
|
code,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a dynamic code for a discount id.
|
||||||
|
* @param {string} discountId - the id of the discount to create a code for
|
||||||
|
* @param {string} code - the code to identify the discount by
|
||||||
|
* @return {Promise} the newly created dynamic code
|
||||||
|
*/
|
||||||
|
async deleteDynamicCode(discountId, code) {
|
||||||
|
const discont = await this.retrieve(discountId)
|
||||||
|
if (!discount.is_dynamic) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"Discount must be set to dynamic"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.dynamicCodeModel_.deleteOne({
|
||||||
|
code,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a valid variant to the discount rule valid_for array.
|
* Adds a valid variant to the discount rule valid_for array.
|
||||||
* @param {string} discountId - id of discount
|
* @param {string} discountId - id of discount
|
||||||
|
|||||||
@@ -245,89 +245,93 @@ class OrderService extends BaseService {
|
|||||||
const dbSession = await this.orderModel_.startSession()
|
const dbSession = await this.orderModel_.startSession()
|
||||||
|
|
||||||
// Initialize DB transaction
|
// Initialize DB transaction
|
||||||
return dbSession.withTransaction(async () => {
|
return dbSession
|
||||||
// Check if order from cart already exists
|
.withTransaction(async () => {
|
||||||
// If so, this function throws
|
// Check if order from cart already exists
|
||||||
const exists = await this.existsByCartId(cart._id)
|
// If so, this function throws
|
||||||
if (exists) {
|
const exists = await this.existsByCartId(cart._id)
|
||||||
throw new MedusaError(
|
if (exists) {
|
||||||
MedusaError.Types.INVALID_ARGUMENT,
|
throw new MedusaError(
|
||||||
"Order from cart already exists"
|
MedusaError.Types.INVALID_ARGUMENT,
|
||||||
|
"Order from cart already exists"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Throw if payment method does not exist
|
||||||
|
if (!cart.payment_method) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_ARGUMENT,
|
||||||
|
"Cart does not contain a payment method"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { payment_method, payment_sessions } = cart
|
||||||
|
|
||||||
|
if (!payment_sessions || !payment_sessions.length) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_ARGUMENT,
|
||||||
|
"cart must have payment sessions"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let paymentSession = payment_sessions.find(
|
||||||
|
ps => ps.provider_id === payment_method.provider_id
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
// Throw if payment method does not exist
|
// Throw if payment method does not exist
|
||||||
if (!cart.payment_method) {
|
if (!paymentSession) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
MedusaError.Types.INVALID_ARGUMENT,
|
MedusaError.Types.INVALID_ARGUMENT,
|
||||||
"Cart does not contain a payment method"
|
"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
|
||||||
const { payment_method, payment_sessions } = cart
|
|
||||||
|
|
||||||
if (!payment_sessions || !payment_sessions.length) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.INVALID_ARGUMENT,
|
|
||||||
"cart must have payment sessions"
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
let paymentSession = payment_sessions.find(
|
// If payment status is not authorized, we throw
|
||||||
ps => ps.provider_id === payment_method.provider_id
|
if (paymentStatus !== "authorized" && paymentStatus !== "succeeded") {
|
||||||
)
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_ARGUMENT,
|
||||||
|
"Payment method is not authorized"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Throw if payment method does not exist
|
const paymentData = await paymentProvider.retrievePayment(
|
||||||
if (!paymentSession) {
|
paymentSession.data
|
||||||
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 o = {
|
||||||
const paymentProvider = this.paymentProviderService_.retrieveProvider(
|
payment_method: {
|
||||||
paymentSession.provider_id
|
provider_id: paymentSession.provider_id,
|
||||||
)
|
data: paymentData,
|
||||||
const paymentStatus = await paymentProvider.getStatus(paymentSession.data)
|
},
|
||||||
|
discounts: cart.discounts,
|
||||||
|
shipping_methods: cart.shipping_methods,
|
||||||
|
items: cart.items,
|
||||||
|
shipping_address: cart.shipping_address,
|
||||||
|
billing_address: cart.shipping_address,
|
||||||
|
region_id: cart.region_id,
|
||||||
|
email: cart.email,
|
||||||
|
customer_id: cart.customer_id,
|
||||||
|
cart_id: cart._id,
|
||||||
|
currency_code: region.currency_code,
|
||||||
|
}
|
||||||
|
|
||||||
// If payment status is not authorized, we throw
|
const orderDocument = await this.orderModel_.create([o], {
|
||||||
if (paymentStatus !== "authorized" && paymentStatus !== "succeeded") {
|
session: dbSession,
|
||||||
throw new MedusaError(
|
})
|
||||||
MedusaError.Types.INVALID_ARGUMENT,
|
|
||||||
"Payment method is not authorized"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const paymentData = await paymentProvider.retrievePayment(
|
// Emit and return
|
||||||
paymentSession.data
|
this.eventBus_.emit(OrderService.Events.PLACED, orderDocument[0])
|
||||||
)
|
return orderDocument[0].toObject()
|
||||||
|
|
||||||
const o = {
|
|
||||||
payment_method: {
|
|
||||||
provider_id: paymentSession.provider_id,
|
|
||||||
data: paymentData,
|
|
||||||
},
|
|
||||||
discounts: cart.discounts,
|
|
||||||
shipping_methods: cart.shipping_methods,
|
|
||||||
items: cart.items,
|
|
||||||
shipping_address: cart.shipping_address,
|
|
||||||
billing_address: cart.shipping_address,
|
|
||||||
region_id: cart.region_id,
|
|
||||||
email: cart.email,
|
|
||||||
customer_id: cart.customer_id,
|
|
||||||
cart_id: cart._id,
|
|
||||||
currency_code: region.currency_code,
|
|
||||||
}
|
|
||||||
|
|
||||||
const orderDocument = await this.orderModel_.create([o], {
|
|
||||||
session: dbSession,
|
|
||||||
})
|
})
|
||||||
|
.then(() => this.orderModel_.findOne({ cart_id: cart._id }))
|
||||||
// Emit and return
|
|
||||||
this.eventBus_.emit(OrderService.Events.PLACED, orderDocument[0])
|
|
||||||
return orderDocument[0]
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ class TotalsService extends BaseService {
|
|||||||
* @return {int} the calculated subtotal
|
* @return {int} the calculated subtotal
|
||||||
*/
|
*/
|
||||||
async getTotal(object) {
|
async getTotal(object) {
|
||||||
const subtotal = this.getSubtotal(object)
|
const subtotal = await this.getSubtotal(object)
|
||||||
const taxTotal = await this.getTaxTotal(object)
|
const taxTotal = await this.getTaxTotal(object)
|
||||||
const discountTotal = await this.getDiscountTotal(object)
|
const discountTotal = await this.getDiscountTotal(object)
|
||||||
const shippingTotal = this.getShippingTotal(object)
|
const shippingTotal = await this.getShippingTotal(object)
|
||||||
|
|
||||||
return subtotal + taxTotal + shippingTotal - discountTotal
|
return subtotal + taxTotal + shippingTotal - discountTotal
|
||||||
}
|
}
|
||||||
@@ -74,9 +74,10 @@ class TotalsService extends BaseService {
|
|||||||
async getTaxTotal(object) {
|
async getTaxTotal(object) {
|
||||||
const subtotal = this.getSubtotal(object)
|
const subtotal = this.getSubtotal(object)
|
||||||
const shippingTotal = this.getShippingTotal(object)
|
const shippingTotal = this.getShippingTotal(object)
|
||||||
|
const discountTotal = await this.getDiscountTotal(object)
|
||||||
const region = await this.regionService_.retrieve(object.region_id)
|
const region = await this.regionService_.retrieve(object.region_id)
|
||||||
const { tax_rate } = region
|
const { tax_rate } = region
|
||||||
return (subtotal + shippingTotal) * tax_rate
|
return (subtotal - discountTotal + shippingTotal) * tax_rate
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -260,7 +261,7 @@ class TotalsService extends BaseService {
|
|||||||
async getDiscountTotal(cart) {
|
async getDiscountTotal(cart) {
|
||||||
let subtotal = this.getSubtotal(cart)
|
let subtotal = this.getSubtotal(cart)
|
||||||
|
|
||||||
if (!cart.discounts) {
|
if (!cart.discounts || !cart.discounts.length) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user