feat(payment): PaymentCollection CRUD (#6124)

This commit is contained in:
Frane Polić
2024-01-22 11:04:46 +01:00
committed by GitHub
parent 3f41d818b6
commit d47e946496
11 changed files with 606 additions and 10 deletions

View File

@@ -0,0 +1,20 @@
export const defaultPaymentCollectionData = [
{
id: "pay-col-id-1",
amount: 100,
region_id: "region-id-1",
currency_code: "usd",
},
{
id: "pay-col-id-2",
amount: 200,
region_id: "region-id-1",
currency_code: "usd",
},
{
id: "pay-col-id-3",
amount: 300,
region_id: "region-id-2",
currency_code: "usd",
},
]

View File

@@ -0,0 +1,22 @@
import { CreatePaymentCollectionDTO } from "@medusajs/types"
import { SqlEntityManager } from "@mikro-orm/postgresql"
import { PaymentCollection } from "../../../src/models"
import { defaultPaymentCollectionData } from "./data"
export * from "./data"
export async function createPaymentCollections(
manager: SqlEntityManager,
paymentCollectionData: CreatePaymentCollectionDTO[] = defaultPaymentCollectionData
): Promise<PaymentCollection[]> {
const collections: PaymentCollection[] = []
for (let data of paymentCollectionData) {
let collection = manager.create(PaymentCollection, data)
await manager.persistAndFlush(collection)
}
return collections
}

View File

@@ -0,0 +1,212 @@
import { IPaymentModuleService } from "@medusajs/types"
import { SqlEntityManager } from "@mikro-orm/postgresql"
import { initialize } from "../../../../src/initialize"
import { DB_URL, MikroOrmWrapper } from "../../../utils"
import { createPaymentCollections } from "../../../__fixtures__/payment-collection"
jest.setTimeout(30000)
describe("Payment Module Service", () => {
let service: IPaymentModuleService
let repositoryManager: SqlEntityManager
beforeEach(async () => {
await MikroOrmWrapper.setupDatabase()
repositoryManager = await MikroOrmWrapper.forkManager()
service = await initialize({
database: {
clientUrl: DB_URL,
schema: process.env.MEDUSA_PAYMNET_DB_SCHEMA,
},
})
await createPaymentCollections(repositoryManager)
})
afterEach(async () => {
await MikroOrmWrapper.clearDatabase()
})
describe("create", () => {
it("should throw an error when required params are not passed", async () => {
let error = await service
.createPaymentCollection([
{
amount: 200,
region_id: "req_123",
} as any,
])
.catch((e) => e)
expect(error.message).toContain(
"Value for PaymentCollection.currency_code is required, 'undefined' found"
)
error = await service
.createPaymentCollection([
{
currency_code: "USD",
region_id: "req_123",
} as any,
])
.catch((e) => e)
expect(error.message).toContain(
"Value for PaymentCollection.amount is required, 'undefined' found"
)
error = await service
.createPaymentCollection([
{
currency_code: "USD",
amount: 200,
} as any,
])
.catch((e) => e)
expect(error.message).toContain(
"Value for PaymentCollection.region_id is required, 'undefined' found"
)
})
it("should create a payment collection successfully", async () => {
const [createdPaymentCollection] = await service.createPaymentCollection([
{ currency_code: "USD", amount: 200, region_id: "reg_123" },
])
expect(createdPaymentCollection).toEqual(
expect.objectContaining({
id: expect.any(String),
status: "not_paid",
payment_providers: [],
payment_sessions: [],
payments: [],
currency_code: "USD",
amount: 200,
})
)
})
})
describe("delete", () => {
it("should delete a Payment Collection", async () => {
let collection = await service.listPaymentCollections({
id: ["pay-col-id-1"],
})
expect(collection.length).toEqual(1)
await service.deletePaymentCollection(["pay-col-id-1"])
collection = await service.listPaymentCollections({
id: ["pay-col-id-1"],
})
expect(collection.length).toEqual(0)
})
})
describe("retrieve", () => {
it("should retrieve a Payment Collection", async () => {
let collection = await service.retrievePaymentCollection("pay-col-id-2")
expect(collection).toEqual(
expect.objectContaining({
id: "pay-col-id-2",
amount: 200,
region_id: "region-id-1",
currency_code: "usd",
})
)
})
it("should fail to retrieve a non existent Payment Collection", async () => {
let error = await service
.retrievePaymentCollection("pay-col-id-not-exists")
.catch((e) => e)
expect(error.message).toContain(
"PaymentCollection with id: pay-col-id-not-exists was not found"
)
})
})
describe("list", () => {
it("should list and count Payment Collection", async () => {
let [collections, count] = await service.listAndCountPaymentCollections()
expect(count).toEqual(3)
expect(collections).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: "pay-col-id-1",
amount: 100,
region_id: "region-id-1",
currency_code: "usd",
}),
expect.objectContaining({
id: "pay-col-id-2",
amount: 200,
region_id: "region-id-1",
currency_code: "usd",
}),
expect.objectContaining({
id: "pay-col-id-3",
amount: 300,
region_id: "region-id-2",
currency_code: "usd",
}),
])
)
})
it("should list Payment Collections by region_id", async () => {
let collections = await service.listPaymentCollections(
{
region_id: "region-id-1",
},
{ select: ["id", "amount", "region_id"] }
)
expect(collections.length).toEqual(2)
expect(collections).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: "pay-col-id-1",
amount: 100,
region_id: "region-id-1",
}),
expect.objectContaining({
id: "pay-col-id-2",
amount: 200,
region_id: "region-id-1",
}),
])
)
})
})
describe("update", () => {
it("should update a Payment Collection", async () => {
await service.updatePaymentCollection({
id: "pay-col-id-2",
currency_code: "eur",
authorized_amount: 200,
})
const collection = await service.retrievePaymentCollection("pay-col-id-2")
expect(collection).toEqual(
expect.objectContaining({
id: "pay-col-id-2",
authorized_amount: 200,
currency_code: "eur",
})
)
})
})
})

View File

@@ -6,11 +6,16 @@ import {
Modules,
} from "@medusajs/modules-sdk"
import { IPaymentModuleService, ModulesSdkTypes } from "@medusajs/types"
import { moduleDefinition } from "../module-definition"
import { InitializeModuleInjectableDependencies } from "../types"
export const initialize = async (
options?: ModulesSdkTypes.ModuleBootstrapDeclaration,
options?:
| ModulesSdkTypes.ModuleServiceInitializeOptions
| ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions
| ExternalModuleDeclaration
| InternalModuleDeclaration,
injectedDependencies?: InitializeModuleInjectableDependencies
): Promise<IPaymentModuleService> => {
const loaded = await MedusaModule.bootstrap<IPaymentModuleService>({

View File

@@ -1,10 +1,11 @@
import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
import { MapToConfig } from "@medusajs/utils"
import { Payment } from "@models"
import { Payment, PaymentCollection } from "@models"
export const LinkableKeys = {
payment_id: Payment.name,
payment_collection_id: PaymentCollection.name,
}
const entityLinkableKeysMap: MapToConfig = {}

View File

@@ -1 +1,2 @@
export { default as PaymentModuleService } from "./payment-module"
export { default as PaymentCollectionService } from "./payment-collection"

View File

@@ -0,0 +1,26 @@
import { PaymentCollection } from "@models"
import {
CreatePaymentCollectionDTO,
DAL,
UpdatePaymentCollectionDTO,
} from "@medusajs/types"
import { ModulesSdkUtils } from "@medusajs/utils"
type InjectedDependencies = {
paymentCollectionRepository: DAL.RepositoryService
}
export default class PaymentCollectionService<
TEntity extends PaymentCollection = PaymentCollection
> extends ModulesSdkUtils.abstractServiceFactory<
InjectedDependencies,
{
create: CreatePaymentCollectionDTO
update: UpdatePaymentCollectionDTO
}
>(PaymentCollection)<TEntity> {
constructor(container: InjectedDependencies) {
// @ts-ignore
super(...arguments)
}
}

View File

@@ -1,29 +1,274 @@
import {
Context,
CreatePaymentCollectionDTO,
CreatePaymentDTO,
CreatePaymentSessionDTO,
DAL,
FilterablePaymentCollectionProps,
FindConfig,
InternalModuleDeclaration,
IPaymentModuleService,
ModuleJoinerConfig,
PaymentCollectionDTO,
PaymentDTO,
SetPaymentSessionsDTO,
UpdatePaymentCollectionDTO,
UpdatePaymentDTO,
} from "@medusajs/types"
import {
InjectManager,
InjectTransactionManager,
MedusaContext,
} from "@medusajs/utils"
import { Payment } from "@models"
import * as services from "@services"
import { joinerConfig } from "../joiner-config"
type InjectedDependencies = {
baseRepository: DAL.RepositoryService
paymentCollectionService: services.PaymentCollectionService
}
// TODO: implement IPaymentModule
export default class PaymentModule<TPayment extends Payment = Payment> {
export default class PaymentModuleService implements IPaymentModuleService {
protected baseRepository_: DAL.RepositoryService
protected paymentCollectionService_: services.PaymentCollectionService
constructor(
{ baseRepository }: InjectedDependencies,
{ baseRepository, paymentCollectionService }: InjectedDependencies,
protected readonly moduleDeclaration: InternalModuleDeclaration
) {
this.baseRepository_ = baseRepository
this.paymentCollectionService_ = paymentCollectionService
}
__joinerConfig(): ModuleJoinerConfig {
return joinerConfig
}
createPaymentCollection(
data: CreatePaymentCollectionDTO,
sharedContext?: Context
): Promise<PaymentCollectionDTO>
createPaymentCollection(
data: CreatePaymentCollectionDTO[],
sharedContext?: Context
): Promise<PaymentCollectionDTO[]>
@InjectTransactionManager("baseRepository_")
async createPaymentCollection(
data: CreatePaymentCollectionDTO | CreatePaymentCollectionDTO[],
@MedusaContext() sharedContext?: Context
): Promise<PaymentCollectionDTO | PaymentCollectionDTO[]> {
const input = Array.isArray(data) ? data : [data]
const collections = await this.paymentCollectionService_.create(
input,
sharedContext
)
return await this.baseRepository_.serialize<PaymentCollectionDTO[]>(
Array.isArray(data) ? collections : collections[0],
{
populate: true,
}
)
}
updatePaymentCollection(
data: UpdatePaymentCollectionDTO[],
sharedContext?: Context
): Promise<PaymentCollectionDTO[]>
updatePaymentCollection(
data: UpdatePaymentCollectionDTO,
sharedContext?: Context
): Promise<PaymentCollectionDTO>
@InjectTransactionManager("baseRepository_")
async updatePaymentCollection(
data: UpdatePaymentCollectionDTO | UpdatePaymentCollectionDTO[],
sharedContext?: Context
): Promise<PaymentCollectionDTO | PaymentCollectionDTO[]> {
const input = Array.isArray(data) ? data : [data]
const result = await this.paymentCollectionService_.update(
input,
sharedContext
)
return await this.baseRepository_.serialize<PaymentCollectionDTO[]>(
Array.isArray(data) ? result : result[0],
{
populate: true,
}
)
}
deletePaymentCollection(
paymentCollectionId: string[],
sharedContext?: Context
): Promise<void>
deletePaymentCollection(
paymentCollectionId: string,
sharedContext?: Context
): Promise<void>
@InjectTransactionManager("baseRepository_")
async deletePaymentCollection(
ids: string | string[],
@MedusaContext() sharedContext?: Context
): Promise<void> {
const paymentCollectionIds = Array.isArray(ids) ? ids : [ids]
await this.paymentCollectionService_.delete(
paymentCollectionIds,
sharedContext
)
}
@InjectManager("baseRepository_")
async retrievePaymentCollection(
paymentCollectionId: string,
config: FindConfig<PaymentCollectionDTO> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<PaymentCollectionDTO> {
const paymentCollection = await this.paymentCollectionService_.retrieve(
paymentCollectionId,
config,
sharedContext
)
return await this.baseRepository_.serialize<PaymentCollectionDTO>(
paymentCollection,
{ populate: true }
)
}
@InjectManager("baseRepository_")
async listPaymentCollections(
filters: FilterablePaymentCollectionProps = {},
config: FindConfig<PaymentCollectionDTO> = {},
@MedusaContext() sharedContext?: Context
): Promise<PaymentCollectionDTO[]> {
const paymentCollections = await this.paymentCollectionService_.list(
filters,
config,
sharedContext
)
return await this.baseRepository_.serialize<PaymentCollectionDTO[]>(
paymentCollections,
{ populate: true }
)
}
@InjectManager("baseRepository_")
async listAndCountPaymentCollections(
filters: FilterablePaymentCollectionProps = {},
config: FindConfig<PaymentCollectionDTO> = {},
@MedusaContext() sharedContext?: Context
): Promise<[PaymentCollectionDTO[], number]> {
const [paymentCollections, count] =
await this.paymentCollectionService_.listAndCount(
filters,
config,
sharedContext
)
return [
await this.baseRepository_.serialize<PaymentCollectionDTO[]>(
paymentCollections,
{ populate: true }
),
count,
]
}
/**
* TODO
*/
authorizePaymentCollection(
paymentCollectionId: string,
sharedContext?: Context | undefined
): Promise<PaymentCollectionDTO> {
throw new Error("Method not implemented.")
}
completePaymentCollection(
paymentCollectionId: string,
sharedContext?: Context | undefined
): Promise<PaymentCollectionDTO> {
throw new Error("Method not implemented.")
}
createPayment(data: CreatePaymentDTO): Promise<PaymentDTO>
createPayment(data: CreatePaymentDTO[]): Promise<PaymentDTO[]>
createPayment(data: unknown): Promise<PaymentDTO | PaymentDTO[]> {
throw new Error("Method not implemented.")
}
capturePayment(
paymentId: string,
amount: number,
sharedContext?: Context | undefined
): Promise<PaymentDTO> {
throw new Error("Method not implemented.")
}
refundPayment(
paymentId: string,
amount: number,
sharedContext?: Context | undefined
): Promise<PaymentDTO> {
throw new Error("Method not implemented.")
}
updatePayment(
data: UpdatePaymentDTO,
sharedContext?: Context | undefined
): Promise<PaymentDTO>
updatePayment(
data: UpdatePaymentDTO[],
sharedContext?: Context | undefined
): Promise<PaymentDTO[]>
updatePayment(
data: unknown,
sharedContext?: unknown
): Promise<PaymentDTO | PaymentDTO[]> {
throw new Error("Method not implemented.")
}
createPaymentSession(
paymentCollectionId: string,
data: CreatePaymentSessionDTO,
sharedContext?: Context | undefined
): Promise<PaymentCollectionDTO>
createPaymentSession(
paymentCollectionId: string,
data: CreatePaymentSessionDTO[],
sharedContext?: Context | undefined
): Promise<PaymentCollectionDTO>
createPaymentSession(
paymentCollectionId: unknown,
data: unknown,
sharedContext?: unknown
): Promise<PaymentCollectionDTO> {
throw new Error("Method not implemented.")
}
authorizePaymentSessions(
paymentCollectionId: string,
sessionIds: string[],
sharedContext?: Context | undefined
): Promise<PaymentCollectionDTO> {
throw new Error("Method not implemented.")
}
completePaymentSessions(
paymentCollectionId: string,
sessionIds: string[],
sharedContext?: Context | undefined
): Promise<PaymentCollectionDTO> {
throw new Error("Method not implemented.")
}
setPaymentSessions(
paymentCollectionId: string,
data: SetPaymentSessionsDTO[],
sharedContext?: Context | undefined
): Promise<PaymentCollectionDTO> {
throw new Error("Method not implemented.")
}
}

View File

@@ -0,0 +1,18 @@
import {
DAL,
CreatePaymentCollectionDTO,
UpdatePaymentCollectionDTO,
} from "@medusajs/types"
import { PaymentCollection } from "@models"
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface IPaymentCollectionRepository<
TEntity extends PaymentCollection = PaymentCollection
> extends DAL.RepositoryService<
TEntity,
{
create: CreatePaymentCollectionDTO
update: UpdatePaymentCollectionDTO
}
> {}

View File

@@ -3,6 +3,34 @@ import { OperatorMap } from "../dal/utils"
/* ********** PAYMENT COLLECTION ********** */
/**
* @enum
*
* The payment collection's status.
*/
export enum PaymentCollectionStatus {
/**
* The payment collection isn't paid.
*/
NOT_PAID = "not_paid",
/**
* The payment collection is awaiting payment.
*/
AWAITING = "awaiting",
/**
* The payment collection is authorized.
*/
AUTHORIZED = "authorized",
/**
* Some of the payments in the payment collection are authorized.
*/
PARTIALLY_AUTHORIZED = "partially_authorized",
/**
* The payment collection is canceled.
*/
CANCELED = "canceled",
}
export interface PaymentCollectionDTO {
/**
* The ID of the Payment Collection

View File

@@ -1,15 +1,29 @@
/**
* TODO
*/
import { PaymentCollectionStatus } from "./common"
/**
* Payment Collection
*/
export interface CreatePaymentCollectionDTO {
region_id: string
currency_code: string
amount: number
metadata?: Record<string, unknown>
}
export interface UpdatePaymentCollectionDTO
extends Partial<CreatePaymentCollectionDTO> {}
extends Partial<CreatePaymentCollectionDTO> {
id: string
authorized_amount?: number
refunded_amount?: number
completed_at?: number
status?: PaymentCollectionStatus
}
/**
* Payment
*/
export interface CreatePaymentDTO {
amount: number
@@ -30,6 +44,10 @@ export interface UpdatePaymentDTO {
customer_id?: string
}
/**
* Payment Session
*/
export interface CreatePaymentSessionDTO {
amount: number
currency_code: string