Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mounting to an element in ShadowDOM throws error on confirmCardPayment #143

Closed
jorenbroekema opened this issue Jan 30, 2021 · 4 comments
Closed

Comments

@jorenbroekema
Copy link

jorenbroekema commented Jan 30, 2021

Summary

Mounting the card element to some place in a shadowRoot works just fine. Doing a confirmCardPayment then throws an error asking if the card element is still mounted. I'm assuming this is due to poor shadow DOM support. Again, an assumption, but there's probably a query selector on your end somewhere searching for the mounted Stripe card iframe or the private input inside it? Normal query selectors do not penetrate shadow DOM :\ but the global Stripe instance probably should be able to do this if it wants to scrape for a mounted Stripe element.

import { loadStripe } from '@stripe/stripe-js';

class MyCustomElement extends HTMLElement {
  connectedCallback() {
    this.attachShadow({ mode: 'open' });
    this.init();
  }
  
  render() {
    this.shadowRoot.innerHTML = `
      <div class="sr-main">
        <form id="payment-form" class="sr-payment-form">
          <div class="sr-combo-inputs-row">
            <div class="sr-input sr-card-element" id="card-element"></div>
          </div>
          <div class="sr-field-error" id="card-errors" role="alert"></div>
          <button id="submit">
            <div class="spinner hidden" id="spinner"></div>
            <span id="button-text">Pay</span><span id="order-amount"></span>
          </button>
        </form>
        <div class="sr-result hidden">
          <p>Payment completed<br /></p>
          <pre>
            <code></code>
          </pre>
        </div>
      </div>
    `;
  }

  async init(data) {
    this.render();
    this.stripe = await loadStripe(data.publishableKey);
    const elements = this.stripe.elements();
    this.card = elements.create('card');
    // This works just fine
    this.card.mount(this.shadowRoot.getElementById('card-element'));
    this.clientSecret = data.clientSecret;
    this.getElementById('payment-form').addEventListener('submit', (ev) => {
      ev.preventDefault();
      this.pay();
    });
  }

  pay() {
    // Error , asking if it is still mounted..
    this.stripe
      .confirmCardPayment(this.clientSecret, {
        payment_method: {
          card: this.card,
          billing_details: {
            name: 'Jenny Rosen',
          },
        },
      })
      .then((result) => {
        if (result.error) {
          // Show error to your customer
          this.showError(result.error.message);
        } else {
          // The payment has been processed!
          this.orderComplete(this.stripe, this.clientSecret);
        }
      });
  }
}

The reason I want shadow DOM support is because I want to create a custom element for my Stripe payment form. Then I can encapsulate my styles in this component and not risk styles leaking inward/outward relative to my application.

Other information

Happens in latest Google Chrome (w10)

@hofman-stripe
Copy link
Contributor

Unfortunately it's not as simple as updating some "query selectors". There is a fundamental incompatibility between the window.frames collection and Shadow Roots See whatwg/html@7b56772 and WICG/webcomponents#145. Unfortunately, Stripe.js currently relies on being able to reach every iframe through this method.

We do have some ideas on how to work around this limitation, but it would require a complete overall of our iframe communication. In the mean time, you can work around this yourself by using slots to "punch holes" through the shadow tree, and keep the Elements in the light DOM. @bennypowers wrote the clever stripe-elements library that does this automatically.

Btw, the Element mounted in the Shadow DOM is actually not functional at all. Not being able to use it with confirmCardPayment or other Stripe.js methods is only one of the issues. I'll try to see if we can better detect this case and error earlier.

@jorenbroekema
Copy link
Author

Aaah I see, yea that makes it more complicated than I anticipated.

I'll go with the slots workaround, that's definitely workable for my use case. Also hey Benny, I totally forgot you wrote that lib 👍 will check it out

@hofman-stripe
Copy link
Contributor

FYI, we just added some detection to report an error when mounting an Element in the Shadow DOM. It should prevent false assumptions that the element is functional.

@bennypowers
Copy link

FYI, we just added some detection to report an error when mounting an Element in the Shadow DOM. It should prevent false assumptions that the element is functional.

I just did a quick test on https://bennypowers.dev/stripe-elements/?path=/docs/fallback-to-stripe-elements--enter-a-publishable-key and it looks like <stripe-elements> still works. Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants