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 Provider Directory#

Start by creating a new directory for your module provider.

If you're creating the module provider in a Medusa application, create it under the src/modules directory. For example, src/modules/my-payment. If you're creating the module provider in a plugin, create it under the src/providers directory. For example, src/providers/my-payment.

NoteThe rest of this guide always uses the src/modules/my-payment directory as an example.

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

constructor#

The constructor allows you to access resources from the module's container using the first parameter, and the module's options using the second parameter.

NoteA module's options are passed when you register it in the Medusa application.

Example

Code
1import { AbstractPaymentProvider } from "@medusajs/framework/utils"2import { Logger } from "@medusajs/framework/types"3
4type Options = {5  apiKey: string6}7
8type InjectedDependencies = {9  logger: Logger10}11
12class MyPaymentProviderService extends AbstractPaymentProvider<Options> {13  protected logger_: Logger14  protected options_: Options15  // assuming you're initializing a client16  protected client17
18  constructor(19    container: InjectedDependencies,20    options: Options21  ) {22    super(container, options)23
24    this.logger_ = container.logger25    this.options_ = options26
27    // TODO initialize your client28  }29  // ...30}31
32export default MyPaymentProviderService

Type Parameters

TConfigobjectOptional
The type of the provider's options passed as a second parameter.

Parameters

cradleRecord<string, unknown>
The module's container cradle used to resolve resources.

identifier#

Each payment provider has a unique identifier defined in its class. The provider's ID will be stored as pp_{identifier}_{id}, where {id} is the provider's id property in the medusa-config.ts.

Example

Code
1class MyPaymentProviderService extends AbstractPaymentProvider<2  Options3> {4  static identifier = "my-payment"5  // ...6}

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.

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  CapturePaymentInput,4  CapturePaymentOutput,5} from "@medusajs/framework/types"6
7class MyPaymentProviderService extends AbstractPaymentProvider<8  Options9> {10  async capturePayment(11    input: CapturePaymentInput12  ): Promise<CapturePaymentOutput> {13    const externalId = input.data?.id14
15      // assuming you have a client that captures the payment16    const newData = await this.client.capturePayment(externalId)17    return {data: newData}18  }19  // ...20}

Parameters

inputPaymentProviderInput
The input to capture the payment. The data field should contain the data from the payment provider. when the payment was created.

Returns

PromisePromise<PaymentProviderOutput>
The new data to store in the payment's data property. Throws in case of an error.

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  AuthorizePaymentInput,4  AuthorizePaymentOutput,5  PaymentSessionStatus6} from "@medusajs/framework/types"7
8class MyPaymentProviderService extends AbstractPaymentProvider<9  Options10> {11  async authorizePayment(12    input: AuthorizePaymentInput13  ): Promise<AuthorizePaymentOutput> {14    const externalId = input.data?.id15
16    // assuming you have a client that authorizes the payment17    const paymentData = await this.client.authorizePayment(externalId)18
19    return {20      data: paymentData,21      status: "authorized"22    }23  }24
25  // ...26}

Parameters

inputPaymentProviderInput
The input to authorize the payment. The data field should contain the data from the payment provider. when the payment was created.

Returns

PromisePromise<AuthorizePaymentOutput>
The status of the authorization, along with the data field about the payment. Throws in case of an error.

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    input: CancelPaymentInput12  ): Promise<CancelPaymentOutput> {13    const externalId = input.data?.id14
15    // assuming you have a client that cancels the payment16    const paymentData = await this.client.cancelPayment(externalId)17    return { data: paymentData }18  }19
20  // ...21}

Parameters

inputPaymentProviderInput
The input to cancel the payment. The data field should contain the data from the payment provider. when the payment was created.

Returns

PromisePromise<PaymentProviderOutput>
The new data to store in the payment's data property, if any. Throws in case of an error.

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  InitiatePaymentInput,4  InitiatePaymentOutput,5} from "@medusajs/framework/types"6
7class MyPaymentProviderService extends AbstractPaymentProvider<8  Options9> {10  async initiatePayment(11    input: InitiatePaymentInput12  ): Promise<InitiatePaymentOutput> {13    const {14      amount,15      currency_code,16      context: customerDetails17    } = input18
19    // assuming you have a client that initializes the payment20    const response = await this.client.init(21      amount, currency_code, customerDetails22    )23
24    return {25      id: response.id26      data: response,27    }28  }29
30  // ...31}

Parameters

inputInitiatePaymentInput
The input to create the payment session.

Returns

PromisePromise<InitiatePaymentOutput>
The new data to store in the payment's data property. Throws in case of an error.

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  DeletePaymentInput,4  DeletePaymentOutput,5} from "@medusajs/framework/types"6
7class MyPaymentProviderService extends AbstractPaymentProvider<8  Options9> {10  async deletePayment(11    input: DeletePaymentInput12  ): Promise<DeletePaymentOutput> {13    const externalId = input.data?.id14
15    // assuming you have a client that cancels the payment16    await this.client.cancelPayment(externalId)17    return {}18  }19  }20
21  // ...22}

Parameters

inputPaymentProviderInput
The input to delete the payment session. The data field should contain the data from the payment provider. when the payment was created.

Returns

PromisePromise<PaymentProviderOutput>
The new data to store in the payment's data property, if any. Throws in case of an error.

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  GetPaymentStatusInput,4  GetPaymentStatusOutput,5  PaymentSessionStatus6} from "@medusajs/framework/types"7
8class MyPaymentProviderService extends AbstractPaymentProvider<9  Options10> {11  async getPaymentStatus(12    input: GetPaymentStatusInput13  ): Promise<GetPaymentStatusOutput> {14    const externalId = input.data?.id15
16    // assuming you have a client that retrieves the payment status17    const status = await this.client.getStatus(externalId)18
19    switch (status) {20      case "requires_capture":21          return {status: "authorized"}22        case "success":23          return {status: "captured"}24        case "canceled":25          return {status: "canceled"}26        default:27          return {status: "pending"}28     }29  }30
31  // ...32}

Parameters

inputPaymentProviderInput
The input to get the payment status. The data field should contain the data from the payment provider. when the payment was created.

Returns

PromisePromise<GetPaymentStatusOutput>
The payment session's status. It can also return additional data from the payment provider.

refundPayment#

This method refunds an amount of a payment previously captured.

Example

Code
1// other imports...2import {3  RefundPaymentInput,4  RefundPaymentOutput,5} from "@medusajs/framework/types"6
7class MyPaymentProviderService extends AbstractPaymentProvider<8  Options9> {10  async refundPayment(11    input: RefundPaymentInput12  ): Promise<RefundPaymentOutput> {13    const externalId = input.data?.id14
15    // assuming you have a client that refunds the payment16    const newData = await this.client.refund(17        externalId,18        input.amount19      )20
21    return {data: newData}22  }23  // ...24}

Parameters

inputRefundPaymentInput
The input to refund the payment. The data field should contain the data from the payment provider. when the payment was created.

Returns

PromisePromise<PaymentProviderOutput>
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  RetrievePaymentInput,4  RetrievePaymentOutput,5} from "@medusajs/framework/types"6
7class MyPaymentProviderService extends AbstractPaymentProvider<8  Options9> {10  async retrievePayment(11    input: RetrievePaymentInput12  ): Promise<RetrievePaymentOutput> {13    const externalId = input.data?.id14
15    // assuming you have a client that retrieves the payment16    return await this.client.retrieve(externalId)17  }18  // ...19}

Parameters

inputPaymentProviderInput
The input to retrieve the payment. The data field should contain the data from the payment provider when the payment was created.

Returns

PromisePromise<PaymentProviderOutput>
The payment's data as found in the the payment provider.

updatePayment#

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

Example

Code
1// other imports...2import {3  UpdatePaymentInput,4  UpdatePaymentOutput,5} from "@medusajs/framework/types"6
7class MyPaymentProviderService extends AbstractPaymentProvider<8  Options9> {10  async updatePayment(11    input: UpdatePaymentInput12  ): Promise<UpdatePaymentOutput> {13    const { amount, currency_code, context } = input14    const externalId = input.data?.id15
16    // assuming you have a client that updates the payment17    const response = await this.client.update(18      externalId,19        {20          amount,21          currency_code,22          context.customer23        }24      )25
26    return response27  }28
29  // ...30}

Parameters

inputUpdatePaymentInput
The input to update the payment. The data field should contain the data from the payment provider. when the payment was created.

Returns

PromisePromise<PaymentProviderOutput>
The new data to store in the payment's data property. Throws in case of an error.

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
1module.exports = defineConfig({2  // ...3  modules: [4    {5      resolve: "@medusajs/medusa/payment",6      options: {7        providers: [8          {9            // if module provider is in a plugin, use `plugin-name/providers/my-payment`10            resolve: "./src/modules/my-payment",11            id: "my-payment",12            options: {13              // provider options...14              apiKey: "..."15            }16          }17        ]18      }19    }20  ]21})

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?
Ask Anything
FAQ
What is Medusa?
How can I create a module?
How can I create a data model?
How do I create a workflow?
How can I extend a data model in the Product Module?
Recipes
How do I build a marketplace with Medusa?
How do I build digital products with Medusa?
How do I build subscription-based purchases with Medusa?
What other recipes are available in the Medusa documentation?
Chat is cleared on refresh
Line break