Adds gift card support
This commit is contained in:
1 parent
4c9b876407
commit
630bf3abc6
33 files changed
+722
-128
No files matched your search
@@ -10,15 +10,18 @@ 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.equals(selected_shipping_option.id))
|
const ids = selected_shipping_option.id.split(".")
|
||||||
|
await Promise.all(ids.map(async id => {
|
||||||
|
const option = shippingOptions.find(({ _id }) => _id.equals(id))
|
||||||
|
|
||||||
if (option) {
|
if (option) {
|
||||||
const newCart = await cartService.addShippingMethod(cart._id, option._id, option.data)
|
await cartService.addShippingMethod(cart._id, option._id, option.data)
|
||||||
const order = await klarnaProviderService.cartToKlarnaOrder(newCart)
|
}
|
||||||
res.json(order)
|
}))
|
||||||
} else {
|
|
||||||
res.sendStatus(400)
|
const newCart = await cartService.retrieve(cart._id)
|
||||||
}
|
const order = await klarnaProviderService.cartToKlarnaOrder(newCart)
|
||||||
|
res.json(order)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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://7e9a5bc2a2eb.ngrok.io"
|
process.env.BACKEND_URL || "https://c8e1abe7d8b3.ngrok.io"
|
||||||
|
|
||||||
this.totalsService_ = totalsService
|
this.totalsService_ = totalsService
|
||||||
|
|
||||||
@@ -73,10 +73,14 @@ class KlarnaProviderService extends PaymentService {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (cart.shipping_methods.length) {
|
if (cart.shipping_methods.length) {
|
||||||
const shippingMethod = cart.shipping_methods[0]
|
const { name, price } = cart.shipping_methods.reduce((acc, next) => {
|
||||||
const price = shippingMethod.price
|
acc.name = [...acc.name, next.name]
|
||||||
|
acc.price += next.price
|
||||||
|
return acc
|
||||||
|
}, { name: [], price: 0 })
|
||||||
|
|
||||||
order_lines.push({
|
order_lines.push({
|
||||||
name: `${shippingMethod.name}`,
|
name: name.join(" + "),
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
type: "shipping_fee",
|
type: "shipping_fee",
|
||||||
unit_price: price * (1 + taxRate) * 100,
|
unit_price: price * (1 + taxRate) * 100,
|
||||||
@@ -167,18 +171,42 @@ class KlarnaProviderService extends PaymentService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the cart does have shipping methods, set the selected shipping method
|
const partitioned = shippingOptions.reduce((acc, next) => {
|
||||||
order.shipping_options = shippingOptions.map((so) => ({
|
if (acc[next.profile_id]) {
|
||||||
id: so._id,
|
acc[next.profile_id] = [...acc[next.profile_id], next]
|
||||||
name: so.name,
|
} else {
|
||||||
price: so.price * (1 + tax_rate) * 100,
|
acc[next.profile_id] = [next]
|
||||||
tax_amount: so.price * tax_rate * 100,
|
}
|
||||||
tax_rate: tax_rate * 10000,
|
return acc
|
||||||
preselected: shippingOptions.length === 1
|
}, {})
|
||||||
}))
|
|
||||||
|
let f = (a, b) => [].concat(...a.map(a => b.map(b => [].concat(a, b))))
|
||||||
|
let cartesian = (a, b, ...c) => b ? cartesian(f(a, b), ...c) : a
|
||||||
|
|
||||||
|
const methods = Object.keys(partitioned).map(k => partitioned[k])
|
||||||
|
const combinations = cartesian(...methods)
|
||||||
|
|
||||||
|
|
||||||
|
order.shipping_options = combinations.map((combination) => {
|
||||||
|
combination = Array.isArray(combination) ? combination : [combination]
|
||||||
|
const details = combination.reduce((acc, next) => {
|
||||||
|
acc.id = [...acc.id, next._id]
|
||||||
|
acc.name = [...acc.name, next.name]
|
||||||
|
acc.price += next.price
|
||||||
|
return acc
|
||||||
|
}, { id: [], name: [], price: 0 })
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: details.id.join("."),
|
||||||
|
name: details.name.join(" + "),
|
||||||
|
price: details.price * (1 + tax_rate) * 100,
|
||||||
|
tax_amount: details.price * tax_rate * 100,
|
||||||
|
tax_rate: tax_rate * 10000,
|
||||||
|
preselected: combinations.length === 1
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(order)
|
|
||||||
return order
|
return order
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ class SendGridService extends BaseService {
|
|||||||
async transactionalEmail(event, data) {
|
async transactionalEmail(event, data) {
|
||||||
let templateId
|
let templateId
|
||||||
switch (event) {
|
switch (event) {
|
||||||
|
case "order.gift_card_created":
|
||||||
|
templateId = this.options_.gift_card_created_template
|
||||||
|
break
|
||||||
case "order.placed":
|
case "order.placed":
|
||||||
templateId = this.options_.order_placed_template
|
templateId = this.options_.order_placed_template
|
||||||
break
|
break
|
||||||
@@ -55,12 +58,14 @@ class SendGridService extends BaseService {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return SendGrid.send({
|
if (templateId) {
|
||||||
template_id: templateId,
|
return SendGrid.send({
|
||||||
from: this.options_.from,
|
template_id: templateId,
|
||||||
to: data.email,
|
from: this.options_.from,
|
||||||
dynamic_template_data: data,
|
to: data.email,
|
||||||
})
|
dynamic_template_data: data,
|
||||||
|
})
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ class OrderSubscriber {
|
|||||||
|
|
||||||
this.eventBus_ = eventBusService
|
this.eventBus_ = eventBusService
|
||||||
|
|
||||||
|
this.eventBus_.subscribe("order.gift_card_created", async (order) => {
|
||||||
|
await this.sendgridService_.transactionalEmail("order.gift_card_created", order)
|
||||||
|
})
|
||||||
|
|
||||||
this.eventBus_.subscribe("order.placed", async (order) => {
|
this.eventBus_.subscribe("order.placed", async (order) => {
|
||||||
await this.sendgridService_.transactionalEmail("order.placed", order)
|
await this.sendgridService_.transactionalEmail("order.placed", order)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -67,7 +67,8 @@
|
|||||||
"passport-http-bearer": "^1.0.1",
|
"passport-http-bearer": "^1.0.1",
|
||||||
"passport-jwt": "^4.0.0",
|
"passport-jwt": "^4.0.0",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
|
"randomatic": "^3.1.1",
|
||||||
"winston": "^3.2.1"
|
"winston": "^3.2.1"
|
||||||
},
|
},
|
||||||
"gitHead": "35e0930650d5f4aedf2610749cd131ae8b7e17cc"
|
"gitHead": "35e0930650d5f4aedf2610749cd131ae8b7e17cc"
|
||||||
}
|
}
|
||||||
@@ -3,7 +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({
|
||||||
title: Validator.string().required(),
|
title: Validator.string().required(),
|
||||||
description: Validator.string(),
|
description: Validator.string().allow(""),
|
||||||
tags: Validator.string(),
|
tags: Validator.string(),
|
||||||
is_giftcard: Validator.boolean().default(false),
|
is_giftcard: Validator.boolean().default(false),
|
||||||
options: Validator.array().items({
|
options: Validator.array().items({
|
||||||
@@ -15,6 +15,7 @@ export default async (req, res) => {
|
|||||||
title: Validator.string().required(),
|
title: Validator.string().required(),
|
||||||
sku: Validator.string(),
|
sku: Validator.string(),
|
||||||
ean: Validator.string(),
|
ean: Validator.string(),
|
||||||
|
barcode: Validator.string(),
|
||||||
prices: Validator.array()
|
prices: Validator.array()
|
||||||
.items({
|
.items({
|
||||||
currency_code: Validator.string().required(),
|
currency_code: Validator.string().required(),
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export default async (req, res) => {
|
|||||||
[
|
[
|
||||||
"title",
|
"title",
|
||||||
"description",
|
"description",
|
||||||
|
"is_giftcard",
|
||||||
"tags",
|
"tags",
|
||||||
"thumbnail",
|
"thumbnail",
|
||||||
"handle",
|
"handle",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export default async (req, res) => {
|
|||||||
[
|
[
|
||||||
"title",
|
"title",
|
||||||
"description",
|
"description",
|
||||||
|
"is_giftcard",
|
||||||
"tags",
|
"tags",
|
||||||
"thumbnail",
|
"thumbnail",
|
||||||
"handle",
|
"handle",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export default async (req, res) => {
|
|||||||
[
|
[
|
||||||
"title",
|
"title",
|
||||||
"description",
|
"description",
|
||||||
|
"is_giftcard",
|
||||||
"tags",
|
"tags",
|
||||||
"thumbnail",
|
"thumbnail",
|
||||||
"handle",
|
"handle",
|
||||||
|
|||||||
@@ -18,6 +18,13 @@ export default async (req, res) => {
|
|||||||
title: Validator.string().optional(),
|
title: Validator.string().optional(),
|
||||||
sku: Validator.string().optional(),
|
sku: Validator.string().optional(),
|
||||||
ean: Validator.string().optional(),
|
ean: Validator.string().optional(),
|
||||||
|
published: Validator.boolean(),
|
||||||
|
image: Validator.string()
|
||||||
|
.allow("")
|
||||||
|
.optional(),
|
||||||
|
barcode: Validator.string()
|
||||||
|
.allow("")
|
||||||
|
.optional(),
|
||||||
prices: Validator.array().items(
|
prices: Validator.array().items(
|
||||||
Validator.object()
|
Validator.object()
|
||||||
.keys({
|
.keys({
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ export default async (req, res) => {
|
|||||||
"options",
|
"options",
|
||||||
"thumbnail",
|
"thumbnail",
|
||||||
"variants",
|
"variants",
|
||||||
|
"is_giftcard",
|
||||||
"published",
|
"published",
|
||||||
],
|
],
|
||||||
["variants"]
|
["variants"]
|
||||||
|
|||||||
@@ -33,13 +33,14 @@ export default async (req, res) => {
|
|||||||
const shippingProfileService = req.scope.resolve("shippingProfileService")
|
const shippingProfileService = req.scope.resolve("shippingProfileService")
|
||||||
|
|
||||||
// Add to default shipping profile
|
// Add to default shipping profile
|
||||||
const { _id } = await shippingProfileService.retrieveDefault()
|
if (!value.profile_id) {
|
||||||
|
const { _id } = await shippingProfileService.retrieveDefault()
|
||||||
|
value.profile_id = _id
|
||||||
|
}
|
||||||
|
|
||||||
const data = await optionService.create({
|
const data = await optionService.create(value)
|
||||||
...value,
|
|
||||||
profile_id: _id,
|
await shippingProfileService.addShippingOption(value.profile_id, data._id)
|
||||||
})
|
|
||||||
await shippingProfileService.addShippingOption(_id, data._id)
|
|
||||||
|
|
||||||
res.status(200).json({ shipping_option: data })
|
res.status(200).json({ shipping_option: data })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export const carts = {
|
|||||||
cartWithPaySessionsDifRegion: {
|
cartWithPaySessionsDifRegion: {
|
||||||
_id: IdMap.getId("cartWithPaySessionsDifRegion"),
|
_id: IdMap.getId("cartWithPaySessionsDifRegion"),
|
||||||
region_id: IdMap.getId("region-france"),
|
region_id: IdMap.getId("region-france"),
|
||||||
|
total: 1,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
_id: IdMap.getId("existingLine"),
|
_id: IdMap.getId("existingLine"),
|
||||||
@@ -81,6 +82,7 @@ export const carts = {
|
|||||||
},
|
},
|
||||||
cartWithPaySessions: {
|
cartWithPaySessions: {
|
||||||
_id: IdMap.getId("cartWithPaySessions"),
|
_id: IdMap.getId("cartWithPaySessions"),
|
||||||
|
total: 1,
|
||||||
region_id: IdMap.getId("testRegion"),
|
region_id: IdMap.getId("testRegion"),
|
||||||
shipping_methods: [],
|
shipping_methods: [],
|
||||||
items: [
|
items: [
|
||||||
@@ -123,6 +125,7 @@ export const carts = {
|
|||||||
},
|
},
|
||||||
cartWithLine: {
|
cartWithLine: {
|
||||||
_id: IdMap.getId("cartWithLine"),
|
_id: IdMap.getId("cartWithLine"),
|
||||||
|
total: 1,
|
||||||
title: "test",
|
title: "test",
|
||||||
region_id: IdMap.getId("testRegion"),
|
region_id: IdMap.getId("testRegion"),
|
||||||
items: [
|
items: [
|
||||||
@@ -166,6 +169,92 @@ export const carts = {
|
|||||||
discounts: [],
|
discounts: [],
|
||||||
customer_id: "",
|
customer_id: "",
|
||||||
},
|
},
|
||||||
|
withGiftCard: {
|
||||||
|
_id: IdMap.getId("withGiftCard"),
|
||||||
|
region_id: IdMap.getId("region-france"),
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("existingLine"),
|
||||||
|
title: "merge line",
|
||||||
|
description: "This is a new line",
|
||||||
|
is_giftcard: false,
|
||||||
|
thumbnail: "test-img-yeah.com/thumb",
|
||||||
|
content: {
|
||||||
|
unit_price: 123,
|
||||||
|
variant: {
|
||||||
|
_id: IdMap.getId("can-cover"),
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
_id: IdMap.getId("product"),
|
||||||
|
},
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
quantity: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("giftline"),
|
||||||
|
title: "GiftCard",
|
||||||
|
description: "Gift card line",
|
||||||
|
thumbnail: "test-img-yeah.com/thumb",
|
||||||
|
metadata: {
|
||||||
|
name: "Test Name",
|
||||||
|
},
|
||||||
|
is_giftcard: true,
|
||||||
|
content: {
|
||||||
|
unit_price: 100,
|
||||||
|
variant: {
|
||||||
|
_id: IdMap.getId("giftCardVar"),
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
_id: IdMap.getId("giftCardProd"),
|
||||||
|
},
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
email: "test",
|
||||||
|
payment_sessions: [
|
||||||
|
{
|
||||||
|
provider_id: "default_provider",
|
||||||
|
data: {
|
||||||
|
money_id: "success",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
payment_method: {
|
||||||
|
provider_id: "default_provider",
|
||||||
|
data: {
|
||||||
|
money_id: "success",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
shipping_methods: [
|
||||||
|
{
|
||||||
|
provider_id: "gls",
|
||||||
|
data: {
|
||||||
|
yes: "sir",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
shipping_address: {
|
||||||
|
first_name: "hi",
|
||||||
|
last_name: "you",
|
||||||
|
country_code: "DK",
|
||||||
|
city: "of lights",
|
||||||
|
address_1: "You bet street",
|
||||||
|
postal_code: "4242",
|
||||||
|
},
|
||||||
|
billing_address: {
|
||||||
|
first_name: "hi",
|
||||||
|
last_name: "you",
|
||||||
|
country_code: "DK",
|
||||||
|
city: "of lights",
|
||||||
|
address_1: "You bet street",
|
||||||
|
postal_code: "4242",
|
||||||
|
},
|
||||||
|
discounts: [],
|
||||||
|
customer_id: "",
|
||||||
|
},
|
||||||
completeCart: {
|
completeCart: {
|
||||||
_id: IdMap.getId("complete-cart"),
|
_id: IdMap.getId("complete-cart"),
|
||||||
region_id: IdMap.getId("region-france"),
|
region_id: IdMap.getId("region-france"),
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ export const discounts = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const DiscountModelMock = {
|
export const DiscountModelMock = {
|
||||||
create: jest.fn().mockReturnValue(Promise.resolve()),
|
create: jest.fn().mockImplementation(data => Promise.resolve(data)),
|
||||||
updateOne: jest.fn().mockImplementation((query, update) => {
|
updateOne: jest.fn().mockImplementation((query, update) => {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -11,7 +11,13 @@ export const profiles = {
|
|||||||
_id: IdMap.getId("profile1"),
|
_id: IdMap.getId("profile1"),
|
||||||
name: "Profile One",
|
name: "Profile One",
|
||||||
products: [IdMap.getId("product1")],
|
products: [IdMap.getId("product1")],
|
||||||
shipping_options: [IdMap.getId("shipping1")],
|
shipping_options: [IdMap.getId("shipping_1")],
|
||||||
|
},
|
||||||
|
profile2: {
|
||||||
|
_id: IdMap.getId("profile2"),
|
||||||
|
name: "Profile two",
|
||||||
|
products: [IdMap.getId("product2")],
|
||||||
|
shipping_options: [IdMap.getId("shipping_2")],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,6 +27,10 @@ export const ShippingProfileModelMock = {
|
|||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}),
|
}),
|
||||||
find: jest.fn().mockImplementation(query => {
|
find: jest.fn().mockImplementation(query => {
|
||||||
|
if (query.products && query.products.$in) {
|
||||||
|
return Promise.resolve([profiles.profile1, profiles.profile2])
|
||||||
|
}
|
||||||
|
|
||||||
return Promise.resolve([])
|
return Promise.resolve([])
|
||||||
}),
|
}),
|
||||||
deleteOne: jest.fn().mockReturnValue(Promise.resolve()),
|
deleteOne: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import mongoose from "mongoose"
|
|||||||
import DiscountRule from "./discount-rule"
|
import DiscountRule from "./discount-rule"
|
||||||
|
|
||||||
export default new mongoose.Schema({
|
export default new mongoose.Schema({
|
||||||
|
is_giftcard: { type: Boolean },
|
||||||
code: { type: String },
|
code: { type: String },
|
||||||
discount_rule: { type: DiscountRule },
|
discount_rule: { type: DiscountRule },
|
||||||
usage_count: { type: Number },
|
usage_count: { type: Number },
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export default new mongoose.Schema({
|
|||||||
description: { type: String },
|
description: { type: String },
|
||||||
thumbnail: { type: String },
|
thumbnail: { type: String },
|
||||||
is_giftcard: { type: Boolean, default: false },
|
is_giftcard: { type: Boolean, default: false },
|
||||||
|
has_shipping: { type: Boolean, default: false },
|
||||||
|
|
||||||
// mongoose doesn't allow multi-type validation but this field allows both
|
// mongoose doesn't allow multi-type validation but this field allows both
|
||||||
// an object containing:
|
// an object containing:
|
||||||
|
|||||||
@@ -46,6 +46,11 @@ export const DiscountServiceMock = {
|
|||||||
removeRegion: jest.fn().mockReturnValue(Promise.resolve()),
|
removeRegion: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
addValidVariant: jest.fn().mockReturnValue(Promise.resolve()),
|
addValidVariant: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
removeValidVariant: jest.fn().mockReturnValue(Promise.resolve()),
|
removeValidVariant: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
|
generateGiftCard: jest.fn().mockReturnValue(
|
||||||
|
Promise.resolve({
|
||||||
|
_id: IdMap.getId("gift_card_id"),
|
||||||
|
})
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
const mock = jest.fn().mockImplementation(() => {
|
const mock = jest.fn().mockImplementation(() => {
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ export const ShippingOptionServiceMock = {
|
|||||||
_id: IdMap.getId("fail"),
|
_id: IdMap.getId("fail"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
return Promise.resolve({ _id: methodId })
|
||||||
}),
|
}),
|
||||||
delete: jest.fn().mockReturnValue(Promise.resolve()),
|
delete: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export const ShippingProfileServiceMock = {
|
|||||||
if (data === IdMap.getId("profile1")) {
|
if (data === IdMap.getId("profile1")) {
|
||||||
return Promise.resolve(profiles.other)
|
return Promise.resolve(profiles.other)
|
||||||
}
|
}
|
||||||
return Promise.resolve()
|
return Promise.resolve(profiles.default)
|
||||||
}),
|
}),
|
||||||
retrieveGiftCardDefault: jest.fn().mockImplementation(data => {
|
retrieveGiftCardDefault: jest.fn().mockImplementation(data => {
|
||||||
return Promise.resolve({ _id: IdMap.getId("giftCardProfile") })
|
return Promise.resolve({ _id: IdMap.getId("giftCardProfile") })
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { RegionServiceMock } from "../__mocks__/region"
|
|||||||
import { EventBusServiceMock } from "../__mocks__/event-bus"
|
import { EventBusServiceMock } from "../__mocks__/event-bus"
|
||||||
import { CustomerServiceMock } from "../__mocks__/customer"
|
import { CustomerServiceMock } from "../__mocks__/customer"
|
||||||
import { ShippingOptionServiceMock } from "../__mocks__/shipping-option"
|
import { ShippingOptionServiceMock } from "../__mocks__/shipping-option"
|
||||||
|
import { TotalsServiceMock } from "../__mocks__/totals"
|
||||||
import { ShippingProfileServiceMock } from "../__mocks__/shipping-profile"
|
import { ShippingProfileServiceMock } from "../__mocks__/shipping-profile"
|
||||||
import { CartModelMock, carts } from "../../models/__mocks__/cart"
|
import { CartModelMock, carts } from "../../models/__mocks__/cart"
|
||||||
import { LineItemServiceMock } from "../__mocks__/line-item"
|
import { LineItemServiceMock } from "../__mocks__/line-item"
|
||||||
@@ -193,7 +194,12 @@ describe("CartService", () => {
|
|||||||
_id: IdMap.getId("emptyCart"),
|
_id: IdMap.getId("emptyCart"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
$push: { items: lineItem },
|
$push: {
|
||||||
|
items: {
|
||||||
|
...lineItem,
|
||||||
|
has_shipping: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -225,7 +231,10 @@ describe("CartService", () => {
|
|||||||
"items._id": IdMap.getId("existingLine"),
|
"items._id": IdMap.getId("existingLine"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
$set: { "items.$.quantity": 20 },
|
$set: {
|
||||||
|
"items.$.quantity": 20,
|
||||||
|
"items.$.has_shipping": false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -267,7 +276,12 @@ describe("CartService", () => {
|
|||||||
_id: IdMap.getId("cartWithLine"),
|
_id: IdMap.getId("cartWithLine"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
$push: { items: lineItem },
|
$push: {
|
||||||
|
items: {
|
||||||
|
...lineItem,
|
||||||
|
has_shipping: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -390,10 +404,9 @@ describe("CartService", () => {
|
|||||||
expect(CartModelMock.updateOne).toHaveBeenCalledWith(
|
expect(CartModelMock.updateOne).toHaveBeenCalledWith(
|
||||||
{
|
{
|
||||||
_id: IdMap.getId("cartWithLine"),
|
_id: IdMap.getId("cartWithLine"),
|
||||||
"items._id": IdMap.getId("existingLine"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
$set: { "items.$.quantity": 9 },
|
$pull: { items: { _id: IdMap.getId("existingLine") } },
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -905,6 +918,7 @@ describe("CartService", () => {
|
|||||||
cartModel: CartModelMock,
|
cartModel: CartModelMock,
|
||||||
regionService: RegionServiceMock,
|
regionService: RegionServiceMock,
|
||||||
paymentProviderService: PaymentProviderServiceMock,
|
paymentProviderService: PaymentProviderServiceMock,
|
||||||
|
totalsService: TotalsServiceMock,
|
||||||
eventBusService: EventBusServiceMock,
|
eventBusService: EventBusServiceMock,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1183,6 +1197,26 @@ describe("CartService", () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("existingLine"),
|
||||||
|
title: "merge line",
|
||||||
|
description: "This is a new line",
|
||||||
|
has_shipping: true,
|
||||||
|
thumbnail: "test-img-yeah.com/thumb",
|
||||||
|
content: {
|
||||||
|
unit_price: 123,
|
||||||
|
variant: {
|
||||||
|
_id: IdMap.getId("can-cover"),
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
_id: IdMap.getId("product"),
|
||||||
|
},
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
quantity: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
shipping_methods: [
|
shipping_methods: [
|
||||||
{
|
{
|
||||||
_id: IdMap.getId("freeShipping"),
|
_id: IdMap.getId("freeShipping"),
|
||||||
@@ -1221,6 +1255,10 @@ describe("CartService", () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
|
items: carts.frCart.items.map(i => ({
|
||||||
|
...i,
|
||||||
|
has_shipping: false,
|
||||||
|
})),
|
||||||
shipping_methods: [
|
shipping_methods: [
|
||||||
{
|
{
|
||||||
_id: IdMap.getId("freeShipping"),
|
_id: IdMap.getId("freeShipping"),
|
||||||
@@ -1261,6 +1299,10 @@ describe("CartService", () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
|
items: carts.frCart.items.map(i => ({
|
||||||
|
...i,
|
||||||
|
has_shipping: false,
|
||||||
|
})),
|
||||||
shipping_methods: [
|
shipping_methods: [
|
||||||
{
|
{
|
||||||
_id: IdMap.getId("freeShipping"),
|
_id: IdMap.getId("freeShipping"),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
} from "../../models/__mocks__/dynamic-discount-code"
|
} 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 { EventBusServiceMock } from "../__mocks__/event-bus"
|
||||||
import { RegionServiceMock } from "../__mocks__/region"
|
import { RegionServiceMock } from "../__mocks__/region"
|
||||||
|
|
||||||
describe("DiscountService", () => {
|
describe("DiscountService", () => {
|
||||||
@@ -274,4 +275,32 @@ describe("DiscountService", () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("generateGiftCard", () => {
|
||||||
|
const discountService = new DiscountService({
|
||||||
|
discountModel: DiscountModelMock,
|
||||||
|
regionService: RegionServiceMock,
|
||||||
|
eventBusService: EventBusServiceMock,
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls model layer create", async () => {
|
||||||
|
await discountService.generateGiftCard(100, IdMap.getId("testRegion"))
|
||||||
|
|
||||||
|
expect(DiscountModelMock.create).toHaveBeenCalledTimes(1)
|
||||||
|
expect(DiscountModelMock.create).toHaveBeenCalledWith({
|
||||||
|
code: expect.stringMatching(/(([A-Z0-9]){4}(-?)){4}/),
|
||||||
|
is_giftcard: true,
|
||||||
|
discount_rule: {
|
||||||
|
type: "fixed",
|
||||||
|
allocation: "total",
|
||||||
|
value: 100,
|
||||||
|
},
|
||||||
|
regions: [IdMap.getId("testRegion")],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
@@ -3,6 +3,7 @@ import { OrderModelMock, orders } from "../../models/__mocks__/order"
|
|||||||
import { carts } from "../../models/__mocks__/cart"
|
import { carts } from "../../models/__mocks__/cart"
|
||||||
import OrderService from "../order"
|
import OrderService from "../order"
|
||||||
import { PaymentProviderServiceMock } from "../__mocks__/payment-provider"
|
import { PaymentProviderServiceMock } from "../__mocks__/payment-provider"
|
||||||
|
import { DiscountServiceMock } from "../__mocks__/discount"
|
||||||
import { FulfillmentProviderServiceMock } from "../__mocks__/fulfillment-provider"
|
import { FulfillmentProviderServiceMock } from "../__mocks__/fulfillment-provider"
|
||||||
import { ShippingProfileServiceMock } from "../__mocks__/shipping-profile"
|
import { ShippingProfileServiceMock } from "../__mocks__/shipping-profile"
|
||||||
import { TotalsServiceMock } from "../__mocks__/totals"
|
import { TotalsServiceMock } from "../__mocks__/totals"
|
||||||
@@ -36,6 +37,7 @@ describe("OrderService", () => {
|
|||||||
const orderService = new OrderService({
|
const orderService = new OrderService({
|
||||||
orderModel: OrderModelMock,
|
orderModel: OrderModelMock,
|
||||||
paymentProviderService: PaymentProviderServiceMock,
|
paymentProviderService: PaymentProviderServiceMock,
|
||||||
|
discountService: DiscountServiceMock,
|
||||||
regionService: RegionServiceMock,
|
regionService: RegionServiceMock,
|
||||||
eventBusService: EventBusServiceMock,
|
eventBusService: EventBusServiceMock,
|
||||||
})
|
})
|
||||||
@@ -60,6 +62,83 @@ describe("OrderService", () => {
|
|||||||
session: expect.anything(),
|
session: expect.anything(),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("creates cart with gift card", async () => {
|
||||||
|
await orderService.createFromCart(carts.withGiftCard)
|
||||||
|
|
||||||
|
const order = {
|
||||||
|
...carts.withGiftCard,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("existingLine"),
|
||||||
|
title: "merge line",
|
||||||
|
description: "This is a new line",
|
||||||
|
is_giftcard: false,
|
||||||
|
thumbnail: "test-img-yeah.com/thumb",
|
||||||
|
content: {
|
||||||
|
unit_price: 123,
|
||||||
|
variant: {
|
||||||
|
_id: IdMap.getId("can-cover"),
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
_id: IdMap.getId("product"),
|
||||||
|
},
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
quantity: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("giftline"),
|
||||||
|
title: "GiftCard",
|
||||||
|
description: "Gift card line",
|
||||||
|
thumbnail: "test-img-yeah.com/thumb",
|
||||||
|
metadata: {
|
||||||
|
giftcard: IdMap.getId("gift_card_id"),
|
||||||
|
name: "Test Name",
|
||||||
|
},
|
||||||
|
is_giftcard: true,
|
||||||
|
content: {
|
||||||
|
unit_price: 100,
|
||||||
|
variant: {
|
||||||
|
_id: IdMap.getId("giftCardVar"),
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
_id: IdMap.getId("giftCardProd"),
|
||||||
|
},
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
currency_code: "eur",
|
||||||
|
cart_id: carts.withGiftCard._id,
|
||||||
|
}
|
||||||
|
|
||||||
|
delete order._id
|
||||||
|
delete order.payment_sessions
|
||||||
|
|
||||||
|
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(2)
|
||||||
|
expect(EventBusServiceMock.emit).toHaveBeenCalledWith(
|
||||||
|
"order.gift_card_created",
|
||||||
|
{
|
||||||
|
currency_code: "eur",
|
||||||
|
tax_rate: 0.25,
|
||||||
|
email: "test",
|
||||||
|
giftcard: expect.any(Object),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(DiscountServiceMock.generateGiftCard).toHaveBeenCalledTimes(1)
|
||||||
|
expect(DiscountServiceMock.generateGiftCard).toHaveBeenCalledWith(
|
||||||
|
100,
|
||||||
|
IdMap.getId("region-france")
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(OrderModelMock.create).toHaveBeenCalledTimes(1)
|
||||||
|
expect(OrderModelMock.create).toHaveBeenCalledWith([order], {
|
||||||
|
session: expect.anything(),
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("retrieve", () => {
|
describe("retrieve", () => {
|
||||||
@@ -297,11 +376,9 @@ describe("OrderService", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("throws if payment is already processed", async () => {
|
it("throws if payment is already processed", async () => {
|
||||||
try {
|
await expect(
|
||||||
await orderService.capturePayment(IdMap.getId("payed-order"))
|
orderService.capturePayment(IdMap.getId("payed-order"))
|
||||||
} catch (error) {
|
).rejects.toThrow("Payment already captured")
|
||||||
expect(error.message).toEqual("Payment already captured")
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -324,16 +401,36 @@ describe("OrderService", () => {
|
|||||||
expect(OrderModelMock.updateOne).toHaveBeenCalledTimes(1)
|
expect(OrderModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||||
expect(OrderModelMock.updateOne).toHaveBeenCalledWith(
|
expect(OrderModelMock.updateOne).toHaveBeenCalledWith(
|
||||||
{ _id: IdMap.getId("test-order") },
|
{ _id: IdMap.getId("test-order") },
|
||||||
{ $set: { fulfillment_status: "fulfilled" } }
|
{
|
||||||
|
$set: {
|
||||||
|
fulfillment_status: "fulfilled",
|
||||||
|
shipping_methods: [
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("expensiveShipping"),
|
||||||
|
items: [],
|
||||||
|
name: "Expensive Shipping",
|
||||||
|
price: 100,
|
||||||
|
profile_id: IdMap.getId("default"),
|
||||||
|
provider_id: "default_provider",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("freeShipping"),
|
||||||
|
items: [],
|
||||||
|
name: "Free Shipping",
|
||||||
|
price: 10,
|
||||||
|
profile_id: IdMap.getId("profile1"),
|
||||||
|
provider_id: "default_provider",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("throws if payment is already processed", async () => {
|
it("throws if payment is already processed", async () => {
|
||||||
try {
|
await expect(
|
||||||
await orderService.createFulfillment(IdMap.getId("fulfilled-order"))
|
orderService.createFulfillment(IdMap.getId("fulfilled-order"))
|
||||||
} catch (error) {
|
).rejects.toThrow("Order is already fulfilled")
|
||||||
expect(error.message).toEqual("Order is already fulfilled")
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -353,6 +353,37 @@ describe("ShippingProfileService", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("fetchCartOptions", () => {
|
||||||
|
const profileService = new ShippingProfileService({
|
||||||
|
shippingProfileModel: ShippingProfileModelMock,
|
||||||
|
shippingOptionService: ShippingOptionServiceMock,
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("fetches correct options", async () => {
|
||||||
|
await profileService.fetchCartOptions({
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
content: { product: { _id: IdMap.getId("product_1") } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: { product: { _id: IdMap.getId("product_2") } },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(ShippingProfileModelMock.find).toBeCalledTimes(1)
|
||||||
|
expect(ShippingProfileModelMock.find).toBeCalledWith({
|
||||||
|
products: { $in: [IdMap.getId("product_1"), IdMap.getId("product_2")] },
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(ShippingOptionServiceMock.validateCartOption).toBeCalledTimes(2)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("addShippingOption", () => {
|
describe("addShippingOption", () => {
|
||||||
const profileService = new ShippingProfileService({
|
const profileService = new ShippingProfileService({
|
||||||
shippingProfileModel: ShippingProfileModelMock,
|
shippingProfileModel: ShippingProfileModelMock,
|
||||||
|
|||||||
@@ -249,6 +249,23 @@ class CartService extends BaseService {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of product ids in a line item.
|
||||||
|
* @param {LineItem} item - the line item to fetch products from
|
||||||
|
* @return {[string]} an array of product ids
|
||||||
|
*/
|
||||||
|
getItemProducts_(item) {
|
||||||
|
// Find all the products in the line item
|
||||||
|
const products = []
|
||||||
|
if (Array.isArray(item.content)) {
|
||||||
|
item.content.forEach(c => products.push(`${c.product._id}`))
|
||||||
|
} else {
|
||||||
|
products.push(`${item.content.product._id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return products
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a line item from the cart.
|
* Removes a line item from the cart.
|
||||||
* @param {string} cartId - the id of the cart that we will remove from
|
* @param {string} cartId - the id of the cart that we will remove from
|
||||||
@@ -258,33 +275,42 @@ class CartService extends BaseService {
|
|||||||
async removeLineItem(cartId, lineItemId) {
|
async removeLineItem(cartId, lineItemId) {
|
||||||
const cart = await this.retrieve(cartId)
|
const cart = await this.retrieve(cartId)
|
||||||
const itemToRemove = cart.items.find(line => line._id.equals(lineItemId))
|
const itemToRemove = cart.items.find(line => line._id.equals(lineItemId))
|
||||||
|
|
||||||
if (!itemToRemove) {
|
if (!itemToRemove) {
|
||||||
return Promise.resolve(cart)
|
return Promise.resolve(cart)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If cart has more than one of those line items, we update the quantity
|
const update = {
|
||||||
// instead of removing it
|
$pull: { items: { _id: itemToRemove._id } },
|
||||||
if (itemToRemove.quantity > 1) {
|
}
|
||||||
const newQuantity = itemToRemove.quantity - 1
|
|
||||||
|
|
||||||
return this.cartModel_
|
// Remove shipping methods if they are not needed
|
||||||
.updateOne(
|
if (cart.shipping_methods && cart.shipping_methods.length) {
|
||||||
{
|
const filteredItems = cart.items.filter(i => !i._id.equals(lineItemId))
|
||||||
_id: cartId,
|
|
||||||
"items._id": itemToRemove._id,
|
let newShippingMethods = await Promise.all(
|
||||||
},
|
cart.shipping_methods.map(async m => {
|
||||||
{
|
const profile = await this.shippingProfileService_.retrieve(
|
||||||
$set: {
|
m.profile_id
|
||||||
"items.$.quantity": newQuantity,
|
)
|
||||||
},
|
const hasItem = filteredItems.find(item => {
|
||||||
|
const products = this.getItemProducts_(item)
|
||||||
|
return products.some(p => profile.products.includes(p))
|
||||||
|
})
|
||||||
|
|
||||||
|
if (hasItem) {
|
||||||
|
return m
|
||||||
}
|
}
|
||||||
)
|
|
||||||
.then(result => {
|
return null
|
||||||
// Notify subscribers
|
|
||||||
this.eventBus_.emit(CartService.Events.UPDATED, result)
|
|
||||||
return result
|
|
||||||
})
|
})
|
||||||
|
)
|
||||||
|
newShippingMethods = newShippingMethods.filter(n => !!n)
|
||||||
|
|
||||||
|
if (newShippingMethods.length !== cart.shipping_methods.length) {
|
||||||
|
update.$set = {
|
||||||
|
shipping_methods: newShippingMethods,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.cartModel_
|
return this.cartModel_
|
||||||
@@ -292,11 +318,7 @@ class CartService extends BaseService {
|
|||||||
{
|
{
|
||||||
_id: cartId,
|
_id: cartId,
|
||||||
},
|
},
|
||||||
{
|
update
|
||||||
$pull: {
|
|
||||||
items: { _id: itemToRemove._id },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
.then(result => {
|
.then(result => {
|
||||||
// Notify subscribers
|
// Notify subscribers
|
||||||
@@ -305,6 +327,34 @@ class CartService extends BaseService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a given line item has a shipping method that can fulfill it.
|
||||||
|
* Returns true if all products in the cart can be fulfilled with the current
|
||||||
|
* shipping methods.
|
||||||
|
* @param {Cart} cart - the cart
|
||||||
|
* @param {LineItem} lineItem - the line item
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
async validateLineItemShipping_(shippingMethods, lineItem) {
|
||||||
|
if (shippingMethods && shippingMethods.length) {
|
||||||
|
const profiles = await Promise.all(
|
||||||
|
shippingMethods.map(m =>
|
||||||
|
this.shippingProfileService_.retrieve(m.profile_id)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
const products = this.getItemProducts_(lineItem)
|
||||||
|
|
||||||
|
// Check if there is a shipping method for each product
|
||||||
|
const hasShipping = products.map(
|
||||||
|
p => !!profiles.find(profile => profile.products.includes(p))
|
||||||
|
)
|
||||||
|
return hasShipping.every(b => b)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a line item to the cart.
|
* Adds a line item to the cart.
|
||||||
* @param {string} cartId - the id of the cart that we will add to
|
* @param {string} cartId - the id of the cart that we will add to
|
||||||
@@ -318,6 +368,11 @@ class CartService extends BaseService {
|
|||||||
this.lineItemService_.isEqual(line, validatedLineItem)
|
this.lineItemService_.isEqual(line, validatedLineItem)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const hasShipping = await this.validateLineItemShipping_(
|
||||||
|
cart.shipping_methods,
|
||||||
|
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
|
||||||
// simply update the quantity of the existing line item
|
// simply update the quantity of the existing line item
|
||||||
if (currentItem) {
|
if (currentItem) {
|
||||||
@@ -345,6 +400,7 @@ class CartService extends BaseService {
|
|||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
"items.$.quantity": newQuantity,
|
"items.$.quantity": newQuantity,
|
||||||
|
"items.$.has_shipping": hasShipping,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -375,7 +431,12 @@ class CartService extends BaseService {
|
|||||||
_id: cartId,
|
_id: cartId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
$push: { items: validatedLineItem },
|
$push: {
|
||||||
|
items: {
|
||||||
|
...validatedLineItem,
|
||||||
|
has_shipping: hasShipping,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.then(result => {
|
.then(result => {
|
||||||
@@ -808,6 +869,25 @@ class CartService extends BaseService {
|
|||||||
const cart = await this.retrieve(cartId)
|
const cart = await this.retrieve(cartId)
|
||||||
const region = await this.regionService_.retrieve(cart.region_id)
|
const region = await this.regionService_.retrieve(cart.region_id)
|
||||||
|
|
||||||
|
const total = await this.totalsService_.getTotal(cart)
|
||||||
|
|
||||||
|
if (total === 0) {
|
||||||
|
return this.cartModel_
|
||||||
|
.updateOne(
|
||||||
|
{
|
||||||
|
_id: cart._id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: { payment_sessions: [] },
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(result => {
|
||||||
|
// Notify subscribers
|
||||||
|
this.eventBus_.emit(CartService.Events.UPDATED, result)
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// If there are existing payment sessions ensure that these are up to date
|
// If there are existing payment sessions ensure that these are up to date
|
||||||
let sessions = []
|
let sessions = []
|
||||||
if (cart.payment_sessions && cart.payment_sessions.length) {
|
if (cart.payment_sessions && cart.payment_sessions.length) {
|
||||||
@@ -817,10 +897,19 @@ class CartService extends BaseService {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await this.paymentProviderService_.updateSession(
|
let data
|
||||||
pSession,
|
try {
|
||||||
cart
|
data = await this.paymentProviderService_.updateSession(
|
||||||
)
|
pSession,
|
||||||
|
cart
|
||||||
|
)
|
||||||
|
} catch (err) {
|
||||||
|
data = await this.paymentProviderService_.createSession(
|
||||||
|
pSession.provider_id,
|
||||||
|
cart
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
provider_id: pSession.provider_id,
|
provider_id: pSession.provider_id,
|
||||||
data,
|
data,
|
||||||
@@ -981,13 +1070,27 @@ class CartService extends BaseService {
|
|||||||
newMethods.push(option)
|
newMethods.push(option)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newItems = await Promise.all(
|
||||||
|
cart.items.map(async item => {
|
||||||
|
const hasShipping = await this.validateLineItemShipping_(
|
||||||
|
newMethods,
|
||||||
|
item
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
has_shipping: hasShipping,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
return this.cartModel_
|
return this.cartModel_
|
||||||
.updateOne(
|
.updateOne(
|
||||||
{
|
{
|
||||||
_id: cart._id,
|
_id: cart._id,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
$set: { shipping_methods: newMethods },
|
$set: { shipping_methods: newMethods, items: newItems },
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.then(result => {
|
.then(result => {
|
||||||
@@ -1045,20 +1148,15 @@ class CartService extends BaseService {
|
|||||||
update.shipping_methods = []
|
update.shipping_methods = []
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (cart.items.length && cart.payment_sessions.length) {
|
if (cart.discounts && cart.discounts.length) {
|
||||||
// update.payment_sessions = await Promise.all(
|
const newDiscounts = cart.discounts.map(d => {
|
||||||
// region.payment_providers.map(async pId => {
|
if (d.regions.includes(regionId)) {
|
||||||
// const data = await this.paymentProviderService_.createSession(pId, {
|
return d
|
||||||
// ...cart,
|
}
|
||||||
// ...update,
|
})
|
||||||
// })
|
|
||||||
// return {
|
update.discounts = newDiscounts.filter(d => !!d)
|
||||||
// provider_id: pId,
|
}
|
||||||
// data,
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// )
|
|
||||||
//}
|
|
||||||
|
|
||||||
// Payment methods are region specific so the user needs to find a
|
// Payment methods are region specific so the user needs to find a
|
||||||
// new payment method
|
// new payment method
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
import _ from "lodash"
|
||||||
|
import randomize from "randomatic"
|
||||||
import { BaseService } from "medusa-interfaces"
|
import { BaseService } from "medusa-interfaces"
|
||||||
import { Validator, MedusaError } from "medusa-core-utils"
|
import { Validator, MedusaError } from "medusa-core-utils"
|
||||||
import _ from "lodash"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides layer to manipulate discounts.
|
* Provides layer to manipulate discounts.
|
||||||
@@ -13,6 +14,7 @@ class DiscountService extends BaseService {
|
|||||||
totalsService,
|
totalsService,
|
||||||
productVariantService,
|
productVariantService,
|
||||||
regionService,
|
regionService,
|
||||||
|
eventBusService,
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
@@ -30,6 +32,9 @@ class DiscountService extends BaseService {
|
|||||||
|
|
||||||
/** @private @const {RegionService} */
|
/** @private @const {RegionService} */
|
||||||
this.regionService_ = regionService
|
this.regionService_ = regionService
|
||||||
|
|
||||||
|
/** @private @const {EventBus} */
|
||||||
|
this.eventBus_ = eventBusService
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,7 +65,7 @@ class DiscountService extends BaseService {
|
|||||||
description: Validator.string(),
|
description: Validator.string(),
|
||||||
type: Validator.string().required(),
|
type: Validator.string().required(),
|
||||||
value: Validator.number()
|
value: Validator.number()
|
||||||
.positive()
|
.min(0)
|
||||||
.required(),
|
.required(),
|
||||||
allocation: Validator.string().required(),
|
allocation: Validator.string().required(),
|
||||||
valid_for: Validator.array().items(Validator.string()),
|
valid_for: Validator.array().items(Validator.string()),
|
||||||
@@ -210,6 +215,38 @@ class DiscountService extends BaseService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a gift card with the specified value which is valid in the
|
||||||
|
* specified region.
|
||||||
|
* @param {number} value - the value that the gift card represents
|
||||||
|
* @param {string} regionId - the id of the region in which the gift card can
|
||||||
|
* be used
|
||||||
|
* @return {Discount} the newly created gift card
|
||||||
|
*/
|
||||||
|
async generateGiftCard(value, regionId) {
|
||||||
|
const region = await this.regionService_.retrieve(regionId)
|
||||||
|
|
||||||
|
const code = [
|
||||||
|
randomize("A0", 4),
|
||||||
|
randomize("A0", 4),
|
||||||
|
randomize("A0", 4),
|
||||||
|
randomize("A0", 4),
|
||||||
|
].join("-")
|
||||||
|
|
||||||
|
const discountRule = this.validateDiscountRule_({
|
||||||
|
type: "fixed",
|
||||||
|
allocation: "total",
|
||||||
|
value,
|
||||||
|
})
|
||||||
|
|
||||||
|
return this.discountModel_.create({
|
||||||
|
code,
|
||||||
|
discount_rule: discountRule,
|
||||||
|
is_giftcard: true,
|
||||||
|
regions: [region._id],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a dynamic code for a discount id.
|
* Creates a dynamic code for a discount id.
|
||||||
* @param {string} discountId - the id of the discount to create a code for
|
* @param {string} discountId - the id of the discount to create a code for
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class LineItemService extends BaseService {
|
|||||||
|
|
||||||
const lineItemSchema = Validator.object({
|
const lineItemSchema = Validator.object({
|
||||||
title: Validator.string().required(),
|
title: Validator.string().required(),
|
||||||
|
is_giftcard: Validator.bool().optional(),
|
||||||
description: Validator.string()
|
description: Validator.string()
|
||||||
.allow("")
|
.allow("")
|
||||||
.optional(),
|
.optional(),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { BaseService } from "medusa-interfaces"
|
|||||||
|
|
||||||
class OrderService extends BaseService {
|
class OrderService extends BaseService {
|
||||||
static Events = {
|
static Events = {
|
||||||
|
GIFT_CARD_CREATED: "order.gift_card_created",
|
||||||
PLACED: "order.placed",
|
PLACED: "order.placed",
|
||||||
UPDATED: "order.updated",
|
UPDATED: "order.updated",
|
||||||
CANCELLED: "order.cancelled",
|
CANCELLED: "order.cancelled",
|
||||||
@@ -14,6 +15,7 @@ class OrderService extends BaseService {
|
|||||||
orderModel,
|
orderModel,
|
||||||
paymentProviderService,
|
paymentProviderService,
|
||||||
shippingProfileService,
|
shippingProfileService,
|
||||||
|
discountService,
|
||||||
fulfillmentProviderService,
|
fulfillmentProviderService,
|
||||||
lineItemService,
|
lineItemService,
|
||||||
totalsService,
|
totalsService,
|
||||||
@@ -43,6 +45,9 @@ class OrderService extends BaseService {
|
|||||||
/** @private @const {RegionService} */
|
/** @private @const {RegionService} */
|
||||||
this.regionService_ = regionService
|
this.regionService_ = regionService
|
||||||
|
|
||||||
|
/** @private @const {DiscountService} */
|
||||||
|
this.discountService_ = discountService
|
||||||
|
|
||||||
/** @private @const {EventBus} */
|
/** @private @const {EventBus} */
|
||||||
this.eventBus_ = eventBusService
|
this.eventBus_ = eventBusService
|
||||||
}
|
}
|
||||||
@@ -306,6 +311,33 @@ class OrderService extends BaseService {
|
|||||||
paymentSession.data
|
paymentSession.data
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Generate gift cards if in cart
|
||||||
|
const items = await Promise.all(
|
||||||
|
cart.items.map(async i => {
|
||||||
|
if (i.is_giftcard) {
|
||||||
|
const giftcard = await this.discountService_
|
||||||
|
.generateGiftCard(i.content.unit_price, region._id)
|
||||||
|
.then(result => {
|
||||||
|
this.eventBus_.emit(OrderService.Events.GIFT_CARD_CREATED, {
|
||||||
|
currency_code: region.currency_code,
|
||||||
|
tax_rate: region.tax_rate,
|
||||||
|
giftcard: result,
|
||||||
|
email: cart.email,
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
...i,
|
||||||
|
metadata: {
|
||||||
|
...i.metadata,
|
||||||
|
giftcard: giftcard._id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
const o = {
|
const o = {
|
||||||
payment_method: {
|
payment_method: {
|
||||||
provider_id: paymentSession.provider_id,
|
provider_id: paymentSession.provider_id,
|
||||||
@@ -313,7 +345,7 @@ class OrderService extends BaseService {
|
|||||||
},
|
},
|
||||||
discounts: cart.discounts,
|
discounts: cart.discounts,
|
||||||
shipping_methods: cart.shipping_methods,
|
shipping_methods: cart.shipping_methods,
|
||||||
items: cart.items,
|
items,
|
||||||
shipping_address: cart.shipping_address,
|
shipping_address: cart.shipping_address,
|
||||||
billing_address: cart.shipping_address,
|
billing_address: cart.shipping_address,
|
||||||
region_id: cart.region_id,
|
region_id: cart.region_id,
|
||||||
@@ -329,7 +361,7 @@ class OrderService extends BaseService {
|
|||||||
|
|
||||||
// Emit and return
|
// Emit and return
|
||||||
this.eventBus_.emit(OrderService.Events.PLACED, orderDocument[0])
|
this.eventBus_.emit(OrderService.Events.PLACED, orderDocument[0])
|
||||||
return orderDocument[0].toObject()
|
return orderDocument[0]
|
||||||
})
|
})
|
||||||
.then(() => this.orderModel_.findOne({ cart_id: cart._id }))
|
.then(() => this.orderModel_.findOne({ cart_id: cart._id }))
|
||||||
}
|
}
|
||||||
@@ -541,14 +573,17 @@ class OrderService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// partition order items to their dedicated shipping method
|
// partition order items to their dedicated shipping method
|
||||||
order.shipping_methods = await this.partitionItems_(shipping_methods, items)
|
updateFields.shipping_methods = await this.partitionItems_(
|
||||||
|
shipping_methods,
|
||||||
|
items
|
||||||
|
)
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
order.shipping_methods.map(method => {
|
updateFields.shipping_methods.map(method => {
|
||||||
const provider = this.fulfillmentProviderService_.retrieveProvider(
|
const provider = this.fulfillmentProviderService_.retrieveProvider(
|
||||||
method.provider_id
|
method.provider_id
|
||||||
)
|
)
|
||||||
provider.createOrder(method.data, method.items)
|
return provider.createOrder(method.data, method.items)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -143,7 +143,14 @@ class ProductService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (update.variants) {
|
if (update.variants) {
|
||||||
update.variants = await Promise.all(
|
const existingVariants = await this.retrieveVariants(validatedId)
|
||||||
|
for (const existing of existingVariants) {
|
||||||
|
if (!update.variants.find(v => v._id && existing._id.equals(v._id))) {
|
||||||
|
await this.deleteVariant(productId, existing._id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
update.variants.map(async variant => {
|
update.variants.map(async variant => {
|
||||||
if (variant._id) {
|
if (variant._id) {
|
||||||
if (variant.prices && variant.prices.length) {
|
if (variant.prices && variant.prices.length) {
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ class ShippingProfileService extends BaseService {
|
|||||||
* Retrieves the default gift card profile
|
* Retrieves the default gift card profile
|
||||||
* @return the shipping profile for gift cards
|
* @return the shipping profile for gift cards
|
||||||
*/
|
*/
|
||||||
async retrieveGiftCardProfile() {
|
async retrieveGiftCardDefault() {
|
||||||
return await this.profileModel_
|
return await this.profileModel_
|
||||||
.findOne({ name: "default_gift_card_profile" })
|
.findOne({ name: "default_gift_card_profile" })
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
@@ -115,7 +115,7 @@ class ShippingProfileService extends BaseService {
|
|||||||
* @return {Promise<ShippingProfile>} the shipping profile
|
* @return {Promise<ShippingProfile>} the shipping profile
|
||||||
*/
|
*/
|
||||||
async createGiftCardDefault() {
|
async createGiftCardDefault() {
|
||||||
const profile = await this.retrieveGiftCardProfile()
|
const profile = await this.retrieveGiftCardDefault()
|
||||||
if (!profile) {
|
if (!profile) {
|
||||||
return this.profileModel_.create({ name: "default_gift_card_profile" })
|
return this.profileModel_.create({ name: "default_gift_card_profile" })
|
||||||
}
|
}
|
||||||
@@ -388,13 +388,21 @@ class ShippingProfileService extends BaseService {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const options = await Promise.all(
|
const options = await Promise.all(
|
||||||
optionIds.map(oId => {
|
optionIds.map(async oId => {
|
||||||
return this.shippingOptionService_
|
const option = await this.shippingOptionService_
|
||||||
.validateCartOption(oId, cart)
|
.validateCartOption(oId, cart)
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
// If validation failed we skip the option
|
// If validation failed we skip the option
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (option) {
|
||||||
|
return {
|
||||||
|
...option,
|
||||||
|
profile: profiles.find(p => p._id.equals(option.profile_id)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -292,36 +292,29 @@ class TotalsService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { type, allocation, value } = discount.discount_rule
|
const { type, allocation, value } = discount.discount_rule
|
||||||
|
let toReturn = 0
|
||||||
|
|
||||||
if (type === "percentage" && allocation === "total") {
|
if (type === "percentage" && allocation === "total") {
|
||||||
return (subtotal / 100) * value
|
toReturn = (subtotal / 100) * value
|
||||||
}
|
} else if (type === "percentage" && allocation === "item") {
|
||||||
|
|
||||||
if (type === "percentage" && allocation === "item") {
|
|
||||||
const itemPercentageDiscounts = await this.getAllocationItemDiscounts(
|
const itemPercentageDiscounts = await this.getAllocationItemDiscounts(
|
||||||
discount,
|
discount,
|
||||||
cart,
|
cart,
|
||||||
"percentage"
|
"percentage"
|
||||||
)
|
)
|
||||||
const totalDiscount = _.sumBy(itemPercentageDiscounts, d => d.amount)
|
toReturn = _.sumBy(itemPercentageDiscounts, d => d.amount)
|
||||||
return totalDiscount
|
} else if (type === "fixed" && allocation === "total") {
|
||||||
}
|
toReturn = value
|
||||||
|
} else if (type === "fixed" && allocation === "item") {
|
||||||
if (type === "fixed" && allocation === "total") {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === "fixed" && allocation === "item") {
|
|
||||||
const itemFixedDiscounts = await this.getAllocationItemDiscounts(
|
const itemFixedDiscounts = await this.getAllocationItemDiscounts(
|
||||||
discount,
|
discount,
|
||||||
cart,
|
cart,
|
||||||
"fixed"
|
"fixed"
|
||||||
)
|
)
|
||||||
const totalDiscount = _.sumBy(itemFixedDiscounts, d => d.amount)
|
toReturn = _.sumBy(itemFixedDiscounts, d => d.amount)
|
||||||
return totalDiscount
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return Math.min(subtotal, toReturn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,17 @@ class OrderSubscriber {
|
|||||||
cartService,
|
cartService,
|
||||||
customerService,
|
customerService,
|
||||||
eventBusService,
|
eventBusService,
|
||||||
|
discountService,
|
||||||
|
totalsService,
|
||||||
}) {
|
}) {
|
||||||
|
this.totalsService_ = totalsService
|
||||||
|
|
||||||
this.paymentProviderService_ = paymentProviderService
|
this.paymentProviderService_ = paymentProviderService
|
||||||
|
|
||||||
this.customerService_ = customerService
|
this.customerService_ = customerService
|
||||||
|
|
||||||
|
this.discountService_ = discountService
|
||||||
|
|
||||||
this.cartService_ = cartService
|
this.cartService_ = cartService
|
||||||
|
|
||||||
this.eventBus_ = eventBusService
|
this.eventBus_ = eventBusService
|
||||||
@@ -33,6 +41,34 @@ class OrderSubscriber {
|
|||||||
this.eventBus_.subscribe("order.placed", async order => {
|
this.eventBus_.subscribe("order.placed", async order => {
|
||||||
await this.cartService_.delete(order.cart_id)
|
await this.cartService_.delete(order.cart_id)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.eventBus_.subscribe("order.placed", this.handleDiscounts)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDiscounts = async order => {
|
||||||
|
await Promise.all(
|
||||||
|
order.discounts.map(async d => {
|
||||||
|
const subtotal = await this.totalsService_.getSubtotal(order)
|
||||||
|
if (d.is_giftcard) {
|
||||||
|
const discountRule = {
|
||||||
|
...d.discount_rule,
|
||||||
|
value: Math.max(0, d.discount_rule.value - subtotal),
|
||||||
|
}
|
||||||
|
|
||||||
|
delete discountRule._id
|
||||||
|
|
||||||
|
return this.discountService_.update(d._id, {
|
||||||
|
discount_rule: discountRule,
|
||||||
|
usage_count: d.usage_count + 1,
|
||||||
|
disabled: discountRule.value === 0,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return this.discountService_.update(d._id, {
|
||||||
|
usage_count: d.usage_count + 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3665,6 +3665,11 @@ is-number@^3.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
kind-of "^3.0.2"
|
kind-of "^3.0.2"
|
||||||
|
|
||||||
|
is-number@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff"
|
||||||
|
integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==
|
||||||
|
|
||||||
is-number@^7.0.0:
|
is-number@^7.0.0:
|
||||||
version "7.0.0"
|
version "7.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
|
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
|
||||||
@@ -4553,6 +4558,11 @@ map-visit@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
object-visit "^1.0.0"
|
object-visit "^1.0.0"
|
||||||
|
|
||||||
|
math-random@^1.0.1:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c"
|
||||||
|
integrity sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==
|
||||||
|
|
||||||
media-typer@0.3.0:
|
media-typer@0.3.0:
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||||
@@ -5501,6 +5511,15 @@ random-bytes@~1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b"
|
resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b"
|
||||||
integrity sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=
|
integrity sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=
|
||||||
|
|
||||||
|
randomatic@^3.1.1:
|
||||||
|
version "3.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed"
|
||||||
|
integrity sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==
|
||||||
|
dependencies:
|
||||||
|
is-number "^4.0.0"
|
||||||
|
kind-of "^6.0.0"
|
||||||
|
math-random "^1.0.1"
|
||||||
|
|
||||||
range-parser@~1.2.1:
|
range-parser@~1.2.1:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
|
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
|
||||||
|
|||||||
Reference in new issue
Block a user