stripe-archive / react-stripe-elements

Moved to stripe/react-stripe-js.
https://github.com/stripe/react-stripe-js
MIT License
3.03k stars 319 forks source link

My Stripe promise with React never resolves even after trying a timeout #536

Closed taimoorgit closed 4 years ago

taimoorgit commented 4 years ago

What should happen: my SPA loads, the Stripe promise runs asynchronously and then I can do things with useStripe, for example redirecting to a checkout.

My code (I've tried wrapping only UpgradePage with Elements, no difference):

const stripePromise = loadStripe(
    process.env.REACT_APP_STRIPE ||
      "pk_test_..."
  );

function App() {
  // Note: if there is an error that needs actual attention, for example. the user entered the wrong inputs, use window alerts.
  const [notice, setNotice] = useState("");

  return (
    <Elements stripe={stripePromise}>
      <Router>
        <Navbar bearerRaw={bearerRaw} />
        <div className='mt-5 mb-5'>
          <Switch>
            <Route exact path='/upgrade/:price'>
              {bearerConfig() ? <UpgradePage notice={notice} setNotice={setNotice} /> : <Redirect to='/login' />}
            </Route>
          </Switch>
        </div>

        <Footer />
      </Router>
    </Elements>
  );
}

UpgradePage, see apiStripeCheckout:

export default function ({ notice, setNotice }) {
  document.title = "Upgrade to Pro - Geolinkify";

  const { price } = useParams();

  const stripe = useStripe();

  const [info, setInfo] = useState(null);
  async function apiGetInfo() {
    try {
      const response = await axios.get(`${SITE_API}/user/info`, bearerConfig());

      setInfo(response.data);

      // If this page is
    } catch (e) {
      setNotice(e.response.data);
    }
  }

  async function apiStripeCheckout() {
    try {
      const response = await axios.post(`${SITE_API}/billing/pro/${price}`, {}, bearerConfig());

      stripe.redirectToCheckout({
        sessionId: response.data.id,
      });
    } catch (e) {
      console.log(e);
      //setNotice(e.response.data);
    }
  }

  const [plan, setPlan] = useState(null);
  async function apiGetStripePlan() {
    try {
      const response = await axios.get(`${SITE_API}/user/info`, bearerConfig());

      setPlan(response.data);

      if (response.data.customer.pro) {
        // If they have a plan, they should manage it in the Stripe portal.
        apiStripePortal();
      } else {
        // Otherwise, they can open a checkout to purchase!
        console.log("Waiting 3 seconds for Stripe to load...");
        setTimeout(() => {
          apiStripeCheckout();
        }, 3000);
      }
    } catch (e) {
      setNotice(e.response.data);
    }
  }

  async function apiStripePortal() {
    try {
      const response = await axios.post(`${SITE_API}/billing/stripe_portal`, {}, bearerConfig());

      window.location.href = response.data;
    } catch (e) {
      window.alert(e.response.data);
    }
  }

  useEffect(() => {
    apiGetStripePlan();
  }, []);

  return (
    <Container>
      <div>
        <h1>You should be redirected soon...</h1>
        <p>Not working? Please contact support!</p>
      </div>
    </Container>
  );
}

The problem is that stripe in UpgradePage is always null. Even after trying to wait 3 seconds before using it, it is still null, and so I can't redirect my customer to the checkout page that the server gives.

After checking the Chrome logs here's what I see:

Waiting 3 seconds for Stripe to load...

TypeError: Cannot read property 'redirectToCheckout' of null
    at apiStripeCheckout (UpgradePage.js:23)

The API key is loaded just fine. And I've checked my test logs in the Stripe dashboard and I don't see anything unusual. Why is the loadStripe promise failing?

taimoorgit commented 4 years ago

My apologies. The issue here is useEffect. What I've done instead in my useEffect call is to add stripe into a dependency array, so when stripe is not null my app will go to Checkout. Exact change:

useEffect(() => {
    //apiGetStripePlan();
    if (stripe) {
      apiGetStripePlan();
    }
  }, [stripe]);