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

@@ -5,6 +5,7 @@ import {
PaymentProvider,
PaymentSession,
RefundReason,
AccountHolder,
} from "@models"
import { default as schema } from "./schema"
@@ -16,12 +17,14 @@ export const joinerConfig = defineJoinerConfig(Modules.PAYMENT, {
PaymentProvider,
PaymentSession,
RefundReason,
AccountHolder,
],
linkableKeys: {
payment_id: Payment.name,
payment_collection_id: PaymentCollection.name,
payment_provider_id: PaymentProvider.name,
refund_reason_id: RefundReason.name,
account_holder_id: AccountHolder.name,
},
alias: [
{

View File

@@ -4,6 +4,132 @@
],
"name": "public",
"tables": [
{
"columns": {
"id": {
"name": "id",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
},
"provider_id": {
"name": "provider_id",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
},
"external_id": {
"name": "external_id",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
},
"email": {
"name": "email",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "text"
},
"data": {
"name": "data",
"type": "jsonb",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"default": "'{}'",
"mappedType": "json"
},
"metadata": {
"name": "metadata",
"type": "jsonb",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "json"
},
"created_at": {
"name": "created_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"length": 6,
"default": "now()",
"mappedType": "datetime"
},
"updated_at": {
"name": "updated_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"length": 6,
"default": "now()",
"mappedType": "datetime"
},
"deleted_at": {
"name": "deleted_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"length": 6,
"mappedType": "datetime"
}
},
"name": "account_holder",
"schema": "public",
"indexes": [
{
"keyName": "IDX_account_holder_deleted_at",
"columnNames": [],
"composite": false,
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_account_holder_deleted_at\" ON \"account_holder\" (deleted_at) WHERE deleted_at IS NULL"
},
{
"keyName": "IDX_account_holder_provider_id_external_id_unique",
"columnNames": [],
"composite": false,
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_account_holder_provider_id_external_id_unique\" ON \"account_holder\" (provider_id, external_id) WHERE deleted_at IS NULL"
},
{
"keyName": "account_holder_pkey",
"columnNames": [
"id"
],
"composite": false,
"constraint": true,
"primary": true,
"unique": true
}
],
"checks": [],
"foreignKeys": {},
"nativeEnums": {}
},
{
"columns": {
"id": {
@@ -172,6 +298,7 @@
"keyName": "IDX_payment_collection_deleted_at",
"columnNames": [],
"composite": false,
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_payment_collection_deleted_at\" ON \"payment_collection\" (deleted_at) WHERE deleted_at IS NULL"
@@ -182,12 +309,14 @@
"id"
],
"composite": false,
"constraint": true,
"primary": true,
"unique": true
}
],
"checks": [],
"foreignKeys": {}
"foreignKeys": {},
"nativeEnums": {}
},
{
"columns": {
@@ -294,6 +423,7 @@
"keyName": "IDX_payment_method_token_deleted_at",
"columnNames": [],
"composite": false,
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_payment_method_token_deleted_at\" ON \"payment_method_token\" (deleted_at) WHERE deleted_at IS NULL"
@@ -304,12 +434,14 @@
"id"
],
"composite": false,
"constraint": true,
"primary": true,
"unique": true
}
],
"checks": [],
"foreignKeys": {}
"foreignKeys": {},
"nativeEnums": {}
},
{
"columns": {
@@ -372,6 +504,7 @@
"keyName": "IDX_payment_provider_deleted_at",
"columnNames": [],
"composite": false,
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_payment_provider_deleted_at\" ON \"payment_provider\" (deleted_at) WHERE deleted_at IS NULL"
@@ -382,12 +515,14 @@
"id"
],
"composite": false,
"constraint": true,
"primary": true,
"unique": true
}
],
"checks": [],
"foreignKeys": {}
"foreignKeys": {},
"nativeEnums": {}
},
{
"columns": {
@@ -420,14 +555,15 @@
"payment_provider_id"
],
"composite": true,
"constraint": true,
"primary": true,
"unique": true
}
],
"checks": [],
"foreignKeys": {
"payment_collection_payment_providers_payment_coll_aa276_foreign": {
"constraintName": "payment_collection_payment_providers_payment_coll_aa276_foreign",
"payment_collection_payment_providers_payment_col_aa276_foreign": {
"constraintName": "payment_collection_payment_providers_payment_col_aa276_foreign",
"columnNames": [
"payment_collection_id"
],
@@ -439,8 +575,8 @@
"deleteRule": "cascade",
"updateRule": "cascade"
},
"payment_collection_payment_providers_payment_provider_id_foreign": {
"constraintName": "payment_collection_payment_providers_payment_provider_id_foreign",
"payment_collection_payment_providers_payment_pro_2d555_foreign": {
"constraintName": "payment_collection_payment_providers_payment_pro_2d555_foreign",
"columnNames": [
"payment_provider_id"
],
@@ -452,7 +588,8 @@
"deleteRule": "cascade",
"updateRule": "cascade"
}
}
},
"nativeEnums": {}
},
{
"columns": {
@@ -606,6 +743,7 @@
"keyName": "IDX_payment_session_payment_collection_id",
"columnNames": [],
"composite": false,
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_payment_session_payment_collection_id\" ON \"payment_session\" (payment_collection_id) WHERE deleted_at IS NULL"
@@ -614,6 +752,7 @@
"keyName": "IDX_payment_session_deleted_at",
"columnNames": [],
"composite": false,
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_payment_session_deleted_at\" ON \"payment_session\" (deleted_at) WHERE deleted_at IS NULL"
@@ -624,6 +763,7 @@
"id"
],
"composite": false,
"constraint": true,
"primary": true,
"unique": true
}
@@ -643,7 +783,8 @@
"deleteRule": "cascade",
"updateRule": "cascade"
}
}
},
"nativeEnums": {}
},
{
"columns": {
@@ -788,6 +929,7 @@
"keyName": "IDX_payment_payment_collection_id",
"columnNames": [],
"composite": false,
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_payment_payment_collection_id\" ON \"payment\" (payment_collection_id) WHERE deleted_at IS NULL"
@@ -796,6 +938,7 @@
"keyName": "IDX_payment_payment_session_id_unique",
"columnNames": [],
"composite": false,
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_payment_payment_session_id_unique\" ON \"payment\" (payment_session_id) WHERE deleted_at IS NULL"
@@ -804,6 +947,7 @@
"keyName": "IDX_payment_deleted_at",
"columnNames": [],
"composite": false,
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_payment_deleted_at\" ON \"payment\" (deleted_at) WHERE deleted_at IS NULL"
@@ -812,6 +956,7 @@
"keyName": "IDX_payment_provider_id",
"columnNames": [],
"composite": false,
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_payment_provider_id\" ON \"payment\" (provider_id) WHERE deleted_at IS NULL"
@@ -820,6 +965,7 @@
"keyName": "IDX_payment_payment_session_id",
"columnNames": [],
"composite": false,
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_payment_payment_session_id\" ON \"payment\" (payment_session_id) WHERE deleted_at IS NULL"
@@ -830,6 +976,7 @@
"id"
],
"composite": false,
"constraint": true,
"primary": true,
"unique": true
}
@@ -861,7 +1008,8 @@
"referencedTableName": "public.payment_session",
"updateRule": "cascade"
}
}
},
"nativeEnums": {}
},
{
"columns": {
@@ -959,6 +1107,7 @@
"keyName": "IDX_capture_payment_id",
"columnNames": [],
"composite": false,
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_capture_payment_id\" ON \"capture\" (payment_id) WHERE deleted_at IS NULL"
@@ -967,6 +1116,7 @@
"keyName": "IDX_capture_deleted_at",
"columnNames": [],
"composite": false,
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_capture_deleted_at\" ON \"capture\" (deleted_at) WHERE deleted_at IS NULL"
@@ -977,6 +1127,7 @@
"id"
],
"composite": false,
"constraint": true,
"primary": true,
"unique": true
}
@@ -996,7 +1147,8 @@
"deleteRule": "cascade",
"updateRule": "cascade"
}
}
},
"nativeEnums": {}
},
{
"columns": {
@@ -1076,6 +1228,7 @@
"keyName": "IDX_refund_reason_deleted_at",
"columnNames": [],
"composite": false,
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_refund_reason_deleted_at\" ON \"refund_reason\" (deleted_at) WHERE deleted_at IS NULL"
@@ -1086,12 +1239,14 @@
"id"
],
"composite": false,
"constraint": true,
"primary": true,
"unique": true
}
],
"checks": [],
"foreignKeys": {}
"foreignKeys": {},
"nativeEnums": {}
},
{
"columns": {
@@ -1207,6 +1362,7 @@
"keyName": "IDX_refund_payment_id",
"columnNames": [],
"composite": false,
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_refund_payment_id\" ON \"refund\" (payment_id) WHERE deleted_at IS NULL"
@@ -1215,6 +1371,7 @@
"keyName": "IDX_refund_refund_reason_id",
"columnNames": [],
"composite": false,
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_refund_refund_reason_id\" ON \"refund\" (refund_reason_id) WHERE deleted_at IS NULL"
@@ -1223,6 +1380,7 @@
"keyName": "IDX_refund_deleted_at",
"columnNames": [],
"composite": false,
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_refund_deleted_at\" ON \"refund\" (deleted_at) WHERE deleted_at IS NULL"
@@ -1233,6 +1391,7 @@
"id"
],
"composite": false,
"constraint": true,
"primary": true,
"unique": true
}
@@ -1265,7 +1424,9 @@
"deleteRule": "set null",
"updateRule": "cascade"
}
}
},
"nativeEnums": {}
}
]
],
"nativeEnums": {}
}

View File

@@ -0,0 +1,27 @@
import { Migration } from '@mikro-orm/migrations';
export class Migration20250123122334 extends Migration {
override async up(): Promise<void> {
this.addSql(`create table if not exists "account_holder" ("id" text not null, "provider_id" text not null, "external_id" text not null, "email" text null, "data" jsonb not null default '{}', "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "account_holder_pkey" primary key ("id"));`);
this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_account_holder_deleted_at" ON "account_holder" (deleted_at) WHERE deleted_at IS NULL;`);
this.addSql(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_account_holder_provider_id_external_id_unique" ON "account_holder" (provider_id, external_id) WHERE deleted_at IS NULL;`);
this.addSql(`alter table if exists "payment_collection_payment_providers" drop constraint if exists "payment_collection_payment_providers_payment_coll_aa276_foreign";`);
this.addSql(`alter table if exists "payment_collection_payment_providers" drop constraint if exists "payment_collection_payment_providers_payment_provider_id_foreign";`);
this.addSql(`alter table if exists "payment_collection_payment_providers" add constraint "payment_collection_payment_providers_payment_col_aa276_foreign" foreign key ("payment_collection_id") references "payment_collection" ("id") on update cascade on delete cascade;`);
this.addSql(`alter table if exists "payment_collection_payment_providers" add constraint "payment_collection_payment_providers_payment_pro_2d555_foreign" foreign key ("payment_provider_id") references "payment_provider" ("id") on update cascade on delete cascade;`);
}
override async down(): Promise<void> {
this.addSql(`drop table if exists "account_holder" cascade;`);
this.addSql(`alter table if exists "payment_collection_payment_providers" drop constraint if exists "payment_collection_payment_providers_payment_col_aa276_foreign";`);
this.addSql(`alter table if exists "payment_collection_payment_providers" drop constraint if exists "payment_collection_payment_providers_payment_pro_2d555_foreign";`);
this.addSql(`alter table if exists "payment_collection_payment_providers" add constraint "payment_collection_payment_providers_payment_coll_aa276_foreign" foreign key ("payment_collection_id") references "payment_collection" ("id") on update cascade on delete cascade;`);
this.addSql(`alter table if exists "payment_collection_payment_providers" add constraint "payment_collection_payment_providers_payment_provider_id_foreign" foreign key ("payment_provider_id") references "payment_provider" ("id") on update cascade on delete cascade;`);
}
}

View File

@@ -0,0 +1,19 @@
import { model } from "@medusajs/framework/utils"
const AccountHolder = model
.define("AccountHolder", {
id: model.id({ prefix: "acchld" }).primaryKey(),
provider_id: model.text(),
external_id: model.text(),
email: model.text().nullable(),
data: model.json().default({}),
metadata: model.json().nullable(),
})
.indexes([
{
on: ["provider_id", "external_id"],
unique: true,
},
])
export default AccountHolder

View File

@@ -6,3 +6,4 @@ export { default as PaymentProvider } from "./payment-provider"
export { default as PaymentSession } from "./payment-session"
export { default as Refund } from "./refund"
export { default as RefundReason } from "./refund-reason"
export { default as AccountHolder } from "./account-holder"

View File

@@ -1,8 +1,25 @@
import crypto from "crypto"
import {
CreatePaymentProviderSession,
PaymentProviderError,
PaymentProviderSessionResponse,
AuthorizePaymentInput,
AuthorizePaymentOutput,
CancelPaymentInput,
CancelPaymentOutput,
CapturePaymentInput,
CapturePaymentOutput,
DeletePaymentInput,
DeletePaymentOutput,
GetPaymentStatusInput,
GetPaymentStatusOutput,
InitiatePaymentInput,
InitiatePaymentOutput,
ProviderWebhookPayload,
RefundPaymentInput,
RefundPaymentOutput,
RetrievePaymentInput,
RetrievePaymentOutput,
UpdatePaymentInput,
UpdatePaymentOutput,
WebhookActionResult,
} from "@medusajs/framework/types"
import {
@@ -23,53 +40,49 @@ export class SystemProviderService extends AbstractPaymentProvider {
}
async initiatePayment(
context: CreatePaymentProviderSession
): Promise<PaymentProviderSessionResponse> {
return { data: {} }
input: InitiatePaymentInput
): Promise<InitiatePaymentOutput> {
return { data: {}, id: crypto.randomUUID() }
}
async getPaymentStatus(
paymentSessionData: Record<string, unknown>
): Promise<PaymentSessionStatus> {
input: GetPaymentStatusInput
): Promise<GetPaymentStatusOutput> {
throw new Error("Method not implemented.")
}
async retrievePayment(
paymentSessionData: Record<string, unknown>
): Promise<Record<string, unknown> | PaymentProviderError> {
input: RetrievePaymentInput
): Promise<RetrievePaymentOutput> {
return {}
}
async authorizePayment(_): Promise<
| PaymentProviderError
| {
status: PaymentSessionStatus
data: PaymentProviderSessionResponse["data"]
}
> {
async authorizePayment(
input: AuthorizePaymentInput
): Promise<AuthorizePaymentOutput> {
return { data: {}, status: PaymentSessionStatus.AUTHORIZED }
}
async updatePayment(
_
): Promise<PaymentProviderError | PaymentProviderSessionResponse> {
return { data: {} } as PaymentProviderSessionResponse
async updatePayment(input: UpdatePaymentInput): Promise<UpdatePaymentOutput> {
return { data: {} }
}
async deletePayment(_): Promise<Record<string, unknown>> {
return {}
async deletePayment(input: DeletePaymentInput): Promise<DeletePaymentOutput> {
return { data: {} }
}
async capturePayment(_): Promise<Record<string, unknown>> {
return {}
async capturePayment(
input: CapturePaymentInput
): Promise<CapturePaymentOutput> {
return { data: {} }
}
async refundPayment(_): Promise<Record<string, unknown>> {
return {}
async refundPayment(input: RefundPaymentInput): Promise<RefundPaymentOutput> {
return { data: {} }
}
async cancelPayment(_): Promise<Record<string, unknown>> {
return {}
async cancelPayment(input: CancelPaymentInput): Promise<CancelPaymentOutput> {
return { data: {} }
}
async getWebhookActionAndData(

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,
}
})

View File

@@ -1,25 +1,36 @@
import {
BigNumberInput,
CreatePaymentProviderSession,
AuthorizePaymentInput,
AuthorizePaymentOutput,
CancelPaymentInput,
CancelPaymentOutput,
CapturePaymentInput,
CapturePaymentOutput,
CreateAccountHolderInput,
CreateAccountHolderOutput,
DAL,
DeleteAccountHolderInput,
DeleteAccountHolderOutput,
DeletePaymentInput,
DeletePaymentOutput,
GetPaymentStatusInput,
GetPaymentStatusOutput,
InitiatePaymentInput,
InitiatePaymentOutput,
IPaymentProvider,
ListPaymentMethodsInput,
ListPaymentMethodsOutput,
Logger,
PaymentMethodResponse,
PaymentProviderAuthorizeResponse,
PaymentProviderContext,
PaymentProviderDataInput,
PaymentProviderError,
PaymentProviderSessionResponse,
PaymentSessionStatus,
ProviderWebhookPayload,
SavePaymentMethod,
SavePaymentMethodResponse,
UpdatePaymentProviderSession,
RefundPaymentInput,
RefundPaymentOutput,
SavePaymentMethodInput,
SavePaymentMethodOutput,
UpdatePaymentInput,
UpdatePaymentOutput,
WebhookActionResult,
} from "@medusajs/framework/types"
import { MedusaError, ModulesSdkUtils } from "@medusajs/framework/utils"
import { ModulesSdkUtils } from "@medusajs/framework/utils"
import { PaymentProvider } from "@models"
import { EOL } from "os"
type InjectedDependencies = {
logger?: Logger
@@ -59,135 +70,128 @@ Please make sure that the provider is registered in the container and it is conf
async createSession(
providerId: string,
sessionInput: CreatePaymentProviderSession
): Promise<PaymentProviderSessionResponse["data"]> {
sessionInput: InitiatePaymentInput
): Promise<InitiatePaymentOutput> {
const provider = this.retrieveProvider(providerId)
const paymentResponse = await provider.initiatePayment(sessionInput)
if (isPaymentProviderError(paymentResponse)) {
this.throwPaymentProviderError(paymentResponse)
}
return (paymentResponse as PaymentProviderSessionResponse).data
return await provider.initiatePayment(sessionInput)
}
async updateSession(
providerId: string,
sessionInput: UpdatePaymentProviderSession
): Promise<PaymentProviderSessionResponse["data"]> {
sessionInput: UpdatePaymentInput
): Promise<UpdatePaymentOutput> {
const provider = this.retrieveProvider(providerId)
const paymentResponse = await provider.updatePayment(sessionInput)
if (isPaymentProviderError(paymentResponse)) {
this.throwPaymentProviderError(paymentResponse)
}
return (paymentResponse as PaymentProviderSessionResponse)?.data
return await provider.updatePayment(sessionInput)
}
async deleteSession(input: PaymentProviderDataInput): Promise<void> {
const provider = this.retrieveProvider(input.provider_id)
const error = await provider.deletePayment(input.data)
if (isPaymentProviderError(error)) {
this.throwPaymentProviderError(error)
}
async deleteSession(
providerId: string,
input: DeletePaymentInput
): Promise<DeletePaymentOutput> {
const provider = this.retrieveProvider(providerId)
return await provider.deletePayment(input)
}
async authorizePayment(
input: PaymentProviderDataInput,
context: Record<string, unknown>
): Promise<{ data: Record<string, unknown>; status: PaymentSessionStatus }> {
const provider = this.retrieveProvider(input.provider_id)
const res = await provider.authorizePayment(input.data, context)
if (isPaymentProviderError(res)) {
this.throwPaymentProviderError(res)
}
const { data, status } = res as PaymentProviderAuthorizeResponse
return { data, status }
providerId: string,
input: AuthorizePaymentInput
): Promise<AuthorizePaymentOutput> {
const provider = this.retrieveProvider(providerId)
return await provider.authorizePayment(input)
}
async getStatus(
input: PaymentProviderDataInput
): Promise<PaymentSessionStatus> {
const provider = this.retrieveProvider(input.provider_id)
return await provider.getPaymentStatus(input.data)
providerId: string,
input: GetPaymentStatusInput
): Promise<GetPaymentStatusOutput> {
const provider = this.retrieveProvider(providerId)
return await provider.getPaymentStatus(input)
}
async capturePayment(
input: PaymentProviderDataInput
): Promise<Record<string, unknown>> {
const provider = this.retrieveProvider(input.provider_id)
const res = await provider.capturePayment(input.data)
if (isPaymentProviderError(res)) {
this.throwPaymentProviderError(res)
}
return res as Record<string, unknown>
providerId: string,
input: CapturePaymentInput
): Promise<CapturePaymentOutput> {
const provider = this.retrieveProvider(providerId)
return await provider.capturePayment(input)
}
async cancelPayment(input: PaymentProviderDataInput): Promise<void> {
const provider = this.retrieveProvider(input.provider_id)
const error = await provider.cancelPayment(input.data)
if (isPaymentProviderError(error)) {
this.throwPaymentProviderError(error)
}
async cancelPayment(
providerId: string,
input: CancelPaymentInput
): Promise<CancelPaymentOutput> {
const provider = this.retrieveProvider(providerId)
return await provider.cancelPayment(input)
}
async refundPayment(
input: PaymentProviderDataInput,
amount: BigNumberInput
): Promise<Record<string, unknown>> {
const provider = this.retrieveProvider(input.provider_id)
providerId: string,
input: RefundPaymentInput
): Promise<RefundPaymentOutput> {
const provider = this.retrieveProvider(providerId)
return await provider.refundPayment(input)
}
const res = await provider.refundPayment(input.data, amount)
if (isPaymentProviderError(res)) {
this.throwPaymentProviderError(res)
async createAccountHolder(
providerId: string,
input: CreateAccountHolderInput
): Promise<CreateAccountHolderOutput> {
const provider = this.retrieveProvider(providerId)
if (!provider.createAccountHolder) {
this.#logger.warn(
`Provider ${providerId} does not support creating account holders`
)
return {} as unknown as CreateAccountHolderOutput
}
return res as Record<string, unknown>
return await provider.createAccountHolder(input)
}
async deleteAccountHolder(
providerId: string,
input: DeleteAccountHolderInput
): Promise<DeleteAccountHolderOutput> {
const provider = this.retrieveProvider(providerId)
if (!provider.deleteAccountHolder) {
this.#logger.warn(
`Provider ${providerId} does not support deleting account holders`
)
return {}
}
return await provider.deleteAccountHolder(input)
}
async listPaymentMethods(
providerId: string,
context: PaymentProviderContext
): Promise<PaymentMethodResponse[]> {
input: ListPaymentMethodsInput
): Promise<ListPaymentMethodsOutput> {
const provider = this.retrieveProvider(providerId)
if (!provider.listPaymentMethods) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
this.#logger.warn(
`Provider ${providerId} does not support listing payment methods`
)
return []
}
return await provider.listPaymentMethods(context)
return await provider.listPaymentMethods(input)
}
async savePaymentMethod(
providerId: string,
input: SavePaymentMethod
): Promise<SavePaymentMethodResponse> {
input: SavePaymentMethodInput
): Promise<SavePaymentMethodOutput> {
const provider = this.retrieveProvider(providerId)
if (!provider.savePaymentMethod) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
this.#logger.warn(
`Provider ${providerId} does not support saving payment methods`
)
return {} as unknown as SavePaymentMethodOutput
}
const res = await provider.savePaymentMethod(input)
if (isPaymentProviderError(res)) {
this.throwPaymentProviderError(res)
}
return res as SavePaymentMethodResponse
return await provider.savePaymentMethod(input)
}
async getWebhookActionAndData(
@@ -198,22 +202,4 @@ Please make sure that the provider is registered in the container and it is conf
return await provider.getWebhookActionAndData(data)
}
private throwPaymentProviderError(errObj: PaymentProviderError) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`${errObj.error}${errObj.detail ? `:${EOL}${errObj.detail}` : ""}`,
errObj.code
)
}
}
function isPaymentProviderError(obj: any): obj is PaymentProviderError {
return (
obj &&
typeof obj === "object" &&
"error" in obj &&
"code" in obj &&
"detail" in obj
)
}