feat(medusa): PaymentCollectionService (#2365)
This commit is contained in:
@@ -36,10 +36,10 @@ export enum PaymentCollectionType {
|
|||||||
@FeatureFlagEntity(OrderEditingFeatureFlag.key)
|
@FeatureFlagEntity(OrderEditingFeatureFlag.key)
|
||||||
export class PaymentCollection extends SoftDeletableEntity {
|
export class PaymentCollection extends SoftDeletableEntity {
|
||||||
@DbAwareColumn({ type: "enum", enum: PaymentCollectionType })
|
@DbAwareColumn({ type: "enum", enum: PaymentCollectionType })
|
||||||
type: string
|
type: PaymentCollectionType
|
||||||
|
|
||||||
@DbAwareColumn({ type: "enum", enum: PaymentCollectionStatus })
|
@DbAwareColumn({ type: "enum", enum: PaymentCollectionStatus })
|
||||||
status: string
|
status: PaymentCollectionStatus
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
description: string
|
description: string
|
||||||
|
|||||||
@@ -0,0 +1,200 @@
|
|||||||
|
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
||||||
|
import { EventBusService, PaymentCollectionService } from "../index"
|
||||||
|
import { PaymentCollectionStatus, PaymentCollectionType } from "../../models"
|
||||||
|
import { EventBusServiceMock } from "../__mocks__/event-bus"
|
||||||
|
|
||||||
|
describe("PaymentCollectionService", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
const paymentCollectionSample = {
|
||||||
|
id: IdMap.getId("payment-collection-id1"),
|
||||||
|
region_id: IdMap.getId("region1"),
|
||||||
|
amount: 100,
|
||||||
|
created_at: new Date(),
|
||||||
|
metadata: {
|
||||||
|
pluginInfo: "xyz",
|
||||||
|
},
|
||||||
|
status: PaymentCollectionStatus.NOT_PAID,
|
||||||
|
}
|
||||||
|
|
||||||
|
const paymentCollectionAuthorizedSample = {
|
||||||
|
id: IdMap.getId("payment-collection-id2"),
|
||||||
|
region_id: IdMap.getId("region1"),
|
||||||
|
amount: 35000,
|
||||||
|
status: PaymentCollectionStatus.AUTHORIZED,
|
||||||
|
}
|
||||||
|
|
||||||
|
const paymentCollectionRepository = MockRepository({
|
||||||
|
findOne: (query) => {
|
||||||
|
const map = {
|
||||||
|
[IdMap.getId("payment-collection-id1")]: paymentCollectionSample,
|
||||||
|
[IdMap.getId("payment-collection-id2")]:
|
||||||
|
paymentCollectionAuthorizedSample,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (map[query?.where?.id]) {
|
||||||
|
return { ...map[query?.where?.id] }
|
||||||
|
}
|
||||||
|
return
|
||||||
|
},
|
||||||
|
create: (data) => {
|
||||||
|
return {
|
||||||
|
...paymentCollectionSample,
|
||||||
|
...data,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const paymentCollectionService = new PaymentCollectionService({
|
||||||
|
manager: MockManager,
|
||||||
|
paymentCollectionRepository,
|
||||||
|
eventBusService: EventBusServiceMock as unknown as EventBusService,
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should retrieve a payment collection", async () => {
|
||||||
|
await paymentCollectionService.retrieve(
|
||||||
|
IdMap.getId("payment-collection-id1")
|
||||||
|
)
|
||||||
|
expect(paymentCollectionRepository.findOne).toHaveBeenCalledTimes(1)
|
||||||
|
expect(paymentCollectionRepository.findOne).toHaveBeenCalledWith({
|
||||||
|
where: { id: IdMap.getId("payment-collection-id1") },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw error if payment collection is not found", async () => {
|
||||||
|
const payCol = paymentCollectionService.retrieve(
|
||||||
|
IdMap.getId("payment-collection-non-existing-id")
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(paymentCollectionRepository.findOne).toHaveBeenCalledTimes(1)
|
||||||
|
expect(payCol).rejects.toThrow(Error)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should create a payment collection", async () => {
|
||||||
|
const entity = await paymentCollectionService.create({
|
||||||
|
region_id: IdMap.getId("region2"),
|
||||||
|
type: PaymentCollectionType.ORDER_EDIT,
|
||||||
|
currency_code: "USD",
|
||||||
|
amount: 190,
|
||||||
|
created_by: IdMap.getId("user-id"),
|
||||||
|
description: "some description",
|
||||||
|
metadata: {
|
||||||
|
abc: 123,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(paymentCollectionRepository.save).toHaveBeenCalledTimes(1)
|
||||||
|
expect(paymentCollectionRepository.save).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: IdMap.getId("payment-collection-id1"),
|
||||||
|
region_id: IdMap.getId("region2"),
|
||||||
|
amount: 190,
|
||||||
|
created_by: IdMap.getId("user-id"),
|
||||||
|
status: PaymentCollectionStatus.NOT_PAID,
|
||||||
|
description: "some description",
|
||||||
|
metadata: {
|
||||||
|
abc: 123,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
|
||||||
|
expect(EventBusServiceMock.emit).toHaveBeenCalledWith(
|
||||||
|
PaymentCollectionService.Events.CREATED,
|
||||||
|
entity
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should update a payment collection with the right arguments", async () => {
|
||||||
|
const submittedChanges = {
|
||||||
|
description: "updated description",
|
||||||
|
status: PaymentCollectionStatus.CAPTURED,
|
||||||
|
metadata: {
|
||||||
|
extra: 123,
|
||||||
|
arr: ["a", "b", "c"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const internalChanges = {
|
||||||
|
...submittedChanges,
|
||||||
|
}
|
||||||
|
internalChanges.metadata = {
|
||||||
|
...internalChanges.metadata,
|
||||||
|
...{
|
||||||
|
pluginInfo: "xyz",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const entity = await paymentCollectionService.update(
|
||||||
|
IdMap.getId("payment-collection-id1"),
|
||||||
|
submittedChanges
|
||||||
|
)
|
||||||
|
expect(paymentCollectionRepository.save).toHaveBeenCalledTimes(1)
|
||||||
|
expect(paymentCollectionRepository.save).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining(internalChanges)
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
|
||||||
|
expect(EventBusServiceMock.emit).toHaveBeenCalledWith(
|
||||||
|
PaymentCollectionService.Events.UPDATED,
|
||||||
|
entity
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw error to update a non-existing payment collection", async () => {
|
||||||
|
const submittedChanges = {
|
||||||
|
description: "updated description",
|
||||||
|
status: PaymentCollectionStatus.CAPTURED,
|
||||||
|
metadata: {
|
||||||
|
extra: 123,
|
||||||
|
arr: ["a", "b", "c"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const payCol = paymentCollectionService.update(
|
||||||
|
IdMap.getId("payment-collection-non-existing"),
|
||||||
|
submittedChanges
|
||||||
|
)
|
||||||
|
expect(paymentCollectionRepository.save).toHaveBeenCalledTimes(0)
|
||||||
|
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(0)
|
||||||
|
expect(payCol).rejects.toThrow(Error)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should delete a payment collection", async () => {
|
||||||
|
const entity = await paymentCollectionService.delete(
|
||||||
|
IdMap.getId("payment-collection-id1")
|
||||||
|
)
|
||||||
|
expect(paymentCollectionRepository.remove).toHaveBeenCalledTimes(1)
|
||||||
|
expect(paymentCollectionRepository.remove).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: IdMap.getId("payment-collection-id1"),
|
||||||
|
region_id: IdMap.getId("region1"),
|
||||||
|
amount: 100,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
|
||||||
|
expect(EventBusServiceMock.emit).toHaveBeenCalledWith(
|
||||||
|
PaymentCollectionService.Events.DELETED,
|
||||||
|
entity
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should ignore to delete a non-existing payment collection", async () => {
|
||||||
|
const entity = await paymentCollectionService.delete(
|
||||||
|
IdMap.getId("payment-collection-non-existing")
|
||||||
|
)
|
||||||
|
expect(paymentCollectionRepository.remove).toHaveBeenCalledTimes(0)
|
||||||
|
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(0)
|
||||||
|
expect(entity).toBe(undefined)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw and error when trying to delete an initialized payment collection", async () => {
|
||||||
|
const entity = paymentCollectionService.delete(
|
||||||
|
IdMap.getId("payment-collection-id2")
|
||||||
|
)
|
||||||
|
expect(paymentCollectionRepository.remove).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
|
expect(entity).rejects.toThrow(Error)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -25,6 +25,7 @@ export { default as OrderService } from "./order"
|
|||||||
export { default as OrderEditService } from "./order-edit"
|
export { default as OrderEditService } from "./order-edit"
|
||||||
export { default as OrderEditItemChangeService } from "./order-edit-item-change"
|
export { default as OrderEditItemChangeService } from "./order-edit-item-change"
|
||||||
export { default as PaymentProviderService } from "./payment-provider"
|
export { default as PaymentProviderService } from "./payment-provider"
|
||||||
|
export { default as PaymentCollectionService } from "./payment-collection"
|
||||||
export { default as PricingService } from "./pricing"
|
export { default as PricingService } from "./pricing"
|
||||||
export { default as PriceListService } from "./price-list"
|
export { default as PriceListService } from "./price-list"
|
||||||
export { default as ProductCollectionService } from "./product-collection"
|
export { default as ProductCollectionService } from "./product-collection"
|
||||||
|
|||||||
@@ -0,0 +1,163 @@
|
|||||||
|
import { DeepPartial, EntityManager, IsNull } from "typeorm"
|
||||||
|
import { MedusaError } from "medusa-core-utils"
|
||||||
|
|
||||||
|
import { FindConfig } from "../types/common"
|
||||||
|
import { buildQuery, isDefined, setMetadata } from "../utils"
|
||||||
|
import { PaymentCollectionRepository } from "../repositories/payment-collection"
|
||||||
|
import { PaymentCollection, PaymentCollectionStatus } from "../models"
|
||||||
|
import { TransactionBaseService } from "../interfaces"
|
||||||
|
import { EventBusService } from "./index"
|
||||||
|
|
||||||
|
import { CreatePaymentCollectionInput } from "../types/payment-collection"
|
||||||
|
|
||||||
|
type InjectedDependencies = {
|
||||||
|
manager: EntityManager
|
||||||
|
paymentCollectionRepository: typeof PaymentCollectionRepository
|
||||||
|
eventBusService: EventBusService
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class PaymentCollectionService extends TransactionBaseService {
|
||||||
|
static readonly Events = {
|
||||||
|
CREATED: "payment-collection.created",
|
||||||
|
UPDATED: "payment-collection.updated",
|
||||||
|
DELETED: "payment-collection.deleted",
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly manager_: EntityManager
|
||||||
|
protected transactionManager_: EntityManager | undefined
|
||||||
|
protected readonly eventBusService_: EventBusService
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
protected readonly paymentCollectionRepository_: typeof PaymentCollectionRepository
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
manager,
|
||||||
|
paymentCollectionRepository,
|
||||||
|
eventBusService,
|
||||||
|
}: InjectedDependencies) {
|
||||||
|
// eslint-disable-next-line prefer-rest-params
|
||||||
|
super(arguments[0])
|
||||||
|
|
||||||
|
this.manager_ = manager
|
||||||
|
this.paymentCollectionRepository_ = paymentCollectionRepository
|
||||||
|
this.eventBusService_ = eventBusService
|
||||||
|
}
|
||||||
|
|
||||||
|
async retrieve(
|
||||||
|
paymentCollectionId: string,
|
||||||
|
config: FindConfig<PaymentCollection> = {}
|
||||||
|
): Promise<PaymentCollection> {
|
||||||
|
const manager = this.transactionManager_ ?? this.manager_
|
||||||
|
const paymentCollectionRepository = manager.getCustomRepository(
|
||||||
|
this.paymentCollectionRepository_
|
||||||
|
)
|
||||||
|
|
||||||
|
const query = buildQuery({ id: paymentCollectionId }, config)
|
||||||
|
const paymentCollection = await paymentCollectionRepository.findOne(query)
|
||||||
|
|
||||||
|
if (!paymentCollection) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`Payment collection with id ${paymentCollectionId} was not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return paymentCollection
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(data: CreatePaymentCollectionInput): Promise<PaymentCollection> {
|
||||||
|
return await this.atomicPhase_(async (transactionManager) => {
|
||||||
|
const paymentCollectionRepository =
|
||||||
|
transactionManager.getCustomRepository(
|
||||||
|
this.paymentCollectionRepository_
|
||||||
|
)
|
||||||
|
|
||||||
|
const paymentCollectionToCreate = paymentCollectionRepository.create({
|
||||||
|
region_id: data.region_id,
|
||||||
|
type: data.type,
|
||||||
|
status: PaymentCollectionStatus.NOT_PAID,
|
||||||
|
currency_code: data.currency_code,
|
||||||
|
amount: data.amount,
|
||||||
|
metadata: data.metadata,
|
||||||
|
created_by: data.created_by,
|
||||||
|
description: data.description,
|
||||||
|
})
|
||||||
|
|
||||||
|
const paymentCollection = await paymentCollectionRepository.save(
|
||||||
|
paymentCollectionToCreate
|
||||||
|
)
|
||||||
|
|
||||||
|
await this.eventBusService_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.emit(PaymentCollectionService.Events.CREATED, paymentCollection)
|
||||||
|
|
||||||
|
return paymentCollection
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(
|
||||||
|
paymentCollectionId: string,
|
||||||
|
data: DeepPartial<PaymentCollection>
|
||||||
|
): Promise<PaymentCollection> {
|
||||||
|
return await this.atomicPhase_(async (manager) => {
|
||||||
|
const paymentCollectionRepo = manager.getCustomRepository(
|
||||||
|
this.paymentCollectionRepository_
|
||||||
|
)
|
||||||
|
|
||||||
|
const paymentCollection = await this.retrieve(paymentCollectionId)
|
||||||
|
|
||||||
|
for (const key of Object.keys(data)) {
|
||||||
|
if (key === "metadata" && data.metadata) {
|
||||||
|
paymentCollection[key] = setMetadata(paymentCollection, data.metadata)
|
||||||
|
} else if (isDefined(data[key])) {
|
||||||
|
paymentCollection[key] = data[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await paymentCollectionRepo.save(paymentCollection)
|
||||||
|
|
||||||
|
await this.eventBusService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.emit(PaymentCollectionService.Events.UPDATED, result)
|
||||||
|
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(
|
||||||
|
paymentCollectionId: string
|
||||||
|
): Promise<PaymentCollection | undefined> {
|
||||||
|
return await this.atomicPhase_(async (manager) => {
|
||||||
|
const paymentCollectionRepo = manager.getCustomRepository(
|
||||||
|
this.paymentCollectionRepository_
|
||||||
|
)
|
||||||
|
|
||||||
|
const paymentCollection = await this.retrieve(paymentCollectionId).catch(
|
||||||
|
() => void 0
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!paymentCollection) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
[
|
||||||
|
PaymentCollectionStatus.CANCELED,
|
||||||
|
PaymentCollectionStatus.NOT_PAID,
|
||||||
|
].includes(paymentCollection.status) === false
|
||||||
|
) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
`Cannot delete payment collection with status ${paymentCollection.status}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
await paymentCollectionRepo.remove(paymentCollection)
|
||||||
|
|
||||||
|
await this.eventBusService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.emit(PaymentCollectionService.Events.DELETED, paymentCollection)
|
||||||
|
|
||||||
|
return paymentCollection
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { PaymentCollection, PaymentCollectionType } from "../models"
|
||||||
|
|
||||||
|
export type CreatePaymentCollectionInput = {
|
||||||
|
region_id: string
|
||||||
|
type: PaymentCollectionType
|
||||||
|
currency_code: string
|
||||||
|
amount: number
|
||||||
|
created_by: string
|
||||||
|
metadata?: any
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultPaymentCollectionRelations = [
|
||||||
|
"region",
|
||||||
|
"region.payment_providers",
|
||||||
|
"payment_sessions",
|
||||||
|
]
|
||||||
|
|
||||||
|
export const defaultPaymentCollectionFields: (keyof PaymentCollection)[] = [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"status",
|
||||||
|
"description",
|
||||||
|
"amount",
|
||||||
|
"authorized_amount",
|
||||||
|
"refunded_amount",
|
||||||
|
"currency_code",
|
||||||
|
"metadata",
|
||||||
|
"region",
|
||||||
|
"payment_sessions",
|
||||||
|
"payments",
|
||||||
|
]
|
||||||
|
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
export const storePaymentCollectionNotAllowedFieldsAndRelations = ["created_by"]
|
||||||
|
|
||||||
|
export const defaultStorePaymentCollectionRelations =
|
||||||
|
defaultPaymentCollectionRelations.filter(
|
||||||
|
(field) =>
|
||||||
|
!storePaymentCollectionNotAllowedFieldsAndRelations.includes(field)
|
||||||
|
)
|
||||||
|
export const defaultStorePaymentCollectionFields =
|
||||||
|
defaultPaymentCollectionFields.filter(
|
||||||
|
(field) =>
|
||||||
|
!storePaymentCollectionNotAllowedFieldsAndRelations.includes(field)
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user