feat: Add support for managing account holder in payment module (#11015)

This commit is contained in:
Stevche Radevski
2025-01-28 08:55:15 +01:00
committed by GitHub
parent a37a9c8023
commit 59cbc0ec77
29 changed files with 1328 additions and 803 deletions

View File

@@ -7,6 +7,7 @@ import {
CreatePaymentMethodDTO,
CreatePaymentSessionDTO,
CreateRefundDTO,
AccountHolderDTO,
DAL,
FilterablePaymentCollectionProps,
FilterablePaymentMethodProps,
@@ -31,13 +32,17 @@ import {
UpdatePaymentCollectionDTO,
UpdatePaymentDTO,
UpdatePaymentSessionDTO,
CreateAccountHolderDTO,
UpsertPaymentCollectionDTO,
WebhookActionResult,
CreateAccountHolderOutput,
InitiatePaymentOutput,
} from "@medusajs/framework/types"
import {
BigNumber,
InjectManager,
InjectTransactionManager,
isPresent,
isString,
MathBN,
MedusaContext,
@@ -48,6 +53,7 @@ import {
promiseAll,
} from "@medusajs/framework/utils"
import {
AccountHolder,
Capture,
Payment,
PaymentCollection,
@@ -66,6 +72,7 @@ type InjectedDependencies = {
refundService: ModulesSdkTypes.IMedusaInternalService<any>
paymentSessionService: ModulesSdkTypes.IMedusaInternalService<any>
paymentCollectionService: ModulesSdkTypes.IMedusaInternalService<any>
accountHolderService: ModulesSdkTypes.IMedusaInternalService<any>
paymentProviderService: PaymentProviderService
}
@@ -76,6 +83,7 @@ const generateMethodForModels = {
Capture,
Refund,
RefundReason,
AccountHolder,
}
export default class PaymentModuleService
@@ -86,6 +94,7 @@ export default class PaymentModuleService
Capture: { dto: CaptureDTO }
Refund: { dto: RefundDTO }
RefundReason: { dto: RefundReasonDTO }
AccountHolder: { dto: AccountHolderDTO }
}>(generateMethodForModels)
implements IPaymentModuleService
{
@@ -107,6 +116,9 @@ export default class PaymentModuleService
typeof PaymentCollection
>
protected paymentProviderService_: PaymentProviderService
protected accountHolderService_: ModulesSdkTypes.IMedusaInternalService<
typeof AccountHolder
>
constructor(
{
@@ -117,6 +129,7 @@ export default class PaymentModuleService
paymentSessionService,
paymentProviderService,
paymentCollectionService,
accountHolderService,
}: InjectedDependencies,
protected readonly moduleDeclaration: InternalModuleDeclaration
) {
@@ -131,6 +144,7 @@ export default class PaymentModuleService
this.paymentSessionService_ = paymentSessionService
this.paymentProviderService_ = paymentProviderService
this.paymentCollectionService_ = paymentCollectionService
this.accountHolderService_ = accountHolderService
}
__joinerConfig(): ModuleJoinerConfig {
@@ -316,7 +330,7 @@ export default class PaymentModuleService
@MedusaContext() sharedContext?: Context
): Promise<PaymentSessionDTO> {
let paymentSession: InferEntityType<typeof PaymentSession> | undefined
let providerPaymentSession: Record<string, unknown> | undefined
let providerPaymentSession: InitiatePaymentOutput | undefined
try {
paymentSession = await this.createPaymentSession_(
@@ -328,7 +342,8 @@ export default class PaymentModuleService
providerPaymentSession = await this.paymentProviderService_.createSession(
input.provider_id,
{
context: { ...input.context, session_id: paymentSession!.id },
context: input.context,
data: { ...input.data, session_id: paymentSession!.id },
amount: input.amount,
currency_code: input.currency_code,
}
@@ -338,15 +353,14 @@ export default class PaymentModuleService
await this.paymentSessionService_.update(
{
id: paymentSession!.id,
data: { ...input.data, ...providerPaymentSession },
data: { ...input.data, ...providerPaymentSession.data },
},
sharedContext
)
)[0]
} catch (error) {
if (providerPaymentSession) {
await this.paymentProviderService_.deleteSession({
provider_id: input.provider_id,
await this.paymentProviderService_.deleteSession(input.provider_id, {
data: input.data,
})
}
@@ -420,8 +434,7 @@ export default class PaymentModuleService
sharedContext
)
await this.paymentProviderService_.deleteSession({
provider_id: session.provider_id,
await this.paymentProviderService_.deleteSession(session.provider_id, {
data: session.data,
})
@@ -460,11 +473,11 @@ export default class PaymentModuleService
}
let { data, status } = await this.paymentProviderService_.authorizePayment(
session.provider_id,
{
provider_id: session.provider_id,
data: session.data,
},
context
context,
}
)
if (
@@ -486,8 +499,7 @@ export default class PaymentModuleService
sharedContext
)
} catch (error) {
await this.paymentProviderService_.cancelPayment({
provider_id: session.provider_id,
await this.paymentProviderService_.cancelPayment(session.provider_id, {
data,
})
@@ -509,7 +521,7 @@ export default class PaymentModuleService
@InjectTransactionManager()
async authorizePaymentSession_(
session: InferEntityType<typeof PaymentSession>,
data: Record<string, unknown>,
data: Record<string, unknown> | undefined,
status: PaymentSessionStatus,
@MedusaContext() sharedContext?: Context
): Promise<InferEntityType<typeof Payment>> {
@@ -711,15 +723,17 @@ export default class PaymentModuleService
isFullyCaptured: boolean,
@MedusaContext() sharedContext: Context = {}
) {
const paymentData = await this.paymentProviderService_.capturePayment({
data: payment.data!,
provider_id: payment.provider_id,
})
const paymentData = await this.paymentProviderService_.capturePayment(
payment.provider_id,
{
data: payment.data!,
}
)
await this.paymentService_.update(
{
id: payment.id,
data: paymentData,
data: paymentData.data,
captured_at: isFullyCaptured ? new Date() : undefined,
},
sharedContext
@@ -817,15 +831,15 @@ export default class PaymentModuleService
@MedusaContext() sharedContext: Context = {}
) {
const paymentData = await this.paymentProviderService_.refundPayment(
payment.provider_id,
{
data: payment.data!,
provider_id: payment.provider_id,
},
refund.raw_amount as BigNumberInput
amount: refund.raw_amount as BigNumberInput,
}
)
await this.paymentService_.update(
{ id: payment.id, data: paymentData },
{ id: payment.id, data: paymentData.data },
sharedContext
)
@@ -843,9 +857,8 @@ export default class PaymentModuleService
sharedContext
)
await this.paymentProviderService_.cancelPayment({
await this.paymentProviderService_.cancelPayment(payment.provider_id, {
data: payment.data!,
provider_id: payment.provider_id,
})
await this.paymentService_.update(
@@ -909,6 +922,82 @@ export default class PaymentModuleService
]
}
@InjectManager()
async createAccountHolder(
input: CreateAccountHolderDTO,
@MedusaContext() sharedContext?: Context
): Promise<AccountHolderDTO> {
if (input.context?.account_holder) {
return input.context.account_holder as AccountHolderDTO
}
let accountHolder: InferEntityType<typeof AccountHolder> | undefined
let providerAccountHolder: CreateAccountHolderOutput | undefined
try {
providerAccountHolder =
await this.paymentProviderService_.createAccountHolder(
input.provider_id,
{ context: input.context }
)
// This can be empty when either the method is not supported or an account holder wasn't created
if (isPresent(providerAccountHolder)) {
accountHolder = await this.accountHolderService_.create(
{
external_id: providerAccountHolder.id,
email: input.context.customer?.email,
data: providerAccountHolder.data,
provider_id: input.provider_id,
},
sharedContext
)
}
} catch (error) {
if (providerAccountHolder) {
await this.paymentProviderService_.deleteAccountHolder(
input.provider_id,
{
context: {
account_holder: providerAccountHolder as {
data: Record<string, unknown>
},
},
}
)
}
if (accountHolder) {
await this.accountHolderService_.delete(accountHolder.id, sharedContext)
}
throw error
}
return await this.baseRepository_.serialize(accountHolder)
}
@InjectManager()
async deleteAccountHolder(
id: string,
@MedusaContext() sharedContext?: Context
): Promise<void> {
const accountHolder = await this.accountHolderService_.retrieve(
id,
{ select: ["id", "provider_id", "external_id", "email", "data"] },
sharedContext
)
await this.accountHolderService_.delete(id, sharedContext)
await this.paymentProviderService_.deleteAccountHolder(
accountHolder.provider_id,
{
context: { account_holder: accountHolder },
}
)
}
@InjectManager()
async listPaymentMethods(
filters: FilterablePaymentMethodProps,
@@ -917,12 +1006,12 @@ export default class PaymentModuleService
): Promise<PaymentMethodDTO[]> {
const res = await this.paymentProviderService_.listPaymentMethods(
filters.provider_id,
filters.context
{ context: filters.context }
)
return res.map((item) => ({
id: item.id,
data: item.data,
data: item.data!,
provider_id: filters.provider_id,
}))
}
@@ -936,12 +1025,12 @@ export default class PaymentModuleService
const paymentMethods =
await this.paymentProviderService_.listPaymentMethods(
filters.provider_id,
filters.context
{ context: filters.context }
)
const normalizedResponse = paymentMethods.map((item) => ({
id: item.id,
data: item.data,
data: item.data!,
provider_id: filters.provider_id,
}))
@@ -950,9 +1039,9 @@ export default class PaymentModuleService
// @ts-ignore
createPaymentMethods(
data: CreatePaymentCollectionDTO,
data: CreatePaymentMethodDTO,
sharedContext?: Context
): Promise<PaymentCollectionDTO>
): Promise<PaymentMethodDTO>
createPaymentMethods(
data: CreatePaymentMethodDTO[],
@@ -975,7 +1064,7 @@ export default class PaymentModuleService
const normalizedResponse = result.map((item, i) => {
return {
id: item.id,
data: item.data,
data: item.data!,
provider_id: input[i].provider_id,
}
})