# Add custom payment methods

Learn how to add custom payment methods to Elements.

# Payment Intents API


Use the [Payment Element](https://docs.stripe.com/payments/payment-element.md) with the Payment Intents API to display over 50 preset [payment methods](https://docs.stripe.com/payments/payment-methods/payment-method-support.md), as well as your custom payment methods, through a single integration. You can also display your custom payment methods alongside other one-click payment method buttons with the [Express Checkout Element](https://docs.stripe.com/elements/express-checkout-element.md).

After creating your custom payment method in the Dashboard, configure the Payment Element or Express Checkout Element to make sure these transactions process and finalize correctly outside of Stripe. You can record these transactions to your Stripe account for reporting purposes.

> When integrating with a third-party payment processor, you’re responsible for complying with [applicable legal requirements](https://docs.stripe.com/payments/payment-methods/custom-payment-methods.md#compliance), including your agreement with your PSP, applicable laws, and so on.

## Before you begin

1. [Create a Stripe account](https://dashboard.stripe.com/register) or [sign in](https://dashboard.stripe.com/login) with your existing account.
2. Follow [this guide](https://docs.stripe.com/payments/accept-a-payment-deferred.md) to complete a payments integration.

## Create your custom payment method [Dashboard]

You can create a custom payment method in the Dashboard by going to **Settings** > **Payments** > [Custom Payment Methods](https://dashboard.stripe.com/settings/custom_payment_methods). Provide the name and logo for the Payment Element to display.

#### Choose the right logo 

- For logos with a transparent background, consider the background color of the Payment Element on your page and make sure that it stands out.
- For logos with a background fill, include rounded corners in your file, if needed.
- Choose a logo variant that can scale down to 16x16 pixels. This is often the standalone logo mark for a brand.

After creating the custom payment method, the Dashboard displays the custom payment method ID (beginning with `cpmt_`) that you need for the next step.

## Add the custom payment method type [Client-side]

Next, add the custom payment method type to your Stripe Elements configuration. In your `checkout.js` file where you initialize Stripe Elements, specify the [customPaymentMethods](https://docs.stripe.com/js/elements_object/create#stripe_elements-options-customPaymentMethods) to add to the Payment Element. Provide the custom payment method ID from the previous step, the `payment.type`, and an optional subtitle.

```javascript
const elements = stripe.elements({
  // ...
  customPaymentMethods: [
    {
      id: '{{CUSTOM_PAYMENT_METHOD_TYPE_ID}}', // Identifier of the custom payment method type created in the Dashboard.
      payment: {
        type: 'static',
        subtitle: 'Optional subtitle',
      }
    }
  ]
});
```

After loading, the Payment Element shows your custom payment method.
![Stripe Payment Element showing a custom payment method called PM Name.](https://b.stripecdn.com/docs-statics-srv/assets/accordion-example.4ef074051c465869aef5100527c9811c.png)

## Optional: Display embedded custom content (Preview) [Client-side]

Use the `embedded` type to display the content for your custom payment method in the Payment Element.
![Stripe Payment Element showing a custom payment method called PM Name, with custom content overlayed in the form container.](https://b.stripecdn.com/docs-statics-srv/assets/accordion-embedded-example.ded460e4e2bf525e7f4fe33aa62bb983.png)

Manage your custom content using these callbacks:

- [handleRender](https://docs.stripe.com/js/elements_object/create#stripe_elements-options-customPaymentMethods-options-embedded-handleRender): Called when a payment method is selected, and contains a reference to a container DOM node that you can render your content in.
- [handleDestroy](https://docs.stripe.com/js/elements_object/create#stripe_elements-options-customPaymentMethods-options-embedded-handleDestroy): Called when a payment method is deselected and the Payment Element is unmounted. Performs cleanup, such as removing event listeners or a custom SDK.

> Only render trusted content within the `container` that’s provided by `handleRender`. Rendering markup that you don’t control, especially from a user or an unsanitized source, can introduce a [cross-site scripting vulnerability (XSS)](https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting).

```javascript
const elements = stripe.elements({
  // ...
  customPaymentMethods: [
    {
      id: '{{CUSTOM_PAYMENT_METHOD_TYPE_ID}}',
      payment: {
        type: 'embedded',
        subtitle: 'Embedded payment method',
        embedded: {
          handleRender: (container) => {
            // Render markup in the embedded content container
            // using the templating system or JavaScript framework
            // of your choice
          },
          handleDestroy: () => {
            // Handle any needed cleanup, like removing SDKs
            // or event listeners
          }
        }
      }
    }
  ]
});
```

Tools like [React Portals](https://react.dev/reference/react-dom/createPortal) allow you to integrate your rendering logic with your application code:

```javascript
import {Elements} from '@stripe/react-stripe-js';
import {loadStripe} from '@stripe/stripe-js';

// Make sure to call `loadStripe` outside of a component's render to avoid
// recreating the `Stripe` object on every render.
const stripePromise = loadStripe('<<YOUR_PUBLISHABLE_KEY>>');

export default function App() {
  const [embedContainer, setEmbedContainer] = useState();

  const options = {
    customPaymentMethods: [
      {
        id: '{{CUSTOM_PAYMENT_METHOD_TYPE_ID}}',
        payment: {
          type: 'embedded',
          subtitle: 'Embedded payment method',
          embedded: {
            handleRender: (container) => {
              setEmbedContainer(container);
            },
            handleDestroy: () => {
              setEmbedContainer(null);
            }
          }
        }
      }
    ]
  };

  return (
    <Elements stripe={stripePromise} options={options}>
      <CheckoutFormWithPaymentElement />
      {embedContainer && createPortal(<EmbeddedCpmContent />, embedContainer)}
    </Elements>
  );
};
```

### Interested in embedded custom content?

To request access to this preview feature, enter your email below and a member of our team will contact you.

```bash
curl https://docs.stripe.com/preview/register \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Referer: https://docs.stripe.com/payments/payment-element/custom-payment-methods" \
  -d '{"email": "EMAIL", "preview": "elements_embedded_cpm_preview"}'
```

## Handle payment method submission [Client-side]

To process custom payment method transactions outside of Stripe, update the `handleSubmit` function that’s called when users click the pay button on your website.

The [elements.submit()](https://docs.stripe.com/js/elements/submit) function retrieves the selected payment method type. For example, you might show a modal, and then either process the payment on your own server or redirect your customer to an external payment page.

```javascript

async function handleSubmit(e) {
  const { submitError, selectedPaymentMethod } = await elements.submit();

  if (selectedPaymentMethod === '{{CUSTOM_PAYMENT_METHOD_TYPE_ID}}') { // Identifier of the custom payment method type created in the Dashboard.
    // Process CPM payment on merchant server and handle redirect
    const res = await fetch("/process-cpm-payment", { method: 'post' });
    ...
  } else {
    // Process Stripe payment methods
    ...
  }
}

```

## Optional: Specify the order of custom payment methods [Client-side]

By default, the Payment Element shows custom payment methods last. To manually specify the order of payment methods, set the [paymentMethodOrder](https://docs.stripe.com/js/elements_object/create_payment_element#payment_element_create-options-paymentMethodOrder) property on the `options` configuration when creating your Payment Element instance.

```javascript

const paymentElement = elements.create('payment', {
  // an array of payment method types, including custom payment method types
  paymentMethodOrder: [...]
});

```

The Express Checkout Element also shows custom payment methods last, by default. To manually specify the order of payment methods, set the [paymentMethodOrder](https://docs.stripe.com/js/elements_object/create_express_checkout_element#express_checkout_element_create-options-paymentMethodOrder) property on the `options` configuration when creating your Express Checkout Element instance.

```javascript

const expressCheckoutElement = elements.create('expressCheckout', {
  // an array of payment method types, including custom payment method types
  paymentMethodOrder: ['apple_pay', '{{CUSTOM_PAYMENT_METHOD_TYPE_ID}}', 'link']
});

```

## Optional: Add custom payment methods to the Express Checkout Element (Preview) [Client-side]

You can also display custom payment methods as buttons in the [Express Checkout Element](https://docs.stripe.com/elements/express-checkout-element.md), alongside wallets such as Apple Pay and Google Pay.

> Custom payment methods in the Express Checkout Element are only supported with the [Payment Intents API](https://docs.stripe.com/payments/payment-intents.md). They aren’t supported with the [Checkout Sessions API](https://docs.stripe.com/api/checkout/sessions.md).

To render your custom button, configure the [customPaymentMethods](https://docs.stripe.com/js/elements_object/create#stripe_elements-options-customPaymentMethods) option with an `expressCheckout` object.

We recommend rendering a button-like component so your custom payment method matches the other buttons in the Express Checkout Element, and works as expected with screen readers and other accessibility features.

#### HTML + JS

```javascript
const elements = stripe.elements({
  mode: 'payment',
  currency: 'usd',
  amount: 2000,
  customPaymentMethods: [
    {
      id: '{{CUSTOM_PAYMENT_METHOD_TYPE_ID}}', // Identifier of the custom payment method type created in the Dashboard.
      expressCheckout: {
        type: 'embedded',
        embedded: {
          handleRender: (container) => {
            // Render your custom button in the container
            const button = document.createElement('button');
            button.textContent = 'Pay with MyWallet';
            button.addEventListener('click', async () => {
              const res = await fetch('/process-cpm-payment', { method: 'post' });
              // Handle the redirect or success
            });
            container.appendChild(button);
          },
          handleDestroy: () => {
            // Clean up when the Express Checkout Element removes the button
          }
        }
      }
    }
  ]
});

const expressCheckoutElement = elements.create('expressCheckout');
expressCheckoutElement.mount('#express-checkout-element');
```

#### React

```jsx
import { useCallback, useRef } from 'react';
import { createRoot } from 'react-dom/client';
import { Elements, ExpressCheckoutElement } from '@stripe/react-stripe-js';
import { loadStripe } from '@stripe/stripe-js';

const stripePromise = loadStripe('<<YOUR_PUBLISHABLE_KEY>>');

function MyWalletButton() {
  const handleClick = async () => {
    const res = await fetch('/process-cpm-payment', { method: 'post' });
    // Handle the redirect or success
  };
  return <button onClick={handleClick}>Pay with MyWallet</button>;
}

function Checkout() {
  const rootRef = useRef(null);

  const handleRender = useCallback((container) => {
    rootRef.current = createRoot(container);
    rootRef.current.render(<MyWalletButton />);
  }, []);

  const handleDestroy = useCallback(() => {
    rootRef.current?.unmount();
    rootRef.current = null;
  }, []);

  const options = {
    mode: 'payment',
    currency: 'usd',
    amount: 2000,
    customPaymentMethods: [
      {
        id: '{{CUSTOM_PAYMENT_METHOD_TYPE_ID}}',
        expressCheckout: {
          type: 'embedded',
          embedded: { handleRender, handleDestroy },
        },
      },
    ],
  };

  return (
    <Elements stripe={stripePromise} options={options}>
      <ExpressCheckoutElement />
    </Elements>
  );
}
```

> Only render trusted content within the `container` that’s provided by `handleRender`. Rendering markup that you don’t control, especially from a user or an unsanitized source, can introduce a [cross-site scripting vulnerability (XSS)](https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting).

#### Detect available payment methods 

Custom payment method IDs appear as keys in the [availablepaymentmethodschange event](https://docs.stripe.com/js/elements_object/express_checkout_element_availablepaymentmethodschange_event), so you can detect when your custom buttons render.

#### Update custom payment methods at runtime 

To add or remove custom payment methods after mounting, call [elements.update()](https://docs.stripe.com/js/elements_object/update) with a new `customPaymentMethods` array. When you remove a custom payment method, Stripe calls its `handleDestroy` callback before removing the button.

### Interested in custom payment methods in the Express Checkout Element?

To request access to this preview feature, enter your email below and a member of our team will contact you.

```bash
curl https://docs.stripe.com/preview/register \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Referer: https://docs.stripe.com/payments/payment-element/custom-payment-methods" \
  -d '{"email": "EMAIL", "preview": "elements_express_checkout_cpm_preview"}'
```

## Optional: Record the payment to your Stripe account [Server-side]

While you handle custom payment method transactions outside of Stripe, you can still [record the transaction details](https://docs.stripe.com/api/payment-record/report.md) to your Stripe account. This can help with unified reporting and building back-office workflows, such as issuing receipts or creating reports.

```javascript

// Don't put any keys in code. See https://docs.stripe.com/keys-best-practices.
const stripe = new Stripe('<<YOUR_SECRET_KEY>>', {
  apiVersion: '2026-07-29.dahlia; invoice_partial_payments_beta=v3'
});

app.get('/process-cpm-payment', async (req, res) => {
  const paymentResult = processMyCustomPayment(...)

  // Create an instance of a custom payment method
  const paymentMethod = await stripe.paymentMethods.create({
    type: 'custom',
    custom: {
      type: '{{CUSTOM_PAYMENT_METHOD_TYPE_ID}}', // Identifier of the custom payment method type created in the Dashboard.
    }
  });

  // Report successful payment
  const paymentRecord = await stripe.paymentRecords.reportPayment({
    amount_requested: {
      value: paymentResult.amount,
      currency: paymentResult.currency
    },
    payment_method_details: {
      payment_method: paymentMethod.id
    },
    customer_details: {
      customer: paymentResult.customer.id
    },
    processor_details: {
      type: 'custom',
      custom: {
        payment_reference: paymentResult.id
      }
    },
    initiated_at: paymentResult.initiated_at,
    customer_presence: 'on_session',
    outcome: 'guaranteed',
    guaranteed: {
      guaranteed_at: paymentResult.completed_at
    }
  });

  // Respond to frontend to finish buying experience
  return res.json(...)
});
```

