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:
Sebastian Rindom
2020-10-28 15:55:50 +01:00
committed by GitHub
parent b33fcfa182
commit f2c62cd232
17 changed files with 316 additions and 113 deletions
+2 -1
View File
@@ -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) 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) 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) 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) 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) 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" ({ discount_rule }) => discount_rule.type !== "free_shipping"
) )
let lineDiscounts = [] let lineDiscounts = []
if (discount) { if (discount && !discount.is_giftcard) {
lineDiscounts = this.totalsService_.getLineDiscounts(fromOrder, discount) lineDiscounts = this.totalsService_.getLineDiscounts(fromOrder, discount)
} }
@@ -511,7 +511,7 @@ class BrightpearlService extends BaseService {
item.content.variant.sku 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, amount: 0,
} }
@@ -522,7 +522,7 @@ class BrightpearlService extends BaseService {
row.name = item.title row.name = item.title
} }
row.net = this.totalsService_.rounded( 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.tax = this.totalsService_.rounded(row.net * fromOrder.tax_rate)
row.quantity = item.quantity row.quantity = item.quantity
@@ -530,10 +530,34 @@ class BrightpearlService extends BaseService {
row.externalRef = item._id row.externalRef = item._id
row.nominalCode = this.options.sales_account_code || "4000" row.nominalCode = this.options.sales_account_code || "4000"
if (item.is_giftcard) {
row.nominalCode = this.options.gift_card_account_code || "4000"
}
return row 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 shippingTotal = this.totalsService_.getShippingTotal(fromOrder)
const shippingMethods = fromOrder.shipping_methods const shippingMethods = fromOrder.shipping_methods
if (shippingMethods.length > 0) { if (shippingMethods.length > 0) {
@@ -7,6 +7,7 @@ describe("GET /admin/discounts", () => {
let subject let subject
beforeAll(async () => { beforeAll(async () => {
jest.clearAllMocks()
subject = await request("GET", `/admin/discounts`, { subject = await request("GET", `/admin/discounts`, {
adminSession: { adminSession: {
jwt: { jwt: {
@@ -22,7 +23,61 @@ describe("GET /admin/discounts", () => {
it("calls service retrieve", () => { it("calls service retrieve", () => {
expect(DiscountServiceMock.list).toHaveBeenCalledTimes(1) 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) => { export default async (req, res) => {
try { try {
const selector = {}
const discountService = req.scope.resolve("discountService") 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 }) res.status(200).json({ discounts: data })
} catch (err) { } catch (err) {
@@ -10,6 +10,10 @@ export default async (req, res) => {
"description", "description",
]) ])
if ("is_giftcard" in req.query) {
query.is_giftcard = req.query.is_giftcard === "true"
}
const limit = parseInt(req.query.limit) || 0 const limit = parseInt(req.query.limit) || 0
const offset = parseInt(req.query.offset) || 0 const offset = parseInt(req.query.offset) || 0
@@ -40,7 +44,6 @@ export default async (req, res) => {
res.json({ products, total_count: numProducts }) res.json({ products, total_count: numProducts })
} catch (error) { } catch (error) {
console.log(error)
throw error throw error
} }
} }
+2
View File
@@ -15,6 +15,8 @@ class DiscountModel extends BaseModel {
starts_at: { type: Date }, starts_at: { type: Date },
ends_at: { type: Date }, ends_at: { type: Date },
regions: { type: [String], default: [] }, regions: { type: [String], default: [] },
original_amount: { type: Number },
created: { type: String, default: Date.now },
metadata: { type: mongoose.Schema.Types.Mixed, default: {} }, metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
} }
} }
+2 -2
View File
@@ -36,8 +36,8 @@ class OrderModel extends BaseModel {
region_id: { type: String, required: true }, region_id: { type: String, required: true },
discounts: { type: [DiscountSchema], default: [] }, discounts: { type: [DiscountSchema], default: [] },
customer_id: { type: String }, customer_id: { type: String },
payment_method: { type: PaymentMethodSchema, required: true }, payment_method: { type: PaymentMethodSchema, default: {} },
shipping_methods: { type: [ShippingMethodSchema], required: true }, shipping_methods: { type: [ShippingMethodSchema], default: [] },
documents: { type: [String], default: [] }, documents: { type: [String], default: [] },
created: { type: String, default: Date.now }, created: { type: String, default: Date.now },
metadata: { type: mongoose.Schema.Types.Mixed, default: {} }, metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
@@ -3,6 +3,10 @@
******************************************************************************/ ******************************************************************************/
import mongoose from "mongoose" 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( export default new mongoose.Schema(
{ {
title: { type: String, required: true }, title: { type: String, required: true },
@@ -4,6 +4,6 @@
import mongoose from "mongoose" import mongoose from "mongoose"
export default new mongoose.Schema({ export default new mongoose.Schema({
provider_id: { type: String, required: true }, provider_id: { type: String },
data: { type: mongoose.Schema.Types.Mixed, default: {} }, data: { type: mongoose.Schema.Types.Mixed, default: {} },
}) })
@@ -40,7 +40,7 @@ export const DiscountServiceMock = {
}) })
}), }),
list: jest.fn().mockImplementation(data => { list: jest.fn().mockImplementation(data => {
return Promise.resolve([]) return Promise.resolve([{}])
}), }),
decorate: jest.fn().mockImplementation(data => { decorate: jest.fn().mockImplementation(data => {
return Promise.resolve(data) return Promise.resolve(data)
@@ -294,6 +294,7 @@ describe("DiscountService", () => {
expect(DiscountModelMock.create).toHaveBeenCalledWith({ expect(DiscountModelMock.create).toHaveBeenCalledWith({
code: expect.stringMatching(/(([A-Z0-9]){4}(-?)){4}/), code: expect.stringMatching(/(([A-Z0-9]){4}(-?)){4}/),
is_giftcard: true, is_giftcard: true,
original_amount: 100,
discount_rule: { discount_rule: {
type: "fixed", type: "fixed",
allocation: "total", allocation: "total",
+32 -21
View File
@@ -45,6 +45,7 @@ describe("OrderService", () => {
const orderService = new OrderService({ const orderService = new OrderService({
orderModel: OrderModelMock, orderModel: OrderModelMock,
paymentProviderService: PaymentProviderServiceMock, paymentProviderService: PaymentProviderServiceMock,
totalsService: TotalsServiceMock,
discountService: DiscountServiceMock, discountService: DiscountServiceMock,
regionService: RegionServiceMock, regionService: RegionServiceMock,
eventBusService: EventBusServiceMock, eventBusService: EventBusServiceMock,
@@ -56,7 +57,10 @@ describe("OrderService", () => {
}) })
it("calls order model functions", async () => { it("calls order model functions", async () => {
await orderService.createFromCart(carts.completeCart) await orderService.createFromCart({
...carts.completeCart,
total: 100,
})
const order = { const order = {
...carts.completeCart, ...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 () => { it("creates cart with gift card", async () => {
await orderService.createFromCart(carts.withGiftCard) await orderService.createFromCart({
...carts.withGiftCard,
total: 100,
})
const order = { const order = {
...carts.withGiftCard, ...carts.withGiftCard,
@@ -105,7 +135,6 @@ describe("OrderService", () => {
description: "Gift card line", description: "Gift card line",
thumbnail: "test-img-yeah.com/thumb", thumbnail: "test-img-yeah.com/thumb",
metadata: { metadata: {
giftcard: IdMap.getId("gift_card_id"),
name: "Test Name", name: "Test Name",
}, },
is_giftcard: true, is_giftcard: true,
@@ -130,24 +159,6 @@ describe("OrderService", () => {
delete order._id delete order._id
delete order.payment_sessions 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).toHaveBeenCalledTimes(1)
expect(OrderModelMock.create).toHaveBeenCalledWith([order], { expect(OrderModelMock.create).toHaveBeenCalledWith([order], {
session: expect.anything(), session: expect.anything(),
+32 -2
View File
@@ -248,6 +248,7 @@ class DiscountService extends BaseService {
discount_rule: discountRule, discount_rule: discountRule,
is_giftcard: true, is_giftcard: true,
regions: [region._id], regions: [region._id],
original_amount: value,
}) })
} }
@@ -444,13 +445,42 @@ class DiscountService extends BaseService {
* @return {Discount} return the decorated discount. * @return {Discount} return the decorated discount.
*/ */
async decorate(discount, fields = [], expandFields = []) { 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)) const decorated = _.pick(discount, fields.concat(requiredFields))
if (expandFields.includes("valid_for")) { if (expandFields.includes("valid_for")) {
let prods = {}
decorated.discount_rule.valid_for = await Promise.all( decorated.discount_rule.valid_for = await Promise.all(
decorated.discount_rule.valid_for.map(async p => { 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
}) })
) )
} }
+16 -2
View File
@@ -37,15 +37,17 @@ class LineItemService extends BaseService {
}) })
const lineItemSchema = Validator.object({ const lineItemSchema = Validator.object({
_id: Validator.any().optional(),
title: Validator.string().required(), title: Validator.string().required(),
is_giftcard: Validator.bool().optional(),
should_merge: Validator.bool().optional(),
description: Validator.string() description: Validator.string()
.allow("") .allow("")
.optional(), .optional(),
thumbnail: Validator.string() thumbnail: Validator.string()
.allow("") .allow("")
.optional(), .optional(),
is_giftcard: Validator.bool().optional(),
should_merge: Validator.bool().optional(),
has_shipping: Validator.bool().optional(),
content: Validator.alternatives() content: Validator.alternatives()
.try(content, Validator.array().items(content)) .try(content, Validator.array().items(content))
.required(), .required(),
@@ -53,6 +55,18 @@ class LineItemService extends BaseService {
.integer() .integer()
.min(1) .min(1)
.required(), .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({}), metadata: Validator.object().default({}),
}) })
+61 -72
View File
@@ -300,92 +300,78 @@ class OrderService extends BaseService {
) )
} }
// Throw if payment method does not exist const total = await this.totalsService_.getTotal(cart)
if (!cart.payment_method) {
throw new MedusaError(
MedusaError.Types.INVALID_ARGUMENT,
"Cart does not contain a payment method"
)
}
let paymentSession = {}
let paymentData = {}
const { payment_method, payment_sessions } = cart const { payment_method, payment_sessions } = cart
if (!payment_sessions || !payment_sessions.length) { // Would be the case if a discount code is applied that covers the item
throw new MedusaError( // total
MedusaError.Types.INVALID_ARGUMENT, if (total !== 0) {
"cart must have payment sessions" // 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( // Throw if payment method does not exist
ps => ps.provider_id === payment_method.provider_id if (!paymentSession) {
) throw new MedusaError(
MedusaError.Types.INVALID_ARGUMENT,
"Cart does not have an authorized payment session"
)
}
// Throw if payment method does not exist const paymentProvider = this.paymentProviderService_.retrieveProvider(
if (!paymentSession) { paymentSession.provider_id
throw new MedusaError( )
MedusaError.Types.INVALID_ARGUMENT, const paymentStatus = await paymentProvider.getStatus(
"Cart does not have an authorized payment session" 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 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 let payment = {}
if (paymentStatus !== "authorized" && paymentStatus !== "succeeded") { if (paymentSession.provider_id) {
throw new MedusaError( payment = {
MedusaError.Types.INVALID_ARGUMENT, provider_id: paymentSession.provider_id,
"Payment method is not authorized" 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 = { const o = {
display_id: await this.counterService_.getNext("orders"), display_id: await this.counterService_.getNext("orders"),
payment_method: { payment_method: payment,
provider_id: paymentSession.provider_id,
data: paymentData,
},
discounts: cart.discounts, discounts: cart.discounts,
shipping_methods: cart.shipping_methods, shipping_methods: cart.shipping_methods,
items, items: cart.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,
@@ -397,9 +383,11 @@ class OrderService extends BaseService {
metadata: cart.metadata || {}, metadata: cart.metadata || {},
} }
const orderDocument = await this.orderModel_.create([o], { const orderDocument = await this.orderModel_
session: dbSession, .create([o], {
}) session: dbSession,
})
.catch(err => console.log(err))
// Emit and return // Emit and return
this.eventBus_.emit(OrderService.Events.PLACED, orderDocument[0]) 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.refunded_total = await this.totalsService_.getRefundedTotal(order)
o.refundable_amount = o.total - o.refunded_total o.refundable_amount = o.total - o.refunded_total
o.created = order._id.getTimestamp() o.created = order._id.getTimestamp()
if (expandFields.includes("region")) { if (expandFields.includes("region")) {
o.region = await this.regionService_.retrieve(order.region_id) o.region = await this.regionService_.retrieve(order.region_id)
} }
+6 -3
View File
@@ -199,7 +199,10 @@ class TotalsService extends BaseService {
if (type === "percentage") { if (type === "percentage") {
percentage = value / 100 percentage = value / 100
} else if (type === "fixed") { } 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 => { return cart.items.map(item => {
@@ -275,7 +278,7 @@ class TotalsService extends BaseService {
if (type === "percentage" && allocation === "total") { if (type === "percentage" && allocation === "total") {
toReturn = (subtotal / 100) * value toReturn = (subtotal / 100) * value
} else if (type === "percentage" && allocation === "item") { } else if (type === "percentage" && allocation === "item") {
const itemPercentageDiscounts = await this.getAllocationItemDiscounts( const itemPercentageDiscounts = this.getAllocationItemDiscounts(
discount, discount,
cart, cart,
"percentage" "percentage"
@@ -284,7 +287,7 @@ class TotalsService extends BaseService {
} else if (type === "fixed" && allocation === "total") { } else if (type === "fixed" && allocation === "total") {
toReturn = value toReturn = value
} else if (type === "fixed" && allocation === "item") { } else if (type === "fixed" && allocation === "item") {
const itemFixedDiscounts = await this.getAllocationItemDiscounts( const itemFixedDiscounts = this.getAllocationItemDiscounts(
discount, discount,
cart, cart,
"fixed" "fixed"
+41 -2
View File
@@ -1,11 +1,12 @@
class OrderSubscriber { class OrderSubscriber {
constructor({ constructor({
paymentProviderService, paymentProviderService,
cartService,
customerService, customerService,
eventBusService, eventBusService,
discountService, discountService,
totalsService, totalsService,
orderService,
regionService,
}) { }) {
this.totalsService_ = totalsService this.totalsService_ = totalsService
@@ -15,7 +16,9 @@ class OrderSubscriber {
this.discountService_ = discountService this.discountService_ = discountService
this.cartService_ = cartService this.orderService_ = orderService
this.regionService_ = regionService
this.eventBus_ = eventBusService this.eventBus_ = eventBusService
@@ -31,6 +34,8 @@ class OrderSubscriber {
}) })
this.eventBus_.subscribe("order.placed", this.handleDiscounts) this.eventBus_.subscribe("order.placed", this.handleDiscounts)
this.eventBus_.subscribe("order.placed", this.handleGiftCards)
} }
handleDiscounts = async order => { 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 export default OrderSubscriber