How to Create a Payment Provider

In this document, you’ll learn how to create a Payment Provider to be used with the Payment Module.


1. Create Module Directory#

Start by creating a new directory for your module. For example, src/modules/my-payment.


2. Create the Payment Provider Service#

Create the file src/modules/my-payment/service.ts that holds the module's main service. It must extend the AbstractPaymentProvider class imported from @medusajs/framework/utils:

src/modules/my-payment/service.ts
1import { AbstractPaymentProvider } from "@medusajs/framework/utils"2
3type Options = {4  apiKey: string5}6
7class MyPaymentProviderService extends AbstractPaymentProvider<8  Options9> {10  // TODO implement methods11}12
13export default MyPaymentProviderService

Type parameters#

TConfigobjectOptional

constructor#

You can use the constructor of the provider's service to access resources in your module's container.

You can also use the constructor to initialize your integration with the third-party provider. For example, if you use a client to connect to the third-party provider’s APIs, you can initialize it in the constructor and use it in other methods in the service.

The provider can also access the module's options as a second parameter.

Example

Code
1import {2  AbstractPaymentProvider3} from "@medusajs/framework/utils"4import { Logger } from "@medusajs/framework/types"5
6type InjectedDependencies = {7  logger: Logger8}9
10type Options = {11  apiKey: string12}13
14class MyPaymentProviderService extends AbstractPaymentProvider<15  Options16> {17  static identifier = "my-payment"18  protected logger_: Logger19  protected options_: Options20  // Assuming you're using a client to integrate21  // with a third-party service22  protected client23
24  constructor(25    { logger }: InjectedDependencies,26    options: Options27  ) {28    // @ts-ignore29    super(...arguments)30
31    this.logger_ = logger32    this.options_ = options33
34    // Assuming you're initializing a client35    this.client = new Client(options)36  }37
38  // ...39}40
41export default MyPaymentProviderService

Parameters

The module's container used to resolve resources.
configTConfig
The options passed to the payment module provider.

validateOptions#

This method validates the options of the provider set in medusa-config.ts. Implementing this method is optional. It's useful if your provider requires custom validation.

If the options aren't valid, throw an error.

Example

Code
1class MyPaymentProviderService extends AbstractPaymentProvider<Options> {2  static validateOptions(options: Record<any, any>) {3    if (!options.apiKey) {4      throw new MedusaError(5        MedusaError.Types.INVALID_DATA,6        "API key is required in the provider's options."7      )8    }9  }10}

Parameters

optionsRecord<any, any>
The provider's options.

Returns

voidvoid
This method validates the options of the provider set in medusa-config.ts. Implementing this method is optional. It's useful if your provider requires custom validation. If the options aren't valid, throw an error.

capturePayment#

This method is used to capture a payment. The payment is captured in one of the following scenarios:

  • The authorizePayment method returns the status captured, which automatically executed this method after authorization.
  • The merchant requests to capture the payment after its associated payment session was authorized.
  • A webhook event occurred that instructs the payment provider to capture the payment session. Learn more about handing webhook events in this guide.

In this method, use the third-party provider to capture the payment.

Example

Code
1// other imports...2import {3  PaymentProviderError,4  PaymentProviderSessionResponse,5} from "@medusajs/framework/types"6
7class MyPaymentProviderService extends AbstractPaymentProvider<8  Options9> {10  async capturePayment(11    paymentData: Record<string, unknown>12  ): Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]> {13    const externalId = paymentData.id14
15    try {16      const newData = await this.client.capturePayment(externalId)17
18      return {19        ...newData,20        id: externalId21      }22    } catch (e) {23      return {24        error: e,25        code: "unknown",26        detail: e27      }28    }29  }30
31  // ...32}

Parameters

paymentDataRecord<string, unknown>
The data property of the payment. Make sure to store in it any helpful identification for your third-party integration.

Returns

PromisePromise<Record<string, unknown> | PaymentProviderError>
The new data to store in the payment's data property, or an error object.

authorizePayment#

This method authorizes a payment session. When authorized successfully, a payment is created by the Payment Module which can be later captured using the capturePayment method.

Refer to this guide to learn more about how this fits into the payment flow and how to handle required actions.

To automatically capture the payment after authorization, return the status captured.

Example

Code
1// other imports...2import {3  PaymentProviderError,4  PaymentProviderSessionResponse,5  PaymentSessionStatus6} from "@medusajs/framework/types"7
8class MyPaymentProviderService extends AbstractPaymentProvider<9  Options10> {11  async authorizePayment(12    paymentSessionData: Record<string, unknown>,13    context: Record<string, unknown>14  ): Promise<15    PaymentProviderError | {16      status: PaymentSessionStatus17      data: PaymentProviderSessionResponse["data"]18    }19  > {20    const externalId = paymentSessionData.id21
22    try {23      const paymentData = await this.client.authorizePayment(externalId)24
25      return {26        data: {27          ...paymentData,28          id: externalId29        },30        status: "authorized"31      }32    } catch (e) {33      return {34        error: e,35        code: "unknown",36        detail: e37      }38    }39  }40
41  // ...42}

Parameters

paymentSessionDataRecord<string, unknown>
The data property of the payment session. Make sure to store in it any helpful identification for your third-party integration.
contextRecord<string, unknown>
The context in which the payment is being authorized. For example, in checkout, the context has a cart_id property indicating the ID of the associated cart.

Returns

PromisePromise<PaymentProviderError | object>
Either an object of the new data to store in the created payment's data property and the payment's status, or an error object. Make sure to set in data anything useful to later retrieve the session.

cancelPayment#

This method cancels a payment.

Example

Code
1// other imports...2import {3  PaymentProviderError,4  PaymentProviderSessionResponse,5} from "@medusajs/framework/types"6
7class MyPaymentProviderService extends AbstractPaymentProvider<8  Options9> {10  async cancelPayment(11    paymentData: Record<string, unknown>12  ): Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]> {13    const externalId = paymentData.id14
15    try {16      const paymentData = await this.client.cancelPayment(externalId)17    } catch (e) {18      return {19        error: e,20        code: "unknown",21        detail: e22      }23    }24  }25
26  // ...27}

Parameters

paymentDataRecord<string, unknown>
The data property of the payment. Make sure to store in it any helpful identification for your third-party integration.

Returns

PromisePromise<Record<string, unknown> | PaymentProviderError>
An error object if an error occurs, or the data received from the integration.

initiatePayment#

This method is used when a payment session is created. It can be used to initiate the payment in the third-party session, before authorizing or capturing the payment later.

Example

Code
1// other imports...2import {3  PaymentProviderError,4  PaymentProviderSessionResponse,5} from "@medusajs/framework/types"6
7class MyPaymentProviderService extends AbstractPaymentProvider<8  Options9> {10  async initiatePayment(11    context: CreatePaymentProviderSession12  ): Promise<PaymentProviderError | PaymentProviderSessionResponse> {13    const {14      amount,15      currency_code,16      context: customerDetails17    } = context18
19    try {20      const response = await this.client.init(21        amount, currency_code, customerDetails22      )23
24      return {25        ...response,26        data: {27          id: response.id28        }29      }30    } catch (e) {31      return {32        error: e,33        code: "unknown",34        detail: e35      }36    }37  }38
39  // ...40}

Parameters

contextCreatePaymentProviderSession
The details of the payment session and its context.

Returns

PromisePromise<PaymentProviderError | PaymentProviderSessionResponse>
An object whose data property is set in the created payment session, or an error object. Make sure to set in data anything useful to later retrieve the session.

deletePayment#

This method is used when a payment session is deleted, which can only happen if it isn't authorized, yet.

Use this to delete or cancel the payment in the third-party service.

Example

Code
1// other imports...2import {3  PaymentProviderError,4  PaymentProviderSessionResponse,5} from "@medusajs/framework/types"6
7class MyPaymentProviderService extends AbstractPaymentProvider<8  Options9> {10  async deletePayment(11    paymentSessionData: Record<string, unknown>12  ): Promise<13    PaymentProviderError | PaymentProviderSessionResponse["data"]14  > {15    const externalId = paymentSessionData.id16
17    try {18      await this.client.cancelPayment(externalId)19    } catch (e) {20      return {21        error: e,22        code: "unknown",23        detail: e24      }25    }26  }27
28  // ...29}

Parameters

paymentSessionDataRecord<string, unknown>
The data property of the payment session. Make sure to store in it any helpful identification for your third-party integration.

Returns

PromisePromise<Record<string, unknown> | PaymentProviderError>
An error object or the response from the third-party service.

getPaymentStatus#

This method gets the status of a payment session based on the status in the third-party integration.

Example

Code
1// other imports...2import {3  PaymentSessionStatus4} from "@medusajs/framework/types"5
6class MyPaymentProviderService extends AbstractPaymentProvider<7  Options8> {9  async getPaymentStatus(10    paymentSessionData: Record<string, unknown>11  ): Promise<PaymentSessionStatus> {12    const externalId = paymentSessionData.id13
14    try {15      const status = await this.client.getStatus(externalId)16
17      switch (status) {18        case "requires_capture":19          return "authorized"20        case "success":21          return "captured"22        case "canceled":23          return "canceled"24        default:25          return "pending"26      }27    } catch (e) {28      return "error"29    }30  }31
32  // ...33}

Parameters

paymentSessionDataRecord<string, unknown>
The data property of the payment session. Make sure to store in it any helpful identification for your third-party integration.

Returns

PromisePromise<PaymentSessionStatus>
The payment session's status.

refundPayment#

This method refunds an amount of a payment previously captured.

Example

Code
1// other imports...2import {3  PaymentProviderError,4  PaymentProviderSessionResponse,5} from "@medusajs/framework/types"6
7class MyPaymentProviderService extends AbstractPaymentProvider<8  Options9> {10  async refundPayment(11    paymentData: Record<string, unknown>,12    refundAmount: number13  ): Promise<14    PaymentProviderError | PaymentProviderSessionResponse["data"]15  > {16    const externalId = paymentData.id17
18    try {19      const newData = await this.client.refund(20        externalId,21        refundAmount22      )23
24      return {25        ...newData,26        id: externalId27      }28    } catch (e) {29      return {30        error: e,31        code: "unknown",32        detail: e33      }34    }35  }36
37  // ...38}

Parameters

paymentDataRecord<string, unknown>
The data property of the payment. Make sure to store in it any helpful identification for your third-party integration.
refundAmountnumber
The amount to refund.

Returns

PromisePromise<Record<string, unknown> | PaymentProviderError>
The new data to store in the payment's data property, or an error object.

retrievePayment#

Retrieves the payment's data from the third-party service.

Example

Code
1// other imports...2import {3  PaymentProviderError,4  PaymentProviderSessionResponse,5} from "@medusajs/framework/types"6
7class MyPaymentProviderService extends AbstractPaymentProvider<8  Options9> {10  async retrievePayment(11    paymentSessionData: Record<string, unknown>12  ): Promise<13    PaymentProviderError | PaymentProviderSessionResponse["data"]14  > {15    const externalId = paymentSessionData.id16
17    try {18      return await this.client.retrieve(externalId)19    } catch (e) {20      return {21        error: e,22        code: "unknown",23        detail: e24      }25    }26  }27
28  // ...29}

Parameters

paymentSessionDataRecord<string, unknown>
The data property of the payment. Make sure to store in it any helpful identification for your third-party integration.

Returns

PromisePromise<Record<string, unknown> | PaymentProviderError>
An object to be stored in the payment's data property, or an error object.

updatePayment#

Update a payment in the third-party service that was previously initiated with the initiatePayment method.

Example

Code
1// other imports...2import {3  UpdatePaymentProviderSession,4  PaymentProviderError,5  PaymentProviderSessionResponse,6} from "@medusajs/framework/types"7
8class MyPaymentProviderService extends AbstractPaymentProvider<9  Options10> {11  async updatePayment(12    context: UpdatePaymentProviderSession13  ): Promise<PaymentProviderError | PaymentProviderSessionResponse> {14    const {15      amount,16      currency_code,17      context: customerDetails,18      data19    } = context20    const externalId = data.id21
22    try {23      const response = await this.client.update(24        externalId,25        {26          amount,27          currency_code,28          customerDetails29        }30      )31
32      return {33        ...response,34        data: {35          id: response.id36        }37      }38    } catch (e) {39      return {40        error: e,41        code: "unknown",42        detail: e43      }44    }45  }46
47  // ...48}

Parameters

contextUpdatePaymentProviderSession
The details of the payment session and its context.

Returns

PromisePromise<PaymentProviderError | PaymentProviderSessionResponse>
An object whose data property is set in the updated payment session, or an error object. Make sure to set in data anything useful to later retrieve the session.

getWebhookActionAndData#

This method is executed when a webhook event is received from the third-party payment provider. Use it to process the action of the payment provider.

Learn more in this documentation

Example

Code
1// other imports...2import {3  BigNumber4} from "@medusajs/framework/utils"5import {6  ProviderWebhookPayload,7  WebhookActionResult8} from "@medusajs/framework/types"9
10class MyPaymentProviderService extends AbstractPaymentProvider<11  Options12> {13  async getWebhookActionAndData(14    payload: ProviderWebhookPayload["payload"]15  ): Promise<WebhookActionResult> {16    const {17      data,18      rawData,19      headers20    } = payload21
22    try {23      switch(data.event_type) {24        case "authorized_amount":25          return {26            action: "authorized",27            data: {28              session_id: (data.metadata as Record<string, any>).session_id,29              amount: new BigNumber(data.amount as number)30            }31          }32        case "success":33          return {34            action: "captured",35            data: {36              session_id: (data.metadata as Record<string, any>).session_id,37              amount: new BigNumber(data.amount as number)38            }39          }40        default:41          return {42            action: "not_supported"43          }44      }45    } catch (e) {46      return {47        action: "failed",48        data: {49          session_id: (data.metadata as Record<string, any>).session_id,50          amount: new BigNumber(data.amount as number)51        }52      }53    }54  }55
56  // ...57}

Parameters

dataobject
The webhook event's data

Returns

PromisePromise<WebhookActionResult>
The webhook result. If the action's value is captured, the payment is captured within Medusa as well. If the action's value is authorized, the associated payment session is authorized within Medusa.

3. Create Module Definition File#

Create the file src/modules/my-payment/index.ts with the following content:

src/modules/my-payment/index.ts
1import MyPaymentProviderService from "./service"2import { 3  ModuleProvider, 4  Modules5} from "@medusajs/framework/utils"6
7export default ModuleProvider(Modules.PAYMENT, {8  services: [MyPaymentProviderService],9})

This exports the module's definition, indicating that the MyPaymentProviderService is the module's service.


4. Use Module#

To use your Payment Module Provider, add it to the providers array of the Payment Module in medusa-config.ts:

medusa-config.ts
1import { Modules } from "@medusajs/framework/utils"2
3// ...4
5module.exports = defineConfig({6  // ...7  modules: [8    {9      resolve: "@medusajs/medusa/payment",10      options: {11        providers: [12          {13            resolve: "./src/modules/my-payment",14            id: "my-payment",15            options: {16              // provider options...17              apiKey: "..."18            }19          }20        ]21      }22    }23  ]24})

5. Test it Out#

Before you use your payment provider, enable it in a region using the Medusa Admin.

Then, go through checkout to place an order. Your payment provider is used to authorize the payment.


Useful Guides#

Was this page helpful?