Payment Element: Reuse a Payment Method

⭐️ Minimal reference integration: A Payment Element which uses a Customer Session to display saved Payment Methods

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

⚙️ Integration

Loading...


      

🧑‍💻 Code

<?php

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

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

if (isset($_GET['createDemoCustomer'])) {
  $customer = $stripe->customers->create([
    'description' => 'Reuse Payment Method Demo Customer',
  ]);
  
  // This would normally be confirmed client-side so the customer could handle any potential next actions
  $setupIntent = $stripe->setupIntents->create([
    'customer' => $customer->id,
    'payment_method' => 'pm_card_visa',
    'confirm' => true,
    'automatic_payment_methods' => [
      'enabled' => true,
      'allow_redirects' => 'never',
    ],
  ]);
  
  // This would normally be set automatically when the customer checks the save box in the Payment Element
  $paymentMethod = $stripe->paymentMethods->update($setupIntent->payment_method, [
    'allow_redisplay' => 'always',
  ]);
  
  header('Content-Type: application/json');
  echo json_encode([
    'customerID' => $customer->id,
  ]);
  
  exit;
}
else if (isset($_GET['prepareToPay'])) {
  $customerID = $_GET['prepareToPay'];
  
  $customerSession = $stripe->customerSessions->create([
    'customer' => $customerID,
    'components' => [
      'payment_element' => [
        'enabled' => true,
        'features' => [
          'payment_method_save' => 'enabled',
          'payment_method_save_usage' => 'off_session',
          'payment_method_redisplay' => 'enabled',
          'payment_method_remove' => 'enabled',
        ],
      ],
    ],
  ]);
  
  $paymentIntent = $stripe->paymentIntents->create([
    'customer' => $customerID,
    'amount' => 4200,
    'currency' => 'usd',
  ]);
  
  header('Content-Type: application/json');
  echo json_encode([
    'paymentIntentClientSecret' => $paymentIntent->client_secret,
    'customerSessionClientSecret' => $customerSession->client_secret,
  ]);
  
  exit;
}
<script src="https://js.stripe.com/v3/"></script>

<p id="loading">Loading...</p>

<section id="demoCustomer" hidden>
  <p>Use the <a href="/test/payment-element-save/">Payment Element: Save a Payment Method</a> test integration to save an arbitrary Payment Method to reuse here, or press the "Create a Demo Customer" button below to do the following:</p>
  
  <ul>
    <li>Create a demo <a href="https://docs.stripe.com/api/customers">Customer</a></li>
    <li>Create and confirm a <a href="https://docs.stripe.com/api/setup_intents">Setup Intent</a> to attach <code>pm_card_visa</code> to the demo Customer</li>
    <li>Update the Payment Method's <a href="https://docs.stripe.com/api/payment_methods/update#update_payment_method-allow_redisplay"><code>allow_redisplay</code> property</a> to <code>true</code></li>
    <li>Reload this page with the Customer's ID in a query parameter so the <a href="https://docs.stripe.com/js/element/payment_element">Payment Element</a> can be loaded with a <a href="https://docs.stripe.com/api/customer_sessions">Customer Session</a> set to display the Customer's saved Payment Method</li>
  </ul>
  
  <p><button type="button" id="createDemoCustomer">Create Demo Customer</button></p>
</section>

<section id="paymentForm" hidden>
  <p class="customerInfo"></p>
  
  <p id="payment-element"></p>
  
  <p><button type="button" id="pay">Pay</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 thisPath = '/test/payment-element-reuse';

const loadingElement = document.querySelector('#loading');

const demoCustomerSection = document.querySelector('#demoCustomer');
const createDemoCustomerButton = document.querySelector('#createDemoCustomer');
createDemoCustomerButton.addEventListener('click', createDemoCustomer);

const paymentFormSection = document.querySelector('#paymentForm');
const paymentFormCustomerInfoElement = document.querySelector('#paymentForm .customerInfo');
const payButton = document.querySelector('#pay');

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

const urlSearchParams = new URLSearchParams(window.location.search);

const stripe = Stripe('pk_test_51O2hxMC4JnNRtz8VToJJbGHrFTPPr6TkP09h7ql3YJaqpNcxoSNxtk38glyzi9VrZKStns858YynOO2ZyGmU7VRi00CIUWuUdk');

async function createDemoCustomer() {
  const result = await fetch('?createDemoCustomer');
  
  const response = await result.json();
  
  window.location.href = thisPath + '?customer=' + response.customerID;
}

async function prepareToPay() {
  const customerID = urlSearchParams.get('customer');
  
  const result = await fetch('?prepareToPay=' + customerID);
  
  const response = await result.json();
  
  paymentFormCustomerInfoElement.innerHTML = 'Customer Session created for <code>' + customerID + '</code><br><small><a href="/test/payment-element-reuse/">Stop using this Customer</a></small>';
  
  const elements = stripe.elements({
    clientSecret: response.paymentIntentClientSecret,
    customerSessionClientSecret: response.customerSessionClientSecret,
  });
  
  const paymentElement = elements.create('payment');
  
  paymentElement.mount('#payment-element');
  
  payButton.addEventListener('click', async (event) => {
    outputElement.innerHTML = 'Processing...';
    
    const {error} = await stripe.confirmPayment({
      elements: elements,
      confirmParams: {
        return_url: window.location.href,
      }
    });
      
    if (error) {
      outputElement.textContent = 'Error: Unable to confirm payment: ' + error.message;
      console.log({error});
    }
  });
  
  loadingElement.hidden = true;
}

// If there's no Customer ID, display the form to create a demo Customer
if (!urlSearchParams.has('customer')) {
  demoCustomerSection.hidden = false;
  loadingElement.hidden = true;
}
// If there is a Customer ID, initialize and display the Payment Element
else {
  paymentFormSection.hidden = false;
  prepareToPay();
}

// If the URL contains a Payment Intent's client secret, retrieve the Setup Intent and display it
if (urlSearchParams.has('payment_intent_client_secret')) {
  stripe.retrievePaymentIntent(urlSearchParams.get('payment_intent_client_secret')).then(result => {
    if (result.error) {
      output.textContent = "Unable to retrieve Payment Intent using client secret in URL:\n\n" + JSON.stringify(result.error, null, 2);
      return;
    }
    
    output.textContent = "Payment Intent retrieved using client secret in URL:\n\n" + JSON.stringify(result.paymentIntent, null, 2);
  });
}