Skip to main content

How to Create a Payment Provider

In this document, you’ll learn how to add a Payment Provider to your Medusa server. If you’re unfamiliar with the Payment architecture in Medusa, make sure to check out the overview first.

Overview

A Payment Provider is the payment method used to authorize, capture, and refund payment, among other actions. An example of a Payment Provider is Stripe.

By default, Medusa has a manual payment provider that has minimal implementation. It can be synonymous with a Cash on Delivery payment method. It allows store operators to manage the payment themselves but still keep track of its different stages on Medusa.

Adding a Payment Provider is as simple as creating a service file in src/servicesCopy to Clipboard. A Payment Provider is essentially a service that extends AbstractPaymentServiceCopy to Clipboard from the core Medusa package @medusajs/medusaCopy to Clipboard.

Payment Provider Services must have a static property identifierCopy to Clipboard. It's the name that will be used to install and refer to the Payment Provider in the Medusa server.

Payment Providers are loaded and installed at the server startup.

The Payment Provider service is also required to implement the following methods:

  1. createPaymentCopy to Clipboard: Called when a Payment Session for the Payment Provider is to be created.
  2. retrievePaymentCopy to Clipboard: Used to retrieve payment data from the third-party provider, if there’s any.
  3. getStatusCopy to Clipboard: Used to get the status of a Payment or Payment Session.
  4. updatePaymentCopy to Clipboard: Used to update the Payment Session whenever the cart and its related data are updated.
  5. updatePaymentDataCopy to Clipboard: Used to update the dataCopy to Clipboard field of Payment Sessions. Specifically called when a request is sent to the Update Payment Session endpoint.
  6. deletePaymentCopy to Clipboard: Used to perform any action necessary before a Payment Session is deleted.
  7. authorizePaymentCopy to Clipboard: Used to authorize the payment amount of the cart before the order or swap is created.
  8. getPaymentDataCopy to Clipboard: Used to retrieve the data that should be stored in the dataCopy to Clipboard field of a new Payment instance after the payment amount has been authorized.
  9. capturePaymentCopy to Clipboard: Used to capture the payment amount of an order or swap.
  10. refundPaymentCopy to Clipboard: Used to refund a payment amount of an order or swap.
  11. cancelPaymentCopy to Clipboard: Used to perform any necessary action with the third-party payment provider when an order or swap is canceled.

All these methods must be declared async in the Payment Provider Service.

These methods are used at different points in the Checkout flow as well as when processing the order after it’s placed.

Checkout Flow - Payment


Create a Payment Provider

The first step to create a payment provider is to create a JavaScript or TypeScript file in src/servicesCopy to Clipboard. The file's name should be the name of the payment provider.

For example, create the file src/services/my-payment.tsCopy to Clipboard with the following content:

src/services/my-payment.ts
import { 
AbstractPaymentService, PaymentContext, Data,
Payment, PaymentSession, PaymentSessionStatus,
PaymentSessionData, Cart, PaymentData,
PaymentSessionResponse } from "@medusajs/medusa"

import { EntityManager } from "typeorm"

class MyPaymentService extends AbstractPaymentService {
protected manager_: EntityManager
protected transactionManager_: EntityManager | undefined

async getPaymentData(paymentSession: PaymentSession): Promise<Data> {
throw new Error("Method not implemented.")
}
async updatePaymentData(
paymentSessionData: PaymentSessionData,
data: Data
): Promise<PaymentSessionData> {
throw new Error("Method not implemented.")
}
async createPayment(
context: Cart & PaymentContext
): Promise<PaymentSessionResponse> {
throw new Error("Method not implemented.")
}
async retrievePayment(paymentData: Data): Promise<Data> {
throw new Error("Method not implemented.")
}
async updatePayment(
paymentSessionData: PaymentSessionData,
cart: Cart
): Promise<PaymentSessionData> {
throw new Error("Method not implemented.")
}
async authorizePayment(
paymentSession: PaymentSession,
context: Data
): Promise<{ data: PaymentSessionData; status: PaymentSessionStatus }> {
throw new Error("Method not implemented.")
}
async capturePayment(payment: Payment): Promise<PaymentData> {
throw new Error("Method not implemented.")
}
async refundPayment(
payment: Payment,
refundAmount: number
): Promise<PaymentData> {
throw new Error("Method not implemented.")
}
async cancelPayment(payment: Payment): Promise<PaymentData> {
throw new Error("Method not implemented.")
}
async deletePayment(paymentSession: PaymentSession): Promise<void> {
throw new Error("Method not implemented.")
}
async getStatus(data: Data): Promise<PaymentSessionStatus> {
throw new Error("Method not implemented.")
}

}

export default MyPaymentService
Report Incorrect CodeReport Incorrect CodeCopy to ClipboardCopy to Clipboard

Where MyPaymentServiceCopy to Clipboard is the name of your Payment Provider service. For example, Stripe’s Payment Provider Service is called StripeProviderServiceCopy to Clipboard.

Payment Providers must extend AbstractPaymentServiceCopy to Clipboard from the core Medusa package @medusajs/medusaCopy to Clipboard.

Following the naming convention of Services, the name of the file should be the slug name of the Payment Provider, and the name of the class should be the camel case name of the Payment Provider suffixed with “Service”. In the example above, the name of the file should be my-payment.jsCopy to Clipboard. You can learn more in the service documentation.

Identifier

As mentioned in the overview, Payment Providers should have a static identifierCopy to Clipboard property.

The PaymentProviderCopy to Clipboard entity has 2 properties: identifierCopy to Clipboard and is_installedCopy to Clipboard. The value of the identifierCopy to Clipboard property in the class will be used when the Payment Provider is created in the database.

The value of this property will also be used to reference the Payment Provider throughout the Medusa server. For example, the identifier is used when a Payment Session in a cart is selected on checkout.

constructor

You can use the constructorCopy to Clipboard of your Payment Provider to have access to different services in Medusa through dependency injection.

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.

Additionally, if you’re creating your Payment Provider as an external plugin to be installed on any Medusa server and you want to access the options added for the plugin, you can access it in the constructor. The options are passed as a second parameter:

class MyPaymentService extends AbstractPaymentService {
// ...
constructor(container, options) {
super(container)
// you can access options here
}
// ...
}
Report Incorrect CodeReport Incorrect CodeCopy to ClipboardCopy to Clipboard

createPayment

This method is called during checkout when Payment Sessions are initialized to present payment options to the customer. It is used to allow you to make any necessary calls to the third-party provider to initialize the payment.

For example, in Stripe this method is used to initialize a Payment Intent for the customer.

The method receives a context object as a first parameter. This object is of type PaymentContextCopy to Clipboard and has the following properties:

type PaymentContext = {
cart: {
context: Record<string, unknown>
id: string
email: string
shipping_address: Address | null
shipping_methods: ShippingMethod[]
}
currency_code: string
amount: number
resource_id?: string
customer?: Customer
}
Report Incorrect CodeReport Incorrect CodeCopy to ClipboardCopy to Clipboard

Before v1.7.2, the first parameter was of type CartCopy to Clipboard. This method remains backwards compatible, but will be changed in the future. So, it's recommended to change the type of the first parameter to PaymentContextCopy to Clipboard.

This method must return an object of type PaymentSessionResponseCopy to Clipboard. It should have the following properties:

type PaymentSessionResponse = {
update_requests: { customer_metadata: Record<string, unknown> }
session_data: Record<string, unknown>
}
Report Incorrect CodeReport Incorrect CodeCopy to ClipboardCopy to Clipboard

Where:

  • session_dataCopy to Clipboard is the data that is going to be stored in the dataCopy to Clipboard field of the Payment Session to be created. As mentioned in the Architecture Overview, the dataCopy to Clipboard field is useful to hold any data required by the third-party provider to process the payment or retrieve its details at a later point.
  • update_requestsCopy to Clipboard is an object that can be used to pass data from the payment provider plugin to the core to update internal resources. Currently, it only has one attribute customer_metadataCopy to Clipboard which allows updating the metadataCopy to Clipboard field of the customer.

An example of a minimal implementation of createPaymentCopy to Clipboard:

import { PaymentContext, PaymentSessionResponse } from "@medusajs/medusa"

class MyPaymentService extends AbstractPaymentService {
// ...
async createPayment(
context: Cart & PaymentContext
): Promise<PaymentSessionResponse> {
// prepare data
return {
session_data,
update_requests,
}
}
}
Report Incorrect CodeReport Incorrect CodeCopy to ClipboardCopy to Clipboard

retrievePayment

This method is used to provide a uniform way of retrieving the payment information from the third-party provider. For example, in Stripe’s Payment Provider Service this method is used to retrieve the payment intent details from Stripe.

This method accepts the dataCopy to Clipboard field of a Payment Session or a Payment. So, you should make sure to store in the dataCopy to Clipboard field any necessary data that would allow you to retrieve the payment data from the third-party provider.

This method must return an object containing the data from the third-party provider.

An example of a minimal implementation of retrievePaymentCopy to Clipboard where you don’t need to interact with the third-party provider:

import { Data } from "@medusajs/medusa"
// ...

class MyPaymentService extends AbstractPaymentService {
// ...
async retrievePayment(paymentData: Data): Promise<Data> {
return {}
}
}
Report Incorrect CodeReport Incorrect CodeCopy to ClipboardCopy to Clipboard

getStatus

This method is used to get the status of a Payment or a Payment Session.

Its main usage is in the place order workflow. If the status returned is not authorizedCopy to Clipboard, then the payment is considered failed, an error will be thrown, and the order will not be placed.

This method accepts the dataCopy to Clipboard field of the Payment or Payment Session as a parameter. You can use this data to interact with the third-party provider to check the status of the payment if necessary.

This method returns a string that represents the status. The status must be one of the following values:

  1. authorizedCopy to Clipboard: The payment was successfully authorized.
  2. pendingCopy to Clipboard: The payment is still pending. This is the default status of a Payment Session.
  3. requires_moreCopy to Clipboard: The payment requires more actions from the customer. For example, if the customer must complete a 3DS check before the payment is authorized.
  4. errorCopy to Clipboard: If an error occurred with the payment.
  5. canceledCopy to Clipboard: If the payment was canceled.

An example of a minimal implementation of getStatusCopy to Clipboard where you don’t need to interact with the third-party provider:

import { Data, PaymentSessionStatus } from "@medusajs/medusa"
// ...

class MyPaymentService extends AbstractPaymentService {
// ...
async getStatus(data: Data): Promise<PaymentSessionStatus> {
return PaymentSessionStatus.AUTHORIZED
}
}
Report Incorrect CodeReport Incorrect CodeCopy to ClipboardCopy to Clipboard

This code block assumes the status is stored in the dataCopy to Clipboard field as demonstrated in the createPaymentCopy to Clipboard method.

updatePayment

This method is used to perform any necessary updates on the payment. This method is called whenever the cart or any of its related data is updated. For example, when a line item is added to the cart or when a shipping method is selected.

A line item refers to a product in the cart.

It accepts the dataCopy to Clipboard field of the Payment Session as the first parameter and a context object as a second parameter. This object is of type PaymentContextCopy to Clipboard and has the following properties:

type PaymentContext = {
cart: {
context: Record<string, unknown>
id: string
email: string
shipping_address: Address | null
shipping_methods: ShippingMethod[]
}
currency_code: string
amount: number
resource_id?: string
customer?: Customer
}
Report Incorrect CodeReport Incorrect CodeCopy to ClipboardCopy to Clipboard

Before v1.7.2, the second parameter was of type CartCopy to Clipboard. This method remains backwards compatible, but will be changed in the future. So, it's recommended to change the type of the first parameter to PaymentContextCopy to Clipboard.

You can utilize this method to interact with the third-party provider and update any details regarding the payment if necessary.

This method must return an object of type PaymentSessionResponseCopy to Clipboard. It should have the following properties:

type PaymentSessionResponse = {
update_requests: { customer_metadata: Record<string, unknown> }
session_data: Record<string, unknown>
}
Report Incorrect CodeReport Incorrect CodeCopy to ClipboardCopy to Clipboard

Where:

  • session_dataCopy to Clipboard is the data that is going to be stored in the dataCopy to Clipboard field of the Payment Session to be created. As mentioned in the Architecture Overview, the dataCopy to Clipboard field is useful to hold any data required by the third-party provider to process the payment or retrieve its details at a later point.
  • update_requestsCopy to Clipboard is an object that can be used to request from the Medusa core to update internal resources. Currently, it only has one attribute customer_metadataCopy to Clipboard which allows updating the metadataCopy to Clipboard field of the customer.

An example of a minimal implementation of updatePaymentCopy to Clipboard:

import { 
PaymentSessionData,
Cart,
PaymentContext,
PaymentSessionResponse,
} from "@medusajs/medusa"
// ...

class MyPaymentService extends AbstractPaymentService {
// ...
async updatePayment(
paymentSessionData: PaymentSessionData,
cart: Cart
): Promise<PaymentSessionData> {
// prepare data
return {
session_data,
update_requests,
}
}
}
Report Incorrect CodeReport Incorrect CodeCopy to ClipboardCopy to Clipboard

updatePaymentData

This method is used to update the dataCopy to Clipboard field of a Payment Session. Particularly, it is called when a request is sent to the Update Payment Session endpoint. This endpoint receives a dataCopy to Clipboard object in the body of the request that should be used to update the existing dataCopy to Clipboard field of the Payment Session.

This method accepts the current dataCopy to Clipboard field of the Payment Session as the first parameter, and the new dataCopy to Clipboard field sent in the body request as the second parameter.

You can utilize this method to interact with the third-party provider and make any necessary updates based on the dataCopy to Clipboard field passed in the body of the request.

This method must return an object that will be stored in the dataCopy to Clipboard field of the Payment Session.

An example of a minimal implementation of updatePaymentDataCopy to Clipboard that returns the updatedDataCopy to Clipboard passed in the body of the request as-is to update the dataCopy to Clipboard field of the Payment Session.

import { Data, PaymentSessionData } from "@medusajs/medusa"
// ...

class MyPaymentService extends AbstractPaymentService {
// ...
async updatePaymentData(
paymentSessionData: PaymentSessionData,
data: Data
): Promise<PaymentSessionData> {
return updatedData
}
}
Report Incorrect CodeReport Incorrect CodeCopy to ClipboardCopy to Clipboard

deletePayment

This method is used to perform any actions necessary before a Payment Session is deleted. The Payment Session is deleted in one of the following cases:

  1. When a request is sent to delete the Payment Session.
  2. When the Payment Session is refreshed. The Payment Session is deleted so that a newer one is initialized instead.
  3. When the Payment Provider is no longer available. This generally happens when the store operator removes it from the available Payment Provider in the admin.
  4. When the region of the store is changed based on the cart information and the Payment Provider is not available in the new region.

It accepts the Payment Session as an object for its first parameter.

You can use this method to interact with the third-party provider to delete data related to the Payment Session if necessary.

An example of a minimal implementation of deletePaymentCopy to Clipboard where no interaction with a third-party provider is required:

import { PaymentSession } from "@medusajs/medusa"
// ...

class MyPaymentService extends AbstractPaymentService {
// ...
async deletePayment(paymentSession: PaymentSession): Promise<void> {
return
}
}
Report Incorrect CodeReport Incorrect CodeCopy to ClipboardCopy to Clipboard

authorizePayment

This method is used to authorize payment using the Payment Session for an order. This is called when the cart is completed and before the order is created.

This method is also used for authorizing payments of a swap of an order.

The payment authorization might require additional action from the customer before it is declared authorized. Once that additional action is performed, the authorizePaymentCopy to Clipboard method will be called again to validate that the payment is now fully authorized. So, you should make sure to implement it for this case as well, if necessary.

Once the payment is authorized successfully and the Payment Session status is set to authorizedCopy to Clipboard, the order can then be placed.

If the payment authorization fails, then an error will be thrown and the order will not be created.

The payment authorization status is determined using the getStatusCopy to Clipboard method as mentioned earlier. If the status is requires_moreCopy to Clipboard then it means additional actions are required from the customer. If the workflow process reaches the “Start Create Order” step and the status is not authorizedCopy to Clipboard, then the payment is considered failed.

This method accepts the Payment Session as an object for its first parameter, and a contextCopy to Clipboard object as a second parameter. The contextCopy to Clipboard object contains the following properties:

  1. ipCopy to Clipboard: The customer’s IP.
  2. idempotency_keyCopy to Clipboard: The Idempotency Key that is associated with the current cart. It is useful when retrying payments, retrying checkout at a failed point, or for payments that require additional actions from the customer.

This method must return an object containing the property statusCopy to Clipboard which is a string that indicates the current status of the payment, and the property dataCopy to Clipboard which is an object containing any additional information required to perform additional payment processing such as capturing the payment. The values of both of these properties are stored in the Payment Session’s statusCopy to Clipboard and dataCopy to Clipboard fields respectively.

You can utilize this method to interact with the third-party provider and perform any actions necessary to authorize the payment.

An example of a minimal implementation of authorizePaymentCopy to Clipboard that doesn’t need to interact with any third-party provider:

import { 
Data,
PaymentSession,
PaymentSessionStatus,
PaymentSessionData,
} from "@medusajs/medusa"
// ...

class MyPaymentService extends AbstractPaymentService {
// ...
async authorizePayment(
paymentSession: PaymentSession,
context: Data
): Promise<{ data: PaymentSessionData; status: PaymentSessionStatus }> {
return {
status: PaymentSessionStatus.AUTHORIZED,
data: {
id: "test",
},
}
}
}
Report Incorrect CodeReport Incorrect CodeCopy to ClipboardCopy to Clipboard

getPaymentData

After the payment is authorized using authorizePaymentCopy to Clipboard, a Payment instance will be created. The dataCopy to Clipboard field of the Payment instance will be set to the value returned from the getPaymentDataCopy to Clipboard method in the Payment Provider.

This method accepts the Payment Session as an object for its first parameter.

This method must return an object to be stored in the dataCopy to Clipboard field of the Payment instance. You can either use it as-is or make any changes to it if necessary.

An example of a minimal implementation of getPaymentDataCopy to Clipboard:

import { Data, PaymentSession } from "@medusajs/medusa"
// ...

class MyPaymentService extends AbstractPaymentService {
// ...
async getPaymentData(paymentSession: PaymentSession): Promise<Data> {
return paymentSession.data
}
}
Report Incorrect CodeReport Incorrect CodeCopy to ClipboardCopy to Clipboard

capturePayment

This method is used to capture the payment amount of an order. This is typically triggered manually by the store operator from the admin.

This method is also used for capturing payments of a swap of an order.

You can utilize this method to interact with the third-party provider and perform any actions necessary to capture the payment.

This method accepts the Payment as an object for its first parameter.

This method must return an object that will be stored in the dataCopy to Clipboard field of the Payment.

An example of a minimal implementation of capturePaymentCopy to Clipboard that doesn’t need to interact with a third-party provider:

import { Data, Payment } from "@medusajs/medusa"
// ...

class MyPaymentService extends AbstractPaymentService {
// ...
async capturePayment(payment: Payment): Promise<Data> {
return {
status: "captured",
}
}
}
Report Incorrect CodeReport Incorrect CodeCopy to ClipboardCopy to Clipboard

refundPayment

This method is used to refund an order’s payment. This is typically triggered manually by the store operator from the admin. The refund amount might be the total order amount or part of it.

This method is also used for refunding payments of a swap of an order.

You can utilize this method to interact with the third-party provider and perform any actions necessary to refund the payment.

This method accepts the Payment as an object for its first parameter, and the amount to refund as a second parameter.

This method must return an object that is stored in the dataCopy to Clipboard field of the Payment.

An example of a minimal implementation of refundPaymentCopy to Clipboard that doesn’t need to interact with a third-party provider:

import { Data, Payment } from "@medusajs/medusa"
// ...

class MyPaymentService extends AbstractPaymentService {
// ...
async refundPayment(
payment: Payment,
refundAmount: number
): Promise<Data> {
return {
id: "test",
}
}
}
Report Incorrect CodeReport Incorrect CodeCopy to ClipboardCopy to Clipboard

cancelPayment

This method is used to cancel an order’s payment. This method is typically triggered by one of the following situations:

  1. Before an order is placed and after the payment is authorized, an inventory check is done on products to ensure that products are still available for purchase. If the inventory check fails for any of the products, the payment is canceled.
  2. If the store operator cancels the order from the admin.

This method is also used for canceling payments of a swap of an order.

You can utilize this method to interact with the third-party provider and perform any actions necessary to cancel the payment.

This method accepts the Payment as an object for its first parameter.

This method must return an object that is stored in the dataCopy to Clipboard field of the Payment.

An example of a minimal implementation of cancelPaymentCopy to Clipboard that doesn’t need to interact with a third-party provider:

import { Data, Payment } from "@medusajs/medusa"
// ...

class MyPaymentService extends AbstractPaymentService {
// ...
async cancelPayment(payment: Payment): Promise<Data> {
return {
id: "test",
}
}
}
Report Incorrect CodeReport Incorrect CodeCopy to ClipboardCopy to Clipboard

Optional Methods

retrieveSavedMethods

This method can be added to your Payment Provider service if your third-party provider supports saving the customer’s payment methods. Please note that in Medusa there is no way to save payment methods.

This method is called when a request is sent to Retrieve Saved Payment Methods.

This method accepts the customer as an object for its first parameter.

This method returns an array of saved payment methods retrieved from the third-party provider. You have the freedom to shape the items in the array as you see fit since they will be returned as-is for the response to the request.

If you’re using Medusa’s Next.js or Gatsby storefront starters, note that the presentation of this method is not implemented. You’ll need to implement the UI and pages for this method based on your implementation and the provider you are using.

An example of the implementation of retrieveSavedMethodsCopy to Clipboard taken from Stripe’s Payment Provider:

import { Customer, Data } from "@medusajs/medusa"
// ...

class MyPaymentService extends AbstractPaymentService {
// ...
/**
* Fetches a customers saved payment methods if registered in Stripe.
* @param {object} customer - customer to fetch saved cards for
* @return {Promise<Array<object>>} saved payments methods
*/
async retrieveSavedMethods(customer: Customer): Promise<Data[]> {
if (customer.metadata && customer.metadata.stripe_id) {
const methods = await this.stripe_.paymentMethods.list({
customer: customer.metadata.stripe_id,
type: "card",
})

return methods.data
}

return Promise.resolve([])
}
}
Report Incorrect CodeReport Incorrect CodeCopy to ClipboardCopy to Clipboard

See Also