How to Create a Payment Module Provider
In this document, you’ll learn how to create a Payment Module Provider to be used with the Payment Module.
Implementation Example#
As you implement your Payment Module Provider, it can be useful to refer to an existing provider and how it's implemeted.
If you need to refer to an existing implementation as an example, check the Stripe Payment Module Provider in the Medusa repository.
Understanding Payment Module Provider Implementation#
The Payment Module Provider handles processing payment with a third-party provirder. However, it's not responsible for managing payment concepts within Medusa, such as payment sessions or collections. These concepts are handled by the Payment Module which uses your Payment Module Provider within core operations.
For example, when the merchant captures an order's payment, the Payment Module uses the Payment Module Provider to capture the payment, the makes updates to the Payment
record associated with the order. So, you only have to implement the third-party payment processing logic in your Payment Module Provider.
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
.
src/modules/my-payment
directory as an example.2. Create the Payment Module Provider's Service#
Create the file src/modules/my-payment/service.ts
that holds the module provider's main service. It must extend the AbstractPaymentProvider
class imported from @medusajs/framework/utils
:
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.
If you're creating a client or establishing a connection with a third-party service, do it in the constructor.
Example
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
TConfig
objectOptionalParameters
cradle
Record<string, unknown>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
authorizePayment#
This method authorizes a payment session using the third-party payment provider.
During checkout, the customer may need to perform actions required by the payment provider, such as entering their card details or confirming the payment. Once that is done, the customer can place their order.
During cart-completion before placing the order, this method is used to authorize the cart's payment session with the third-party payment provider. The payment can later be captured using the capturePayment method.
When authorized successfully, a Payment
is created by the Payment
Module, and it's associated with the order.
Understanding data
property
The data
property of the method's parameter contains the PaymentSession
record's data
property, which was
returned by the initiatePayment method.
The data
property returned by this method is then stored in the created Payment
record. You can store data relevant to later capture or process the payment.
For example, you can store the ID of the payment in the third-party provider to reference it later.
Example
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
The input to authorize the payment. The data
field should contain the data from the payment provider. when the payment was created.
data
field should contain the data from the payment provider. when the payment was created.Returns
Promise
Promise<AuthorizePaymentOutput>data
field about the payment. Throws in case of an error.cancelPayment#
This method cancels a payment in the third-party payment provider. It's used when the admin user cancels an order. The order can only be canceled if the payment is not captured yet.
Understanding data
property
The data
property of the method's parameter contains the Payment
record's data
property, which was
returned by the authorizePayment method.
The data
property returned by this method is then stored in the Payment
record. You can store data relevant for any further processing of the payment.
Example
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
input
CancelPaymentInputThe input to cancel the payment. The data
field should contain the data from the payment provider. when the payment was created.
input
CancelPaymentInputdata
field should contain the data from the payment provider. when the payment was created.Returns
Promise
Promise<CancelPaymentOutput>data
property, if any. Throws in case of an error.capturePayment#
This method captures a payment using the third-party provider. In this method, use the third-party provider to capture the payment.
When an order is placed, the payment is authorized using the authorizePayment method. Then, the admin user can capture the payment, which triggers this method.
This method can also be triggered by a webhook event if the getWebhookActionAndData method returns the action captured
.
Understanding data
property
The data
property of the input parameter contains data that was previously stored in the Payment record's data
property, which was
returned by the authorizePayment method.
The data
property returned by this method is then stored in the Payment
record. You can store data relevant to later refund or process the payment.
For example, you can store the ID of the payment in the third-party provider to reference it later.
Example
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 {18 data: {19 ...newData,20 id: externalId,21 }22 }23 }24 // ...25}
Parameters
input
CapturePaymentInputThe input to capture the payment. The data
field should contain the data from the payment provider. when the payment was created.
input
CapturePaymentInputdata
field should contain the data from the payment provider. when the payment was created.Returns
Promise
Promise<CapturePaymentOutput>data
property. Throws in case of an error.createAccountHolder#
This method is used when creating an account holder in Medusa, allowing you to create the equivalent account in the third-party payment provider. An account holder is useful to later save payment methods, such as credit cards, for a customer in the third-party payment provider using the savePaymentMethod method.
The returned data will be stored in the account holder created in Medusa. For example,
the returned id
property will be stored in the account holder's external_id
property.
Medusa creates an account holder when a payment session initialized for a registered customer.
v2.5.0
.Example
1import { MedusaError } from "@medusajs/framework/utils"2 3class MyPaymentProviderService extends AbstractPaymentProvider<4 Options5> {6 async createAccountHolder({ context, data }: CreateAccountHolderInput) {7 const { account_holder, customer } = context8 9 if (account_holder?.data?.id) {10 return { id: account_holder.data.id as string }11 }12 13 if (!customer) {14 throw new MedusaError(15 MedusaError.Types.INVALID_DATA,16 "Missing customer data."17 )18 }19 20 // assuming you have a client that creates the account holder21 const providerAccountHolder = await this.client.createAccountHolder({22 email: customer.email,23 ...data24 })25 26 return {27 id: providerAccountHolder.id,28 data: providerAccountHolder as unknown as Record<string, unknown>29 }30}
Parameters
Input data including the details of the account holder to create.
Returns
Promise
Promise<CreateAccountHolderOutput>deleteAccountHolder#
This method is used when an account holder is deleted in Medusa, allowing you to also delete the equivalent account holder in the third-party payment provider.
v2.5.0
.Example
1import { MedusaError } from "@medusajs/framework/utils"2 3class MyPaymentProviderService extends AbstractPaymentProvider<4 Options5> {6 async deleteAccountHolder({ context }: DeleteAccountHolderInput) {7 const { account_holder } = context8 const accountHolderId = account_holder?.data?.id as string | undefined9 if (!accountHolderId) {10 throw new MedusaError(11 MedusaError.Types.INVALID_DATA,12 "Missing account holder ID."13 )14 }15 16 // assuming you have a client that deletes the account holder17 await this.client.deleteAccountHolder({18 id: accountHolderId19 })20 21 return {}22 }23}
Parameters
Input data including the details of the account holder to delete.
Returns
Promise
Promise<DeleteAccountHolderOutput>deletePayment#
This method deletes a payment session in the third-party payment provider.
When a customer chooses a payment method during checkout, then chooses a different one, this method is triggered to delete the previous payment session.
If your provider doesn't support deleting a payment session, you can just return an empty object or
an object that contains the same received data
property.
Understanding data
property
The data
property of the method's parameter contains the PaymentSession
record's data
property, which was
returned by the initiatePayment method.
Example
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 data: input.data19 }20 }21 22 // ...23}
Parameters
input
DeletePaymentInputThe input to delete the payment session. The data
field should contain the data from the payment provider. when the payment was created.
input
DeletePaymentInputdata
field should contain the data from the payment provider. when the payment was created.Returns
Promise
Promise<DeletePaymentOutput>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
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
The input to get the payment status. The data
field should contain the data from the payment provider. when the payment was created.
data
field should contain the data from the payment provider. when the payment was created.Returns
Promise
Promise<GetPaymentStatusOutput>data
from the payment provider.getWebhookActionAndData#
This method is executed when a webhook event is received from the third-party payment provider. Medusa uses the data returned by this method to perform actions in the Medusa application, such as completing the associated cart if the payment was authorized successfully.
Learn more in the Webhook Events documentation.
Example
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 // assuming the session_id is stored in the metadata of the payment29 // in the third-party provider30 session_id: (data.metadata as Record<string, any>).session_id,31 amount: new BigNumber(data.amount as number)32 }33 }34 case "success":35 return {36 action: "captured",37 data: {38 // assuming the session_id is stored in the metadata of the payment39 // in the third-party provider40 session_id: (data.metadata as Record<string, any>).session_id,41 amount: new BigNumber(data.amount as number)42 }43 }44 default:45 return {46 action: "not_supported",47 data: {48 session_id: "",49 amount: new BigNumber(0)50 }51 }52 }53 } catch (e) {54 return {55 action: "failed",56 data: {57 // assuming the session_id is stored in the metadata of the payment58 // in the third-party provider59 session_id: (data.metadata as Record<string, any>).session_id,60 amount: new BigNumber(data.amount as number)61 }62 }63 }64 }65 66 // ...67}
Parameters
data
objectThe webhook event's data
data
objectReturns
Promise
Promise<WebhookActionResult>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 and the associated cart
will be completed to create an order.initiatePayment#
This method initializes a payment session with the third-party payment provider.
When a customer chooses a payment method during checkout, this method is triggered to perform any initialization action with the third-party provider, such as creating a payment session.
Understanding data
property
The data
property returned by this method will be stored in the created PaymentSession
record. You can store data relevant to later authorize or process the payment.
For example, you can store the ID of the payment session in the third-party provider to reference it later.
The data
property is also available to storefronts, allowing you to store data necessary for the storefront to integrate
the payment provider in the checkout flow. For example, you can store the client token to use with the payment provider's SDK.
data
property, as it's publicly accessible.Example
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.id,26 data: response,27 }28 }29 30 // ...31}
Parameters
input
InitiatePaymentInputThe input to create the payment session.
input
InitiatePaymentInputReturns
Promise
Promise<InitiatePaymentOutput>data
property. Throws in case of an error.listPaymentMethods#
This method is used to retrieve the list of saved payment methods for an account holder in the third-party payment provider. A payment provider that supports saving payment methods must implement this method.
v2.5.0
.Example
1import { MedusaError } from "@medusajs/framework/utils"2 3class MyPaymentProviderService extends AbstractPaymentProvider<4 Options5> {6 async listPaymentMethods({ context }: ListPaymentMethodsInput) {7 const { account_holder } = context8 const accountHolderId = account_holder?.data?.id as string | undefined9 10 if (!accountHolderId) {11 throw new MedusaError(12 MedusaError.Types.INVALID_DATA,13 "Missing account holder ID."14 )15 }16 17 // assuming you have a client that lists the payment methods18 const paymentMethods = await this.client.listPaymentMethods({19 customer_id: accountHolderId20 })21 22 return paymentMethods.map((pm) => ({23 id: pm.id,24 data: pm as unknown as Record<string, unknown>25 }))26 }27}
Parameters
Input data including the details of the account holder to list payment methods for.
Returns
Promise
Promise<ListPaymentMethodsOutput>refundPayment#
This method refunds an amount using the third-party payment provider. This method is triggered when the admin user refunds a payment of an order.
Understanding data
property
The data
property of the method's parameter contains the Payment
record's data
property, which was
returned by the capturePayment or refundPayment method.
The data
property returned by this method is then stored in the Payment
record. You can store data relevant to later refund or process the payment.
For example, you can store the ID of the payment in the third-party provider to reference it later.
data
property
of the input parameter contains the data from the last refund.Example
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 {22 data: input.data,23 }24 }25 // ...26}
Parameters
input
RefundPaymentInputThe input to refund the payment. The data
field should contain the data from the payment provider. when the payment was created.
input
RefundPaymentInputdata
field should contain the data from the payment provider. when the payment was created.Returns
Promise
Promise<RefundPaymentOutput>data
property, or an error object.retrievePayment#
This method retrieves the payment's data from the third-party payment provider.
Example
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
input
RetrievePaymentInputThe input to retrieve the payment. The data
field should contain the data from the payment provider when the payment was created.
input
RetrievePaymentInputdata
field should contain the data from the payment provider when the payment was created.Returns
Promise
Promise<RetrievePaymentOutput>savePaymentMethod#
This method is used to save a customer's payment method, such as a credit card, in the third-party payment provider. A payment provider that supports saving payment methods must implement this method.
v2.5.0
.Example
1import { MedusaError } from "@medusajs/framework/utils"2 3class MyPaymentProviderService extends AbstractPaymentProvider<4 Options5> {6 async savePaymentMethod({ context, data }: SavePaymentMethodInput) { *7 const accountHolderId = context?.account_holder?.data?.id as8 | string9 | undefined10 11 if (!accountHolderId) {12 throw new MedusaError(13 MedusaError.Types.INVALID_DATA,14 "Missing account holder ID."15 )16 }17 18 // assuming you have a client that saves the payment method19 const paymentMethod = await this.client.savePaymentMethod({20 customer_id: accountHolderId,21 ...data22 })23 24 return {25 id: paymentMethod.id,26 data: paymentMethod as unknown as Record<string, unknown>27 }28 }29}
Parameters
The details of the payment method to save.
Returns
Promise
Promise<SavePaymentMethodOutput>updateAccountHolder#
This method is used when updating an account holder in Medusa, allowing you to update the equivalent account in the third-party payment provider.
The returned data will be stored in the account holder created in Medusa. For example,
the returned id
property will be stored in the account holder's external_id
property.
v2.5.1
.Example
1import { MedusaError } from "@medusajs/framework/utils"2 3class MyPaymentProviderService extends AbstractPaymentProvider<4 Options5> {6 async updateAccountHolder({ context, data }: UpdateAccountHolderInput) {7 const { account_holder, customer } = context8 9 if (!account_holder?.data?.id) {10 throw new MedusaError(11 MedusaError.Types.INVALID_DATA,12 "Missing account holder ID."13 )14 }15 16 // assuming you have a client that updates the account holder17 const providerAccountHolder = await this.client.updateAccountHolder({18 id: account_holder.data.id,19 ...data20 })21 22 return {23 id: providerAccountHolder.id,24 data: providerAccountHolder as unknown as Record<string, unknown>25 }26}
Parameters
Input data including the details of the account holder to update.
Returns
Promise
Promise<UpdateAccountHolderOutput>updatePayment#
This method updates a payment in the third-party service that was previously initiated with the initiatePayment method.
Example
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 // Validate context.customer17 if (!context || !context.customer) {18 throw new Error("Context must include a valid customer.");19 }20 21 // assuming you have a client that updates the payment22 const response = await this.client.update(23 externalId,24 {25 amount,26 currency_code,27 context.customer28 }29 )30 31 return response32 }33 34 // ...35}
Parameters
input
UpdatePaymentInputThe input to update the payment. The data
field should contain the data from the payment provider. when the payment was created.
input
UpdatePaymentInputdata
field should contain the data from the payment provider. when the payment was created.Returns
Promise
Promise<UpdatePaymentOutput>data
property. Throws in case of an error.validateOptions#
This method validates the options of the provider set in medusa-config.ts
.
Implementing this method is optional, but it's useful to ensure that the required
options are passed to the provider, or if you have any custom validation logic.
If the options aren't valid, throw an error.
Example
Parameters
options
Record<any, any>medusa-config.ts
.3. Create Module Provider Definition File#
Create the file src/modules/my-payment/index.ts
with the following content:
This exports the module provider's definition, indicating that the MyPaymentProviderService
is the module provider's service.
4. Use Module Provider#
To use your Payment Module Provider, add it to the providers
array of the Payment Module in 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 Module Provider, enable it in a region using the Medusa Admin.
Then, go through checkout to place an order. Your Payment Module Provider is used to authorize the payment.