diff --git a/www/apps/resources/app/commerce-modules/customer/links-to-other-modules/page.mdx b/www/apps/resources/app/commerce-modules/customer/links-to-other-modules/page.mdx
index bf91bf2576..67657e8069 100644
--- a/www/apps/resources/app/commerce-modules/customer/links-to-other-modules/page.mdx
+++ b/www/apps/resources/app/commerce-modules/customer/links-to-other-modules/page.mdx
@@ -18,11 +18,106 @@ Read-only links are used to query data across modules, but the relations aren't
+- [`Customer` data model \<\> `AccountHolder` data model of Payment Module](#payment-module).
- [`Cart` data model of Cart Module \<\> `Customer` data model](#cart-module). (Read-only).
- [`Order` data model of Order Module \<\> `Customer` data model](#order-module). (Read-only).
---
+## Payment Module
+
+Medusa defines a link between the `Customer` and `AccountHolder` data models, allowing payment providers to save payment methods for a customer, if the payment provider supports it.
+
+
+
+This link is available starting from Medusa `v2.5.0`.
+
+
+
+### Retrieve with Query
+
+To retrieve the account holder associated with a customer with [Query](!docs!/learn/fundamentals/module-links/query), pass `customer.*` in `fields`:
+
+
+
+
+```ts
+const { data: customers } = await query.graph({
+ entity: "customer",
+ fields: [
+ "account_holder.*",
+ ],
+})
+
+// customers.account_holder
+```
+
+
+
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: customers } = useQueryGraphStep({
+ entity: "customer",
+ fields: [
+ "account_holder.*",
+ ],
+})
+
+// customers.account_holder
+```
+
+
+
+
+### Manage with Link
+
+To manage the account holders of a customer, use [Link](!docs!/learn/fundamentals/module-links/link):
+
+
+
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.CUSTOMER]: {
+ customer_id: "cus_123",
+ },
+ [Modules.PAYMENT]: {
+ account_holder_id: "acchld_123",
+ },
+})
+```
+
+
+
+
+```ts
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.CUSTOMER]: {
+ customer_id: "cus_123",
+ },
+ [Modules.PAYMENT]: {
+ account_holder_id: "acchld_123",
+ },
+})
+```
+
+
+
+
+---
+
## Cart Module
Medusa defines a read-only link between the `Customer` data model and the [Cart Module](../../cart/page.mdx)'s `Cart` data model. This means you can retrieve the details of a customer's carts, but you don't manage the links in a pivot table in the database. The customer of a cart is determined by the `customer_id` property of the `Cart` data model.
diff --git a/www/apps/resources/app/commerce-modules/payment/account-holder/page.mdx b/www/apps/resources/app/commerce-modules/payment/account-holder/page.mdx
new file mode 100644
index 0000000000..34de83ba80
--- /dev/null
+++ b/www/apps/resources/app/commerce-modules/payment/account-holder/page.mdx
@@ -0,0 +1,47 @@
+export const metadata = {
+ title: `Account Holders and Saved Payment Methods`,
+}
+
+# {metadata.title}
+
+In this documentation, you'll learn about account holders, and how they're used to save payment methods in third-party payment providers.
+
+
+
+Account holders are available starting from Medusa `v2.5.0`.
+
+
+
+## What's an Account Holder?
+
+An account holder represents a customer that can have saved payment methods in a third-party service. It's represented by the `AccountHolder` data model.
+
+It holds fields retrieved from the third-party provider, such as:
+
+- `external_id`: The ID of the equivalent customer or account holder in the third-party provider.
+- `data`: Data returned by the payment provider when the account holder is created.
+
+A payment provider that supports saving payment methods for customers would create the equivalent of an account holder in the third-party provider. Then, whenever a payment method is saved, it would be saved under the account holder in the third-party provider.
+
+---
+
+## Save Payment Methods
+
+If a payment provider supports saving payment methods for a customer, they must implement the following methods:
+
+- `createAccountHolder`: Creates an account holder in the payment provider. The Payment Module uses this method before creating the account holder in Medusa, and uses the returned data to set fields like `external_id` and `data` in the created `AccountHolder` record.
+- `deleteAccountHolder`: Deletes an account holder in the payment provider. The Payment Module uses this method when an account holder is deleted in Medusa.
+- `savePaymentMethod`: Saves a payment method for an account holder in the payment provider.
+- `listPaymentMethods`: Lists saved payment methods in the third-party service for an account holder. This is useful when displaying the customer's saved payment methods in the storefront.
+
+Learn more about implementing these methods in the [Create Payment Provider guide](/references/payment/provider).
+
+---
+
+## Account Holder in Medusa Payment Flows
+
+In the Medusa application, when a payment session is created for a registered customer, the Medusa application uses the Payment Module to create an account holder for the customer.
+
+Consequently, the Payment Module uses the payment provider to create an account holder in the third-party service, then creates the account holder in Medusa.
+
+This flow is only supported if the chosen payment provider has implemented the necessary [save payment methods](#save-payment-methods).
diff --git a/www/apps/resources/app/commerce-modules/payment/links-to-other-modules/page.mdx b/www/apps/resources/app/commerce-modules/payment/links-to-other-modules/page.mdx
index 27c4d17f2e..649f423133 100644
--- a/www/apps/resources/app/commerce-modules/payment/links-to-other-modules/page.mdx
+++ b/www/apps/resources/app/commerce-modules/payment/links-to-other-modules/page.mdx
@@ -13,6 +13,7 @@ This document showcases the module links defined between the Payment Module and
The Payment Module has the following links to other modules:
- [`Cart` data model of Cart Module \<\> `PaymentCollection` data model](#cart-module).
+- [`Customer` data model of Customer Module \<\> `AccountHolder` data model](#customer-module).
- [`Order` data model of Order Module \<\> `PaymentCollection` data model](#order-module).
- [`OrderClaim` data model of Order Module \<\> `PaymentCollection` data model](#order-module).
- [`OrderExchange` data model of Order Module \<\> `PaymentCollection` data model](#order-module).
@@ -112,6 +113,100 @@ createRemoteLinkStep({
---
+## Customer Module
+
+Medusa defines a link between the `Customer` and `AccountHolder` data models, allowing payment providers to save payment methods for a customer, if the payment provider supports it.
+
+
+
+This link is available starting from Medusa `v2.5.0`.
+
+
+
+### Retrieve with Query
+
+To retrieve the customer associated with an account holder with [Query](!docs!/learn/fundamentals/module-links/query), pass `customer.*` in `fields`:
+
+
+
+
+```ts
+const { data: accountHolders } = await query.graph({
+ entity: "account_holder",
+ fields: [
+ "customer.*",
+ ],
+})
+
+// accountHolders.customer
+```
+
+
+
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: accountHolders } = useQueryGraphStep({
+ entity: "account_holder",
+ fields: [
+ "customer.*",
+ ],
+})
+
+// accountHolders.customer
+```
+
+
+
+
+### Manage with Link
+
+To manage the account holders of a customer, use [Link](!docs!/learn/fundamentals/module-links/link):
+
+
+
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.CUSTOMER]: {
+ customer_id: "cus_123",
+ },
+ [Modules.PAYMENT]: {
+ account_holder_id: "acchld_123",
+ },
+})
+```
+
+
+
+
+```ts
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.CUSTOMER]: {
+ customer_id: "cus_123",
+ },
+ [Modules.PAYMENT]: {
+ account_holder_id: "acchld_123",
+ },
+})
+```
+
+
+
+
+---
+
## Order Module
An order's payment details are stored in a payment collection. This also applies for claims and exchanges.
diff --git a/www/apps/resources/app/commerce-modules/payment/page.mdx b/www/apps/resources/app/commerce-modules/payment/page.mdx
index 467729bcad..109737f848 100644
--- a/www/apps/resources/app/commerce-modules/payment/page.mdx
+++ b/www/apps/resources/app/commerce-modules/payment/page.mdx
@@ -21,6 +21,7 @@ Learn more about why modules are isolated in [this documentation](!docs!/learn/f
- [Authorize, Capture, and Refund Payments](./payment/page.mdx): Authorize, capture, and refund payments for a single resource.
- [Payment Collection Management](./payment-collection/page.mdx): Store and manage all payments of a single resources, such as a cart, in payment collections.
- [Integrate Third-Party Payment Providers](./payment-provider/page.mdx): Use payment providers like [Stripe](./payment-provider/stripe/page.mdx) to handle and process payments, or integrate custom payment providers.
+- [Saved Payment Methods](./account-holder/page.mdx): Save payment methods for customers in third-party payment providers.
- [Handle Webhook Events](./webhook-events/page.mdx): Handle webhook events from third-party providers and process the associated payment.
---
diff --git a/www/apps/resources/generated/edit-dates.mjs b/www/apps/resources/generated/edit-dates.mjs
index db3a93be58..f0cb2ce3ed 100644
--- a/www/apps/resources/generated/edit-dates.mjs
+++ b/www/apps/resources/generated/edit-dates.mjs
@@ -52,7 +52,7 @@ export const generatedEditDates = {
"app/commerce-modules/payment/payment-provider/page.mdx": "2024-10-09T11:07:27.269Z",
"app/commerce-modules/payment/payment-session/page.mdx": "2024-10-09T10:58:00.960Z",
"app/commerce-modules/payment/webhook-events/page.mdx": "2024-11-19T11:45:02.167Z",
- "app/commerce-modules/payment/page.mdx": "2025-01-16T10:33:31.791Z",
+ "app/commerce-modules/payment/page.mdx": "2025-01-31T09:38:17.917Z",
"app/commerce-modules/pricing/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00",
"app/commerce-modules/pricing/_events/page.mdx": "2024-07-03T19:27:13+03:00",
"app/commerce-modules/pricing/concepts/page.mdx": "2024-10-09T13:37:25.678Z",
@@ -2124,7 +2124,7 @@ export const generatedEditDates = {
"app/commerce-modules/order/edit/page.mdx": "2024-10-09T08:50:05.334Z",
"app/commerce-modules/order/links-to-other-modules/page.mdx": "2025-01-06T11:19:35.604Z",
"app/commerce-modules/order/order-change/page.mdx": "2024-10-09T09:59:40.745Z",
- "app/commerce-modules/payment/links-to-other-modules/page.mdx": "2025-01-06T11:19:35.593Z",
+ "app/commerce-modules/payment/links-to-other-modules/page.mdx": "2025-01-31T09:22:05.326Z",
"references/core_flows/Common/Steps_Common/functions/core_flows.Common.Steps_Common.useQueryGraphStep/page.mdx": "2025-01-13T17:30:23.157Z",
"references/core_flows/Payment/Workflows_Payment/functions/core_flows.Payment.Workflows_Payment.processPaymentWorkflow/page.mdx": "2025-01-27T11:43:50.940Z",
"references/helper_steps/functions/helper_steps.useQueryGraphStep/page.mdx": "2025-01-17T16:43:22.242Z",
@@ -5737,7 +5737,7 @@ export const generatedEditDates = {
"app/commerce-modules/tax/admin-widget-zones/page.mdx": "2024-12-24T08:47:13.176Z",
"app/commerce-modules/user/admin-widget-zones/page.mdx": "2024-12-24T08:48:14.186Z",
"app/commerce-modules/currency/links-to-other-modules/page.mdx": "2024-12-24T14:47:10.556Z",
- "app/commerce-modules/customer/links-to-other-modules/page.mdx": "2024-12-24T14:48:54.689Z",
+ "app/commerce-modules/customer/links-to-other-modules/page.mdx": "2025-01-31T09:26:54.541Z",
"app/commerce-modules/fulfillment/events/page.mdx": "2024-12-31T09:37:49.253Z",
"app/commerce-modules/payment/events/page.mdx": "2024-12-31T09:41:56.582Z",
"references/core_flows/Payment/Steps_Payment/functions/core_flows.Payment.Steps_Payment.refundPaymentsStep/page.mdx": "2025-01-27T11:43:50.929Z",
@@ -5902,6 +5902,7 @@ export const generatedEditDates = {
"references/types/HttpTypes/interfaces/types.HttpTypes.StoreProductTypeResponse/page.mdx": "2025-01-27T11:43:54.212Z",
"references/types/interfaces/types.BaseProductTypeListParams/page.mdx": "2025-01-27T11:43:54.550Z",
"references/core_flows/Order/Steps_Order/variables/core_flows.Order.Steps_Order.updateOrderChangesStepId/page.mdx": "2025-01-27T11:43:49.278Z",
+ "app/commerce-modules/payment/account-holder/page.mdx": "2025-01-31T09:37:41.595Z",
"app/troubleshooting/test-errors/page.mdx": "2025-01-31T13:08:42.639Z",
"app/commerce-modules/product/variant-inventory/page.mdx": "2025-02-03T12:19:45.706Z",
"app/examples/guides/custom-item-price/page.mdx": "2025-02-07T09:21:11.170Z"
diff --git a/www/apps/resources/generated/files-map.mjs b/www/apps/resources/generated/files-map.mjs
index ec4c7687b8..14ac97c4f3 100644
--- a/www/apps/resources/generated/files-map.mjs
+++ b/www/apps/resources/generated/files-map.mjs
@@ -431,6 +431,10 @@ export const filesMap = [
"filePath": "/www/apps/resources/app/commerce-modules/page.mdx",
"pathname": "/commerce-modules"
},
+ {
+ "filePath": "/www/apps/resources/app/commerce-modules/payment/account-holder/page.mdx",
+ "pathname": "/commerce-modules/payment/account-holder"
+ },
{
"filePath": "/www/apps/resources/app/commerce-modules/payment/events/page.mdx",
"pathname": "/commerce-modules/payment/events"
diff --git a/www/apps/resources/generated/sidebar.mjs b/www/apps/resources/generated/sidebar.mjs
index 74f1d31c2f..6e29237134 100644
--- a/www/apps/resources/generated/sidebar.mjs
+++ b/www/apps/resources/generated/sidebar.mjs
@@ -8369,6 +8369,14 @@ export const generatedSidebar = [
"title": "Payment Provider Module",
"children": []
},
+ {
+ "loaded": true,
+ "isPathHref": true,
+ "type": "link",
+ "path": "/commerce-modules/payment/account-holder",
+ "title": "Account Holder",
+ "children": []
+ },
{
"loaded": true,
"isPathHref": true,
diff --git a/www/apps/resources/sidebars/payment.mjs b/www/apps/resources/sidebars/payment.mjs
index 867fa3c6d4..5ea32df1c0 100644
--- a/www/apps/resources/sidebars/payment.mjs
+++ b/www/apps/resources/sidebars/payment.mjs
@@ -43,6 +43,11 @@ export const paymentSidebar = [
path: "/commerce-modules/payment/payment-provider",
title: "Payment Provider Module",
},
+ {
+ type: "link",
+ path: "/commerce-modules/payment/account-holder",
+ title: "Account Holder",
+ },
{
type: "link",
path: "/commerce-modules/payment/webhook-events",