import { AccountHolderDTO, BigNumberInput, CaptureDTO, Context, CreateAccountHolderDTO, CreateAccountHolderOutput, CreateCaptureDTO, CreatePaymentCollectionDTO, CreatePaymentMethodDTO, CreatePaymentSessionDTO, CreateRefundDTO, DAL, FilterablePaymentCollectionProps, FilterablePaymentMethodProps, FilterablePaymentProviderProps, FindConfig, InferEntityType, InitiatePaymentOutput, InternalModuleDeclaration, IPaymentModuleService, Logger, ModuleJoinerConfig, ModulesSdkTypes, PaymentCollectionDTO, PaymentCollectionUpdatableFields, PaymentDTO, PaymentMethodDTO, PaymentProviderDTO, PaymentSessionDTO, ProviderWebhookPayload, RefundDTO, RefundReasonDTO, UpdateAccountHolderDTO, UpdateAccountHolderOutput, UpdatePaymentCollectionDTO, UpdatePaymentDTO, UpdatePaymentSessionDTO, UpsertPaymentCollectionDTO, WebhookActionResult, } from "@medusajs/framework/types" import { BigNumber, InjectManager, InjectTransactionManager, isPresent, isString, MathBN, MedusaContext, MedusaError, ModulesSdkUtils, PaymentCollectionStatus, PaymentSessionStatus, promiseAll, } from "@medusajs/framework/utils" import { AccountHolder, Capture, Payment, PaymentCollection, PaymentSession, Refund, RefundReason, } from "@models" import { joinerConfig } from "../joiner-config" import PaymentProviderService from "./payment-provider" type InjectedDependencies = { logger?: Logger baseRepository: DAL.RepositoryService paymentService: ModulesSdkTypes.IMedusaInternalService captureService: ModulesSdkTypes.IMedusaInternalService refundService: ModulesSdkTypes.IMedusaInternalService paymentSessionService: ModulesSdkTypes.IMedusaInternalService paymentCollectionService: ModulesSdkTypes.IMedusaInternalService accountHolderService: ModulesSdkTypes.IMedusaInternalService paymentProviderService: PaymentProviderService } const generateMethodForModels = { PaymentCollection, PaymentSession, Payment, Capture, Refund, RefundReason, AccountHolder, } export default class PaymentModuleService extends ModulesSdkUtils.MedusaService<{ PaymentCollection: { dto: PaymentCollectionDTO } PaymentSession: { dto: PaymentSessionDTO } Payment: { dto: PaymentDTO } Capture: { dto: CaptureDTO } Refund: { dto: RefundDTO } RefundReason: { dto: RefundReasonDTO } AccountHolder: { dto: AccountHolderDTO } }>(generateMethodForModels) implements IPaymentModuleService { protected baseRepository_: DAL.RepositoryService protected paymentService_: ModulesSdkTypes.IMedusaInternalService< typeof Payment > protected captureService_: ModulesSdkTypes.IMedusaInternalService< typeof Capture > protected refundService_: ModulesSdkTypes.IMedusaInternalService< typeof Refund > protected paymentSessionService_: ModulesSdkTypes.IMedusaInternalService< typeof PaymentSession > protected paymentCollectionService_: ModulesSdkTypes.IMedusaInternalService< typeof PaymentCollection > protected paymentProviderService_: PaymentProviderService protected accountHolderService_: ModulesSdkTypes.IMedusaInternalService< typeof AccountHolder > constructor( { baseRepository, paymentService, captureService, refundService, paymentSessionService, paymentProviderService, paymentCollectionService, accountHolderService, }: InjectedDependencies, protected readonly moduleDeclaration: InternalModuleDeclaration ) { // @ts-ignore super(...arguments) this.baseRepository_ = baseRepository this.refundService_ = refundService this.captureService_ = captureService this.paymentService_ = paymentService this.paymentSessionService_ = paymentSessionService this.paymentProviderService_ = paymentProviderService this.paymentCollectionService_ = paymentCollectionService this.accountHolderService_ = accountHolderService } __joinerConfig(): ModuleJoinerConfig { return joinerConfig } protected roundToCurrencyPrecision( amount: BigNumberInput, currencyCode: string ): BigNumberInput { let precision: number | undefined = undefined try { const formatted = Intl.NumberFormat(undefined, { style: "currency", currency: currencyCode, }).format(0.1111111) precision = formatted.split(".")[1].length } catch { // Unknown currency, keep the full precision } return MathBN.convert(amount, precision) } // @ts-expect-error createPaymentCollections( data: CreatePaymentCollectionDTO, sharedContext?: Context ): Promise // @ts-expect-error createPaymentCollections( data: CreatePaymentCollectionDTO[], sharedContext?: Context ): Promise @InjectManager() // @ts-expect-error async createPaymentCollections( data: CreatePaymentCollectionDTO | CreatePaymentCollectionDTO[], @MedusaContext() sharedContext?: Context ): Promise { const input = Array.isArray(data) ? data : [data] const collections = await this.createPaymentCollections_( input, sharedContext ) return await this.baseRepository_.serialize( Array.isArray(data) ? collections : collections[0], { populate: true, } ) } @InjectTransactionManager() async createPaymentCollections_( data: CreatePaymentCollectionDTO[], @MedusaContext() sharedContext?: Context ): Promise[]> { return await this.paymentCollectionService_.create(data, sharedContext) } // @ts-expect-error updatePaymentCollections( paymentCollectionId: string, data: PaymentCollectionUpdatableFields, sharedContext?: Context ): Promise // @ts-expect-error updatePaymentCollections( selector: FilterablePaymentCollectionProps, data: PaymentCollectionUpdatableFields, sharedContext?: Context ): Promise @InjectManager() // @ts-expect-error async updatePaymentCollections( idOrSelector: string | FilterablePaymentCollectionProps, data: PaymentCollectionUpdatableFields, @MedusaContext() sharedContext?: Context ): Promise { let updateData: UpdatePaymentCollectionDTO[] = [] if (isString(idOrSelector)) { updateData = [ { id: idOrSelector, ...data, }, ] } else { const collections = await this.paymentCollectionService_.list( idOrSelector, {}, sharedContext ) updateData = collections.map((c) => ({ id: c.id, ...data, })) } const result = await this.updatePaymentCollections_( updateData, sharedContext ) return await this.baseRepository_.serialize( Array.isArray(data) ? result : result[0], { populate: true, } ) } @InjectManager() async updatePaymentCollections_( data: UpdatePaymentCollectionDTO[], @MedusaContext() sharedContext?: Context ): Promise[]> { return await this.paymentCollectionService_.update(data, sharedContext) } upsertPaymentCollections( data: UpsertPaymentCollectionDTO[], sharedContext?: Context ): Promise upsertPaymentCollections( data: UpsertPaymentCollectionDTO, sharedContext?: Context ): Promise @InjectManager() async upsertPaymentCollections( data: UpsertPaymentCollectionDTO | UpsertPaymentCollectionDTO[], @MedusaContext() sharedContext?: Context ): Promise { const input = Array.isArray(data) ? data : [data] const forUpdate = input.filter( (collection): collection is UpdatePaymentCollectionDTO => !!collection.id ) const forCreate = input.filter( (collection): collection is CreatePaymentCollectionDTO => !collection.id ) const operations: Promise[]>[] = [] if (forCreate.length) { operations.push(this.createPaymentCollections_(forCreate, sharedContext)) } if (forUpdate.length) { operations.push(this.updatePaymentCollections_(forUpdate, sharedContext)) } const result = (await promiseAll(operations)).flat() return await this.baseRepository_.serialize< PaymentCollectionDTO[] | PaymentCollectionDTO >(Array.isArray(data) ? result : result[0]) } completePaymentCollections( paymentCollectionId: string, sharedContext?: Context ): Promise completePaymentCollections( paymentCollectionId: string[], sharedContext?: Context ): Promise // Should we remove this and use `updatePaymentCollections` instead? @InjectManager() async completePaymentCollections( paymentCollectionId: string | string[], @MedusaContext() sharedContext?: Context ): Promise { const input = Array.isArray(paymentCollectionId) ? paymentCollectionId.map((id) => ({ id, completed_at: new Date(), })) : [{ id: paymentCollectionId, completed_at: new Date() }] // TODO: what checks should be done here? e.g. captured_amount === amount? const updated = await this.paymentCollectionService_.update( input, sharedContext ) return await this.baseRepository_.serialize( Array.isArray(paymentCollectionId) ? updated : updated[0], { populate: true } ) } @InjectManager() async createPaymentSession( paymentCollectionId: string, input: CreatePaymentSessionDTO, @MedusaContext() sharedContext?: Context ): Promise { let paymentSession: InferEntityType | undefined let providerPaymentSession: InitiatePaymentOutput | undefined try { paymentSession = await this.createPaymentSession_( paymentCollectionId, input, sharedContext ) providerPaymentSession = await this.paymentProviderService_.createSession( input.provider_id, { context: { idempotency_key: paymentSession!.id, ...input.context, }, data: { ...input.data, session_id: paymentSession!.id }, amount: input.amount, currency_code: input.currency_code, } ) paymentSession = await this.paymentSessionService_.update( { id: paymentSession!.id, data: { ...input.data, ...providerPaymentSession.data }, status: providerPaymentSession.status ?? PaymentSessionStatus.PENDING, }, sharedContext ) } catch (error) { if (providerPaymentSession) { await this.paymentProviderService_.deleteSession(input.provider_id, { data: input.data, }) } if (paymentSession) { await this.paymentSessionService_.delete( paymentSession.id, sharedContext ) } throw error } return await this.baseRepository_.serialize(paymentSession) } @InjectTransactionManager() async createPaymentSession_( paymentCollectionId: string, data: CreatePaymentSessionDTO, @MedusaContext() sharedContext?: Context ): Promise> { const paymentSession = await this.paymentSessionService_.create( { payment_collection_id: paymentCollectionId, provider_id: data.provider_id, amount: data.amount, currency_code: data.currency_code, context: data.context, data: data.data, metadata: data.metadata, }, sharedContext ) return paymentSession } @InjectManager() async updatePaymentSession( data: UpdatePaymentSessionDTO, @MedusaContext() sharedContext?: Context ): Promise { const session = await this.paymentSessionService_.retrieve( data.id, { select: ["id", "status", "data", "provider_id"] }, sharedContext ) const providerData = await this.paymentProviderService_.updateSession( session.provider_id, { data: data.data, amount: data.amount, currency_code: data.currency_code, context: data.context, } ) const updated = await this.paymentSessionService_.update( { id: session.id, amount: data.amount, currency_code: data.currency_code, data: providerData.data, // Allow the caller to explicitly set the status (eg. due to a webhook), fallback to the update response, and finally to the existing status. status: data.status ?? providerData.status ?? session.status, metadata: data.metadata, }, sharedContext ) return await this.baseRepository_.serialize(updated, { populate: true }) } @InjectManager() async deletePaymentSession( id: string, @MedusaContext() sharedContext?: Context ): Promise { const session = await this.paymentSessionService_.retrieve( id, { select: ["id", "data", "provider_id"] }, sharedContext ) await this.paymentProviderService_.deleteSession(session.provider_id, { data: session.data, }) await this.paymentSessionService_.delete(id, sharedContext) } @InjectManager() async authorizePaymentSession( id: string, context: Record, @MedusaContext() sharedContext?: Context ): Promise { const session = await this.paymentSessionService_.retrieve( id, { select: [ "id", "data", "provider_id", "amount", "raw_amount", "currency_code", "authorized_at", "payment_collection_id", ], relations: ["payment", "payment_collection"], }, sharedContext ) // this method needs to be idempotent if (session.payment && session.authorized_at) { return await this.baseRepository_.serialize(session.payment, { populate: true, }) } let { data, status } = await this.paymentProviderService_.authorizePayment( session.provider_id, { data: session.data, context: { idempotency_key: session.id, ...context }, } ) if ( status !== PaymentSessionStatus.AUTHORIZED && status !== PaymentSessionStatus.CAPTURED ) { await this.paymentSessionService_.update( { id: session.id, status, data, }, sharedContext ) throw new MedusaError( MedusaError.Types.NOT_ALLOWED, `Session: ${session.id} was not authorized with the provider.` ) } let payment try { payment = await this.authorizePaymentSession_( session, data, status as PaymentSessionStatus, sharedContext ) } catch (error) { await this.paymentProviderService_.cancelPayment(session.provider_id, { data, context: { idempotency_key: payment?.id, ...context, }, }) throw error } await this.maybeUpdatePaymentCollection_( session.payment_collection_id, sharedContext ) return await this.baseRepository_.serialize(payment, { populate: true, }) } @InjectTransactionManager() async authorizePaymentSession_( session: InferEntityType, data: Record | undefined, status: PaymentSessionStatus, @MedusaContext() sharedContext?: Context ): Promise> { let autoCapture = false if (status === PaymentSessionStatus.CAPTURED) { status = PaymentSessionStatus.AUTHORIZED autoCapture = true } await this.paymentSessionService_.update( { id: session.id, data, status, ...(session.authorized_at === null ? { authorized_at: new Date(), } : {}), }, sharedContext ) const payment = await this.paymentService_.create( { amount: session.amount, currency_code: session.currency_code, payment_session: session.id, payment_collection_id: session.payment_collection_id, provider_id: session.provider_id, data, }, sharedContext ) if (autoCapture) { await this.capturePayment( { payment_id: payment.id, amount: session.amount as BigNumberInput }, sharedContext ) } return payment } @InjectManager() async updatePayment( data: UpdatePaymentDTO, @MedusaContext() sharedContext?: Context ): Promise { // NOTE: currently there is no update with the provider but maybe data could be updated const result = await this.paymentService_.update(data, sharedContext) return await this.baseRepository_.serialize(result) } // TODO: This method should return a capture, not a payment @InjectManager() async capturePayment( data: CreateCaptureDTO, @MedusaContext() sharedContext: Context = {} ): Promise { const payment = await this.paymentService_.retrieve( data.payment_id, { select: [ "id", "data", "provider_id", "payment_collection_id", "amount", "raw_amount", "currency_code", "captured_at", "canceled_at", ], relations: ["captures.raw_amount"], }, sharedContext ) const { isFullyCaptured, capture } = await this.capturePayment_( data, payment, sharedContext ) try { await this.capturePaymentFromProvider_( payment, capture, isFullyCaptured, sharedContext ) } catch (error) { if (capture?.id) { await super.deleteCaptures({ id: capture.id }, sharedContext) } throw error } await this.maybeUpdatePaymentCollection_( payment.payment_collection_id, sharedContext ) return await this.baseRepository_.serialize(payment, { populate: true, }) } @InjectTransactionManager() private async capturePayment_( data: CreateCaptureDTO, payment: InferEntityType, @MedusaContext() sharedContext: Context = {} ): Promise<{ isFullyCaptured: boolean capture?: InferEntityType }> { if (payment.canceled_at) { throw new MedusaError( MedusaError.Types.INVALID_DATA, `The payment: ${payment.id} has been canceled.` ) } if (payment.captured_at) { return { isFullyCaptured: true } } // If no custom amount is passed, we assume the full amount needs to be captured if (!data.amount) { data.amount = payment.amount as number } const capturedAmount = payment.captures.reduce((captureAmount, next) => { return MathBN.add(captureAmount, next.raw_amount as BigNumberInput) }, MathBN.convert(0)) const authorizedAmount = new BigNumber(payment.raw_amount as BigNumberInput) const newCaptureAmount = new BigNumber(data.amount) const remainingToCapture = MathBN.sub(authorizedAmount, capturedAmount) if ( MathBN.gt( this.roundToCurrencyPrecision(newCaptureAmount, payment.currency_code), this.roundToCurrencyPrecision(remainingToCapture, payment.currency_code) ) ) { throw new MedusaError( MedusaError.Types.INVALID_DATA, `You cannot capture more than the authorized amount substracted by what is already captured.` ) } // When the entire authorized amount has been captured, we return it as complete const totalCaptured = MathBN.convert( MathBN.add(capturedAmount, newCaptureAmount) ) const isFullyCaptured = MathBN.gte( this.roundToCurrencyPrecision(totalCaptured, payment.currency_code), this.roundToCurrencyPrecision(authorizedAmount, payment.currency_code) ) const capture = await this.captureService_.create( { payment: data.payment_id, amount: data.amount, captured_by: data.captured_by, }, sharedContext ) return { isFullyCaptured, capture } } @InjectManager() private async capturePaymentFromProvider_( payment: InferEntityType, capture: InferEntityType | undefined, isFullyCaptured: boolean, @MedusaContext() sharedContext: Context = {} ) { const paymentData = await this.paymentProviderService_.capturePayment( payment.provider_id, { data: payment.data!, context: { idempotency_key: capture?.id, }, } ) await this.paymentService_.update( { id: payment.id, data: paymentData.data, captured_at: isFullyCaptured ? new Date() : undefined, }, sharedContext ) return payment } @InjectManager() async refundPayment( data: CreateRefundDTO, @MedusaContext() sharedContext: Context = {} ): Promise { const payment = await this.paymentService_.retrieve( data.payment_id, { select: [ "id", "data", "provider_id", "payment_collection_id", "amount", "raw_amount", ], relations: ["captures.raw_amount", "refunds.raw_amount"], }, sharedContext ) const refund = await this.refundPayment_(payment, data, sharedContext) try { await this.refundPaymentFromProvider_(payment, refund, sharedContext) } catch (error) { await super.deleteRefunds({ id: refund.id }, sharedContext) throw error } await this.maybeUpdatePaymentCollection_( payment.payment_collection_id, sharedContext ) return await this.retrievePayment( payment.id, { relations: ["refunds"] }, sharedContext ) } @InjectTransactionManager() private async refundPayment_( payment: InferEntityType, data: CreateRefundDTO, @MedusaContext() sharedContext: Context = {} ): Promise> { if (!data.amount) { data.amount = payment.amount as BigNumberInput } const capturedAmount = payment.captures.reduce((captureAmount, next) => { const amountAsBigNumber = new BigNumber(next.raw_amount as BigNumberInput) return MathBN.add(captureAmount, amountAsBigNumber) }, MathBN.convert(0)) const refundedAmount = payment.refunds.reduce((refundedAmount, next) => { return MathBN.add(refundedAmount, next.raw_amount as BigNumberInput) }, MathBN.convert(0)) const totalRefundedAmount = MathBN.add(refundedAmount, data.amount) if (MathBN.lt(capturedAmount, totalRefundedAmount)) { throw new MedusaError( MedusaError.Types.INVALID_DATA, `You cannot refund more than what is captured on the payment.` ) } const refund = await this.refundService_.create( { payment: data.payment_id, amount: data.amount, created_by: data.created_by, note: data.note, refund_reason_id: data.refund_reason_id, }, sharedContext ) return refund } @InjectManager() private async refundPaymentFromProvider_( payment: InferEntityType, refund: InferEntityType, @MedusaContext() sharedContext: Context = {} ) { const paymentData = await this.paymentProviderService_.refundPayment( payment.provider_id, { data: payment.data!, amount: refund.raw_amount as BigNumberInput, context: { idempotency_key: refund.id, }, } ) await this.paymentService_.update( { id: payment.id, data: paymentData.data }, sharedContext ) return payment } @InjectManager() async cancelPayment( paymentId: string, @MedusaContext() sharedContext?: Context ): Promise { const payment = await this.paymentService_.retrieve( paymentId, { select: ["id", "data", "provider_id"] }, sharedContext ) await this.paymentProviderService_.cancelPayment(payment.provider_id, { data: payment.data!, context: { idempotency_key: payment.id, }, }) await this.paymentService_.update( { id: paymentId, canceled_at: new Date() }, sharedContext ) return await this.retrievePayment(payment.id, {}, sharedContext) } @InjectManager() private async maybeUpdatePaymentCollection_( paymentCollectionId: string, sharedContext?: Context ) { const paymentCollection = await this.paymentCollectionService_.retrieve( paymentCollectionId, { select: ["amount", "raw_amount", "status", "currency_code"], relations: [ "payment_sessions.amount", "payment_sessions.raw_amount", "payments.captures.amount", "payments.captures.raw_amount", "payments.refunds.amount", "payments.refunds.raw_amount", ], }, sharedContext ) const paymentSessions = paymentCollection.payment_sessions const captures = paymentCollection.payments .map((pay) => [...pay.captures]) .flat() const refunds = paymentCollection.payments .map((pay) => [...pay.refunds]) .flat() let authorizedAmount = MathBN.convert(0) let capturedAmount = MathBN.convert(0) let refundedAmount = MathBN.convert(0) let completedAt: Date | undefined for (const ps of paymentSessions) { if (ps.status === PaymentSessionStatus.AUTHORIZED) { authorizedAmount = MathBN.add(authorizedAmount, ps.amount) } } for (const capture of captures) { capturedAmount = MathBN.add(capturedAmount, capture.amount) } for (const refund of refunds) { refundedAmount = MathBN.add(refundedAmount, refund.amount) } let status = paymentSessions.length === 0 ? PaymentCollectionStatus.NOT_PAID : PaymentCollectionStatus.AWAITING if (MathBN.gt(authorizedAmount, 0)) { status = MathBN.gte( this.roundToCurrencyPrecision( authorizedAmount, paymentCollection.currency_code ), this.roundToCurrencyPrecision( paymentCollection.amount, paymentCollection.currency_code ) ) ? PaymentCollectionStatus.AUTHORIZED : PaymentCollectionStatus.PARTIALLY_AUTHORIZED } if ( MathBN.gte( this.roundToCurrencyPrecision( capturedAmount, paymentCollection.currency_code ), this.roundToCurrencyPrecision( paymentCollection.amount, paymentCollection.currency_code ) ) ) { status = PaymentCollectionStatus.COMPLETED completedAt = new Date() } await this.paymentCollectionService_.update( { id: paymentCollectionId, status, authorized_amount: authorizedAmount, captured_amount: capturedAmount, refunded_amount: refundedAmount, completed_at: completedAt, }, sharedContext ) } @InjectManager() async listPaymentProviders( filters: FilterablePaymentProviderProps = {}, config: FindConfig = {}, @MedusaContext() sharedContext?: Context ): Promise { const providers = await this.paymentProviderService_.list( filters, config, sharedContext ) return await this.baseRepository_.serialize( providers, { populate: true, } ) } @InjectManager() async listAndCountPaymentProviders( filters: FilterablePaymentProviderProps = {}, config: FindConfig = {}, @MedusaContext() sharedContext?: Context ): Promise<[PaymentProviderDTO[], number]> { const [providers, count] = await this.paymentProviderService_.listAndCount( filters, config, sharedContext ) return [ await this.baseRepository_.serialize(providers, { populate: true, }), count, ] } @InjectManager() async createAccountHolder( input: CreateAccountHolderDTO, @MedusaContext() sharedContext?: Context ): Promise { if (input.context?.account_holder) { return input.context.account_holder as AccountHolderDTO } let accountHolder: InferEntityType | undefined let providerAccountHolder: CreateAccountHolderOutput | undefined providerAccountHolder = await this.paymentProviderService_.createAccountHolder( input.provider_id, { context: { idempotency_key: input.context?.customer?.id, ...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 ) } return await this.baseRepository_.serialize(accountHolder) } @InjectManager() async updateAccountHolder( input: UpdateAccountHolderDTO, @MedusaContext() sharedContext?: Context ): Promise { if (!input.context?.account_holder) { throw new MedusaError( MedusaError.Types.INVALID_DATA, "Missing account holder data while updating account holder." ) } let accountHolder: InferEntityType | undefined let providerAccountHolder: UpdateAccountHolderOutput | undefined providerAccountHolder = await this.paymentProviderService_.updateAccountHolder( input.provider_id, { context: input.context, } ) // The data field can be empty when either the method is not supported or an account holder wasn't updated // We still want to do the update as we might only be updating the metadata accountHolder = await this.accountHolderService_.update( { id: input.id, ...(providerAccountHolder?.data ? { data: providerAccountHolder.data } : {}), metadata: input.metadata, }, sharedContext ) return await this.baseRepository_.serialize(accountHolder) } @InjectManager() async deleteAccountHolder( id: string, @MedusaContext() sharedContext?: Context ): Promise { 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, config: FindConfig = {}, @MedusaContext() sharedContext?: Context ): Promise { const res = await this.paymentProviderService_.listPaymentMethods( filters.provider_id, { context: filters.context } ) return res.map((item) => ({ id: item.id, data: item.data!, provider_id: filters.provider_id, })) } @InjectManager() async listAndCountPaymentMethods( filters: FilterablePaymentMethodProps, config: FindConfig = {}, @MedusaContext() sharedContext?: Context ): Promise<[PaymentMethodDTO[], number]> { const paymentMethods = await this.paymentProviderService_.listPaymentMethods( filters.provider_id, { context: filters.context } ) const normalizedResponse = paymentMethods.map((item) => ({ id: item.id, data: item.data!, provider_id: filters.provider_id, })) return [normalizedResponse, paymentMethods.length] } // @ts-ignore createPaymentMethods( data: CreatePaymentMethodDTO, sharedContext?: Context ): Promise createPaymentMethods( data: CreatePaymentMethodDTO[], sharedContext?: Context ): Promise @InjectManager() async createPaymentMethods( data: CreatePaymentMethodDTO | CreatePaymentMethodDTO[], @MedusaContext() sharedContext?: Context ): Promise { const input = Array.isArray(data) ? data : [data] const result = await promiseAll( input.map((item) => this.paymentProviderService_.savePaymentMethod(item.provider_id, item) ), { aggregateErrors: true } ) const normalizedResponse = result.map((item, i) => { return { id: item.id, data: item.data!, provider_id: input[i].provider_id, } }) return Array.isArray(data) ? normalizedResponse : normalizedResponse[0] } @InjectManager() async getWebhookActionAndData( eventData: ProviderWebhookPayload, @MedusaContext() sharedContext?: Context ): Promise { const providerId = `pp_${eventData.provider}` return await this.paymentProviderService_.getWebhookActionAndData( providerId, eventData.payload ) } }