weareseeed / react-square-web-payments-sdk

Easily create PCI-compliant inputs to accept payments online with the Square Payments API. It supports the following payment methods: credit and debit cards, ACH bank transfers, Apple Pay, Google Pay, Gift Cards and Afterpay/Clearpay.
https://react-square-payments.weareseeed.com/
MIT License
33 stars 35 forks source link

Uncaught (in promise) PaymentMethodNotAttachedError: Card has not been attached to the page. It must be attached before a payment method can be tokenized #46

Closed chinesehemp closed 2 years ago

chinesehemp commented 2 years ago

What version of React are you using?

17.0.1

What version of Node.js are you using?

16.4.0

What browser are you using?

Chrome

What operating system are you using?

macOS

How are you deploying your application?

firebase

Describe the Bug

I'm getting this error in the console...

Using v2.5.0 of the package (latest) and never had this error previously.

Screenshot 2022-04-14 at 18 29 23

Expected Behavior

No error appears in the console.

To Reproduce

import React, { useState } from "react"
import { SafeAreaView } from 'react-native-safe-area-context';
import { ScrollView, View, Image } from "react-native"
import { Appbar, ActivityIndicator, Chip, Dialog, Portal, Paragraph, Button } from "react-native-paper"
import { LinearGradient } from "expo-linear-gradient"
import NavigationService from "../../navigation/NavigationService"
import { RouteProp, Theme } from "@react-navigation/native"
import { functions } from "../../services/firebase"
import { useFunctionsCall } from "@react-query-firebase/functions"
import { SquarePaymentsForm, CreditCardInput } from "react-square-web-payments-sdk"
import { TokenResult, VerifyBuyerResponseDetails } from "@square/web-payments-sdk-types"
import tailwind from "tailwind-rn"
import { GetEvent } from "../../services/react-query/queries/fl_event"
import { useQueryClient } from "react-query"

type RequestData = object
type ResponseData = object

interface IProps {
  theme: Theme
  route: RouteProp<{
    params: {
      about: string,
      doorTime: string,
      eventId: string,
      price: string,
      startDate: string
    }
  }, "params">
}

const Pay: React.FC<IProps> = ({ route }) => {
  const [isError, setIsError] = useState(false)
  const [paymentDialogVisible, setPaymentDialogVisible] = useState(false)
  const [errorDialogVisible, setErrorDialogVisible] = useState(false)
  const [dialogError, setDialogError] = useState("")
  const { status, isLoading, data } = GetEvent(route.params.eventId)

  const amountString = (parseInt(route.params.price) / 100).toFixed(2)
  console.log("amountString", amountString)
  const squareCreatePayment = useFunctionsCall<RequestData, ResponseData>(
    functions,
    "squareCreatePayment",
    {},
    {
      onSuccess(data) {
        console.log("squareCreatePayment SUCCESS: ", data)
        createTicket(data.payment)
      },
      onError(error) {
        console.error("squareCreatePayment response error", error)
        setPaymentDialogVisible(false)
        setDialogError(JSON.stringify(error))
        setErrorDialogVisible(true)
      },
      onMutate(variables) {
        console.log("squareCreatePayment VARIABLES: ", variables)
      }
    }
  )

  const queryClient = useQueryClient()

  const firebaseCreateTicket = useFunctionsCall<RequestData, ResponseData>(
    functions,
    "firebaseCreateTicket",
    {},
    {
      onSuccess(data) {
        console.log("firebaseCreateTicket SUCCESS: ", data)
        setPaymentDialogVisible(false)
        console.log("invalidating event query Event: ", route.params.eventId)
        queryClient.invalidateQueries(["Event", route.params.eventId])
        queryClient.invalidateQueries(["Tickets", route.params.eventId])
        NavigationService.navigate('Ticket', { id: data })
      },
      onError(error) {
        console.error("firebaseCreateTicket ERROR: ", error)
        setPaymentDialogVisible(false)
      },
      onMutate(variables) {
        console.log("firebaseCreateTicket VARIABLES: ", variables)
      }
    }
  )
  const createPayment = (token: TokenResult, buyer: VerifyBuyerResponseDetails) => {
    if (!isError) {
      setPaymentDialogVisible(true)
      squareCreatePayment.mutate({
        token,
        buyer,
        route,
      })
    } else {
      console.log("createPayment Error")
    }
  }
  const createTicket = (payment: any, buyer: VerifyBuyerResponseDetails) => {
    console.log("createTicket payment: ", payment)
    firebaseCreateTicket.mutate({
      route,
      payment,
      buyer,
    })
  }
  return (
    <SafeAreaView style={tailwind("flex-1")}>
      <Appbar
        style={{
          height: 50
        }}
      >
        <Appbar.BackAction
          onPress={() => {
            NavigationService.navigate('Event', { id: route.params.eventId })
          }}
          color="#FFF"
        />
        <Appbar.Content
          title="Purchase ticket"
          titleStyle={tailwind("text-base font-medium -ml-4")}
          subtitle={data && data.about}
          subtitleStyle={tailwind("text-xs -ml-4")}
        />
      </Appbar>
      {isLoading && <ActivityIndicator style={tailwind("mt-2")} animating={true} color={"#000"} />}
      <ScrollView>
        {data &&
          <>
            <View
              style={tailwind("w-full h-48 justify-center")}
            >
              <Image
                source={{ uri: data.image[0].url }}
                style={tailwind("w-full h-full")}
              />
              <LinearGradient
                colors={["transparent", "rgba(0,0,0,0.8)"]}
                style={tailwind("absolute top-0 left-0 right-0 h-48")}
              />
            </View>
            <View style={tailwind("mx-4 mt-2")}>
              <SquarePaymentsForm
                applicationId="hidden-from-public"
                locationId="hidden-from-public"
                cardTokenizeResponseReceived={async (token, buyer) => {
                  // console.log({ token, buyer })
                  createPayment(token, buyer)
                }}
                createVerificationDetails={() => ({
                  amount: amountString,
                  /* collected from the buyer */
                  billingContact: {
                    addressLines: ["123 Main Street", "Apartment 1"],
                    familyName: "Doe",
                    givenName: "John",
                    countryCode: "GB",
                    city: "London",
                  },
                  currencyCode: "GBP",
                  intent: "CHARGE",
                })}
              >
                <CreditCardInput
                  focus="cardNumber"
                  text={"Pay £" + amountString}
                  overrideStyles={{
                    background: "black",
                  }}
                  errorClassAdded={
                    () => {
                      setIsError(true)
                    }
                  }
                  errorClassRemoved={
                    () => {
                      setIsError(false)
                    }
                  }
                  focusClassAdded={
                    () => {
                      setPaymentDialogVisible(false)
                    }
                  }
                />
              </SquarePaymentsForm>
            </View>
          </>
        }
      </ScrollView>
      <Portal>
        <Dialog
          visible={paymentDialogVisible}
          onDismiss={() => setPaymentDialogVisible(!paymentDialogVisible)}
          style={tailwind("bg-white items-center    ")}
        >
          <Dialog.Title>
            Purchase in progress
          </Dialog.Title>
          <Dialog.Content>
            <ActivityIndicator style={tailwind("mb-6")} animating={true} color={"#000"} />
            <Paragraph>Processing your payment</Paragraph>
          </Dialog.Content>
        </Dialog>
        <Dialog
          visible={errorDialogVisible}
          onDismiss={() => setErrorDialogVisible(false)}
          style={tailwind("bg-white")}
        >
          <Dialog.Title>Error</Dialog.Title>
          <Dialog.Content>
            <Paragraph>Ops... This is embarresing. Something went wrong. Here"s the error message: {dialogError}</Paragraph>
          </Dialog.Content>
          <Dialog.Actions>
            <Button
              onPress={() => setErrorDialogVisible(false)}
              color="#000"
              mode="text"
              uppercase={false}
            >
              Okay
            </Button>
          </Dialog.Actions>
        </Dialog>
      </Portal>
    </SafeAreaView>
  )
}

export default Pay
danestves commented 2 years ago

Hi @chinesehemp, we have identified the error and have fixed on react-square-web-payments-sdk@canary but this canary version have many breaking changes, if you want you can give it a try or wait until we have the PR finished and the new version/docs published ✌️

danestves commented 2 years ago

New version is published https://github.com/weareseeed/react-square-web-payments-sdk/releases/tag/v3.0.0