feat(payment): payment and session methods (#6138)

This commit is contained in:
Frane Polić
2024-02-06 13:40:22 +01:00
committed by GitHub
parent 5cabe9585f
commit 2104843826
13 changed files with 1239 additions and 319 deletions
+1 -1
View File
@@ -31,7 +31,7 @@ export default class Capture {
index: "IDX_capture_payment_id",
fieldName: "payment_id",
})
payment: Payment
payment!: Payment
@Property({
onCreate: () => new Date(),
@@ -5,6 +5,7 @@ import {
ManyToOne,
OneToOne,
OnInit,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"
@@ -15,6 +16,8 @@ import Payment from "./payment"
@Entity({ tableName: "payment_session" })
export default class PaymentSession {
[OptionalProps]?: "status"
@PrimaryKey({ columnType: "text" })
id: string
@@ -36,7 +39,7 @@ export default class PaymentSession {
@Enum({
items: () => PaymentSessionStatus,
})
status: PaymentSessionStatus
status: PaymentSessionStatus = PaymentSessionStatus.PENDING
@Property({
columnType: "timestamptz",
@@ -47,15 +50,17 @@ export default class PaymentSession {
@ManyToOne({
index: "IDX_payment_session_payment_collection_id",
fieldName: "payment_collection_id",
onDelete: "cascade",
})
payment_collection!: PaymentCollection
@OneToOne({
entity: () => Payment,
mappedBy: (payment) => payment.session,
mappedBy: (payment) => payment.payment_session,
cascade: ["soft-remove"] as any,
nullable: true,
})
payment!: Payment
payment?: Payment | null
@BeforeCreate()
onCreate() {
+5 -4
View File
@@ -115,16 +115,17 @@ export default class Payment {
@ManyToOne({
index: "IDX_payment_payment_collection_id",
fieldName: "payment_collection_id",
onDelete: "cascade",
})
payment_collection: PaymentCollection
payment_collection!: PaymentCollection
@OneToOne({ owner: true, fieldName: "session_id" })
session: PaymentSession
payment_session!: PaymentSession
/** COMPUTED PROPERTIES START **/
// captured_amount: number // sum of the associated captures
// refunded_amount: number // sum of the associated refunds
captured_amount: number // sum of the associated captures
refunded_amount: number // sum of the associated refunds
/** COMPUTED PROPERTIES END **/
+1 -1
View File
@@ -26,7 +26,7 @@ export default class Refund {
index: "IDX_refund_payment_id",
fieldName: "payment_id",
})
payment: Payment
payment!: Payment
@Property({
onCreate: () => new Date(),
+301 -67
View File
@@ -1,8 +1,11 @@
import {
CaptureDTO,
Context,
CreateCaptureDTO,
CreatePaymentCollectionDTO,
CreatePaymentDTO,
CreatePaymentSessionDTO,
CreateRefundDTO,
DAL,
InternalModuleDeclaration,
IPaymentModuleService,
@@ -10,6 +13,8 @@ import {
ModulesSdkTypes,
PaymentCollectionDTO,
PaymentDTO,
PaymentSessionDTO,
RefundDTO,
SetPaymentSessionsDTO,
UpdatePaymentCollectionDTO,
UpdatePaymentDTO,
@@ -18,6 +23,8 @@ import {
InjectTransactionManager,
MedusaContext,
ModulesSdkUtils,
MedusaError,
InjectManager,
} from "@medusajs/utils"
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
@@ -25,49 +32,58 @@ import {
Capture,
Payment,
PaymentCollection,
PaymentMethodToken,
PaymentProvider,
PaymentSession,
Refund,
} from "@models"
type InjectedDependencies = {
baseRepository: DAL.RepositoryService
paymentService: ModulesSdkTypes.InternalModuleService<any>
captureService: ModulesSdkTypes.InternalModuleService<any>
refundService: ModulesSdkTypes.InternalModuleService<any>
paymentSessionService: ModulesSdkTypes.InternalModuleService<any>
paymentCollectionService: ModulesSdkTypes.InternalModuleService<any>
}
const generateMethodForModels = [
Capture,
PaymentCollection,
PaymentMethodToken,
PaymentProvider,
PaymentSession,
Refund,
]
const generateMethodForModels = [PaymentCollection, PaymentSession]
export default class PaymentModuleService<
TPaymentCollection extends PaymentCollection = PaymentCollection
TPaymentCollection extends PaymentCollection = PaymentCollection,
TPayment extends Payment = Payment,
TCapture extends Capture = Capture,
TRefund extends Refund = Refund,
TPaymentSession extends PaymentSession = PaymentSession
>
extends ModulesSdkUtils.abstractModuleServiceFactory<
// TODO revisit when moving forward frane
InjectedDependencies,
PaymentDTO,
PaymentCollectionDTO,
{
Capture: { dto: any }
PaymentCollection: { dto: any }
PaymentMethodToken: { dto: any }
PaymentProvider: { dto: any }
PaymentSession: { dto: any }
Refund: { dto: any }
PaymentCollection: { dto: PaymentCollectionDTO }
PaymentSession: { dto: PaymentSessionDTO }
Payment: { dto: PaymentDTO }
Capture: { dto: CaptureDTO }
Refund: { dto: RefundDTO }
}
>(Payment, generateMethodForModels, entityNameToLinkableKeysMap)
>(PaymentCollection, generateMethodForModels, entityNameToLinkableKeysMap)
implements IPaymentModuleService
{
protected baseRepository_: DAL.RepositoryService
protected paymentService_: ModulesSdkTypes.InternalModuleService<TPayment>
protected captureService_: ModulesSdkTypes.InternalModuleService<TCapture>
protected refundService_: ModulesSdkTypes.InternalModuleService<TRefund>
protected paymentSessionService_: ModulesSdkTypes.InternalModuleService<TPaymentSession>
protected paymentCollectionService_: ModulesSdkTypes.InternalModuleService<TPaymentCollection>
constructor(
{ baseRepository, paymentCollectionService }: InjectedDependencies,
{
baseRepository,
paymentService,
captureService,
refundService,
paymentSessionService,
paymentCollectionService,
}: InjectedDependencies,
protected readonly moduleDeclaration: InternalModuleDeclaration
) {
// @ts-ignore
@@ -75,6 +91,10 @@ export default class PaymentModuleService<
this.baseRepository_ = baseRepository
this.refundService_ = refundService
this.captureService_ = captureService
this.paymentService_ = paymentService
this.paymentSessionService_ = paymentSessionService
this.paymentCollectionService_ = paymentCollectionService
}
@@ -140,6 +160,256 @@ export default class PaymentModuleService<
)
}
createPayment(
data: CreatePaymentDTO,
sharedContext?: Context
): Promise<PaymentDTO>
createPayment(
data: CreatePaymentDTO[],
sharedContext?: Context
): Promise<PaymentDTO[]>
@InjectTransactionManager("baseRepository_")
async createPayment(
data: CreatePaymentDTO | CreatePaymentDTO[],
@MedusaContext() sharedContext?: Context
): Promise<PaymentDTO | PaymentDTO[]> {
let input = Array.isArray(data) ? data : [data]
input = input.map((inputData) => ({
payment_collection: inputData.payment_collection_id,
payment_session: inputData.payment_session_id,
...inputData,
}))
const payments = await this.paymentService_.create(input, sharedContext)
return await this.baseRepository_.serialize<PaymentDTO[]>(
Array.isArray(data) ? payments : payments[0],
{
populate: true,
}
)
}
updatePayment(
data: UpdatePaymentDTO,
sharedContext?: Context | undefined
): Promise<PaymentDTO>
updatePayment(
data: UpdatePaymentDTO[],
sharedContext?: Context | undefined
): Promise<PaymentDTO[]>
@InjectTransactionManager("baseRepository_")
async updatePayment(
data: UpdatePaymentDTO | UpdatePaymentDTO[],
@MedusaContext() sharedContext?: Context
): Promise<PaymentDTO | PaymentDTO[]> {
const input = Array.isArray(data) ? data : [data]
const result = await this.paymentService_.update(input, sharedContext)
return await this.baseRepository_.serialize<PaymentDTO[]>(
Array.isArray(data) ? result : result[0],
{
populate: true,
}
)
}
capturePayment(
data: CreateCaptureDTO,
sharedContext?: Context
): Promise<PaymentDTO>
capturePayment(
data: CreateCaptureDTO[],
sharedContext?: Context
): Promise<PaymentDTO[]>
@InjectManager("baseRepository_")
async capturePayment(
data: CreateCaptureDTO | CreateCaptureDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<PaymentDTO | PaymentDTO[]> {
const input = Array.isArray(data) ? data : [data]
const payments = await this.capturePaymentBulk_(input, sharedContext)
return await this.baseRepository_.serialize(
Array.isArray(data) ? payments : payments[0],
{ populate: true }
)
}
@InjectTransactionManager("baseRepository_")
protected async capturePaymentBulk_(
data: CreateCaptureDTO[],
@MedusaContext() sharedContext?: Context
): Promise<Payment[]> {
let payments = await this.paymentService_.list(
{ id: data.map((d) => d.payment_id) },
{},
sharedContext
)
const inputMap = new Map(data.map((d) => [d.payment_id, d]))
for (const payment of payments) {
const input = inputMap.get(payment.id)!
if (payment.captured_at) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"The payment is already fully captured."
)
}
// TODO: revisit when https://github.com/medusajs/medusa/pull/6253 is merged
// if (payment.captured_amount + input.amount > payment.authorized_amount) {
// throw new MedusaError(
// MedusaError.Types.INVALID_DATA,
// `Total captured amount for payment: ${payment.id} exceeds authorized amount.`
// )
// }
}
await this.captureService_.create(
data.map((d) => ({
payment: d.payment_id,
amount: d.amount,
captured_by: d.captured_by,
})),
sharedContext
)
let fullyCapturedPaymentsId: string[] = []
for (const payment of payments) {
const input = inputMap.get(payment.id)!
// TODO: revisit when https://github.com/medusajs/medusa/pull/6253 is merged
// if (payment.captured_amount + input.amount === payment.amount) {
// fullyCapturedPaymentsId.push(payment.id)
// }
}
if (fullyCapturedPaymentsId.length) {
await this.paymentService_.update(
fullyCapturedPaymentsId.map((id) => ({ id, captured_at: new Date() })),
sharedContext
)
}
// TODO: set PaymentCollection status if fully captured
return await this.paymentService_.list(
{ id: data.map((d) => d.payment_id) },
{
relations: ["captures"],
},
sharedContext
)
}
refundPayment(
data: CreateRefundDTO,
sharedContext?: Context
): Promise<PaymentDTO>
refundPayment(
data: CreateRefundDTO[],
sharedContext?: Context
): Promise<PaymentDTO[]>
@InjectManager("baseRepository_")
async refundPayment(
data: CreateRefundDTO | CreateRefundDTO[],
@MedusaContext() sharedContext?: Context
): Promise<PaymentDTO | PaymentDTO[]> {
const input = Array.isArray(data) ? data : [data]
const payments = await this.refundPaymentBulk_(input, sharedContext)
return await this.baseRepository_.serialize(
Array.isArray(data) ? payments : payments[0],
{ populate: true }
)
}
@InjectTransactionManager("baseRepository_")
async refundPaymentBulk_(
data: CreateRefundDTO[],
@MedusaContext() sharedContext?: Context
): Promise<Payment[]> {
const payments = await this.paymentService_.list(
{ id: data.map(({ payment_id }) => payment_id) },
{},
sharedContext
)
const inputMap = new Map(data.map((d) => [d.payment_id, d]))
// TODO: revisit when https://github.com/medusajs/medusa/pull/6253 is merged
// for (const payment of payments) {
// const input = inputMap.get(payment.id)!
// if (payment.captured_amount < input.amount) {
// throw new MedusaError(
// MedusaError.Types.INVALID_DATA,
// `Refund amount for payment: ${payment.id} cannot be greater than the amount captured on the payment.`
// )
// }
// }
await this.refundService_.create(
data.map((d) => ({
payment: d.payment_id,
amount: d.amount,
captured_by: d.created_by,
})),
sharedContext
)
return await this.paymentService_.list(
{ id: data.map(({ payment_id }) => payment_id) },
{
relations: ["refunds"],
},
sharedContext
)
}
createPaymentSession(
paymentCollectionId: string,
data: CreatePaymentSessionDTO,
sharedContext?: Context | undefined
): Promise<PaymentCollectionDTO>
createPaymentSession(
paymentCollectionId: string,
data: CreatePaymentSessionDTO[],
sharedContext?: Context | undefined
): Promise<PaymentCollectionDTO>
@InjectTransactionManager("baseRepository_")
async createPaymentSession(
paymentCollectionId: string,
data: CreatePaymentSessionDTO | CreatePaymentSessionDTO[],
@MedusaContext() sharedContext?: Context
): Promise<PaymentCollectionDTO> {
let input = Array.isArray(data) ? data : [data]
input = input.map((inputData) => ({
payment_collection: paymentCollectionId,
...inputData,
}))
await this.paymentSessionService_.create(input, sharedContext)
return await this.retrievePaymentCollection(
paymentCollectionId,
{
relations: ["payment_sessions"],
},
sharedContext
)
}
/**
* TODO
*/
@@ -156,56 +426,20 @@ export default class PaymentModuleService<
): 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
cancelPayment(paymentId: string, sharedContext?: Context): Promise<PaymentDTO>
cancelPayment(
paymentId: string[],
sharedContext?: Context
): Promise<PaymentDTO[]>
updatePayment(
data: unknown,
sharedContext?: unknown
cancelPayment(
paymentId: string | string[],
sharedContext?: Context
): 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[],