Payment Element: Save a Payment Method

⭐️ Minimal reference integration: A Payment Element which uses a Customer Session and Setup Intent to save a Payment Method for future use

⚠️ Note: This integration is not production ready and should only be used as a reference.

⚙️ Integration

Press the "Prepare to Save" button below to do the following:


  

🧑‍💻 Code

<?php

$stripeConfiguration = [
  'api_key' => 'sk_•••',
  'stripe_version' => '2023-10-16',
];

$stripe = new \Stripe\StripeClient($stripeConfiguration);

if (isset($_GET['prepareToSave'])) {
  $customer = $stripe->customers->create([
    'description' => 'Save Payment Method Example Customer',
  ]);
  
  $customerSession = $stripe->customerSessions->create([
    'customer' => $customer->id,
    'components' => [
      'payment_element' => [
        'enabled' => true,
        'features' => [
          'payment_method_save' => 'enabled',
          'payment_method_save_usage' => 'off_session',
          // We only want to save a new Payment Method, not display or manage existing ones
          'payment_method_redisplay' => 'disabled',
          'payment_method_remove' => 'disabled',
        ],
      ],
    ],
  ]);
  
  $setupIntent = $stripe->setupIntents->create([
    'customer' => $customer->id,
  ]);
  
  header('Content-Type: application/json');
  echo json_encode([
    'setupIntentClientSecret' => $setupIntent->client_secret,
    'customerSessionClientSecret' => $customerSession->client_secret,
  ]);
  
  exit;
}

if (isset($_GET['showFullSetupIntent'])) {
  $setupIntent = $stripe->setupIntents->retrieve($_GET['showFullSetupIntent'], [
    'expand' => [
      'customer',
      'payment_method',
    ],
  ]);
  
  header('Content-Type: application/json');
  echo $setupIntent->toJSON();
  
  exit;
}
<script src="https://js.stripe.com/v3/"></script>

<section id="paymentMethodSaved" hidden>
  <p>Payment Method saved!</p>
  
  <p class="reuseLink"><a href="">View it in action on the Payment Element: Reuse a Payment Method test integration!</a></p>
</section>

<section id="prepareToSaveSection">
  <p>Press the "Prepare to Save" button below to do the following:</p>
  
  <ul>
    <li>Create a <a href="https://docs.stripe.com/api/customers">Customer</a></li>
    <li>Create a <a href="https://docs.stripe.com/api/customer_sessions">Customer Session</a></li>
    <li>Create a <a href="https://docs.stripe.com/api/setup_intents">Setup Intent</a></li>
    <li><a href="https://docs.stripe.com/js/elements_object/create">Initialize Stripe Elements</a> with the Customer Session's and Setup Intent's client secrets</li>
    <li>Create and mount a <a href="https://docs.stripe.com/js/element/payment_element">Payment Element</a> to collect and save a <a href="https://docs.stripe.com/api/payment_methods">Payment Method</a> for future use</li>
  </ul>
  
  <p><button type="button" id="prepareToSave">Prepare to Save</button></p>
</section>

<section id="savePaymentMethodSection" hidden>
  <p class="loading">Loading...</p>
  
  <p id="payment-element"></p>
  
  <p><button type="button" id="savePaymentMethod">Save Payment Method</button></p>
</section>

<pre id="output"></pre>
#payment-element {
  /* Provides a visual outline of the Element */
  border: solid 1px;
  /* Required for dark mode legibility */
  background-color: white;
  /* Aesthetics */
  padding: 1rem;
  border-radius: .25rem;
}
const paymentMethodSavedSection = document.querySelector('#paymentMethodSaved');
const paymentMethodSavedReuseLink = document.querySelector('#paymentMethodSaved .reuseLink a');

const prepareToSaveSection = document.querySelector('#prepareToSaveSection');
const prepareToSaveButton = document.querySelector('#prepareToSave');

const savePaymentMethodSection = document.querySelector('#savePaymentMethodSection');
const savePaymentMethodLoadingElement = document.querySelector('#savePaymentMethodSection .loading');
const savePaymentMethodButton = document.querySelector('#savePaymentMethod');

const outputElement = document.querySelector('#output');

const stripe = Stripe('pk_test_51O2hxMC4JnNRtz8VToJJbGHrFTPPr6TkP09h7ql3YJaqpNcxoSNxtk38glyzi9VrZKStns858YynOO2ZyGmU7VRi00CIUWuUdk');

prepareToSaveButton.addEventListener('click', (event) => {
  prepareToSaveSection.hidden = true;
  prepareToSave();
});

async function prepareToSave() {
  savePaymentMethodSection.hidden = false;
    
  const result = await fetch('?prepareToSave');
  
  const response = await result.json();
  
  const elements = stripe.elements({
    clientSecret: response.setupIntentClientSecret,
    customerSessionClientSecret: response.customerSessionClientSecret,
  });
  
  const paymentElement = elements.create('payment');
  
  paymentElement.mount('#payment-element');
  
  savePaymentMethodButton.addEventListener('click', async (event) => {
    outputElement.innerHTML = 'Processing...';
    
    const {error} = await stripe.confirmSetup({
      elements: elements,
      confirmParams: {
        return_url: window.location.href,
      }
    });
      
    if (error) {
      outputElement.textContent = 'Error: Unable to confirm payment: ' + error.message;
      console.log({error});
    }
  });
  
  savePaymentMethodLoadingElement.hidden = true;
}

// If the URL contains a Setup Intent's client secret, retrieve the Setup Intent and display it
const urlSearchParams = new URLSearchParams(window.location.search);

if (urlSearchParams.has('setup_intent_client_secret')) {
  (async () => {
    output.textContent = "Loading...";
    
    const setupIntentID = urlSearchParams.get('setup_intent');
    
    const result = await fetch('?showFullSetupIntent=' + setupIntentID);
    
    const response = await result.json();
    
    paymentMethodSavedReuseLink.href = '/test/payment-element-reuse?customer=' + response.customer.id;
    
    paymentMethodSavedSection.hidden = false;
    
    output.textContent = "The Setup Intent from the URL, retrieved with a secret key on the server with customer and payment_method expanded:\n\n" + JSON.stringify(response, null, 2);
  })();
}