Skip to content
Tutorials

Add stored payment methods to My Account

Shoppers who save a card with Payment Services during checkout can view and remove those vaulted methods on a My Account page. The Commerce boilerplate doesn’t include this by default, so this tutorial shows you how to add it.

  • Create the commerce-payment-methods block
  • Edit the PaymentMethods container
  • Create the block config — blocks/your-block-name/_your-block-name.json

By the end of this tutorial, you’ll have:

  • A commerce-payment-methods block that redirects guests to login and renders the User Account PaymentMethods container for signed-in shoppers.
  • A with-header and minified-view block configuration that controls the container’s title and compact layout.
  • An optional filterPaymentMethodCodes prop that limits the list to Payment Services–vaulted tokens.

Create a block. The naming is up to you, but commerce-payment-methods is a clear convention. This block should:

  1. Redirect guests to login (same pattern as other account blocks).
  2. Initialize the User Account drop-in via account.js.
  3. Render PaymentMethods with optional with header and minified view from the block configuration.

Example decorate implementation:

import { PaymentMethods } from '/@dropins/storefront-account/containers/PaymentMethods.js';
import { render as accountRenderer } from '/@dropins/storefront-account/render.js';
import { readBlockConfig } from '/scripts/aem.js';
import {
CUSTOMER_LOGIN_PATH,
checkIsAuthenticated,
rootLink,
} from '/scripts/commerce.js';
import '/scripts/initializers/account.js';
export default async function decorate(block) {
const {
'minified-view': minifiedViewConfig = 'false',
'with-header': withHeaderConfig = 'true',
} = readBlockConfig(block);
if (!checkIsAuthenticated()) {
window.location.href = rootLink(CUSTOMER_LOGIN_PATH);
} else {
await accountRenderer.render(PaymentMethods, {
withHeader: withHeaderConfig === 'true',
minifiedView: minifiedViewConfig === 'true',
})(block);
}
}

Place the block on the account page (or desired section) where the list should appear. Use the same section metadata, block registration, and readBlockConfig patterns as other commerce blocks (for example, customer info or orders).

Expose authoring keys in decorate that map to readBlockConfig. For example:

  • with-header — Controls whether a section title and divider are shown for the payments area (full account pages often omit this).
  • minified-view — Enables a compact layout with condensed rows and a simplified empty state, ideal for dashboards alongside other widgets.

Add a _your-block-name.json (and optional README) next to the block source so authors get defaults and labels consistent with the rest of your project.

Limit the list to Payment Services tokens (optional)

Section titled “Limit the list to Payment Services tokens (optional)”

Vault tokens include a payment_method_code. Payment Services methods typically use codes that start with payment_services (for example, hosted-field variants). The PaymentMethods container accepts filterPaymentMethodCodes: an array of strings. A token is shown if its code equals or starts with any entry.

The example decorate above doesn’t include this prop. To display only Payment Services–vaulted methods (and hide saved methods from other gateways), add it to the render call. For example:

await accountRenderer.render(PaymentMethods, {
withHeader: withHeaderConfig === 'true',
minifiedView: minifiedViewConfig === 'true',
filterPaymentMethodCodes: ['payment_services'],
})(block);

Adjust the prefix list if your Admin or integration uses a different payment method code pattern.

  • By default, the container calls getCustomerPaymentTokens, which executes the customerPaymentTokens query and transforms the returned vault details (JSON) into rows for the PaymentCard component. The details include Payment Services fields like brand and last four digits.
  • If another part of the page already has token rows on the client, you can emit account/customerPaymentTokens on the event bus so the container shows that list without an extra getCustomerPaymentTokens round trip (see Events and TypeScript declaration). That is optional injection only; Remove still calls deletePaymentToken on the GraphQL API, not the bus. Payment Services + GraphQL alone is the usual path for My Account.
  • Removal uses a confirmation step before the token is deleted. See Remove confirmation for how that flow works.