InvalidAccessError: Must create a new ApplePaySession from a user gesture handler

I have troubles sometimes (with slow internet connections) when the customers clicks on my Buy Now button, i got the error "Must crerate a new ApplePaySession from a user gesture hanlder..." I removed some logics to shipping to nt make this too long, but essentially my code is:


export default function ApplePayButton({setLoadingApple}) {

  // Effects
  React.useEffect(() => {
    // listen click
    const button = document.querySelector('apple-pay-button')
    if(button){
      button.addEventListener('click', startApplePaySession)
    }
    return () => {
      button.removeEventListener('click', startApplePaySession)
    }
  }, [])

  // Methods
  const selectStep = step => {
    // Check if step is available
    if (state.steps.allowed >= step) {
      dispatch({
        type: 'UPDATE_STEPS',
        data: {
          ...state.steps,
          current: step
        }
      })
    }
  }

  const startApplePaySession = () => {
    const apple = window.buckedup.payment_extensions.find(extension => extension.code === 'apple_pay')
    if (window.ApplePaySession && apple) {
      setLoadingApple(true)
      let clientToken
      if (window.braintree_config) {
        const {
          token
        } = window.braintree_config
        clientToken = token
      }
      window.braintree.client.create({
        authorization: clientToken
      }, (clientErr, clientInstance) => {
        if (clientErr) {
          setLoadingApple(false)
          console.error('Error creating client:', clientErr)
          return
        }
        window.braintree.applePay.create({
          client: clientInstance
        }, (applePayErr, applePayInstance) => {
          if (applePayErr) {
            setLoadingApple(false)
            console.error('Error creating applePayInstance:', applePayErr)
            return
          }
          const amount = state.totals.amount_due.amount
          const appleSession = window.ApplePaySession
          // Build request object
          const request = applePayInstance.createPaymentRequest({
            'countryCode': 'US',
            'currencyCode': 'USD',
            'merchantCapabilities': [
              'supports3DS',
              'supportsDebit',
              'supportsCredit'
            ],
            'shippingMethods': [],
            'shippingType': 'shipping',
            'supportedNetworks': [
              'visa',
              'masterCard',
              'amex',
              'discover'
            ],
            'requiredBillingContactFields': [
              'postalAddress',
              'name'
            ],
            'requiredShippingContactFields': [
              'postalAddress',
              'name',
              'phone',
              'email'
            ],
            'total': {
              'label': 'Bucked Up',
              'amount': amount,
              'type': 'final'
            }
          })

          // Define ApplePayPaymentRequest
          const session = new appleSession(3, request)
          // Validate merchant
          session.onvalidatemerchant = async event => {
            // Do request
            const response = await getApplePay()
            applePayInstance.performValidation({
              validationURL: event.validationURL,
              displayName: 'Bucked Up'
            }, (err, merchantSession) => {
              if (err) {
                setLoadingApple(false)
                return
              }
              session.completeMerchantValidation(response.data)
              setLoadingApple(false)
            })
          }
          // Validate payment method
          session.onpaymentmethodselected = async event => {
            // Update totals
            const totals = await postApplePayment()
            const update = {
              'newTotal': {
                'label': 'Bucked Up',
                'amount': totals.data.amount_owed
              }
            }
            session.completePaymentMethodSelection(update)
          }

          // Request to track shipping user data
          session.onshippingcontactselected = async event => {

            // Extract user available
            const dataShipping = event.shippingContact

            let body

            // Do first request to checkout endpoint
            const responseData = await shippingService(body)

            if (responseData.success) {

              // Get Shipping methods
              const body = {
                combined_shipments: true
              }

              const responseMethods = await getMethods(body)

              if (responseMethods.success) {

                const methods = responseMethods.data.shipments

                // Do request to update totals
                const responseTotals = await postApplePayment()
                const update = {
                  'newTotal': {
                    'label': 'Bucked Up',
                    'amount': responseTotals.data.amount_owed
                  },
                  'newShippingMethods': mappedMethods
                }
                session.completeShippingContactSelection(update)
              }

            }

          }

          // Authorize purchase
          session.onpaymentauthorized = async (event) => {
            applePayInstance.tokenize({
              token: event.payment.token
            }, async (tokenizeErr, payload) => {
              if (tokenizeErr) {
                console.error('Error tokenizing Apple Pay:', tokenizeErr)
                session.completePayment(appleSession.STATUS_FAILURE)
                return
              }
              const nonce = payload.nonce

              const billing = event.payment.billingContact
              const shipping = event.payment.shippingContact

              const body = {
                shipping,
                billing
              }
              const response = await patchApplePayment(body)

              if (response.success) {

                // Process payment
                const bodyBraintree = {
                  apple_pay: true,
                  payment_method_nonce: nonce
                }

                const responseBraintree = await postBraintree(bodyBraintree)

                if (responseBraintree.success) {

                  // Define ApplePayPaymentAuthorizationResult
                  const result = {
                    'status': appleSession.STATUS_SUCCESS
                  }

                  session.completePayment(result)

                  location.href = `${API_URL}checkout/thank-you`

                }

              }

            })

          }

          session.oncancel = (event) => {

            console.log(event, 'session cancel')
            setLoadingApple(false)

            selectStep(1)

          }

          session.begin()

        }
        )

      })

    }

  }

  return (

    <apple-pay-button buttonstyle='black' locale='en' type='plane' />

  )

}
InvalidAccessError: Must create a new ApplePaySession from a user gesture handler
 
 
Q