Paypal SDK Unloaded problem

General information

  • SDK version: 3.76.4 (also tried 3.85.2)
  • Environment: Both Sandbox and Production
  • Browser: Safari, Firefox

Issue description

Paypal button is not loading on Safari (this is the first browser that we observe the issue). We have implemented everything according to the documents, however it is not loading for some browsers (eg: loading for Chrome successfully). Scripts are also added under nuxt.config.js to make sure they are loading.\

\

Implemented PaypalBrainTreeButton in vuejs as seen below:

<template>
  <div>
    <div v-show="isNewPaymentFeatureEnabled()">
      <div
        v-show="isButtonLoading || !isPaymentOptionsLoaded"
        class="skeleton-loading-info loading t-h-11 fix-right"
      />
      <div class="t-relative t-z-0">
        <div
          v-show="!isButtonLoading && isPaymentOptionsLoaded"
          id="paypal-button"
        ></div>
      </div>
    </div>
    <div v-show="!isNewPaymentFeatureEnabled()">
      <div
        v-show="isButtonLoading"
        class="skeleton-loading-info loading t-h-11 fix-right"
      />
      <div class="t-relative t-z-0">
        <div v-show="!isButtonLoading" id="paypal-button"></div>
      </div>
    </div>
    <div>
      <v-overlay
        :value="$store.getters.spinnerDisabled"
        color="#FFFFFF"
        opacity="0.7"
      >
        <img
          src="../assets/image/modanisa-flower.png"
          alt="Modanisa logo for Paypal Button"
          class="t-h-20 t-w-20 rotate linear infinite t-m-auto t-block"
        />
      </v-overlay>
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'
import { mapGetters } from 'vuex'
import utils from '~/utils/utils'

declare global {
  interface Paypal {
    client: any
    paypalCheckout: any
    FUNDING: any
    Buttons: any
  }
  interface Braintree {
    paypalCheckout: any
    client: any
  }
  interface Window {
    paypal: Paypal
    braintree: Braintree
  }
}

@Component({
  computed: {
    ...mapGetters({
      isFeatureEnabled: 'isFeatureEnabled',
      isFeatureFlagsLoaded: 'isFeatureFlagsLoaded'
    })
  }
})
export default class PaypalBraintreeButton extends Vue {
  private currency: string =
    this.$store.getters[`${this.getActiveStoreModuleName()}/currency`]

  private totalPrice: string =
    this.$store.getters[`${this.getActiveStoreModuleName()}/totalPrice`]

  private authorizationToken =
    this.$store.getters['payment/selectedPaymentOption']?.token?.braintree

  private paypalClientId = utils.getPaypalClientId()

  private locale = this.$store.getters.checkoutParams.language.startsWith('AR')
    ? 'ar_SA'
    : 'en_US'

  private paymentSdkScript: any = {
    scripts: [
      {
        url: 'https://js.braintreegateway.com/web/3.76.4/js/client.min.js',
        name: 'braintree-client'
      },
      {
        url: 'https://js.braintreegateway.com/web/3.76.4/js/paypal-checkout.min.js',
        name: 'braintree-paypal-checkout'
      }
    ]
  }

  beforeMount(): void {
    this.setupPayPal()
    this.$store.dispatch('payment/buttonLoadStarted')
  }

  loadAllScripts(paymentSdkScript: any) {
    const promises: Array<Promise<any>> = []
    paymentSdkScript.scripts.forEach((script: any) => {
      if (document.getElementById(script.name) !== null) {
        return
      }
      promises.push(this.loadScript(script.url, script.name))
    })
  }

  getActiveStoreModuleName() {
    return this.$store.getters.pageParams.checkoutVersion.toLowerCase() === 'v2'
      ? 'checkoutV2'
      : 'checkout'
  }

  loadScript(url: string, name: string) {
    return new Promise(function (resolve, reject) {
      const script = document.createElement('script')
      script.src = url
      script.async = false
      script.setAttribute('id', name)
      script.onload = function () {
        resolve(url)
      }
      script.onerror = function () {
        reject(url)
      }
      document.body.appendChild(script)
    })
  }

  isNewPaymentFeatureEnabled() {
    if (this.$store.getters.isFeatureFlagsLoaded) {
      return this.$store.getters.isFeatureEnabled('new_payment_options')
    } else {
      return false
    }
  }

  get isButtonLoading() {
    return this.$store.getters['payment/spinnerEnabled']
  }

  get isPaymentOptionsLoaded() {
    return this.$store.getters.isPaymentOptionsLoaded
  }

  // loadPayPalSDK(onReady: any) {
  //   const paypalSDK = document.createElement('script')
  //   paypalSDK.async = false
  //   paypalSDK.setAttribute(
  //     'src',
  //     `https://www.paypal.com/sdk/js?components=buttons&client-id=${this.paypalClientId}&currency=${this.currency}&intent=capture&locale=${this.locale}`
  //   )
  //   paypalSDK.addEventListener('load', onReady)
  //   document.head.appendChild(paypalSDK)
  // }

  setupPayPal() {
    const placeOrder = (payload: any): void => {
      this.$store.dispatch('spinnerDisabled', { isValid: true })
      const callback = this.$store.getters.placeOrderCallback
      if (callback) {
        callback(this.$store.getters['payment/selectedPaymentOption'], payload)
      }
    }

    const validate = (): boolean => {
      const callback = this.$store.getters.placeOrderCallback
      if (callback) {
        return callback(null)
      }
      return false
    }

    const braintree = window.braintree
    if (braintree === undefined || braintree.client == null) {
      return this.loadAllScripts(this.paymentSdkScript)
    }
    const currency = this.currency
    const amount = this.totalPrice
    const store = this.$store
    const locale = this.$store.getters.checkoutParams.language.startsWith('AR')
      ? 'ar_SA'
      : 'en_US'
    const clientID = this.paypalClientId

    // Create a client.
    braintree.client
      .create({
        authorization: this.authorizationToken
      })
      .then(function (clientInstance: any) {
        // Create a PayPal Checkout component.
        return braintree.paypalCheckout.create({
          client: clientInstance
        })
      })
      .then(function (paypalCheckoutInstance: any) {
        return paypalCheckoutInstance.loadPayPalSDK({
          'client-id': clientID,
          currency,
          intent: 'capture',
          locale
        })
      })
      .then(function (paypalCheckoutInstance: any) {
        let intentId = ''

        return window.paypal
          .Buttons({
            style: {
              layout: 'horizontal',
              size: 'responsive',
              label: 'checkout',
              tagline: 'false',
              color: 'blue',
              shape: 'rect'
            },

            fundingSource: window.paypal.FUNDING.PAYPAL,
            onClick() {
              return validate()
            },
            createOrder() {
              const createPaymentIntent =
                store.getters['payment/createPaymentIntent']
              return createPaymentIntent().then((paymentIntentId: string) => {
                intentId = paymentIntentId
                return paypalCheckoutInstance.createPayment({
                  flow: 'checkout',
                  amount,
                  currency,
                  intent: 'capture'
                })
              })
            },

            onApprove(data: any) {
              return paypalCheckoutInstance
                .tokenizePayment(data)
                .then((payload: any) => {
                  placeOrder({ nonce: payload.nonce, intentId })
                })
            },

            onCancel() {
              // TODO failure payment call
            },

            onError(err: any) {
              // TODO failure payment call
              // eslint-disable-next-line no-console
              console.error('PayPal error', err)
            }
          })
          .render('#paypal-button')
      })
      .then(function () {
        store.dispatch('payment/buttonLoadCompleted')
      })
  }
}
</script>

<style scoped>
@media screen and (max-width: 400px) {
  #paypal-button-container {
    width: 100%;
  }
}

@media screen and (min-width: 400px) {
  #paypal-button-container {
    width: 250px;
  }
}

.rotate {
  animation: rotation 4s;
}

.linear {
  animation-timing-function: linear;
}

.infinite {
  animation-iteration-count: infinite;
}

@keyframes rotation {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(359deg);
  }
}
</style>

nuxt.config.js 's head:

 head: {
    title: '',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      {
        hid: 'description',
        name: 'description',
        content: process.env.npm_package_description || 'checkout'
      }
    ],
    link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }],
    script: [
      {
        src: 'https://js.braintreegateway.com/web/3.76.4/js/client.min.js'
      },
      {
        src: 'https://js.braintreegateway.com/web/3.76.4/js/paypal-checkout.min.js'
      }
    ]
  },

is there anybody has idea about issue ?

Hi @hakantrg
you should try with .
The <Suspense> component gives us the ability to display top-level loading / error states while we wait on these nested async dependencies to be resolved.
here is a helpful source : Suspense