Accept Payment in Checkout Flow
In this guide, you'll learn how to implement it using workflows or the Payment Module.
Why Implement the Payment Flow?#
Medusa already provides a built-in payment flow that allows you to accept payments from customers, which you can learn about in the Accept Payment Flow in Checkout guide.
You may need to implement a custom payment flow if you have a different use case, or you're using the Payment Module separately from the Medusa application.
This guide will help you understand how to implement a payment flow using the Payment Module's main service or workflows.
You can also follow this guide to get a general understanding of how the payment flow works in the Medusa application.
How to Implement the Accept Payment Flow?#
1. Create a Payment Collection
A payment collection holds all details related to a resource’s payment operations. So, you start off by creating a payment collection.
In the Medusa application, you associate the payment collection with a cart, which is the resource that the customer is trying to pay for.
For example:
2. Show Payment Providers#
Next, you'll show the customer the available payment providers to choose from.
In the Medusa application, you need to use Query to retrieve the available payment providers in a region.
3. Create Payment Sessions#
The payment collection has one or more payment sessions, each being a payment amount to be authorized by a payment provider.
So, once the customer selects a payment provider, create a payment session for the selected payment provider.
This will also initialize the payment session in the third-party payment provider.
createPaymentSessionsWorkflow, Medusa validates that the chosen payment provider is enabled in the cart's region. If the provider is not enabled in the region, the workflow throws an error. This validation only applies to payment collections linked to an active (not yet completed) cart; payment collections on orders are not affected.For example:
4. Authorize Payment Session
Once the customer places the order, you need to authorize the payment session with the third-party payment provider.
For example:
When the payment authorization is successful, a payment is created and returned.
Handling Deferred Authorization
Some payment methods (for example, bank transfers, payment links, or vouchers like OXXO or boleto) don't authorize immediately. For these methods, the payment provider returns a pending_authorization status instead of authorized.
When this happens:
authorizePaymentSessionreturnsnullinstead of a payment object.- The payment session's
statusis updated topending_authorization. - The cart can still be completed and an order is created with an "awaiting" payment status.
- Medusa will re-attempt authorization automatically when it receives a webhook event from the payment provider confirming the payment.
- Admins can also manually trigger a re-check from the order details page in the Medusa Admin.
Handling Additional Action
authorizePaymentSessionStep, you don't need to implement this logic as it's implemented in the step.If the payment authorization isn't successful, whether because it requires additional action or for another reason, the method updates the payment session with the new status and throws an error.
In that case, you can catch that error and, if the session's status property is requires_more, handle the additional action, then retry the authorization.
For example:
try {
const payment =
await paymentModuleService.authorizePaymentSession(
paymentSession.id,
{}
)
} catch (e) {
// retrieve the payment session again
const updatedPaymentSession = (
await paymentModuleService.listPaymentSessions({
id: [paymentSession.id],
})
)[0]
if (updatedPaymentSession.status === "requires_more") {
// TODO perform required action
// TODO authorize payment again.
}
}
5. Payment Flow Complete
The payment flow is complete once the payment session is authorized and the payment is created.
You can then:
- Complete the cart using the completeCartWorkflow if you're using the Medusa application.
- Capture the payment either using the capturePaymentWorkflow or capturePayment method.
- Refund captured amounts using the refundPaymentsWorkflow or refundPayment method.
Capture Payments#
To capture a payment, use the capturePaymentWorkflow or the Payment Module's capturePayment method.
If you pass an amount, it must be greater than 0. If you omit the amount, Medusa captures the full payment amount.
0 as the capture amount was treated as if no amount was provided, defaulting to the full payment amount. Now, passing 0 or a negative number throws an error.Refund Payments#
If you need to refund a payment, use the refundPaymentsWorkflow.
If you pass an amount, it must be greater than 0. If you omit the amount, Medusa refunds the full payment amount.
0 as the refund amount was treated as if no amount was provided, defaulting to the full payment amount. Now, passing 0 or a negative number throws an error.For example:
Retry Refunds Safely with an Idempotency Key
To retry a refund, such as when a background job or a return flow issues it, pass an idempotency_key to the refundPaymentWorkflow or the Payment Module's refundPayment method. The Payment Module forwards the key to the payment provider, so retrying a refund with the same key never refunds the customer twice, even when the response of the original request was lost.
Without a key, every request starts a new refund. So, if you retry a refund whose first attempt may have succeeded, the customer can receive the refund twice.
For example:
When you pass an idempotency_key, the Payment Module handles a retry with the same key as follows:
- If the previous refund with that key succeeded, the Payment Module doesn't send the refund to the payment provider again. The
refundPaymentWorkflowalso doesn't create credit lines or emit thepayment.refundedevent a second time. - If the previous refund with that key failed, the Payment Module retries it with the same key.
- If the previous refund with that key is still waiting for the payment provider's response, the Payment Module throws a conflict error. It only retries the refund if it has been pending for more than 15 minutes.
- If the amount is different from the amount of the previous refund with that key, the Payment Module throws an error.
You can also pass the idempotency_key in the request body of the Refund Payment API route.
status property whose value is pending while the payment provider processes the refund, succeeded once the provider confirms it, or failed if the provider doesn't confirm it. Medusa soft-deletes failed refunds, so they don't count toward the payment's refunded amount.refundPaymentWorkflow returns the payment, which it retrieves before issuing the refund. So, the payment's refunds property doesn't include the new refund. To get the new refund, retrieve the payment again after the workflow finishes.