diff --git a/.changeset/thirty-parents-design.md b/.changeset/thirty-parents-design.md new file mode 100644 index 0000000000..c355091fec --- /dev/null +++ b/.changeset/thirty-parents-design.md @@ -0,0 +1,5 @@ +--- +"@medusajs/medusa": patch +--- + +feat(medusa): Payment Processor API \ No newline at end of file diff --git a/packages/medusa/src/interfaces/payment-processor.ts b/packages/medusa/src/interfaces/payment-processor.ts new file mode 100644 index 0000000000..fe9da026a9 --- /dev/null +++ b/packages/medusa/src/interfaces/payment-processor.ts @@ -0,0 +1,171 @@ +import { Address, Customer, PaymentSessionStatus } from "../models" +import { MedusaContainer } from "../types/global" + +export type PaymentProcessorContext = { + billing_address?: Address | null + email: string + currency_code: string + amount: number + resource_id?: string + customer?: Customer + context: Record + paymentSessionData: Record +} + +export type PaymentProcessorSessionResponse = { + update_requests: { customer_metadata: Record } + session_data: Record +} + +export interface PaymentProcessorError { + error: string + code: number + details: any +} + +/** + * The new payment service plugin interface + * This work is still experimental and can be changed until it becomes stable + */ +export interface PaymentProcessor { + /** + * Return a unique identifier to retrieve the payment plugin provider + */ + getIdentifier(): string + + /** + * Used to initialise anything like an SDK or similar + */ + init(): Promise + + /** + * Initiate a payment session with the external provider + */ + initiatePayment( + context: PaymentProcessorContext + ): Promise + + /** + * Update an existing payment session + * @param context + */ + updatePayment( + context: PaymentProcessorContext + ): Promise + + /** + * Refund an existing session + * @param context + */ + refundPayment( + context: PaymentProcessorContext + ): Promise + + /** + * Authorize an existing session if it is not already authorized + * @param context + */ + authorizePayment( + context: PaymentProcessorContext + ): Promise + + /** + * Capture an existing session + * @param context + */ + capturePayment( + context: PaymentProcessorContext + ): Promise + + /** + * Delete an existing session + */ + deletePayment(paymentId: string): Promise + + /** + * Retrieve an existing session + */ + retrievePayment( + paymentId: string + ): Promise< + PaymentProcessorError | PaymentProcessorSessionResponse["session_data"] + > + + /** + * Cancel an existing session + */ + cancelPayment(paymentId: string): Promise + + /** + * Return the status of the session + */ + getPaymentStatus(paymentId: string): Promise +} + +/** + * Payment processor in charge of creating , managing and processing a payment + */ +export abstract class AbstractPaymentProcessor implements PaymentProcessor { + protected constructor( + protected readonly container: MedusaContainer, + protected readonly config?: Record // eslint-disable-next-line @typescript-eslint/no-empty-function + ) {} + + protected static identifier: string + + public getIdentifier(): string { + const ctr = this.constructor as typeof AbstractPaymentProcessor + + if (!ctr.identifier) { + throw new Error(`Missing static property "identifier".`) + } + + return ctr.identifier + } + + abstract init(): Promise + + abstract capturePayment( + context: PaymentProcessorContext + ): Promise + + abstract authorizePayment( + context: PaymentProcessorContext + ): Promise + + abstract cancelPayment( + paymentId: string + ): Promise + + abstract initiatePayment( + context: PaymentProcessorContext + ): Promise + + abstract deletePayment( + paymentId: string + ): Promise + + abstract getPaymentStatus(paymentId: string): Promise + + abstract refundPayment( + context: PaymentProcessorContext + ): Promise + + abstract retrievePayment( + paymentId: string + ): Promise< + PaymentProcessorError | PaymentProcessorSessionResponse["session_data"] + > + + abstract updatePayment( + context: PaymentProcessorContext + ): Promise +} + +/** + * Return if the input object is AbstractPaymentProcessor + * @param obj + */ +export function isPaymentProcessor(obj: unknown): boolean { + return obj instanceof AbstractPaymentProcessor +} diff --git a/packages/medusa/src/interfaces/payment-service.ts b/packages/medusa/src/interfaces/payment-service.ts index e4141ac8f6..d4cbf266db 100644 --- a/packages/medusa/src/interfaces/payment-service.ts +++ b/packages/medusa/src/interfaces/payment-service.ts @@ -33,17 +33,30 @@ export type PaymentSessionResponse = { session_data: Record } +/** + * @deprecated use the new PaymentProcessor interface instead + */ export interface PaymentService extends TransactionBaseService { getIdentifier(): string + /** + * @deprecated use PaymentProcessor.retrievePayment instead + * @param paymentSession + */ getPaymentData(paymentSession: PaymentSession): Promise + /** + * @deprecated use PaymentProcessor.updatePayment instead + * @param paymentSessionData + * @param data + */ updatePaymentData( paymentSessionData: PaymentSessionData, data: Data ): Promise /** + * @deprecated use PaymentProcessor.initiatePayment instead * @param context The type of this argument is meant to be temporary and once the previous method signature * will be removed, the type will only be PaymentContext instead of Cart & PaymentContext */ @@ -55,31 +68,78 @@ export interface PaymentService extends TransactionBaseService { */ createPayment(cart: Cart): Promise + /** + * @deprecated use PaymentProcessor.retrievePayment instead + * @param paymentData + */ retrievePayment(paymentData: PaymentData): Promise + updatePayment( + paymentSessionData: PaymentSessionData, + context: Cart & PaymentContext + ): Promise + + /** + * @deprecated use PaymentProcessor.updatePayment instead + * @param paymentSessionData + * @param cart + */ updatePayment( paymentSessionData: PaymentSessionData, cart: Cart ): Promise + /** + * @deprecated use PaymentProcessor.authorizePayment instead + * @param paymentSession + * @param context + */ authorizePayment( paymentSession: PaymentSession, context: Data ): Promise<{ data: PaymentSessionData; status: PaymentSessionStatus }> + /** + * @deprecated use PaymentProcessor.capturePayment instead + * @param payment + */ capturePayment(payment: Payment): Promise + /** + * @deprecated use PaymentProcessor.refundPayment instead + * @param payment + * @param refundAmount + */ refundPayment(payment: Payment, refundAmount: number): Promise + /** + * @deprecated use PaymentProcessor.cancelPayment instead + * @param payment + */ cancelPayment(payment: Payment): Promise + /** + * @deprecated use PaymentProcessor.cancelPayment instead + * @param paymentSession + */ deletePayment(paymentSession: PaymentSession): Promise + /** + * @deprecated use PaymentProcessor.getSavedMethods instead + * @param customer + */ retrieveSavedMethods(customer: Customer): Promise + /** + * @deprecated use PaymentProcessor.getPaymentStatus instead + * @param data + */ getStatus(data: Data): Promise } +/** + * @deprecated use the AbstractPaymentProcessor instead + */ export abstract class AbstractPaymentService extends TransactionBaseService implements PaymentService @@ -97,10 +157,16 @@ export abstract class AbstractPaymentService return (this.constructor as typeof AbstractPaymentService).identifier } + /** + * @deprecated + */ public abstract getPaymentData( paymentSession: PaymentSession ): Promise + /** + * @deprecated + */ public abstract updatePaymentData( paymentSessionData: PaymentSessionData, data: Data @@ -120,6 +186,9 @@ export abstract class AbstractPaymentService */ public abstract createPayment(cart: Cart): Promise + /** + * @deprecated + */ public abstract retrievePayment(paymentData: PaymentData): Promise /** @@ -142,30 +211,55 @@ export abstract class AbstractPaymentService cart: Cart ): Promise + /** + * @deprecated + */ public abstract authorizePayment( paymentSession: PaymentSession, context: Data ): Promise<{ data: PaymentSessionData; status: PaymentSessionStatus }> + /** + * @deprecated + */ public abstract capturePayment(payment: Payment): Promise + /** + * @deprecated + */ public abstract refundPayment( payment: Payment, refundAmount: number ): Promise + /** + * @deprecated + */ public abstract cancelPayment(payment: Payment): Promise + /** + * @deprecated + */ public abstract deletePayment(paymentSession: PaymentSession): Promise + /** + * @deprecated + */ // eslint-disable-next-line @typescript-eslint/no-unused-vars public async retrieveSavedMethods(customer: Customer): Promise { return [] } + /** + * @deprecated + */ public abstract getStatus(data: Data): Promise } +/** + * Return if the input object is one of AbstractPaymentService or PaymentService or AbstractPaymentPluginService + * @param obj + */ export function isPaymentService(obj: unknown): boolean { return obj instanceof AbstractPaymentService || obj instanceof PaymentService } diff --git a/packages/medusa/src/services/payment-provider.ts b/packages/medusa/src/services/payment-provider.ts index 07cb1faa44..a3844cbe0e 100644 --- a/packages/medusa/src/services/payment-provider.ts +++ b/packages/medusa/src/services/payment-provider.ts @@ -193,7 +193,7 @@ export default class PaymentProviderService extends TransactionBaseService { ) as Cart | PaymentSessionInput const provider = this.retrieveProvider(providerId) - const context = this.buildPaymentContext(data) + const context = this.buildPaymentProcessorContext(data) if (!isDefined(context.currency_code) || !isDefined(context.amount)) { throw new MedusaError( @@ -279,7 +279,7 @@ export default class PaymentProviderService extends TransactionBaseService { return await this.atomicPhase_(async (transactionManager) => { const provider = this.retrieveProvider(paymentSession.provider_id) - const context = this.buildPaymentContext(sessionInput) + const context = this.buildPaymentProcessorContext(sessionInput) const sessionData = await provider .withTransaction(transactionManager) @@ -640,7 +640,7 @@ export default class PaymentProviderService extends TransactionBaseService { * @param cartOrData * @protected */ - protected buildPaymentContext( + protected buildPaymentProcessorContext( cartOrData: Cart | PaymentSessionInput ): Cart & PaymentContext { const cart =