refactor(medusa): cleanup, fix, migrate baseService and therefore fix errors (#1701)
* refactor(medusa): cleanup, fix, migrate baseService and therefore fix errors for ts version > 4.5 * test(medusa): Fix discount tests * fix(medusa): build * feat(medusa): Remove unnecessary await
This commit is contained in:
@@ -2,6 +2,9 @@ import { IdMap } from "medusa-test-utils"
|
|||||||
import Scrypt from "scrypt-kdf"
|
import Scrypt from "scrypt-kdf"
|
||||||
|
|
||||||
export const CustomerServiceMock = {
|
export const CustomerServiceMock = {
|
||||||
|
withTransaction: function () {
|
||||||
|
return this
|
||||||
|
},
|
||||||
create: jest.fn().mockImplementation((data) => {
|
create: jest.fn().mockImplementation((data) => {
|
||||||
return Promise.resolve({ ...data, id: IdMap.getId("lebron") })
|
return Promise.resolve({ ...data, id: IdMap.getId("lebron") })
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ export const users = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const UserServiceMock = {
|
export const UserServiceMock = {
|
||||||
|
withTransaction: function () {
|
||||||
|
return this
|
||||||
|
},
|
||||||
create: jest.fn().mockImplementation(data => {
|
create: jest.fn().mockImplementation(data => {
|
||||||
if (data.email === "oliver@test.dk") {
|
if (data.email === "oliver@test.dk") {
|
||||||
return Promise.resolve(users.testUser)
|
return Promise.resolve(users.testUser)
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
import AuthService from "../auth"
|
import AuthService from "../auth"
|
||||||
|
import { MockManager } from "medusa-test-utils"
|
||||||
import { users, UserServiceMock } from "../__mocks__/user"
|
import { users, UserServiceMock } from "../__mocks__/user"
|
||||||
import { customers, CustomerServiceMock } from "../__mocks__/customer"
|
import { CustomerServiceMock } from "../__mocks__/customer"
|
||||||
|
|
||||||
|
const managerMock = MockManager
|
||||||
|
|
||||||
describe("AuthService", () => {
|
describe("AuthService", () => {
|
||||||
|
const authService = new AuthService({
|
||||||
|
manager: managerMock,
|
||||||
|
userService: UserServiceMock,
|
||||||
|
customerService: CustomerServiceMock
|
||||||
|
})
|
||||||
|
|
||||||
describe("authenticate", () => {
|
describe("authenticate", () => {
|
||||||
let authService
|
|
||||||
authService = new AuthService({ userService: UserServiceMock })
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
})
|
})
|
||||||
@@ -33,8 +40,6 @@ describe("AuthService", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("authenticateCustomer", () => {
|
describe("authenticateCustomer", () => {
|
||||||
let authService
|
|
||||||
authService = new AuthService({ customerService: CustomerServiceMock })
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
})
|
})
|
||||||
@@ -62,8 +67,6 @@ describe("AuthService", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("authenticateAPIToken", () => {
|
describe("authenticateAPIToken", () => {
|
||||||
let authService
|
|
||||||
authService = new AuthService({ userService: UserServiceMock })
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -761,7 +761,9 @@ describe("DiscountService", () => {
|
|||||||
let discountService
|
let discountService
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
discountService = new DiscountService({})
|
discountService = new DiscountService({
|
||||||
|
manager: MockManager
|
||||||
|
})
|
||||||
const hasReachedLimitMock = jest.fn().mockImplementation(() => false)
|
const hasReachedLimitMock = jest.fn().mockImplementation(() => false)
|
||||||
const isDisabledMock = jest.fn().mockImplementation(() => false)
|
const isDisabledMock = jest.fn().mockImplementation(() => false)
|
||||||
const isValidForRegionMock = jest
|
const isValidForRegionMock = jest
|
||||||
@@ -1064,7 +1066,9 @@ describe("DiscountService", () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const discountService = new DiscountService({})
|
const discountService = new DiscountService({
|
||||||
|
manager: MockManager
|
||||||
|
})
|
||||||
discountService.retrieve = retrieveMock
|
discountService.retrieve = retrieveMock
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|||||||
@@ -1,21 +1,32 @@
|
|||||||
import Scrypt from "scrypt-kdf"
|
import Scrypt from "scrypt-kdf"
|
||||||
import { BaseService } from "medusa-interfaces"
|
|
||||||
import { AuthenticateResult } from "../types/auth"
|
import { AuthenticateResult } from "../types/auth"
|
||||||
import { User } from "../models/user"
|
import { User, Customer } from "../models"
|
||||||
import { Customer } from "../models/customer"
|
import { TransactionBaseService } from "../interfaces"
|
||||||
|
import UserService from "./user"
|
||||||
|
import CustomerService from "./customer"
|
||||||
|
import { EntityManager } from "typeorm"
|
||||||
|
|
||||||
|
type InjectedDependencies = {
|
||||||
|
manager: EntityManager
|
||||||
|
userService: UserService
|
||||||
|
customerService: CustomerService
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Can authenticate a user based on email password combination
|
* Can authenticate a user based on email password combination
|
||||||
* @extends BaseService
|
* @extends BaseService
|
||||||
*/
|
*/
|
||||||
class AuthService extends BaseService {
|
class AuthService extends TransactionBaseService<AuthService> {
|
||||||
constructor({ userService, customerService }) {
|
protected manager_: EntityManager
|
||||||
super()
|
protected transactionManager_: EntityManager | undefined
|
||||||
|
protected readonly userService_: UserService
|
||||||
|
protected readonly customerService_: CustomerService
|
||||||
|
|
||||||
/** @private @const {UserService} */
|
constructor({ manager, userService, customerService }: InjectedDependencies) {
|
||||||
|
super({ manager, userService, customerService })
|
||||||
|
|
||||||
|
this.manager_ = manager
|
||||||
this.userService_ = userService
|
this.userService_ = userService
|
||||||
|
|
||||||
/** @private @const {CustomerService} */
|
|
||||||
this.customerService_ = customerService
|
this.customerService_ = customerService
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,7 +36,10 @@ class AuthService extends BaseService {
|
|||||||
* @param {string} hash - the hash to compare against
|
* @param {string} hash - the hash to compare against
|
||||||
* @return {bool} the result of the comparison
|
* @return {bool} the result of the comparison
|
||||||
*/
|
*/
|
||||||
async comparePassword_(password: string, hash: string): Promise<boolean> {
|
protected async comparePassword_(
|
||||||
|
password: string,
|
||||||
|
hash: string
|
||||||
|
): Promise<boolean> {
|
||||||
const buf = Buffer.from(hash, "base64")
|
const buf = Buffer.from(hash, "base64")
|
||||||
return Scrypt.verify(buf, password)
|
return Scrypt.verify(buf, password)
|
||||||
}
|
}
|
||||||
@@ -39,30 +53,36 @@ class AuthService extends BaseService {
|
|||||||
* error: a string with the error message
|
* error: a string with the error message
|
||||||
*/
|
*/
|
||||||
async authenticateAPIToken(token: string): Promise<AuthenticateResult> {
|
async authenticateAPIToken(token: string): Promise<AuthenticateResult> {
|
||||||
if (process.env.NODE_ENV === "development") {
|
return await this.atomicPhase_(async (transactionManager) => {
|
||||||
|
if (process.env.NODE_ENV?.startsWith("dev")) {
|
||||||
|
try {
|
||||||
|
const user: User = await this.userService_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.retrieve(token)
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
user,
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const user: User = await this.userService_.retrieve(token)
|
const user: User = await this.userService_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.retrieveByApiToken(token)
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
user,
|
user,
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// ignore
|
return {
|
||||||
|
success: false,
|
||||||
|
error: "Invalid API Token",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
try {
|
|
||||||
const user: User = await this.userService_.retrieveByApiToken(token)
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
user,
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: "Invalid API Token",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -79,35 +99,39 @@ class AuthService extends BaseService {
|
|||||||
email: string,
|
email: string,
|
||||||
password: string
|
password: string
|
||||||
): Promise<AuthenticateResult> {
|
): Promise<AuthenticateResult> {
|
||||||
try {
|
return await this.atomicPhase_(async (transactionManager) => {
|
||||||
const userPasswordHash: User = await this.userService_.retrieveByEmail(
|
try {
|
||||||
email,
|
const userPasswordHash: User = await this.userService_
|
||||||
{
|
.withTransaction(transactionManager)
|
||||||
select: ["password_hash"],
|
.retrieveByEmail(email, {
|
||||||
}
|
select: ["password_hash"],
|
||||||
)
|
})
|
||||||
|
|
||||||
const passwordsMatch = await this.comparePassword_(
|
const passwordsMatch = await this.comparePassword_(
|
||||||
password,
|
password,
|
||||||
userPasswordHash.password_hash
|
userPasswordHash.password_hash
|
||||||
)
|
)
|
||||||
|
|
||||||
if (passwordsMatch) {
|
if (passwordsMatch) {
|
||||||
const user = await this.userService_.retrieveByEmail(email)
|
const user = await this.userService_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.retrieveByEmail(email)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
user: user,
|
user: user,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log("error ->", error)
|
||||||
|
// ignore
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: "Invalid email or password",
|
error: "Invalid email or password",
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -124,32 +148,39 @@ class AuthService extends BaseService {
|
|||||||
email: string,
|
email: string,
|
||||||
password: string
|
password: string
|
||||||
): Promise<AuthenticateResult> {
|
): Promise<AuthenticateResult> {
|
||||||
try {
|
return await this.atomicPhase_(async (transactionManager) => {
|
||||||
const customerPasswordHash: Customer =
|
try {
|
||||||
await this.customerService_.retrieveByEmail(email, {
|
const customerPasswordHash: Customer = await this.customerService_
|
||||||
select: ["password_hash"],
|
.withTransaction(transactionManager)
|
||||||
})
|
.retrieveByEmail(email, {
|
||||||
if (customerPasswordHash.password_hash) {
|
select: ["password_hash"],
|
||||||
const passwordsMatch = await this.comparePassword_(
|
})
|
||||||
password,
|
if (customerPasswordHash.password_hash) {
|
||||||
customerPasswordHash.password_hash
|
const passwordsMatch = await this.comparePassword_(
|
||||||
)
|
password,
|
||||||
|
customerPasswordHash.password_hash
|
||||||
|
)
|
||||||
|
|
||||||
if (passwordsMatch) {
|
if (passwordsMatch) {
|
||||||
const customer = await this.customerService_.retrieveByEmail(email)
|
const customer = await this.customerService_
|
||||||
return {
|
.withTransaction(transactionManager)
|
||||||
success: true,
|
.retrieveByEmail(email)
|
||||||
customer,
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
customer,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// ignore
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
// ignore
|
return {
|
||||||
}
|
success: false,
|
||||||
return {
|
error: "Invalid email or password",
|
||||||
success: false,
|
}
|
||||||
error: "Invalid email or password",
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,47 +1,51 @@
|
|||||||
import { MedusaError } from "medusa-core-utils"
|
import { MedusaError } from "medusa-core-utils"
|
||||||
import { BaseService } from "medusa-interfaces"
|
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
import { EventBusService } from "."
|
import { EventBusService } from "."
|
||||||
import { DiscountCondition, DiscountConditionType } from "../models"
|
import {
|
||||||
|
DiscountCondition,
|
||||||
|
DiscountConditionCustomerGroup,
|
||||||
|
DiscountConditionProduct,
|
||||||
|
DiscountConditionProductCollection,
|
||||||
|
DiscountConditionProductTag,
|
||||||
|
DiscountConditionProductType,
|
||||||
|
DiscountConditionType,
|
||||||
|
} from "../models"
|
||||||
import { DiscountConditionRepository } from "../repositories/discount-condition"
|
import { DiscountConditionRepository } from "../repositories/discount-condition"
|
||||||
import { FindConfig } from "../types/common"
|
import { FindConfig } from "../types/common"
|
||||||
import { UpsertDiscountConditionInput } from "../types/discount"
|
import { UpsertDiscountConditionInput } from "../types/discount"
|
||||||
import { PostgresError } from "../utils/exception-formatter"
|
import { PostgresError } from "../utils/exception-formatter"
|
||||||
|
import { TransactionBaseService } from "../interfaces"
|
||||||
|
import { buildQuery } from "../utils"
|
||||||
|
|
||||||
|
type InjectedDependencies = {
|
||||||
|
manager: EntityManager
|
||||||
|
discountConditionRepository: typeof DiscountConditionRepository
|
||||||
|
eventBusService: EventBusService
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides layer to manipulate discount conditions.
|
* Provides layer to manipulate discount conditions.
|
||||||
* @implements {BaseService}
|
* @implements {BaseService}
|
||||||
*/
|
*/
|
||||||
class DiscountConditionService extends BaseService {
|
class DiscountConditionService extends TransactionBaseService<DiscountConditionService> {
|
||||||
protected readonly manager_: EntityManager
|
|
||||||
protected readonly discountConditionRepository_: typeof DiscountConditionRepository
|
protected readonly discountConditionRepository_: typeof DiscountConditionRepository
|
||||||
protected readonly eventBus_: EventBusService
|
protected readonly eventBus_: EventBusService
|
||||||
protected transactionManager_?: EntityManager
|
|
||||||
|
|
||||||
constructor({ manager, discountConditionRepository, eventBusService }) {
|
protected manager_: EntityManager
|
||||||
super()
|
protected transactionManager_: EntityManager | undefined
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
manager,
|
||||||
|
discountConditionRepository,
|
||||||
|
eventBusService,
|
||||||
|
}: InjectedDependencies) {
|
||||||
|
super({ manager, discountConditionRepository, eventBusService })
|
||||||
|
|
||||||
this.manager_ = manager
|
this.manager_ = manager
|
||||||
this.discountConditionRepository_ = discountConditionRepository
|
this.discountConditionRepository_ = discountConditionRepository
|
||||||
this.eventBus_ = eventBusService
|
this.eventBus_ = eventBusService
|
||||||
}
|
}
|
||||||
|
|
||||||
withTransaction(transactionManager: EntityManager): DiscountConditionService {
|
|
||||||
if (!transactionManager) {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
const cloned = new DiscountConditionService({
|
|
||||||
manager: transactionManager,
|
|
||||||
discountConditionRepository: this.discountConditionRepository_,
|
|
||||||
eventBusService: this.eventBus_,
|
|
||||||
})
|
|
||||||
|
|
||||||
cloned.transactionManager_ = transactionManager
|
|
||||||
|
|
||||||
return cloned
|
|
||||||
}
|
|
||||||
|
|
||||||
async retrieve(
|
async retrieve(
|
||||||
conditionId: string,
|
conditionId: string,
|
||||||
config?: FindConfig<DiscountCondition>
|
config?: FindConfig<DiscountCondition>
|
||||||
@@ -51,7 +55,7 @@ class DiscountConditionService extends BaseService {
|
|||||||
this.discountConditionRepository_
|
this.discountConditionRepository_
|
||||||
)
|
)
|
||||||
|
|
||||||
const query = this.buildQuery_({ id: conditionId }, config)
|
const query = buildQuery({ id: conditionId }, config)
|
||||||
|
|
||||||
const condition = await conditionRepo.findOne(query)
|
const condition = await conditionRepo.findOne(query)
|
||||||
|
|
||||||
@@ -103,7 +107,17 @@ class DiscountConditionService extends BaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async upsertCondition(data: UpsertDiscountConditionInput): Promise<void> {
|
async upsertCondition(
|
||||||
|
data: UpsertDiscountConditionInput
|
||||||
|
): Promise<
|
||||||
|
(
|
||||||
|
| DiscountConditionProduct
|
||||||
|
| DiscountConditionProductType
|
||||||
|
| DiscountConditionProductCollection
|
||||||
|
| DiscountConditionProductTag
|
||||||
|
| DiscountConditionCustomerGroup
|
||||||
|
)[]
|
||||||
|
> {
|
||||||
let resolvedConditionType
|
let resolvedConditionType
|
||||||
|
|
||||||
return await this.atomicPhase_(
|
return await this.atomicPhase_(
|
||||||
@@ -164,7 +178,7 @@ class DiscountConditionService extends BaseService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(discountConditionId: string): Promise<DiscountCondition> {
|
async delete(discountConditionId: string): Promise<DiscountCondition | void> {
|
||||||
return await this.atomicPhase_(async (manager: EntityManager) => {
|
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||||
const conditionRepo = manager.getCustomRepository(
|
const conditionRepo = manager.getCustomRepository(
|
||||||
this.discountConditionRepository_
|
this.discountConditionRepository_
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { parse, toSeconds } from "iso8601-duration"
|
import { parse, toSeconds } from "iso8601-duration"
|
||||||
import { isEmpty, omit } from "lodash"
|
import { isEmpty, omit } from "lodash"
|
||||||
import { MedusaError } from "medusa-core-utils"
|
import { MedusaError } from "medusa-core-utils"
|
||||||
import { BaseService } from "medusa-interfaces"
|
|
||||||
import {
|
import {
|
||||||
Brackets,
|
Brackets,
|
||||||
DeepPartial,
|
DeepPartial,
|
||||||
@@ -15,19 +14,17 @@ import {
|
|||||||
RegionService,
|
RegionService,
|
||||||
TotalsService,
|
TotalsService,
|
||||||
} from "."
|
} from "."
|
||||||
import { Cart } from "../models/cart"
|
import { Cart, Discount, LineItem, Region } from "../models"
|
||||||
import { Discount } from "../models/discount"
|
|
||||||
import {
|
import {
|
||||||
AllocationType as DiscountAllocation,
|
AllocationType as DiscountAllocation,
|
||||||
DiscountRule,
|
DiscountRule,
|
||||||
DiscountRuleType,
|
DiscountRuleType,
|
||||||
} from "../models/discount-rule"
|
} from "../models/discount-rule"
|
||||||
import { LineItem } from "../models/line-item"
|
|
||||||
import { DiscountRepository } from "../repositories/discount"
|
import { DiscountRepository } from "../repositories/discount"
|
||||||
import { DiscountConditionRepository } from "../repositories/discount-condition"
|
import { DiscountConditionRepository } from "../repositories/discount-condition"
|
||||||
import { DiscountRuleRepository } from "../repositories/discount-rule"
|
import { DiscountRuleRepository } from "../repositories/discount-rule"
|
||||||
import { GiftCardRepository } from "../repositories/gift-card"
|
import { GiftCardRepository } from "../repositories/gift-card"
|
||||||
import { FindConfig } from "../types/common"
|
import { FindConfig, Selector } from "../types/common"
|
||||||
import {
|
import {
|
||||||
CreateDiscountInput,
|
CreateDiscountInput,
|
||||||
CreateDiscountRuleInput,
|
CreateDiscountRuleInput,
|
||||||
@@ -39,22 +36,28 @@ import {
|
|||||||
import { isFuture, isPast } from "../utils/date-helpers"
|
import { isFuture, isPast } from "../utils/date-helpers"
|
||||||
import { formatException } from "../utils/exception-formatter"
|
import { formatException } from "../utils/exception-formatter"
|
||||||
import DiscountConditionService from "./discount-condition"
|
import DiscountConditionService from "./discount-condition"
|
||||||
|
import CustomerService from "./customer"
|
||||||
|
import { TransactionBaseService } from "../interfaces"
|
||||||
|
import { buildQuery, setMetadata } from "../utils"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides layer to manipulate discounts.
|
* Provides layer to manipulate discounts.
|
||||||
* @implements {BaseService}
|
* @implements {BaseService}
|
||||||
*/
|
*/
|
||||||
class DiscountService extends BaseService {
|
class DiscountService extends TransactionBaseService<DiscountService> {
|
||||||
private manager_: EntityManager
|
protected manager_: EntityManager
|
||||||
private discountRepository_: typeof DiscountRepository
|
protected transactionManager_: EntityManager | undefined
|
||||||
private discountRuleRepository_: typeof DiscountRuleRepository
|
|
||||||
private giftCardRepository_: typeof GiftCardRepository
|
protected readonly discountRepository_: typeof DiscountRepository
|
||||||
private discountConditionRepository_: typeof DiscountConditionRepository
|
protected readonly customerService_: CustomerService
|
||||||
private discountConditionService_: DiscountConditionService
|
protected readonly discountRuleRepository_: typeof DiscountRuleRepository
|
||||||
private totalsService_: TotalsService
|
protected readonly giftCardRepository_: typeof GiftCardRepository
|
||||||
private productService_: ProductService
|
protected readonly discountConditionRepository_: typeof DiscountConditionRepository
|
||||||
private regionService_: RegionService
|
protected readonly discountConditionService_: DiscountConditionService
|
||||||
private eventBus_: EventBusService
|
protected readonly totalsService_: TotalsService
|
||||||
|
protected readonly productService_: ProductService
|
||||||
|
protected readonly regionService_: RegionService
|
||||||
|
protected readonly eventBus_: EventBusService
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
manager,
|
manager,
|
||||||
@@ -69,67 +72,22 @@ class DiscountService extends BaseService {
|
|||||||
customerService,
|
customerService,
|
||||||
eventBusService,
|
eventBusService,
|
||||||
}) {
|
}) {
|
||||||
super()
|
// eslint-disable-next-line prefer-rest-params
|
||||||
|
super(arguments[0])
|
||||||
|
|
||||||
/** @private @const {EntityManager} */
|
|
||||||
this.manager_ = manager
|
this.manager_ = manager
|
||||||
|
|
||||||
/** @private @const {DiscountRepository} */
|
|
||||||
this.discountRepository_ = discountRepository
|
this.discountRepository_ = discountRepository
|
||||||
|
|
||||||
/** @private @const {DiscountRuleRepository} */
|
|
||||||
this.discountRuleRepository_ = discountRuleRepository
|
this.discountRuleRepository_ = discountRuleRepository
|
||||||
|
|
||||||
/** @private @const {GiftCardRepository} */
|
|
||||||
this.giftCardRepository_ = giftCardRepository
|
this.giftCardRepository_ = giftCardRepository
|
||||||
|
|
||||||
/** @private @const {DiscountConditionRepository} */
|
|
||||||
this.discountConditionRepository_ = discountConditionRepository
|
this.discountConditionRepository_ = discountConditionRepository
|
||||||
|
|
||||||
/** @private @const {DiscountConditionRepository} */
|
|
||||||
this.discountConditionService_ = discountConditionService
|
this.discountConditionService_ = discountConditionService
|
||||||
|
|
||||||
/** @private @const {TotalsService} */
|
|
||||||
this.totalsService_ = totalsService
|
this.totalsService_ = totalsService
|
||||||
|
|
||||||
/** @private @const {ProductService} */
|
|
||||||
this.productService_ = productService
|
this.productService_ = productService
|
||||||
|
|
||||||
/** @private @const {RegionService} */
|
|
||||||
this.regionService_ = regionService
|
this.regionService_ = regionService
|
||||||
|
|
||||||
/** @private @const {CustomerService} */
|
|
||||||
this.customerService_ = customerService
|
this.customerService_ = customerService
|
||||||
|
|
||||||
/** @private @const {EventBus} */
|
|
||||||
this.eventBus_ = eventBusService
|
this.eventBus_ = eventBusService
|
||||||
}
|
}
|
||||||
|
|
||||||
withTransaction(transactionManager: EntityManager): DiscountService {
|
|
||||||
if (!transactionManager) {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
const cloned = new DiscountService({
|
|
||||||
manager: transactionManager,
|
|
||||||
discountRepository: this.discountRepository_,
|
|
||||||
discountRuleRepository: this.discountRuleRepository_,
|
|
||||||
giftCardRepository: this.giftCardRepository_,
|
|
||||||
discountConditionRepository: this.discountConditionRepository_,
|
|
||||||
discountConditionService: this.discountConditionService_,
|
|
||||||
totalsService: this.totalsService_,
|
|
||||||
productService: this.productService_,
|
|
||||||
regionService: this.regionService_,
|
|
||||||
customerService: this.customerService_,
|
|
||||||
eventBusService: this.eventBus_,
|
|
||||||
})
|
|
||||||
|
|
||||||
cloned.transactionManager_ = transactionManager
|
|
||||||
cloned.manager_ = transactionManager
|
|
||||||
|
|
||||||
return cloned
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a discount rule with provided data given that the data is validated.
|
* Creates a discount rule with provided data given that the data is validated.
|
||||||
* @param {DiscountRule} discountRule - the discount rule to create
|
* @param {DiscountRule} discountRule - the discount rule to create
|
||||||
@@ -157,12 +115,14 @@ class DiscountService extends BaseService {
|
|||||||
selector: FilterableDiscountProps = {},
|
selector: FilterableDiscountProps = {},
|
||||||
config: FindConfig<Discount> = { relations: [], skip: 0, take: 10 }
|
config: FindConfig<Discount> = { relations: [], skip: 0, take: 10 }
|
||||||
): Promise<Discount[]> {
|
): Promise<Discount[]> {
|
||||||
const discountRepo = this.manager_.getCustomRepository(
|
return await this.atomicPhase_(async (transactionManager) => {
|
||||||
this.discountRepository_
|
const discountRepo = transactionManager.getCustomRepository(
|
||||||
)
|
this.discountRepository_
|
||||||
|
)
|
||||||
|
|
||||||
const query = this.buildQuery_(selector, config)
|
const query = buildQuery(selector as Selector<Discount>, config)
|
||||||
return discountRepo.find(query)
|
return await discountRepo.find(query)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -178,37 +138,39 @@ class DiscountService extends BaseService {
|
|||||||
order: { created_at: "DESC" },
|
order: { created_at: "DESC" },
|
||||||
}
|
}
|
||||||
): Promise<[Discount[], number]> {
|
): Promise<[Discount[], number]> {
|
||||||
const discountRepo = this.manager_.getCustomRepository(
|
return await this.atomicPhase_(async (transactionManager) => {
|
||||||
this.discountRepository_
|
const discountRepo = transactionManager.getCustomRepository(
|
||||||
)
|
this.discountRepository_
|
||||||
|
)
|
||||||
|
|
||||||
let q
|
let q
|
||||||
if ("q" in selector) {
|
if ("q" in selector) {
|
||||||
q = selector.q
|
q = selector.q
|
||||||
delete selector.q
|
delete selector.q
|
||||||
}
|
|
||||||
|
|
||||||
const query = this.buildQuery_(selector, config)
|
|
||||||
|
|
||||||
if (q) {
|
|
||||||
const where = query.where
|
|
||||||
|
|
||||||
delete where.code
|
|
||||||
|
|
||||||
query.where = (qb: SelectQueryBuilder<Discount>): void => {
|
|
||||||
qb.where(where)
|
|
||||||
|
|
||||||
qb.andWhere(
|
|
||||||
new Brackets((qb) => {
|
|
||||||
qb.where({ code: ILike(`%${q}%`) })
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const [discounts, count] = await discountRepo.findAndCount(query)
|
const query = buildQuery(selector as Selector<Discount>, config)
|
||||||
|
|
||||||
return [discounts, count]
|
if (q) {
|
||||||
|
const where = query.where
|
||||||
|
|
||||||
|
delete where.code
|
||||||
|
|
||||||
|
query.where = (qb: SelectQueryBuilder<Discount>): void => {
|
||||||
|
qb.where(where)
|
||||||
|
|
||||||
|
qb.andWhere(
|
||||||
|
new Brackets((qb) => {
|
||||||
|
qb.where({ code: ILike(`%${q}%`) })
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [discounts, count] = await discountRepo.findAndCount(query)
|
||||||
|
|
||||||
|
return [discounts, count]
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -218,7 +180,7 @@ class DiscountService extends BaseService {
|
|||||||
* @return {Promise} the result of the create operation
|
* @return {Promise} the result of the create operation
|
||||||
*/
|
*/
|
||||||
async create(discount: CreateDiscountInput): Promise<Discount> {
|
async create(discount: CreateDiscountInput): Promise<Discount> {
|
||||||
return this.atomicPhase_(async (manager: EntityManager) => {
|
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||||
const discountRepo = manager.getCustomRepository(this.discountRepository_)
|
const discountRepo = manager.getCustomRepository(this.discountRepository_)
|
||||||
const ruleRepo = manager.getCustomRepository(this.discountRuleRepository_)
|
const ruleRepo = manager.getCustomRepository(this.discountRuleRepository_)
|
||||||
|
|
||||||
@@ -240,11 +202,11 @@ class DiscountService extends BaseService {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (discount.regions) {
|
if (discount.regions) {
|
||||||
discount.regions = await Promise.all(
|
discount.regions = (await Promise.all(
|
||||||
discount.regions.map((regionId) =>
|
discount.regions.map((regionId) =>
|
||||||
this.regionService_.withTransaction(manager).retrieve(regionId)
|
this.regionService_.withTransaction(manager).retrieve(regionId)
|
||||||
)
|
)
|
||||||
)
|
)) as Region[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const discountRule = ruleRepo.create(validatedRule)
|
const discountRule = ruleRepo.create(validatedRule)
|
||||||
@@ -286,22 +248,23 @@ class DiscountService extends BaseService {
|
|||||||
discountId: string,
|
discountId: string,
|
||||||
config: FindConfig<Discount> = {}
|
config: FindConfig<Discount> = {}
|
||||||
): Promise<Discount> {
|
): Promise<Discount> {
|
||||||
const discountRepo = this.manager_.getCustomRepository(
|
return await this.atomicPhase_(async (transactionManager) => {
|
||||||
this.discountRepository_
|
const discountRepo = transactionManager.getCustomRepository(
|
||||||
)
|
this.discountRepository_
|
||||||
|
|
||||||
const validatedId = this.validateId_(discountId)
|
|
||||||
const query = this.buildQuery_({ id: validatedId }, config)
|
|
||||||
const discount = await discountRepo.findOne(query)
|
|
||||||
|
|
||||||
if (!discount) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_FOUND,
|
|
||||||
`Discount with ${discountId} was not found`
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
return discount
|
const query = buildQuery({ id: discountId }, config)
|
||||||
|
const discount = await discountRepo.findOne(query)
|
||||||
|
|
||||||
|
if (!discount) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`Discount with ${discountId} was not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return discount
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -314,29 +277,28 @@ class DiscountService extends BaseService {
|
|||||||
discountCode: string,
|
discountCode: string,
|
||||||
config: FindConfig<Discount> = {}
|
config: FindConfig<Discount> = {}
|
||||||
): Promise<Discount> {
|
): Promise<Discount> {
|
||||||
const discountRepo = this.manager_.getCustomRepository(
|
return await this.atomicPhase_(async (transactionManager) => {
|
||||||
this.discountRepository_
|
const discountRepo = transactionManager.getCustomRepository(
|
||||||
)
|
this.discountRepository_
|
||||||
|
)
|
||||||
|
|
||||||
let query = this.buildQuery_(
|
let query = buildQuery({ code: discountCode, is_dynamic: false }, config)
|
||||||
{ code: discountCode, is_dynamic: false },
|
let discount = await discountRepo.findOne(query)
|
||||||
config
|
|
||||||
)
|
|
||||||
let discount = await discountRepo.findOne(query)
|
|
||||||
|
|
||||||
if (!discount) {
|
|
||||||
query = this.buildQuery_({ code: discountCode, is_dynamic: true }, config)
|
|
||||||
discount = await discountRepo.findOne(query)
|
|
||||||
|
|
||||||
if (!discount) {
|
if (!discount) {
|
||||||
throw new MedusaError(
|
query = buildQuery({ code: discountCode, is_dynamic: true }, config)
|
||||||
MedusaError.Types.NOT_FOUND,
|
discount = await discountRepo.findOne(query)
|
||||||
`Discount with code ${discountCode} was not found`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return discount
|
if (!discount) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`Discount with code ${discountCode} was not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return discount
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -349,7 +311,7 @@ class DiscountService extends BaseService {
|
|||||||
discountId: string,
|
discountId: string,
|
||||||
update: UpdateDiscountInput
|
update: UpdateDiscountInput
|
||||||
): Promise<Discount> {
|
): Promise<Discount> {
|
||||||
return this.atomicPhase_(async (manager) => {
|
return await this.atomicPhase_(async (manager) => {
|
||||||
const discountRepo: DiscountRepository = manager.getCustomRepository(
|
const discountRepo: DiscountRepository = manager.getCustomRepository(
|
||||||
this.discountRepository_
|
this.discountRepository_
|
||||||
)
|
)
|
||||||
@@ -365,7 +327,7 @@ class DiscountService extends BaseService {
|
|||||||
const ruleToUpdate = omit(update.rule, "conditions")
|
const ruleToUpdate = omit(update.rule, "conditions")
|
||||||
|
|
||||||
if (!isEmpty(ruleToUpdate)) {
|
if (!isEmpty(ruleToUpdate)) {
|
||||||
update.rule = ruleToUpdate
|
update.rule = ruleToUpdate as UpdateDiscountRuleInput
|
||||||
}
|
}
|
||||||
|
|
||||||
const { rule, metadata, regions, ...rest } = update
|
const { rule, metadata, regions, ...rest } = update
|
||||||
@@ -403,7 +365,7 @@ class DiscountService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
discount.metadata = await this.setMetadata_(discount.id, metadata)
|
discount.metadata = await setMetadata(discount, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rule) {
|
if (rule) {
|
||||||
@@ -416,12 +378,10 @@ class DiscountService extends BaseService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedRule = ruleRepo.create({
|
discount.rule = ruleRepo.create({
|
||||||
...discount.rule,
|
...discount.rule,
|
||||||
...ruleUpdate,
|
...ruleUpdate,
|
||||||
})
|
} as DiscountRule)
|
||||||
|
|
||||||
discount.rule = updatedRule
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key of Object.keys(rest).filter(
|
for (const key of Object.keys(rest).filter(
|
||||||
@@ -432,8 +392,7 @@ class DiscountService extends BaseService {
|
|||||||
|
|
||||||
discount.code = discount.code.toUpperCase()
|
discount.code = discount.code.toUpperCase()
|
||||||
|
|
||||||
const updated = await discountRepo.save(discount)
|
return await discountRepo.save(discount)
|
||||||
return updated
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,7 +406,7 @@ class DiscountService extends BaseService {
|
|||||||
discountId: string,
|
discountId: string,
|
||||||
data: CreateDynamicDiscountInput
|
data: CreateDynamicDiscountInput
|
||||||
): Promise<Discount> {
|
): Promise<Discount> {
|
||||||
return this.atomicPhase_(async (manager) => {
|
return await this.atomicPhase_(async (manager) => {
|
||||||
const discountRepo = manager.getCustomRepository(this.discountRepository_)
|
const discountRepo = manager.getCustomRepository(this.discountRepository_)
|
||||||
|
|
||||||
const discount = await this.retrieve(discountId)
|
const discount = await this.retrieve(discountId)
|
||||||
@@ -483,9 +442,8 @@ class DiscountService extends BaseService {
|
|||||||
)
|
)
|
||||||
toCreate.ends_at = lastValidDate
|
toCreate.ends_at = lastValidDate
|
||||||
}
|
}
|
||||||
const created = await discountRepo.create(toCreate)
|
const created: Discount = discountRepo.create(toCreate)
|
||||||
const result = await discountRepo.save(created)
|
return await discountRepo.save(created)
|
||||||
return result
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -496,19 +454,17 @@ class DiscountService extends BaseService {
|
|||||||
* @return {Promise} the newly created dynamic code
|
* @return {Promise} the newly created dynamic code
|
||||||
*/
|
*/
|
||||||
async deleteDynamicCode(discountId: string, code: string): Promise<void> {
|
async deleteDynamicCode(discountId: string, code: string): Promise<void> {
|
||||||
return this.atomicPhase_(async (manager) => {
|
return await this.atomicPhase_(async (manager) => {
|
||||||
const discountRepo = manager.getCustomRepository(this.discountRepository_)
|
const discountRepo = manager.getCustomRepository(this.discountRepository_)
|
||||||
const discount = await discountRepo.findOne({
|
const discount = await discountRepo.findOne({
|
||||||
where: { parent_discount_id: discountId, code },
|
where: { parent_discount_id: discountId, code },
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!discount) {
|
if (!discount) {
|
||||||
return Promise.resolve()
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await discountRepo.softRemove(discount)
|
await discountRepo.softRemove(discount)
|
||||||
|
|
||||||
return Promise.resolve()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -519,7 +475,7 @@ class DiscountService extends BaseService {
|
|||||||
* @return {Promise} the result of the update operation
|
* @return {Promise} the result of the update operation
|
||||||
*/
|
*/
|
||||||
async addRegion(discountId: string, regionId: string): Promise<Discount> {
|
async addRegion(discountId: string, regionId: string): Promise<Discount> {
|
||||||
return this.atomicPhase_(async (manager) => {
|
return await this.atomicPhase_(async (manager) => {
|
||||||
const discountRepo = manager.getCustomRepository(this.discountRepository_)
|
const discountRepo = manager.getCustomRepository(this.discountRepository_)
|
||||||
|
|
||||||
const discount = await this.retrieve(discountId, {
|
const discount = await this.retrieve(discountId, {
|
||||||
@@ -543,8 +499,7 @@ class DiscountService extends BaseService {
|
|||||||
|
|
||||||
discount.regions = [...discount.regions, region]
|
discount.regions = [...discount.regions, region]
|
||||||
|
|
||||||
const updated = await discountRepo.save(discount)
|
return await discountRepo.save(discount)
|
||||||
return updated
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,7 +510,7 @@ class DiscountService extends BaseService {
|
|||||||
* @return {Promise} the result of the update operation
|
* @return {Promise} the result of the update operation
|
||||||
*/
|
*/
|
||||||
async removeRegion(discountId: string, regionId: string): Promise<Discount> {
|
async removeRegion(discountId: string, regionId: string): Promise<Discount> {
|
||||||
return this.atomicPhase_(async (manager) => {
|
return await this.atomicPhase_(async (manager) => {
|
||||||
const discountRepo = manager.getCustomRepository(this.discountRepository_)
|
const discountRepo = manager.getCustomRepository(this.discountRepository_)
|
||||||
|
|
||||||
const discount = await this.retrieve(discountId, {
|
const discount = await this.retrieve(discountId, {
|
||||||
@@ -570,8 +525,7 @@ class DiscountService extends BaseService {
|
|||||||
|
|
||||||
discount.regions = discount.regions.filter((r) => r.id !== regionId)
|
discount.regions = discount.regions.filter((r) => r.id !== regionId)
|
||||||
|
|
||||||
const updated = await discountRepo.save(discount)
|
return await discountRepo.save(discount)
|
||||||
return updated
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -581,18 +535,16 @@ class DiscountService extends BaseService {
|
|||||||
* @return {Promise} the result of the delete operation
|
* @return {Promise} the result of the delete operation
|
||||||
*/
|
*/
|
||||||
async delete(discountId: string): Promise<void> {
|
async delete(discountId: string): Promise<void> {
|
||||||
return this.atomicPhase_(async (manager) => {
|
return await this.atomicPhase_(async (manager) => {
|
||||||
const discountRepo = manager.getCustomRepository(this.discountRepository_)
|
const discountRepo = manager.getCustomRepository(this.discountRepository_)
|
||||||
|
|
||||||
const discount = await discountRepo.findOne({ where: { id: discountId } })
|
const discount = await discountRepo.findOne({ where: { id: discountId } })
|
||||||
|
|
||||||
if (!discount) {
|
if (!discount) {
|
||||||
return Promise.resolve()
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await discountRepo.softRemove(discount)
|
await discountRepo.softRemove(discount)
|
||||||
|
|
||||||
return Promise.resolve()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -600,7 +552,7 @@ class DiscountService extends BaseService {
|
|||||||
discountRuleId: string,
|
discountRuleId: string,
|
||||||
productId: string | undefined
|
productId: string | undefined
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
return this.atomicPhase_(async (manager) => {
|
return await this.atomicPhase_(async (manager) => {
|
||||||
const discountConditionRepo: DiscountConditionRepository =
|
const discountConditionRepo: DiscountConditionRepository =
|
||||||
manager.getCustomRepository(this.discountConditionRepository_)
|
manager.getCustomRepository(this.discountConditionRepository_)
|
||||||
|
|
||||||
@@ -626,98 +578,102 @@ class DiscountService extends BaseService {
|
|||||||
lineItem: LineItem,
|
lineItem: LineItem,
|
||||||
cart: Cart
|
cart: Cart
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
let adjustment = 0
|
return await this.atomicPhase_(async () => {
|
||||||
|
let adjustment = 0
|
||||||
|
|
||||||
if (!lineItem.allow_discounts) {
|
if (!lineItem.allow_discounts) {
|
||||||
return adjustment
|
return adjustment
|
||||||
}
|
}
|
||||||
|
|
||||||
const discount = await this.retrieve(discountId, { relations: ["rule"] })
|
const discount = await this.retrieve(discountId, { relations: ["rule"] })
|
||||||
|
|
||||||
const { type, value, allocation } = discount.rule
|
const { type, value, allocation } = discount.rule
|
||||||
|
|
||||||
const fullItemPrice = lineItem.unit_price * lineItem.quantity
|
const fullItemPrice = lineItem.unit_price * lineItem.quantity
|
||||||
|
|
||||||
if (type === DiscountRuleType.PERCENTAGE) {
|
if (type === DiscountRuleType.PERCENTAGE) {
|
||||||
adjustment = Math.round((fullItemPrice / 100) * value)
|
adjustment = Math.round((fullItemPrice / 100) * value)
|
||||||
} else if (
|
} else if (
|
||||||
type === DiscountRuleType.FIXED &&
|
type === DiscountRuleType.FIXED &&
|
||||||
allocation === DiscountAllocation.TOTAL
|
allocation === DiscountAllocation.TOTAL
|
||||||
) {
|
) {
|
||||||
// when a fixed discount should be applied to the total,
|
// when a fixed discount should be applied to the total,
|
||||||
// we create line adjustments for each item with an amount
|
// we create line adjustments for each item with an amount
|
||||||
// relative to the subtotal
|
// relative to the subtotal
|
||||||
const subtotal = this.totalsService_.getSubtotal(cart, {
|
const subtotal = this.totalsService_.getSubtotal(cart, {
|
||||||
excludeNonDiscounts: true,
|
excludeNonDiscounts: true,
|
||||||
})
|
})
|
||||||
const nominator = Math.min(value, subtotal)
|
const nominator = Math.min(value, subtotal)
|
||||||
const itemRelativeToSubtotal = lineItem.unit_price / subtotal
|
const itemRelativeToSubtotal = lineItem.unit_price / subtotal
|
||||||
const totalItemPercentage = itemRelativeToSubtotal * lineItem.quantity
|
const totalItemPercentage = itemRelativeToSubtotal * lineItem.quantity
|
||||||
adjustment = Math.round(nominator * totalItemPercentage)
|
adjustment = Math.round(nominator * totalItemPercentage)
|
||||||
} else {
|
} else {
|
||||||
adjustment = value * lineItem.quantity
|
adjustment = value * lineItem.quantity
|
||||||
}
|
}
|
||||||
// if the amount of the discount exceeds the total price of the item,
|
// if the amount of the discount exceeds the total price of the item,
|
||||||
// we return the total item price, else the fixed amount
|
// we return the total item price, else the fixed amount
|
||||||
return adjustment >= fullItemPrice ? fullItemPrice : adjustment
|
return adjustment >= fullItemPrice ? fullItemPrice : adjustment
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async validateDiscountForCartOrThrow(
|
async validateDiscountForCartOrThrow(
|
||||||
cart: Cart,
|
cart: Cart,
|
||||||
discount: Discount
|
discount: Discount
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (this.hasReachedLimit(discount)) {
|
return await this.atomicPhase_(async () => {
|
||||||
throw new MedusaError(
|
if (this.hasReachedLimit(discount)) {
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
|
||||||
"Discount has been used maximum allowed times"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.hasNotStarted(discount)) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
|
||||||
"Discount is not valid yet"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.hasExpired(discount)) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
|
||||||
"Discount is expired"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isDisabled(discount)) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
|
||||||
"The discount code is disabled"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const isValidForRegion = await this.isValidForRegion(
|
|
||||||
discount,
|
|
||||||
cart.region_id
|
|
||||||
)
|
|
||||||
if (!isValidForRegion) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.INVALID_DATA,
|
|
||||||
"The discount is not available in current region"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cart.customer_id) {
|
|
||||||
const canApplyForCustomer = await this.canApplyForCustomer(
|
|
||||||
discount.rule.id,
|
|
||||||
cart.customer_id
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!canApplyForCustomer) {
|
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
"Discount is not valid for customer"
|
"Discount has been used maximum allowed times"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if (this.hasNotStarted(discount)) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"Discount is not valid yet"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.hasExpired(discount)) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"Discount is expired"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isDisabled(discount)) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"The discount code is disabled"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValidForRegion = await this.isValidForRegion(
|
||||||
|
discount,
|
||||||
|
cart.region_id
|
||||||
|
)
|
||||||
|
if (!isValidForRegion) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
"The discount is not available in current region"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cart.customer_id) {
|
||||||
|
const canApplyForCustomer = await this.canApplyForCustomer(
|
||||||
|
discount.rule.id,
|
||||||
|
cart.customer_id
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!canApplyForCustomer) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"Discount is not valid for customer"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
hasReachedLimit(discount: Discount): boolean {
|
hasReachedLimit(discount: Discount): boolean {
|
||||||
@@ -746,24 +702,26 @@ class DiscountService extends BaseService {
|
|||||||
discount: Discount,
|
discount: Discount,
|
||||||
region_id: string
|
region_id: string
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
let regions = discount.regions
|
return await this.atomicPhase_(async () => {
|
||||||
|
let regions = discount.regions
|
||||||
|
|
||||||
if (discount.parent_discount_id) {
|
if (discount.parent_discount_id) {
|
||||||
const parent = await this.retrieve(discount.parent_discount_id, {
|
const parent = await this.retrieve(discount.parent_discount_id, {
|
||||||
relations: ["rule", "regions"],
|
relations: ["rule", "regions"],
|
||||||
})
|
})
|
||||||
|
|
||||||
regions = parent.regions
|
regions = parent.regions
|
||||||
}
|
}
|
||||||
|
|
||||||
return regions.find(({ id }) => id === region_id) !== undefined
|
return regions.find(({ id }) => id === region_id) !== undefined
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async canApplyForCustomer(
|
async canApplyForCustomer(
|
||||||
discountRuleId: string,
|
discountRuleId: string,
|
||||||
customerId: string | undefined
|
customerId: string | undefined
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
return this.atomicPhase_(async (manager) => {
|
return await this.atomicPhase_(async (manager) => {
|
||||||
const discountConditionRepo: DiscountConditionRepository =
|
const discountConditionRepo: DiscountConditionRepository =
|
||||||
manager.getCustomRepository(this.discountConditionRepository_)
|
manager.getCustomRepository(this.discountConditionRepository_)
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import jwt from "jsonwebtoken"
|
import jwt from "jsonwebtoken"
|
||||||
import { MedusaError, Validator } from "medusa-core-utils"
|
|
||||||
import { BaseService } from "medusa-interfaces"
|
|
||||||
import Scrypt from "scrypt-kdf"
|
import Scrypt from "scrypt-kdf"
|
||||||
|
import { MedusaError, Validator } from "medusa-core-utils"
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
import { User } from "../models/user"
|
import { User } from "../models"
|
||||||
import { UserRepository } from "../repositories/user"
|
import { UserRepository } from "../repositories/user"
|
||||||
import { FindConfig } from "../types/common"
|
import { FindConfig } from "../types/common"
|
||||||
import {
|
import {
|
||||||
@@ -12,6 +11,8 @@ import {
|
|||||||
UpdateUserInput,
|
UpdateUserInput,
|
||||||
} from "../types/user"
|
} from "../types/user"
|
||||||
import EventBusService from "./event-bus"
|
import EventBusService from "./event-bus"
|
||||||
|
import { TransactionBaseService } from "../interfaces"
|
||||||
|
import { buildQuery, setMetadata } from "../utils"
|
||||||
|
|
||||||
type UserServiceProps = {
|
type UserServiceProps = {
|
||||||
userRepository: typeof UserRepository
|
userRepository: typeof UserRepository
|
||||||
@@ -23,45 +24,24 @@ type UserServiceProps = {
|
|||||||
* Provides layer to manipulate users.
|
* Provides layer to manipulate users.
|
||||||
* @extends BaseService
|
* @extends BaseService
|
||||||
*/
|
*/
|
||||||
class UserService extends BaseService {
|
class UserService extends TransactionBaseService<UserService> {
|
||||||
static Events = {
|
static Events = {
|
||||||
PASSWORD_RESET: "user.password_reset",
|
PASSWORD_RESET: "user.password_reset",
|
||||||
}
|
}
|
||||||
|
|
||||||
private userRepository_: typeof UserRepository
|
protected manager_: EntityManager
|
||||||
private eventBus_: EventBusService
|
protected transactionManager_: EntityManager
|
||||||
private manager_: EntityManager
|
protected readonly userRepository_: typeof UserRepository
|
||||||
private transactionManager_: EntityManager
|
protected readonly eventBus_: EventBusService
|
||||||
|
|
||||||
constructor({ userRepository, eventBusService, manager }: UserServiceProps) {
|
constructor({ userRepository, eventBusService, manager }: UserServiceProps) {
|
||||||
super()
|
super({ userRepository, eventBusService, manager })
|
||||||
|
|
||||||
/** @private @const {UserRepository} */
|
|
||||||
this.userRepository_ = userRepository
|
this.userRepository_ = userRepository
|
||||||
|
|
||||||
/** @private @const {EventBus} */
|
|
||||||
this.eventBus_ = eventBusService
|
this.eventBus_ = eventBusService
|
||||||
|
|
||||||
/** @private @const {EntityManager} */
|
|
||||||
this.manager_ = manager
|
this.manager_ = manager
|
||||||
}
|
}
|
||||||
|
|
||||||
withTransaction(transactionManager: EntityManager): UserService {
|
|
||||||
if (!transactionManager) {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
const cloned = new UserService({
|
|
||||||
manager: transactionManager,
|
|
||||||
userRepository: this.userRepository_,
|
|
||||||
eventBusService: this.eventBus_,
|
|
||||||
})
|
|
||||||
|
|
||||||
cloned.transactionManager_ = transactionManager
|
|
||||||
|
|
||||||
return cloned
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to validate user email.
|
* Used to validate user email.
|
||||||
* @param {string} email - email to validate
|
* @param {string} email - email to validate
|
||||||
@@ -86,8 +66,12 @@ class UserService extends BaseService {
|
|||||||
* @return {Promise} the result of the find operation
|
* @return {Promise} the result of the find operation
|
||||||
*/
|
*/
|
||||||
async list(selector: FilterableUserProps, config = {}): Promise<User[]> {
|
async list(selector: FilterableUserProps, config = {}): Promise<User[]> {
|
||||||
const userRepo = this.manager_.getCustomRepository(this.userRepository_)
|
return await this.atomicPhase_(async (transactionManager) => {
|
||||||
return userRepo.find(this.buildQuery_(selector, config))
|
const userRepo = transactionManager.getCustomRepository(
|
||||||
|
this.userRepository_
|
||||||
|
)
|
||||||
|
return await userRepo.find(buildQuery(selector, config))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -98,20 +82,23 @@ class UserService extends BaseService {
|
|||||||
* @return {Promise<User>} the user document.
|
* @return {Promise<User>} the user document.
|
||||||
*/
|
*/
|
||||||
async retrieve(userId: string, config: FindConfig<User> = {}): Promise<User> {
|
async retrieve(userId: string, config: FindConfig<User> = {}): Promise<User> {
|
||||||
const userRepo = this.manager_.getCustomRepository(this.userRepository_)
|
return await this.atomicPhase_(async (transactionManager) => {
|
||||||
const validatedId = this.validateId_(userId)
|
const userRepo = transactionManager.getCustomRepository(
|
||||||
const query = this.buildQuery_({ id: validatedId }, config)
|
this.userRepository_
|
||||||
|
|
||||||
const user = await userRepo.findOne(query)
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_FOUND,
|
|
||||||
`User with id: ${userId} was not found`
|
|
||||||
)
|
)
|
||||||
}
|
const query = buildQuery({ id: userId }, config)
|
||||||
|
|
||||||
return user
|
const user = await userRepo.findOne(query)
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`User with id: ${userId} was not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return user
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -125,21 +112,25 @@ class UserService extends BaseService {
|
|||||||
apiToken: string,
|
apiToken: string,
|
||||||
relations: string[] = []
|
relations: string[] = []
|
||||||
): Promise<User> {
|
): Promise<User> {
|
||||||
const userRepo = this.manager_.getCustomRepository(this.userRepository_)
|
return await this.atomicPhase_(async (transactionManager) => {
|
||||||
|
const userRepo = transactionManager.getCustomRepository(
|
||||||
const user = await userRepo.findOne({
|
this.userRepository_
|
||||||
where: { api_token: apiToken },
|
|
||||||
relations,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_FOUND,
|
|
||||||
`User with api token: ${apiToken} was not found`
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
return user
|
const user = await userRepo.findOne({
|
||||||
|
where: { api_token: apiToken },
|
||||||
|
relations,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`User with api token: ${apiToken} was not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return user
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -153,19 +144,23 @@ class UserService extends BaseService {
|
|||||||
email: string,
|
email: string,
|
||||||
config: FindConfig<User> = {}
|
config: FindConfig<User> = {}
|
||||||
): Promise<User> {
|
): Promise<User> {
|
||||||
const userRepo = this.manager_.getCustomRepository(this.userRepository_)
|
return await this.atomicPhase_(async (transactionManager) => {
|
||||||
|
const userRepo = transactionManager.getCustomRepository(
|
||||||
const query = this.buildQuery_({ email: email.toLowerCase() }, config)
|
this.userRepository_
|
||||||
const user = await userRepo.findOne(query)
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_FOUND,
|
|
||||||
`User with email: ${email} was not found`
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
return user
|
const query = buildQuery({ email: email.toLowerCase() }, config)
|
||||||
|
const user = await userRepo.findOne(query)
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`User with email: ${email} was not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return user
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -186,7 +181,7 @@ class UserService extends BaseService {
|
|||||||
* @return {Promise} the result of create
|
* @return {Promise} the result of create
|
||||||
*/
|
*/
|
||||||
async create(user: CreateUserInput, password: string): Promise<User> {
|
async create(user: CreateUserInput, password: string): Promise<User> {
|
||||||
return this.atomicPhase_(async (manager: EntityManager) => {
|
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||||
const userRepo = manager.getCustomRepository(this.userRepository_)
|
const userRepo = manager.getCustomRepository(this.userRepository_)
|
||||||
|
|
||||||
const createData = { ...user } as CreateUserInput & {
|
const createData = { ...user } as CreateUserInput & {
|
||||||
@@ -203,7 +198,7 @@ class UserService extends BaseService {
|
|||||||
|
|
||||||
const created = userRepo.create(createData)
|
const created = userRepo.create(createData)
|
||||||
|
|
||||||
return userRepo.save(created)
|
return await userRepo.save(created)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,11 +209,10 @@ class UserService extends BaseService {
|
|||||||
* @return {Promise} the result of create
|
* @return {Promise} the result of create
|
||||||
*/
|
*/
|
||||||
async update(userId: string, update: UpdateUserInput): Promise<User> {
|
async update(userId: string, update: UpdateUserInput): Promise<User> {
|
||||||
return this.atomicPhase_(async (manager: EntityManager) => {
|
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||||
const userRepo = manager.getCustomRepository(this.userRepository_)
|
const userRepo = manager.getCustomRepository(this.userRepository_)
|
||||||
const validatedId = this.validateId_(userId)
|
|
||||||
|
|
||||||
const user = await this.retrieve(validatedId)
|
const user = await this.retrieve(userId)
|
||||||
|
|
||||||
const { email, password_hash, metadata, ...rest } = update
|
const { email, password_hash, metadata, ...rest } = update
|
||||||
|
|
||||||
@@ -237,14 +231,14 @@ class UserService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
user.metadata = this.setMetadata_(user, metadata)
|
user.metadata = setMetadata(user, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(rest)) {
|
for (const [key, value] of Object.entries(rest)) {
|
||||||
user[key as keyof User] = value
|
user[key as keyof User] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
return userRepo.save(user)
|
return await userRepo.save(user)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,8 +248,8 @@ class UserService extends BaseService {
|
|||||||
* castable as an ObjectId
|
* castable as an ObjectId
|
||||||
* @return {Promise} the result of the delete operation.
|
* @return {Promise} the result of the delete operation.
|
||||||
*/
|
*/
|
||||||
async delete(userId: string): Promise<null> {
|
async delete(userId: string): Promise<void> {
|
||||||
return this.atomicPhase_(async (manager: EntityManager) => {
|
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||||
const userRepo = manager.getCustomRepository(this.userRepository_)
|
const userRepo = manager.getCustomRepository(this.userRepository_)
|
||||||
|
|
||||||
// Should not fail, if user does not exist, since delete is idempotent
|
// Should not fail, if user does not exist, since delete is idempotent
|
||||||
@@ -280,7 +274,7 @@ class UserService extends BaseService {
|
|||||||
* @return {Promise} the result of the update operation
|
* @return {Promise} the result of the update operation
|
||||||
*/
|
*/
|
||||||
async setPassword_(userId: string, password: string): Promise<User> {
|
async setPassword_(userId: string, password: string): Promise<User> {
|
||||||
return this.atomicPhase_(async (manager: EntityManager) => {
|
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||||
const userRepo = manager.getCustomRepository(this.userRepository_)
|
const userRepo = manager.getCustomRepository(this.userRepository_)
|
||||||
|
|
||||||
const user = await this.retrieve(userId)
|
const user = await this.retrieve(userId)
|
||||||
@@ -295,7 +289,7 @@ class UserService extends BaseService {
|
|||||||
|
|
||||||
user.password_hash = hashedPassword
|
user.password_hash = hashedPassword
|
||||||
|
|
||||||
return userRepo.save(user)
|
return await userRepo.save(user)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,20 +303,25 @@ class UserService extends BaseService {
|
|||||||
* @return {string} the generated JSON web token
|
* @return {string} the generated JSON web token
|
||||||
*/
|
*/
|
||||||
async generateResetPasswordToken(userId: string): Promise<string> {
|
async generateResetPasswordToken(userId: string): Promise<string> {
|
||||||
const user = await this.retrieve(userId, {
|
return await this.atomicPhase_(async (transactionManager) => {
|
||||||
select: ["id", "email", "password_hash"],
|
const user = await this.retrieve(userId, {
|
||||||
})
|
select: ["id", "email", "password_hash"],
|
||||||
const secret = user.password_hash
|
})
|
||||||
const expiry = Math.floor(Date.now() / 1000) + 60 * 15
|
const secret = user.password_hash
|
||||||
const payload = { user_id: user.id, email: user.email, exp: expiry }
|
const expiry = Math.floor(Date.now() / 1000) + 60 * 15
|
||||||
const token = jwt.sign(payload, secret)
|
const payload = { user_id: user.id, email: user.email, exp: expiry }
|
||||||
|
const token = jwt.sign(payload, secret)
|
||||||
|
|
||||||
// Notify subscribers
|
// Notify subscribers
|
||||||
this.eventBus_.emit(UserService.Events.PASSWORD_RESET, {
|
await this.eventBus_
|
||||||
email: user.email,
|
.withTransaction(transactionManager)
|
||||||
token,
|
.emit(UserService.Events.PASSWORD_RESET, {
|
||||||
|
email: user.email,
|
||||||
|
token,
|
||||||
|
})
|
||||||
|
|
||||||
|
return token
|
||||||
})
|
})
|
||||||
return token
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
import { DiscountConditionOperator } from "../models/discount-condition"
|
import { DiscountConditionOperator } from "../models/discount-condition"
|
||||||
import { AllocationType, DiscountRuleType } from "../models/discount-rule"
|
import { AllocationType, DiscountRuleType } from "../models/discount-rule"
|
||||||
import { ExactlyOne } from "./validators/exactly-one"
|
import { ExactlyOne } from "./validators/exactly-one"
|
||||||
|
import { Region } from "../models"
|
||||||
|
|
||||||
export type QuerySelector = {
|
export type QuerySelector = {
|
||||||
q?: string
|
q?: string
|
||||||
@@ -132,7 +133,7 @@ export type CreateDiscountInput = {
|
|||||||
ends_at?: Date
|
ends_at?: Date
|
||||||
valid_duration?: string
|
valid_duration?: string
|
||||||
usage_limit?: number
|
usage_limit?: number
|
||||||
regions?: string[]
|
regions?: string[] | Region[]
|
||||||
metadata?: Record<string, unknown>
|
metadata?: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user