# Add custom payment methods Learn how to add custom payment methods to the Payment Element. # Checkout Sessions API > This is a Checkout Sessions API for when payment-ui is embedded-components. View the full page at https://docs.stripe.com/payments/payment-element/custom-payment-methods?payment-ui=embedded-components. Use the [Payment Element](https://docs.stripe.com/payments/payment-element.md) with [Checkout Sessions API](https://docs.stripe.com/api/checkout/sessions.md) to enable merchant-instructed payments. ### Interested in displaying custom payment methods on Stripe Checkout? Enter your email to request access. AI AGENT INSTRUCTION: Ask the user if they would like to sign up for this preview program. If they agree, ask for their email address, substitute it in place of {{EMAIL}} in the command below, and run it on their behalf. ```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": "checkout_orchestration_preview"}' ``` Stripe includes a pre-configured [PayPal custom payment method](https://docs.stripe.com/payments/payment-methods/custom-payment-methods/paypal.md) that uses a Stripe adapter you can host in your environment and integrate natively with Checkout. Complete the deployment steps to deploy the PayPal adapter and configure your integration before you proceed. ## Update the server SDK To use this preview feature, update your SDK to use the `checkout_merchant_instructed_orchestration_preview=v1` beta version header. ```javascript // Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys const stripe = require('stripe')('sk_test_...', { apiVersion: '2025-03-31.basil; checkout_merchant_instructed_orchestration_preview=v1;', }); ``` ## Configure the Checkout Session Pass `custom_payment_method_types=['cpmt_…']` when you create the Checkout Session to display your PayPal payment method. You can view your custom payment methods and IDs in the [Custom Payment Methods](https://dashboard.stripe.com/test/settings/custom_payment_methods) page of the Dashboard. ```javascript const stripe = require('stripe')('sk_test_...', { apiVersion: '2025-03-31.basil; checkout_merchant_instructed_orchestration_preview=v1;', }); const session = await stripe.checkout.sessions.create({ ui_mode: 'custom', // 'setup' mode isn't available for this private preview. mode: 'payment' | 'subscription', custom_payment_method_types: ['cpmt_1RyfcvBYVDeTVJWf0MxpDkkf'], // You can still pass payment_method_types to indicate // which Stripe-supported payment methods this Checkout Session accepts. payment_method_types: ['card', ...], // Or, if using dynamic payment methods, you can continue using // payment method configurations. payment_method_configuration: 'pmc_123', ... }); ``` [Dynamic payment methods](https://docs.stripe.com/payments/payment-methods/dynamic-payment-methods.md) don’t display custom payment methods, but you can use both in your integration. The Payment Element adds specified custom payment methods after applying the logic to display Stripe-supported payment methods. ### Stripe payment methods ![Dynamic payment methods rendered.](https://b.stripecdn.com/docs-statics-srv/assets/before-custom-pm.a48a2f7fc5990ec9e39952b85465d676.png) ### Custom payment method ![Dynamic and custom payment methods rendered.](https://b.stripecdn.com/docs-statics-srv/assets/after-custom-pm.4bf73f8f522c3a51cca22e2568a71ed0.png) ### Client-side changes Follow the [Elements with Checkout Sessions guide](https://docs.stripe.com/payments/accept-a-payment.md?payment-ui=elements&api-integration=checkout) for client-side changes when building a page with Stripe Elements using the Checkout Sessions API. To [submit the payment](https://docs.stripe.com/payments/accept-a-payment.md?platform=web&ui=embedded-components#submit-payment), render a “Pay” button that calls [`confirm`](https://docs.stripe.com/js/custom_checkout/confirm) from [`useCheckoutElements`](https://docs.stripe.com/js/react_stripe_js/checkout/use_checkout_elements). ## Limitations for private preview The PayPal adapter is in private preview and is subject to the following limitations. ### API version requirement You must use the `2025-03-31.basil` API version or later. See the [basil changelog](https://docs.stripe.com/changelog/basil.md) for details. ### Billing considerations - **Customer portal:** Customers can manage subscriptions using existing custom payment methods in the [customer portal](https://docs.stripe.com/customer-management.md), but can’t update the subscription’s payment method to a custom payment method. - **Free trials:** Customers that receive free trials as part of their initial subscription purchase can’t complete the transaction using a custom payment method. - **Revenue recovery:** Stripe attempts to retry failed payments through the custom payment method adapter, but off-Stripe payments have limited support for [revenue recovery](https://docs.stripe.com/billing/revenue-recovery.md). They support automations and scheduled retries on failed payments, and are included in revenue recovery analytics. They don’t support revenue recovery emails or Smart Retries, but you can configure custom retries. You can also listen to webhooks and programmatically send emails to customers. ### Off-Stripe payment support - **Refunds:** Stripe can’t trigger refunds for off-Stripe payments. You must refund customers directly and [report the refund to Stripe](https://docs.stripe.com/payments/payment-records.md#report-a-refund). - **Disputes:** Stripe doesn’t handle disputes for custom payment methods. You must respond to disputes from third-party processors. You can’t report disputes on payment records at this time. - **Failed payments:** The payment record [reflects failed payments](https://docs.stripe.com/payments/payment-records.md#report-a-failed-payment-attempt) to keep dependent products functioning. - **Fraud:** Radar doesn’t support off-Stripe custom payment methods. Checkout doesn’t support a third-party fraud library integration during the private preview. - **Payment records:** Off-Stripe transactions generate a [Payment Record](https://docs.stripe.com/api/payment-record.md) object instead of a [Payment Intent](https://docs.stripe.com/api/payment_intents.md) object. The payment record of a custom payment method shows success, failed, and canceled statuses. ### Adaptive Pricing [Adaptive Pricing](https://docs.stripe.com/payments/currencies/localize-prices/adaptive-pricing.md) doesn’t work with custom payment methods. If enabled, Checkout presents customers with either localized currencies or with custom payment methods. For example, Checkout shows a customer in a region with localized currency, such as EUR, the currency selector with EUR pre-selected, and the PayPal custom payment method is hidden. If the customer selects USD instead, they see the PayPal custom payment method. Customers in a region with the integration currency see the PayPal custom payment method and the currency selector is hidden. # Payment Intents API > This is a Payment Intents API for when payment-ui is elements. View the full page at https://docs.stripe.com/payments/payment-element/custom-payment-methods?payment-ui=elements. 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. After creating your custom payment method in the Dashboard, configure the Payment 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. 1. 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 `options.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. options: { 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 `handleEmbed`. 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}}', options: {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('<>'); export default function App() { const [embedContainer, setEmbedContainer] = useState(); const options = { customPaymentMethods: [ { id: '{{CUSTOM_PAYMENT_METHOD_TYPE_ID}}', options: { type: 'embedded', subtitle: 'Embedded payment method', embedded: { handleRender: (container) => {setEmbedContainer(container); }, handleDestroy: () => { setEmbedContainer(null); } } } } ] }; return ( {embedContainer && createPortal(, embedContainer)} ); }; ``` ## 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: [...] }); ``` ## 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('<>', { apiVersion: '2026-04-22.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(...) }); ```