Tokenization failed - an unknown error occured

I’ve been trying to port over the web-payments-quickstart with card payments to fit into my Vue JS app for a while now and I keep running into the issue that the tokenization fails with an unknown error. I am certain the the Application ID and the Location ID are correct and the sandbox CDN is also imported in a script tag in the index.html file. Has anyone run into the same issue when trying to use Square payments with a JS framework?

Here’s my JS code:

<script>
// import axios from "axios"
import CONST from "../helpers/constants.vue"

export default {
  name: 'payment',
  data() {
    return {
      appId: 'sandbox-sq0idb-bub0FOKgPJgECKJ5OSrZPg',
      locationId: 'LE56BBNQJRGDY',
      payments: null,
      card: null
    }
  },
  async mounted() {
    if (!window.Square) {
          throw new Error('Square.js failed to load properly');
        }
    
    // Initialize Payment
    try {
      this.payments = window.Square.payments(this.appId, this.locationId);
      // console.log(this.payments)
    } catch {
      const statusContainer = document.getElementById(
        'payment-status-container'
      );
      statusContainer.className = 'missing-credentials';
      statusContainer.style.visibility = 'visible';
      return;
    }

    // Initialize Card
    try {
          this.card = await this.initializeCard(this.payments);
        } catch (e) {
          console.error('Initializing Card failed', e);
          return;
        }

    // const cardButton = document.getElementById('card-button');
    // cardButton.addEventListener('click', async function (event) {
    //   await this.handlePaymentMethodSubmission(event, this.card);
    // });
  },
  methods: {
    async initializeCard(payments) {
        const card = await payments.card();
        await card.attach('#card-container');

        return card;
      },

      async createPayment(token) {
        let locationId = this.locationId
        const body = JSON.stringify({
          locationId,
          sourceId: token,
        });

        let url = CONST.URLS.ReservationUrls.CreatePayment(token,this.reservationCode);
          const paymentResponse = await fetch(url, {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body,
          });

        if (paymentResponse.ok) {
          return paymentResponse.json();
        }

        const errorBody = await paymentResponse.text();
        throw new Error(errorBody);
      },

      async tokenize(paymentMethod) {
        const tokenResult = await paymentMethod.tokenize();
        
        if (tokenResult.status === 'OK') {
          return tokenResult.token;
        } else {
          let errorMessage = `Tokenization failed with status: ${tokenResult.status}`;
          if (tokenResult.errors) {
            errorMessage += ` and errors: ${JSON.stringify(
              tokenResult.errors
            )}`;
          }
          throw new Error(tokenResult);
        }
      },

      displayPaymentResults(status) {
        const statusContainer = document.getElementById(
          'payment-status-container'
        );
        if (status === 'SUCCESS') {
          statusContainer.classList.remove('is-failure');
          statusContainer.classList.add('is-success');
        } else {
          statusContainer.classList.remove('is-success');
          statusContainer.classList.add('is-failure');
        }

        statusContainer.style.visibility = 'visible';
      },

      async handlePaymentMethodSubmission(paymentMethod) {
          // event.preventDefault();
          // console.log(paymentMethod)
          const cardButton = document.getElementById('card-button');
          try {
            // disable the submit button as we await tokenization and make a payment request.
            cardButton.disabled = true;
            const token = await this.tokenize(paymentMethod);
            const paymentResults = await createPayment(token);
            this.displayPaymentResults('SUCCESS');

            console.debug('Payment Success', paymentResults);
          } catch (e) {
            cardButton.disabled = false;
            this.displayPaymentResults('FAILURE');
            console.error(e.message);
          }
        }
  }
}
</script>

And here is the corresponding HTML:

<template>
  <div>
    <form id="payment-form" @submit.prevent="this.handlePaymentMethodSubmission(this.card)">
        <div id="card-container"></div>
        <button id="card-button" type="submit">Pay $1.00</button>
      </form>
      <div id="payment-status-container"></div>
  </div>
</template>
1 Like

I don’t see the path to our JS library https://sandbox.web.squarecdn.com/v1/square.js. Did you Add an empty <script> tag to the <head> of index.html after <script type="text/javascript" src="https://sandbox.web.squarecdn.com/v1/square.js"></script> ? :slightly_smiling_face:

Hey Brian,

the JS library https://sandbox.web.squarecdn.com/v1/square.js is included in the index.html file which I did not provide the code from. I am certain that it is imported correctly, as the line if (!window.Square) { throw new Error('Square.js failed to load properly'); } does not throw an error.

So the form is initializing and it’s just on tokenize that you’re getting an unknown error? :slightly_smiling_face:

Yes that’s exactly the problem I’m running into.

Do you have steps to reproduce this or is the page live? :slightly_smiling_face:

@gabeha did you manage to get anywhere with this issue? We have users encountering the same error sporadically.

@Bryan-Square I’ve also reported this previously but it’s not something that’s easily reproducible.

Yes I had based my code on the Github web-payments-quickstart which - in my opinion - over complicated things by abstracting too much into separate functions. Because the examples are written in plain JS, translating this into the life cycle hooks of a framework such as Vue, I must’ve encountered the issue of some promises from API calls not yet being fulfilled. I therefore dug into the Docs (https://developer.squareup.com/docs/web-payments/take-card-payment) and adjusted my code:

async mounted() {
    if (!window.Square) {
          throw new Error('Square.js failed to load properly');
        }
    
        const payments = Square.payments(CONST.SQUARE.APPLICATION_ID, CONST.SQUARE.LOCATION_ID);
        const card = await payments.card();
        const reservationCode = this.$store.getters.getReservationCode
        // console.log(`Reservation code is ${reservationCode}`)
        await card.attach('#card-container');
        const cardButton  = document.getElementById('card-button');
        cardButton.addEventListener('click', async (event) => {
          event.preventDefault();
          try {
            cardButton.disabled = true;
            const result = await card.tokenize();
            if (result.status === 'OK') {
              console.log(`Payment token is ${result.token}`);
              const paymentResults = await this.createPayment(result.token, reservationCode);
              this.displayPaymentResults('SUCCESS');
              console.debug('Payment Success', paymentResults);
            }
            } catch (e) {
              cardButton.disabled = false;
              this.displayPaymentResults('FAILURE');
              console.error(e);
            }
        });
  },

The code above is executed when this component is mounted into the DOM, i.e. when the payment component is loaded. It only refers to two separately defined methods (createPayment() and displayPaymentResult()) which are pretty much copy&pasted from the Docs.

I hope this helped :slight_smile:

1 Like

Thanks for the insights. Do you recall if your issue was easily reproducible?

My logic seems very similar to yours and 99% of the time the token is generated successfully.

Perhaps I’m receiving the same error but for a different reason.

Hm, I have not encountered the tokenization working only sometimes. For me, it didn’t work at all, until I changed up the code, at which point, it always worked.