Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

GrahamMorbyDev's avatar

Stripe Elements Mounted vs Method

Hey Guys

I have a mounted Stripe element that Im loading on the mounted() hook

 // Create a Stripe client.
    const stripe = Stripe("secret");

    // Create an instance of Elements.
    const elements = stripe.elements();
    // Create an instance of the card Element.
    const style = {
      base: {
        color: "#32325d",
        fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
        fontSmoothing: "antialiased",
        fontSize: "16px",
        "::placeholder": {
          color: "#aab7c4"
        }
      },
      invalid: {
        color: "#fa755a",
        iconColor: "#fa755a"
      }
    };

    let card = elements.create("card", { style: style });
    // Add an instance of the card Element into the `card-element` <div>.
    card.mount("#card-element");

    // Handle real-time validation errors from the card Element.
    card.addEventListener("change", function(event) {
      var displayError = document.getElementById("card-errors");
      if (event.error) {
        displayError.textContent = event.error.message;
      } else {
        displayError.textContent = "";
      }
    });

Standard one from the docs

I then roll a method to do a payment intent and handle the card payment

So my issue is that the below code doesn't seem to be aware of the stripe instance

 let card = Element;
          if (selectedCard) {
            card = {
              payment_method: selectedCard
            };
          }

          //var stripe = Stripe("secret");
          stripe.handleCardPayment(response.data.data.client_secret, card).then(function(result) {
                console.log(result)
            });

Is there a way to have the whole page be aware of that mounted instance, I have tried adding them to data points to no avail

Thanks

0 likes
6 replies
martinbean's avatar
Level 80

@grahammorbydev Hey, I’ve been in the same boat as you! The default Stripe JavaScript SDK isn’t great to work with in that it’s asynchronous and not an NPM module you can install and import… unless React devs /sideeyes

How I’ve approached this in my own apps is to have a StripeCardElement element component that other components can use and interact with. The card element component is pretty “dumb”—all it does is initialise Stripe and the element:

<!-- resources/js/components/StripeCardElement.vue -->
<template>
  <div>
    <div id="card-element" ref="cardElement" />
    <div id="card-error" role="alert" if="error" v-text="error" />
  </div>
</template>

<script>
export default {
  created() {
    this.stripe = window.Stripe(this.publishableKey);

    this.cardElement = this.stripe.elements().create('card');
  },
  data() {
    return {
      cardElement: null,
      stripe: null
    };
  },
  mounted() {
    this.cardElement.mount(this.$refs.cardElement);
  },
  props: {
    error: {
      default: null,
      required: false,
      type: String
    },
    publishableKey: {
      required: true,
      type: String,
      validator: (key) => key.startsWith('pk_')
    }
  }
}
</script>

When the component is created, it initialises Stripe, and then when mounted actually mounts the card element.

So, in other components (say, a checkout component), you can import this component, give it a reference, and interact with it. Here’s how I do that:

<!-- resources/js/components/SimpleCheckout.vue -->
<template>
  <form method="POST" v-on:submit.prevent="onSubmit">
    <fieldset>
      <div class="form-group">
        <label>Cardholder name</label>
        <input type="text" name="cardholder_name" v-model.trim="cardholderName" autocomplete="cc-name" required />
      </div>
      <div class="form-group">
        <stripe-card-element
          ref="stripeCardElement"
          :error="cardError"
          :publishable-key="stripePublishableKey"
        />
      </div>
    </fieldset>
  </form>
</template>

<script>
import StripeCardElement from './StripeCardElement.vue';

export default {
  components: {
    StripeCardElement
  },
  data() {
    return {
      cardError: null,
      cardholderName: ''
    };
  },
  methods: {
    onSubmit() {
      // Interact with Stripe card element here
      const data = {
        payment_method: {
          billing_details: {
            name: this.cardholderName
          }
        }
      };

      this.$refs.stripeCardElement.confirmCardPayment(clientSecret, data).then((paymentIntent) => {
        console.log(paymentIntent);

        // Check PaymentIntent was successful
        // Show success message if it was
      }).catch((error) => {
        console.error(error);

        this.cardError = error.message;
      });
    }
  },
  props: {
    stripePublishableKey: {
      required: true,
      type: String
    }
  }
}
</script>

You may have noticed my confirmCardPayment intent looks a little different to the one in the Stripe documentation. That’s because I proxy it in the StripeCardElement component:

methods: {
  confirmCardPayment(clientSecret, data) {
    // Payment method can be either object or string (ID of existing payment method)
    // If is object, set reference to card element
    if (data.hasOwnProperty('payment_method') && typeof data.payment_method === 'object') {
      data.payment_method.card = this.cardElement;
    }

    return new Promise((resolve, reject) => {
      // Call the actual stripe.confirmCardPayment method
      // Returns a Promise that either:
      //  a. resolves with PaymentIntent
      //  b. rejects with error
      // Improvement on Stripe SDK, which just resolves and you have to check yourself
      // if result was error or successful
      this.stripe.confirmCardPayment(clientSecret, data).then((result) => {
        result.error ? reject(result.error) : resolve(result.paymentIntent);
      });
    });
  }
}

And that‘s pretty much it!

I appreciate this is a lot of code and a lot to get your head around, so if you do have any questions about the above or working with Stripe in Vue components, let me know and I’ll be happy to help!

GrahamMorbyDev's avatar

I think it makes sense

mounted() {
    this.cardElement.mount(this.$refs.cardElement);
  },

This Im guessing is the reference point and the parent picks this up here

this.$refs.stripeCardElement.confirmCardPayment

Am I on the right track??

GrahamMorbyDev's avatar

@martinbean

My Stripe component is as follows

<template>
  <div>
    <label for="card-element">Credit or debit card</label>
    <div id="card-element" ref="cardElement">
      <!-- A Stripe Element will be inserted here. -->
    </div>

    <!-- Used to display form errors. -->
    <div id="card-errors" role="alert"></div>
    <button class="btn btn-primary mt-3" v-on:click="triggerPay()">Pay £{{amountprop / 100}}</button>
  </div>
</template>
<script>
export default {
  components: {},
  props: ["amountprop"],
  data() {
    return {
      cardElement: null,
      stripe: null,
      stripe_pk: "secret",
      test: process.env.STRIPE_PK
    };
  },
  mounted() {
    // Create a Stripe client.
    const stripe = window.Stripe(this.stripe_pk);
    // Create an instance of Elements.
    var elements = stripe.elements();
    // Create an instance of the card Element.
    var style = {
      base: {
        color: "#32325d",
        fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
        fontSmoothing: "antialiased",
        fontSize: "16px",
        "::placeholder": {
          color: "#aab7c4"
        }
      },
      invalid: {
        color: "#fa755a",
        iconColor: "#fa755a"
      }
    };
    var card = elements.create("card", { style: style });

    // Handle real-time validation errors from the card Element.
    card.addEventListener("change", function(event) {
      var displayError = document.getElementById("card-errors");
      if (event.error) {
        displayError.textContent = event.error.message;
      } else {
        displayError.textContent = "";
      }
    });
    // Add an instance of the card Element into the `card-element` <div>.
    card.mount(this.$refs.cardElement);
  },
  created() {},
  computed: {},
  methods: {
    triggerPay() {
      this.$emit("triggerPay");
    },
    cardPayment(client_secret, data) {
        console.log(client_secret);
        console.log(data);
        Stripe.handleCardPayment(client_secret, data).then(function(result) {
            console.log(result)
        });
    }
  },
  watch: {}
};
</script>
<style>
</style>

Then the pay button fires an event and triggers the method

Which contains

this.$refs.stripeCardElement
            .cardPayment(response.data.data.client_secret, card)

So I feel like I'm back in the same place as the cardPayment() method isn't aware of the mounted stuff

Also to clarify I'm loading stripe as a JS Script in the head element

martinbean's avatar

@grahammorbydev You don’t call handleCardPayment on the global Stripe object; you call it on the instance after you’ve instantiated the SDK.

That’s why in my example, I assign it to a data prop using this.stripe, so I can then call methods on that instance elsewhere in my component.

martinbean's avatar

@grahammorbydev No problem! Glad I was able to help :)

Like I say, feel free to let me know if you have any more questions about working with Stripe or Vue. Have worked with both for years so know my way around them.

Please or to participate in this conversation.