Stripe integration with quasar q-field



  • Hi,

    I am new to quasar and vue and it took me some time to figure out implementing stripe elements with quasar components. I hope it maybe helpful to someone. (it’s not perfect but gives you the basic idea)

    <!-- StripeForm.vue -->
    <template>
      <div id="payment-form">
    
        <div class="q-mt-md q-mb-md text-negative" v-if="submissionError">
          <div id="card-errors" role="alert">{{ submissionError }}</div>
        </div>
    
        <q-field label="Card Number"
                 stack-label
                 class="q-mb-md"
                 :error-message="errors['cardNumber']"
                 :error="!isCardNumberValid">
    
          <template v-slot:control>
            <div class="self-center full-width no-outline">
              <div id="cardNumber" ref="cardNumber"></div>
            </div>
          </template>
    
        </q-field>
    
        <div class="row q-col-gutter-lg">
          <div class="col-6">
            <q-field label="Card Expiry"
                     stack-label
                     class="q-mb-md"
                     :error-message="errors['cardExpiry']"
                     :error="!isCardExpiryValid">
    
              <template v-slot:control>
                <div class="self-center full-width no-outline">
                  <div id="cardExpiry" ref="cardExpiry"></div>
                </div>
              </template>
    
            </q-field>
          </div>
          <div class="col-6">
            <q-field label="Card CVC"
                     stack-label
                     class="q-mb-md"
                     :error-message="errors['cardCvc']"
                     :error="!isCardCvcValid">
    
              <template v-slot:control>
                <div class="self-center full-width no-outline">
                  <div id="cardCvc" ref="cardCvc"></div>
                </div>
              </template>
    
            </q-field>
          </div>
        </div>
    
        <q-btn
          unelevated
          color="accent"
          label="Make Payment"
          :loading="loading"
          @click="submitForm"/>
    
      </div>
    </template>
    
    <script>
    // https://github.com/stripe/stripe-js
    import {loadStripe} from '@stripe/stripe-js/pure';
    
    export default {
      props: {
        data: {type: Object, required: false, default: () => {}}
      },
    
      data() {
        return {
          loading: false,
          stripe: null,
          elements: null,
          card: {
            cardNumber: null,
            cardExpiry: null,
            cardCvc: null
          },
          errors: {
            cardNumber: '',
            cardExpiry: '',
            cardCvc: ''
          },
          submissionError: null
        }
      },
    
      computed: {
        isCardNumberValid() {
          return this.isValid('cardNumber');
        },
        isCardExpiryValid() {
          return this.isValid('cardExpiry');
        },
        isCardCvcValid() {
          return this.isValid('cardCvc');
        }
      },
    
      methods: {
        async submitForm(e) {
          e.preventDefault();
    
          try {
            this.loading = true;
            this.submissionError = null;
            const { token, error } = await this.stripe.createToken(this.card['cardNumber']);
            console.log(error);
            if(error) {
              this.submissionError = error.message;
              this.$emit('failed', error);
            } else {
              this.resetForm();
              this.$emit('success', token);
            }
          } catch (error) {
            this.$emit('failed', error);
          } finally {
            this.loading = false;
          }
        },
    
        resetForm() {
          for (const [elementType, item] of Object.entries(this.card)) {
            this.card[elementType].clear();
          }
        },
    
        updated(e) {
          const elementType = e['elementType'];
          const error = e['error'];
    
          if (error) {
            this.errors[elementType] = e['error']['message'];
            return null;
          } else {
            if (this.errors[elementType]) {
              this.errors[elementType] = '';
            }
          }
        },
    
        isValid(elementType) {
          return this.errors[elementType] === '';
        },
    
        errorMessage(elementType) {
          return this.isValid(elementType) ? this.errors[elementType] : false;
        }
      },
    
      async mounted() {
    
        const style = {
          base: {
            fontFamily: '"Roboto", "-apple-system", "Helvetica Neue", Helvetica, Arial, sans-serif',
            '::placeholder': {
              color: '#CFD7E0',
            },
          },
        };
    
        if (!this.stripe) {
          this.stripe = await loadStripe('STRIPE_PUBLIC_KEY_HERE'); //REPLACE THE KEY
        }
    
        if (!this.elements) {
          const cardElements = ['cardNumber', 'cardExpiry', 'cardCvc']
    
          this.elements = this.stripe.elements();
          cardElements.forEach(element => {
            this.card[element] = this.elements.create(element, {style: style});
            this.card[element].mount('#' + element);
            this.card[element].addEventListener('change', (e) => this.updated(e));
          });
        }
      }
    }
    </script>
    
    <style scoped lang="scss">
    .StripeElement--invalid {
      border-color: transparent
    }
    </style>
    
    

Log in to reply