feat(medusa): PaymentCollectionService (#2365)
This commit is contained in:
committed by
GitHub
parent
edd35631f7
commit
d0f274dbe7
@@ -36,10 +36,10 @@ export enum PaymentCollectionType {
|
||||
@FeatureFlagEntity(OrderEditingFeatureFlag.key)
|
||||
export class PaymentCollection extends SoftDeletableEntity {
|
||||
@DbAwareColumn({ type: "enum", enum: PaymentCollectionType })
|
||||
type: string
|
||||
type: PaymentCollectionType
|
||||
|
||||
@DbAwareColumn({ type: "enum", enum: PaymentCollectionStatus })
|
||||
status: string
|
||||
status: PaymentCollectionStatus
|
||||
|
||||
@Column({ nullable: true })
|
||||
description: string
|
||||
|
||||
200
packages/medusa/src/services/__tests__/payment-collection.ts
Normal file
200
packages/medusa/src/services/__tests__/payment-collection.ts
Normal file
@@ -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 OrderEditItemChangeService } from "./order-edit-item-change"
|
||||
export { default as PaymentProviderService } from "./payment-provider"
|
||||
export { default as PaymentCollectionService } from "./payment-collection"
|
||||
export { default as PricingService } from "./pricing"
|
||||
export { default as PriceListService } from "./price-list"
|
||||
export { default as ProductCollectionService } from "./product-collection"
|
||||
|
||||
163
packages/medusa/src/services/payment-collection.ts
Normal file
163
packages/medusa/src/services/payment-collection.ts
Normal file
@@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
46
packages/medusa/src/types/payment-collection.ts
Normal file
46
packages/medusa/src/types/payment-collection.ts
Normal file
@@ -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