feat(medusa): adds support for gift cards (#132)
* fix(medusa-plugin-brightpearl): adds gift cards to gift card nominal code * fix: allow orders with 0 total to be created * fix: gift card ready * tests passing * docs: brightpearl comment * docs: clearer REMEMBER note
This commit is contained in:
@@ -13,8 +13,9 @@ Sends orders to Brightpearl, listens for stock movements, handles returns.
|
||||
default_status_id: [the status id to assign new orders with] (optional: defaults to 1)
|
||||
payment_method_code: [the method code to register payments with] (optional: defaults to 1220)
|
||||
sales_account_code: [nominal code to assign line items to] (optional: defaults to 4000)
|
||||
shipping_account_code: [nominal code to assign shipping line to] (optional: defaults to 4040)jk
|
||||
shipping_account_code: [nominal code to assign shipping line to] (optional: defaults to 4040)
|
||||
discount_account_code: [nominal code to use for Discount-type refunds] (optional)
|
||||
gift_card_account_code: [nominal code to use for gift card products and redeems] (optional: default to 4000)
|
||||
inventory_sync_cron: [cron pattern for inventory sync, if left out the job will not be created] (default: false)
|
||||
```
|
||||
|
||||
|
||||
@@ -501,7 +501,7 @@ class BrightpearlService extends BaseService {
|
||||
({ discount_rule }) => discount_rule.type !== "free_shipping"
|
||||
)
|
||||
let lineDiscounts = []
|
||||
if (discount) {
|
||||
if (discount && !discount.is_giftcard) {
|
||||
lineDiscounts = this.totalsService_.getLineDiscounts(fromOrder, discount)
|
||||
}
|
||||
|
||||
@@ -511,7 +511,7 @@ class BrightpearlService extends BaseService {
|
||||
item.content.variant.sku
|
||||
)
|
||||
|
||||
const discount = lineDiscounts.find((l) => item._id === l.item._id) || {
|
||||
const ld = lineDiscounts.find((l) => item._id === l.item._id) || {
|
||||
amount: 0,
|
||||
}
|
||||
|
||||
@@ -522,7 +522,7 @@ class BrightpearlService extends BaseService {
|
||||
row.name = item.title
|
||||
}
|
||||
row.net = this.totalsService_.rounded(
|
||||
item.content.unit_price * item.quantity - discount.amount
|
||||
item.content.unit_price * item.quantity - ld.amount
|
||||
)
|
||||
row.tax = this.totalsService_.rounded(row.net * fromOrder.tax_rate)
|
||||
row.quantity = item.quantity
|
||||
@@ -530,10 +530,34 @@ class BrightpearlService extends BaseService {
|
||||
row.externalRef = item._id
|
||||
row.nominalCode = this.options.sales_account_code || "4000"
|
||||
|
||||
if (item.is_giftcard) {
|
||||
row.nominalCode = this.options.gift_card_account_code || "4000"
|
||||
}
|
||||
|
||||
return row
|
||||
})
|
||||
)
|
||||
|
||||
// If a gift card was applied to the order we reduce the order amount
|
||||
// correspondingly. This reduces the amount payable, while debiting the
|
||||
// gift card account that was previously credited, when the gift card was
|
||||
// purchased.
|
||||
if (discount && discount.is_giftcard) {
|
||||
const discountTotal = await this.totalsService_.getDiscountTotal(
|
||||
fromOrder
|
||||
)
|
||||
lines.push({
|
||||
name: `Gift Card: ${discount.code}`,
|
||||
net: -1 * discountTotal,
|
||||
tax: this.totalsService_.rounded(
|
||||
-1 * discountTotal * fromOrder.tax_rate
|
||||
),
|
||||
quantity: 1,
|
||||
taxCode: region.tax_code,
|
||||
nominalCode: this.options.gift_card_account_code || "4000",
|
||||
})
|
||||
}
|
||||
|
||||
const shippingTotal = this.totalsService_.getShippingTotal(fromOrder)
|
||||
const shippingMethods = fromOrder.shipping_methods
|
||||
if (shippingMethods.length > 0) {
|
||||
|
||||
@@ -7,6 +7,7 @@ describe("GET /admin/discounts", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
subject = await request("GET", `/admin/discounts`, {
|
||||
adminSession: {
|
||||
jwt: {
|
||||
@@ -22,7 +23,61 @@ describe("GET /admin/discounts", () => {
|
||||
|
||||
it("calls service retrieve", () => {
|
||||
expect(DiscountServiceMock.list).toHaveBeenCalledTimes(1)
|
||||
expect(DiscountServiceMock.list).toHaveBeenCalledWith()
|
||||
expect(DiscountServiceMock.list).toHaveBeenCalledWith({})
|
||||
})
|
||||
})
|
||||
|
||||
describe("is_giftcard filter", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
subject = await request("GET", `/admin/discounts?is_giftcard=true`, {
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("calls service retrieve", () => {
|
||||
expect(DiscountServiceMock.list).toHaveBeenCalledTimes(1)
|
||||
expect(DiscountServiceMock.list).toHaveBeenCalledWith({
|
||||
is_giftcard: true,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("expand region filter", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
subject = await request("GET", `/admin/discounts?expand_fields=regions`, {
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("calls service retrieve", () => {
|
||||
expect(DiscountServiceMock.decorate).toHaveBeenCalledTimes(1)
|
||||
expect(
|
||||
DiscountServiceMock.decorate
|
||||
).toHaveBeenCalledWith(expect.anything(), expect.anything(), ["regions"])
|
||||
expect(DiscountServiceMock.list).toHaveBeenCalledTimes(1)
|
||||
expect(DiscountServiceMock.list).toHaveBeenCalledWith({})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,34 @@
|
||||
export default async (req, res) => {
|
||||
try {
|
||||
const selector = {}
|
||||
|
||||
const discountService = req.scope.resolve("discountService")
|
||||
const data = await discountService.list()
|
||||
|
||||
if ("is_giftcard" in req.query) {
|
||||
selector.is_giftcard = req.query.is_giftcard === "true"
|
||||
}
|
||||
|
||||
let expandFields = []
|
||||
if ("expand_fields" in req.query) {
|
||||
expandFields = req.query.expand_fields.split(",")
|
||||
}
|
||||
|
||||
let includeFields = [
|
||||
"usage_count",
|
||||
"starts_at",
|
||||
"ends_at",
|
||||
"original_amount",
|
||||
"created",
|
||||
]
|
||||
if ("fields" in req.query) {
|
||||
includeFields = req.query.fields.split(",")
|
||||
}
|
||||
|
||||
const raw = await discountService.list(selector)
|
||||
|
||||
const data = await Promise.all(
|
||||
raw.map(d => discountService.decorate(d, includeFields, expandFields))
|
||||
)
|
||||
|
||||
res.status(200).json({ discounts: data })
|
||||
} catch (err) {
|
||||
|
||||
@@ -10,6 +10,10 @@ export default async (req, res) => {
|
||||
"description",
|
||||
])
|
||||
|
||||
if ("is_giftcard" in req.query) {
|
||||
query.is_giftcard = req.query.is_giftcard === "true"
|
||||
}
|
||||
|
||||
const limit = parseInt(req.query.limit) || 0
|
||||
const offset = parseInt(req.query.offset) || 0
|
||||
|
||||
@@ -40,7 +44,6 @@ export default async (req, res) => {
|
||||
|
||||
res.json({ products, total_count: numProducts })
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ class DiscountModel extends BaseModel {
|
||||
starts_at: { type: Date },
|
||||
ends_at: { type: Date },
|
||||
regions: { type: [String], default: [] },
|
||||
original_amount: { type: Number },
|
||||
created: { type: String, default: Date.now },
|
||||
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,8 +36,8 @@ class OrderModel extends BaseModel {
|
||||
region_id: { type: String, required: true },
|
||||
discounts: { type: [DiscountSchema], default: [] },
|
||||
customer_id: { type: String },
|
||||
payment_method: { type: PaymentMethodSchema, required: true },
|
||||
shipping_methods: { type: [ShippingMethodSchema], required: true },
|
||||
payment_method: { type: PaymentMethodSchema, default: {} },
|
||||
shipping_methods: { type: [ShippingMethodSchema], default: [] },
|
||||
documents: { type: [String], default: [] },
|
||||
created: { type: String, default: Date.now },
|
||||
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
******************************************************************************/
|
||||
import mongoose from "mongoose"
|
||||
|
||||
/**
|
||||
* REMEMBER: When updating this line you must also update the LineItemService's
|
||||
* validate method too. Otherwise we cannot copy lines directly.
|
||||
*/
|
||||
export default new mongoose.Schema(
|
||||
{
|
||||
title: { type: String, required: true },
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
import mongoose from "mongoose"
|
||||
|
||||
export default new mongoose.Schema({
|
||||
provider_id: { type: String, required: true },
|
||||
provider_id: { type: String },
|
||||
data: { type: mongoose.Schema.Types.Mixed, default: {} },
|
||||
})
|
||||
|
||||
@@ -40,7 +40,7 @@ export const DiscountServiceMock = {
|
||||
})
|
||||
}),
|
||||
list: jest.fn().mockImplementation(data => {
|
||||
return Promise.resolve([])
|
||||
return Promise.resolve([{}])
|
||||
}),
|
||||
decorate: jest.fn().mockImplementation(data => {
|
||||
return Promise.resolve(data)
|
||||
|
||||
@@ -294,6 +294,7 @@ describe("DiscountService", () => {
|
||||
expect(DiscountModelMock.create).toHaveBeenCalledWith({
|
||||
code: expect.stringMatching(/(([A-Z0-9]){4}(-?)){4}/),
|
||||
is_giftcard: true,
|
||||
original_amount: 100,
|
||||
discount_rule: {
|
||||
type: "fixed",
|
||||
allocation: "total",
|
||||
|
||||
@@ -45,6 +45,7 @@ describe("OrderService", () => {
|
||||
const orderService = new OrderService({
|
||||
orderModel: OrderModelMock,
|
||||
paymentProviderService: PaymentProviderServiceMock,
|
||||
totalsService: TotalsServiceMock,
|
||||
discountService: DiscountServiceMock,
|
||||
regionService: RegionServiceMock,
|
||||
eventBusService: EventBusServiceMock,
|
||||
@@ -56,7 +57,10 @@ describe("OrderService", () => {
|
||||
})
|
||||
|
||||
it("calls order model functions", async () => {
|
||||
await orderService.createFromCart(carts.completeCart)
|
||||
await orderService.createFromCart({
|
||||
...carts.completeCart,
|
||||
total: 100,
|
||||
})
|
||||
|
||||
const order = {
|
||||
...carts.completeCart,
|
||||
@@ -74,8 +78,34 @@ describe("OrderService", () => {
|
||||
})
|
||||
})
|
||||
|
||||
it("creates cart with 0 total", async () => {
|
||||
await orderService.createFromCart({
|
||||
...carts.completeCart,
|
||||
total: 0,
|
||||
})
|
||||
|
||||
const order = {
|
||||
...carts.completeCart,
|
||||
payment_method: {},
|
||||
currency_code: "eur",
|
||||
cart_id: carts.completeCart._id,
|
||||
tax_rate: 0.25,
|
||||
metadata: {},
|
||||
}
|
||||
delete order._id
|
||||
delete order.payment_sessions
|
||||
|
||||
expect(OrderModelMock.create).toHaveBeenCalledTimes(1)
|
||||
expect(OrderModelMock.create).toHaveBeenCalledWith([order], {
|
||||
session: expect.anything(),
|
||||
})
|
||||
})
|
||||
|
||||
it("creates cart with gift card", async () => {
|
||||
await orderService.createFromCart(carts.withGiftCard)
|
||||
await orderService.createFromCart({
|
||||
...carts.withGiftCard,
|
||||
total: 100,
|
||||
})
|
||||
|
||||
const order = {
|
||||
...carts.withGiftCard,
|
||||
@@ -105,7 +135,6 @@ describe("OrderService", () => {
|
||||
description: "Gift card line",
|
||||
thumbnail: "test-img-yeah.com/thumb",
|
||||
metadata: {
|
||||
giftcard: IdMap.getId("gift_card_id"),
|
||||
name: "Test Name",
|
||||
},
|
||||
is_giftcard: true,
|
||||
@@ -130,24 +159,6 @@ describe("OrderService", () => {
|
||||
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),
|
||||
line_item: 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(),
|
||||
|
||||
@@ -248,6 +248,7 @@ class DiscountService extends BaseService {
|
||||
discount_rule: discountRule,
|
||||
is_giftcard: true,
|
||||
regions: [region._id],
|
||||
original_amount: value,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -444,13 +445,42 @@ class DiscountService extends BaseService {
|
||||
* @return {Discount} return the decorated discount.
|
||||
*/
|
||||
async decorate(discount, fields = [], expandFields = []) {
|
||||
const requiredFields = ["_id", "metadata"]
|
||||
const requiredFields = [
|
||||
"_id",
|
||||
"code",
|
||||
"regions",
|
||||
"discount_rule",
|
||||
"is_dynamic",
|
||||
"is_giftcard",
|
||||
"disabled",
|
||||
"metadata",
|
||||
]
|
||||
const decorated = _.pick(discount, fields.concat(requiredFields))
|
||||
|
||||
if (expandFields.includes("valid_for")) {
|
||||
let prods = {}
|
||||
decorated.discount_rule.valid_for = await Promise.all(
|
||||
decorated.discount_rule.valid_for.map(async p => {
|
||||
return this.productService_.retrieve(p)
|
||||
if (p in prods) {
|
||||
return prods[p]
|
||||
}
|
||||
const next = await this.productService_.retrieve(p)
|
||||
prods[p] = next
|
||||
return next
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (expandFields.includes("regions")) {
|
||||
let regions = {}
|
||||
decorated.regions = await Promise.all(
|
||||
decorated.regions.map(async r => {
|
||||
if (r in regions) {
|
||||
return regions[r]
|
||||
}
|
||||
const next = await this.regionService_.retrieve(r)
|
||||
regions[r] = next
|
||||
return next
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -37,15 +37,17 @@ class LineItemService extends BaseService {
|
||||
})
|
||||
|
||||
const lineItemSchema = Validator.object({
|
||||
_id: Validator.any().optional(),
|
||||
title: Validator.string().required(),
|
||||
is_giftcard: Validator.bool().optional(),
|
||||
should_merge: Validator.bool().optional(),
|
||||
description: Validator.string()
|
||||
.allow("")
|
||||
.optional(),
|
||||
thumbnail: Validator.string()
|
||||
.allow("")
|
||||
.optional(),
|
||||
is_giftcard: Validator.bool().optional(),
|
||||
should_merge: Validator.bool().optional(),
|
||||
has_shipping: Validator.bool().optional(),
|
||||
content: Validator.alternatives()
|
||||
.try(content, Validator.array().items(content))
|
||||
.required(),
|
||||
@@ -53,6 +55,18 @@ class LineItemService extends BaseService {
|
||||
.integer()
|
||||
.min(1)
|
||||
.required(),
|
||||
returned: Validator.bool().optional(),
|
||||
fulfilled: Validator.bool().optional(),
|
||||
shipped: Validator.bool().optional(),
|
||||
fulfilled_quantity: Validator.number()
|
||||
.integer()
|
||||
.optional(),
|
||||
returned_quantity: Validator.number()
|
||||
.integer()
|
||||
.optional(),
|
||||
shipped_quantity: Validator.number()
|
||||
.integer()
|
||||
.optional(),
|
||||
metadata: Validator.object().default({}),
|
||||
})
|
||||
|
||||
|
||||
@@ -300,92 +300,78 @@ class OrderService extends BaseService {
|
||||
)
|
||||
}
|
||||
|
||||
// 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 total = await this.totalsService_.getTotal(cart)
|
||||
|
||||
let paymentSession = {}
|
||||
let paymentData = {}
|
||||
const { payment_method, payment_sessions } = cart
|
||||
|
||||
if (!payment_sessions || !payment_sessions.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
"cart must have payment sessions"
|
||||
// Would be the case if a discount code is applied that covers the item
|
||||
// total
|
||||
if (total !== 0) {
|
||||
// Throw if payment method does not exist
|
||||
if (!payment_method) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
"Cart does not contain a payment method"
|
||||
)
|
||||
}
|
||||
|
||||
if (!payment_sessions || !payment_sessions.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
"cart must have payment sessions"
|
||||
)
|
||||
}
|
||||
|
||||
paymentSession = payment_sessions.find(
|
||||
ps => ps.provider_id === payment_method.provider_id
|
||||
)
|
||||
}
|
||||
|
||||
let paymentSession = payment_sessions.find(
|
||||
ps => ps.provider_id === payment_method.provider_id
|
||||
)
|
||||
// Throw if payment method does not exist
|
||||
if (!paymentSession) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
"Cart does not have an authorized payment session"
|
||||
)
|
||||
}
|
||||
|
||||
// Throw if payment method does not exist
|
||||
if (!paymentSession) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
"Cart does not have an authorized payment session"
|
||||
const paymentProvider = this.paymentProviderService_.retrieveProvider(
|
||||
paymentSession.provider_id
|
||||
)
|
||||
const paymentStatus = await paymentProvider.getStatus(
|
||||
paymentSession.data
|
||||
)
|
||||
|
||||
// If payment status is not authorized, we throw
|
||||
if (paymentStatus !== "authorized" && paymentStatus !== "succeeded") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
"Payment method is not authorized"
|
||||
)
|
||||
}
|
||||
|
||||
paymentData = await paymentProvider.retrievePayment(
|
||||
paymentSession.data
|
||||
)
|
||||
}
|
||||
|
||||
const region = await this.regionService_.retrieve(cart.region_id)
|
||||
const paymentProvider = this.paymentProviderService_.retrieveProvider(
|
||||
paymentSession.provider_id
|
||||
)
|
||||
const paymentStatus = await paymentProvider.getStatus(
|
||||
paymentSession.data
|
||||
)
|
||||
|
||||
// If payment status is not authorized, we throw
|
||||
if (paymentStatus !== "authorized" && paymentStatus !== "succeeded") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
"Payment method is not authorized"
|
||||
)
|
||||
let payment = {}
|
||||
if (paymentSession.provider_id) {
|
||||
payment = {
|
||||
provider_id: paymentSession.provider_id,
|
||||
data: paymentData,
|
||||
}
|
||||
}
|
||||
|
||||
const paymentData = await paymentProvider.retrievePayment(
|
||||
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, {
|
||||
line_item: i,
|
||||
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 = {
|
||||
display_id: await this.counterService_.getNext("orders"),
|
||||
payment_method: {
|
||||
provider_id: paymentSession.provider_id,
|
||||
data: paymentData,
|
||||
},
|
||||
payment_method: payment,
|
||||
discounts: cart.discounts,
|
||||
shipping_methods: cart.shipping_methods,
|
||||
items,
|
||||
items: cart.items,
|
||||
shipping_address: cart.shipping_address,
|
||||
billing_address: cart.shipping_address,
|
||||
region_id: cart.region_id,
|
||||
@@ -397,9 +383,11 @@ class OrderService extends BaseService {
|
||||
metadata: cart.metadata || {},
|
||||
}
|
||||
|
||||
const orderDocument = await this.orderModel_.create([o], {
|
||||
session: dbSession,
|
||||
})
|
||||
const orderDocument = await this.orderModel_
|
||||
.create([o], {
|
||||
session: dbSession,
|
||||
})
|
||||
.catch(err => console.log(err))
|
||||
|
||||
// Emit and return
|
||||
this.eventBus_.emit(OrderService.Events.PLACED, orderDocument[0])
|
||||
@@ -1264,6 +1252,7 @@ class OrderService extends BaseService {
|
||||
o.refunded_total = await this.totalsService_.getRefundedTotal(order)
|
||||
o.refundable_amount = o.total - o.refunded_total
|
||||
o.created = order._id.getTimestamp()
|
||||
|
||||
if (expandFields.includes("region")) {
|
||||
o.region = await this.regionService_.retrieve(order.region_id)
|
||||
}
|
||||
|
||||
@@ -199,7 +199,10 @@ class TotalsService extends BaseService {
|
||||
if (type === "percentage") {
|
||||
percentage = value / 100
|
||||
} else if (type === "fixed") {
|
||||
percentage = value / subtotal
|
||||
// If the fixed discount exceeds the subtotal we should
|
||||
// calculate a 100% discount
|
||||
const nominator = Math.min(value, subtotal)
|
||||
percentage = nominator / subtotal
|
||||
}
|
||||
|
||||
return cart.items.map(item => {
|
||||
@@ -275,7 +278,7 @@ class TotalsService extends BaseService {
|
||||
if (type === "percentage" && allocation === "total") {
|
||||
toReturn = (subtotal / 100) * value
|
||||
} else if (type === "percentage" && allocation === "item") {
|
||||
const itemPercentageDiscounts = await this.getAllocationItemDiscounts(
|
||||
const itemPercentageDiscounts = this.getAllocationItemDiscounts(
|
||||
discount,
|
||||
cart,
|
||||
"percentage"
|
||||
@@ -284,7 +287,7 @@ class TotalsService extends BaseService {
|
||||
} else if (type === "fixed" && allocation === "total") {
|
||||
toReturn = value
|
||||
} else if (type === "fixed" && allocation === "item") {
|
||||
const itemFixedDiscounts = await this.getAllocationItemDiscounts(
|
||||
const itemFixedDiscounts = this.getAllocationItemDiscounts(
|
||||
discount,
|
||||
cart,
|
||||
"fixed"
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
class OrderSubscriber {
|
||||
constructor({
|
||||
paymentProviderService,
|
||||
cartService,
|
||||
customerService,
|
||||
eventBusService,
|
||||
discountService,
|
||||
totalsService,
|
||||
orderService,
|
||||
regionService,
|
||||
}) {
|
||||
this.totalsService_ = totalsService
|
||||
|
||||
@@ -15,7 +16,9 @@ class OrderSubscriber {
|
||||
|
||||
this.discountService_ = discountService
|
||||
|
||||
this.cartService_ = cartService
|
||||
this.orderService_ = orderService
|
||||
|
||||
this.regionService_ = regionService
|
||||
|
||||
this.eventBus_ = eventBusService
|
||||
|
||||
@@ -31,6 +34,8 @@ class OrderSubscriber {
|
||||
})
|
||||
|
||||
this.eventBus_.subscribe("order.placed", this.handleDiscounts)
|
||||
|
||||
this.eventBus_.subscribe("order.placed", this.handleGiftCards)
|
||||
}
|
||||
|
||||
handleDiscounts = async order => {
|
||||
@@ -58,6 +63,40 @@ class OrderSubscriber {
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
handleGiftCards = async order => {
|
||||
const region = await this.regionService_.retrieve(order.region_id)
|
||||
const items = await Promise.all(
|
||||
order.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("order.gift_card_created", {
|
||||
line_item: i,
|
||||
currency_code: region.currency_code,
|
||||
tax_rate: region.tax_rate,
|
||||
giftcard: result,
|
||||
email: order.email,
|
||||
})
|
||||
return result
|
||||
})
|
||||
return {
|
||||
...i,
|
||||
metadata: {
|
||||
...i.metadata,
|
||||
giftcard: giftcard._id,
|
||||
},
|
||||
}
|
||||
}
|
||||
return i
|
||||
})
|
||||
)
|
||||
|
||||
return this.orderService_.update(order._id, {
|
||||
items,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default OrderSubscriber
|
||||
|
||||
Reference in New Issue
Block a user