kraffslol / react-native-braintree-xplat

Cross-platform Braintree module for React Native
MIT License
81 stars 121 forks source link

Drop in UI spins forever on Android #21

Open asciiman opened 7 years ago

asciiman commented 7 years ago
import React, { Component } from 'react';
import {
  AppRegistry,
  Text,
  TouchableOpacity,
  Platform,
  View
} from 'react-native';
import BTClient from 'react-native-braintree-xplat';

export default class bt extends Component {
  onPress() {
    const token = "eyJ2ZXJzaW9uIjoyLCJhdXRob3JpemF0aW9uRmluZ2VycHJpbnQiOiIzZWRiNjBlZjZlMjFlYzQwYzRlZjgyMWJjYjViNzI4YjllYmE3NWM1ZDE4YzEzZjdjNTIxMTM0YWU1YTAyOGRifGNyZWF0ZWRfYXQ9MjAxNi0xMC0yMFQwMDoxNzoxMy44MjI0MTI2MzArMDAwMFx1MDAyNm1lcmNoYW50X2lkPTM0OHBrOWNnZjNiZ3l3MmJcdTAwMjZwdWJsaWNfa2V5PTJuMjQ3ZHY4OWJxOXZtcHIiLCJjb25maWdVcmwiOiJodHRwczovL2FwaS5zYW5kYm94LmJyYWludHJlZWdhdGV3YXkuY29tOjQ0My9tZXJjaGFudHMvMzQ4cGs5Y2dmM2JneXcyYi9jbGllbnRfYXBpL3YxL2NvbmZpZ3VyYXRpb24iLCJjaGFsbGVuZ2VzIjpbXSwiZW52aXJvbm1lbnQiOiJzYW5kYm94IiwiY2xpZW50QXBpVXJsIjoiaHR0cHM6Ly9hcGkuc2FuZGJveC5icmFpbnRyZWVnYXRld2F5LmNvbTo0NDMvbWVyY2hhbnRzLzM0OHBrOWNnZjNiZ3l3MmIvY2xpZW50X2FwaSIsImFzc2V0c1VybCI6Imh0dHBzOi8vYXNzZXRzLmJyYWludHJlZWdhdGV3YXkuY29tIiwiYXV0aFVybCI6Imh0dHBzOi8vYXV0aC52ZW5tby5zYW5kYm94LmJyYWludHJlZWdhdGV3YXkuY29tIiwiYW5hbHl0aWNzIjp7InVybCI6Imh0dHBzOi8vY2xpZW50LWFuYWx5dGljcy5zYW5kYm94LmJyYWludHJlZWdhdGV3YXkuY29tLzM0OHBrOWNnZjNiZ3l3MmIifSwidGhyZWVEU2VjdXJlRW5hYmxlZCI6dHJ1ZSwicGF5cGFsRW5hYmxlZCI6dHJ1ZSwicGF5cGFsIjp7ImRpc3BsYXlOYW1lIjoiQWNtZSBXaWRnZXRzLCBMdGQuIChTYW5kYm94KSIsImNsaWVudElkIjpudWxsLCJwcml2YWN5VXJsIjoiaHR0cDovL2V4YW1wbGUuY29tL3BwIiwidXNlckFncmVlbWVudFVybCI6Imh0dHA6Ly9leGFtcGxlLmNvbS90b3MiLCJiYXNlVXJsIjoiaHR0cHM6Ly9hc3NldHMuYnJhaW50cmVlZ2F0ZXdheS5jb20iLCJhc3NldHNVcmwiOiJodHRwczovL2NoZWNrb3V0LnBheXBhbC5jb20iLCJkaXJlY3RCYXNlVXJsIjpudWxsLCJhbGxvd0h0dHAiOnRydWUsImVudmlyb25tZW50Tm9OZXR3b3JrIjp0cnVlLCJlbnZpcm9ubWVudCI6Im9mZmxpbmUiLCJ1bnZldHRlZE1lcmNoYW50IjpmYWxzZSwiYnJhaW50cmVlQ2xpZW50SWQiOiJtYXN0ZXJjbGllbnQzIiwiYmlsbGluZ0FncmVlbWVudHNFbmFibGVkIjp0cnVlLCJtZXJjaGFudEFjY291bnRJZCI6ImFjbWV3aWRnZXRzbHRkc2FuZGJveCIsImN1cnJlbmN5SXNvQ29kZSI6IlVTRCJ9LCJjb2luYmFzZUVuYWJsZWQiOmZhbHNlLCJtZXJjaGFudElkIjoiMzQ4cGs5Y2dmM2JneXcyYiIsInZlbm1vIjoib2ZmIn0=";
    BTClient.setup(token);
    BTClient.showPaymentViewController()
      .then(nonce => {
        console.log(`Complete: ${nonce}`);
      }).catch(err => {
        console.log('err', err);
      });
  }

  render() {
    return (
      <View >
        <TouchableOpacity onPress={this.onPress.bind(this)}>
          <Text>
            Welcome to React Native!
          </Text>
        </TouchableOpacity>
      </View>
    );
  }
}

AppRegistry.registerComponent('bt', () => bt);

image

I'm using the sample token on the braintree developers site. If I start a brand new app with the above code as my index.android.js and click the button, the spinner will come up and never go away. If I click android back and then click the button again the drop in ui will appear.

kraffslol commented 7 years ago

I belive the reason for this is because the setup method is a promise. So what happens is that you are bringing up the payment view before setup has finished. Try waiting for setup to finish before bringing up the payment view.

On Thu, Nov 3, 2016 at 5:47 PM Stephen Horvath notifications@github.com wrote:

import React, { Component } from 'react'; import { AppRegistry, Text, TouchableOpacity, Platform, View } from 'react-native'; import BTClient from 'react-native-braintree-xplat';

export default class bt extends Component { onPress() { const token = "eyJ2ZXJzaW9uIjoyLCJhdXRob3JpemF0aW9uRmluZ2VycHJpbnQiOiIzZWRiNjBlZjZlMjFlYzQwYzRlZjgyMWJjYjViNzI4YjllYmE3NWM1ZDE4YzEzZjdjNTIxMTM0YWU1YTAyOGRifGNyZWF0ZWRfYXQ9MjAxNi0xMC0yMFQwMDoxNzoxMy44MjI0MTI2MzArMDAwMFx1MDAyNm1lcmNoYW50X2lkPTM0OHBrOWNnZjNiZ3l3MmJcdTAwMjZwdWJsaWNfa2V5PTJuMjQ3ZHY4OWJxOXZtcHIiLCJjb25maWdVcmwiOiJodHRwczovL2FwaS5zYW5kYm94LmJyYWludHJlZWdhdGV3YXkuY29tOjQ0My9tZXJjaGFudHMvMzQ4cGs5Y2dmM2JneXcyYi9jbGllbnRfYXBpL3YxL2NvbmZpZ3VyYXRpb24iLCJjaGFsbGVuZ2VzIjpbXSwiZW52aXJvbm1lbnQiOiJzYW5kYm94IiwiY2xpZW50QXBpVXJsIjoiaHR0cHM6Ly9hcGkuc2FuZGJveC5icmFpbnRyZWVnYXRld2F5LmNvbTo0NDMvbWVyY2hhbnRzLzM0OHBrOWNnZjNiZ3l3MmIvY2xpZW50X2FwaSIsImFzc2V0c1VybCI6Imh0dHBzOi8vYXNzZXRzLmJyYWludHJlZWdhdGV3YXkuY29tIiwiYXV0aFVybCI6Imh0dHBzOi8vYXV0aC52ZW5tby5zYW5kYm94LmJyYWludHJlZWdhdGV3YXkuY29tIiwiYW5hbHl0aWNzIjp7InVybCI6Imh0dHBzOi8vY2xpZW50LWFuYWx5dGljcy5zYW5kYm94LmJyYWludHJlZWdhdGV3YXkuY29tLzM0OHBrOWNnZjNiZ3l3MmIifSwidGhyZWVEU2VjdXJlRW5hYmxlZCI6dHJ1ZSwicGF5cGFsRW5hYmxlZCI6dHJ1ZSwicGF5cGFsIjp7ImRpc3BsYXlOYW1lIjoiQWNtZSBXaWRnZXRzLCBMdGQuIChTYW5kYm94KSIsImNsaWVudElkIjpudWxsLCJwcml2YWN5VXJsIjoiaHR0cDovL2V4YW1wbGUuY29tL3BwIiwidXNlckFncmVlbWVudFVybCI6Imh0dHA6Ly9leGFtcGxlLmNvbS90b3MiLCJiYXNlVXJsIjoiaHR0cHM6Ly9hc3NldHMuYnJhaW50cmVlZ2F0ZXdheS5jb20iLCJhc3NldHNVcmwiOiJodHRwczovL2NoZWNrb3V0LnBheXBhbC5jb20iLCJkaXJlY3RCYXNlVXJsIjpudWxsLCJhbGxvd0h0dHAiOnRydWUsImVudmlyb25tZW50Tm9OZXR3b3JrIjp0cnVlLCJlbnZpcm9ubWVudCI6Im9mZmxpbmUiLCJ1bnZldHRlZE1lcmNoYW50IjpmYWxzZSwiYnJhaW50cmVlQ2xpZW50SWQiOiJtYXN0ZXJjbGllbnQzIiwiYmlsbGluZ0FncmVlbWVudHNFbmFibGVkIjp0cnVlLCJtZXJjaGFudEFjY291bnRJZCI6ImFjbWV3aWRnZXRzbHRkc2FuZGJveCIsImN1cnJlbmN5SXNvQ29kZSI6IlVTRCJ9LCJjb2luYmFzZUVuYWJsZWQiOmZhbHNlLCJtZXJjaGFudElkIjoiMzQ4cGs5Y2dmM2JneXcyYiIsInZlbm1vIjoib2ZmIn0="; BTClient.setup(token); BTClient.showPaymentViewController() .then(nonce => { console.log(Complete: ${nonce}); }).catch(err => { console.log('err', err); }); }

render() { return (

Welcome to React Native!
);

} }

AppRegistry.registerComponent('bt', () => bt);

[image: image] https://cloud.githubusercontent.com/assets/645336/19975794/9845fe92-a1ed-11e6-9b98-7e9475667360.png

I'm using the sample token on the braintree developers https://developers.braintreepayments.com/start/hello-client/android/v2 site. If I start a brand new app with the above code as my index.android.js and click the button, the spinner will come up and never go away. If I click android back and then click the button again the drop in ui will appear.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/kraffslol/react-native-braintree-xplat/issues/21, or mute the thread https://github.com/notifications/unsubscribe-auth/AAJScPt7oztbNe_x2tDUGuC2VX0iXdGnks5q6hApgaJpZM4Koo7_ .

asciiman commented 7 years ago
  onPress() {
    const token = "eyJ2ZXJzaW9uIjoyLCJhdXRob3JpemF0aW9uRmluZ2VycHJpbnQiOiIzZWRiNjBlZjZlMjFlYzQwYzRlZjgyMWJjYjViNzI4YjllYmE3NWM1ZDE4YzEzZjdjNTIxMTM0YWU1YTAyOGRifGNyZWF0ZWRfYXQ9MjAxNi0xMC0yMFQwMDoxNzoxMy44MjI0MTI2MzArMDAwMFx1MDAyNm1lcmNoYW50X2lkPTM0OHBrOWNnZjNiZ3l3MmJcdTAwMjZwdWJsaWNfa2V5PTJuMjQ3ZHY4OWJxOXZtcHIiLCJjb25maWdVcmwiOiJodHRwczovL2FwaS5zYW5kYm94LmJyYWludHJlZWdhdGV3YXkuY29tOjQ0My9tZXJjaGFudHMvMzQ4cGs5Y2dmM2JneXcyYi9jbGllbnRfYXBpL3YxL2NvbmZpZ3VyYXRpb24iLCJjaGFsbGVuZ2VzIjpbXSwiZW52aXJvbm1lbnQiOiJzYW5kYm94IiwiY2xpZW50QXBpVXJsIjoiaHR0cHM6Ly9hcGkuc2FuZGJveC5icmFpbnRyZWVnYXRld2F5LmNvbTo0NDMvbWVyY2hhbnRzLzM0OHBrOWNnZjNiZ3l3MmIvY2xpZW50X2FwaSIsImFzc2V0c1VybCI6Imh0dHBzOi8vYXNzZXRzLmJyYWludHJlZWdhdGV3YXkuY29tIiwiYXV0aFVybCI6Imh0dHBzOi8vYXV0aC52ZW5tby5zYW5kYm94LmJyYWludHJlZWdhdGV3YXkuY29tIiwiYW5hbHl0aWNzIjp7InVybCI6Imh0dHBzOi8vY2xpZW50LWFuYWx5dGljcy5zYW5kYm94LmJyYWludHJlZWdhdGV3YXkuY29tLzM0OHBrOWNnZjNiZ3l3MmIifSwidGhyZWVEU2VjdXJlRW5hYmxlZCI6dHJ1ZSwicGF5cGFsRW5hYmxlZCI6dHJ1ZSwicGF5cGFsIjp7ImRpc3BsYXlOYW1lIjoiQWNtZSBXaWRnZXRzLCBMdGQuIChTYW5kYm94KSIsImNsaWVudElkIjpudWxsLCJwcml2YWN5VXJsIjoiaHR0cDovL2V4YW1wbGUuY29tL3BwIiwidXNlckFncmVlbWVudFVybCI6Imh0dHA6Ly9leGFtcGxlLmNvbS90b3MiLCJiYXNlVXJsIjoiaHR0cHM6Ly9hc3NldHMuYnJhaW50cmVlZ2F0ZXdheS5jb20iLCJhc3NldHNVcmwiOiJodHRwczovL2NoZWNrb3V0LnBheXBhbC5jb20iLCJkaXJlY3RCYXNlVXJsIjpudWxsLCJhbGxvd0h0dHAiOnRydWUsImVudmlyb25tZW50Tm9OZXR3b3JrIjp0cnVlLCJlbnZpcm9ubWVudCI6Im9mZmxpbmUiLCJ1bnZldHRlZE1lcmNoYW50IjpmYWxzZSwiYnJhaW50cmVlQ2xpZW50SWQiOiJtYXN0ZXJjbGllbnQzIiwiYmlsbGluZ0FncmVlbWVudHNFbmFibGVkIjp0cnVlLCJtZXJjaGFudEFjY291bnRJZCI6ImFjbWV3aWRnZXRzbHRkc2FuZGJveCIsImN1cnJlbmN5SXNvQ29kZSI6IlVTRCJ9LCJjb2luYmFzZUVuYWJsZWQiOmZhbHNlLCJtZXJjaGFudElkIjoiMzQ4cGs5Y2dmM2JneXcyYiIsInZlbm1vIjoib2ZmIn0=";
    BTClient.setup(token)
      .then(() => {
        BTClient.showPaymentViewController()
          .then(nonce => {
            console.log(`Complete: ${nonce}`);
          }).catch(err => {
            console.log('err', err);
          });
      });
  }

Like this? I tried above and still get the spinner forever. Or do you mean something different?

asciiman commented 7 years ago

It does look like a timing issue. When I run the setup in componentDidMount and then show the payment controller when clicking a button it loads fine. Unfortunately, I'm not able to do this except for a test. The design of this screen has two payment buttons with different prices. So I have to be able to generate a new token upon button click and then immediately go to the payment screen. So therefore I need to be able to wait for the setup to complete successfully before going to the payment screen. It does not look like the setup promise returns before it is actually complete and I end up with that neverending spinner.

Is there some other design that I could use that would allow me to have the two payment buttons and allow sufficient timing for the setup to complete?

card-b commented 7 years ago

I had better luck initializing BTClient in the main component, then passing BTClient as a prop to my child component that loads the payment form. Not sure if that's the best intended design, but I use the form in multiple components so it seemed okay to only initialize BTClient once.

Example:

var BTClient = require('react-native-braintree-xplat');

class myApp extends Component {
  componentWillMount(){

    //initialize BTClient when the app starts
    const token = "eyJ2ZXJzaW9uIjoyLCJhd[...]m1vIjoib2ZmIn0=";

    BTClient.setup(token).then((res) => {
      console.log(res);
    });
  }
  render(){

    //pass BTClient as a prop to the child
    return (<childComponent BTClient={BTClient} />
  }
}

class childComponent extends Component {
  ...
  onPressBuy(){

    //BTClient will be ready by the time the user triggers the event
    this.props.BTClient.showPaymentViewController().then((nonce) => {
      console.log(`Complete: ${nonce}`);
    }).catch((err) => {
      console.log('err', err);
    });
  }
  ...
}
asciiman commented 7 years ago

The issue for me is that I have two different purchase prices that the user could select. I suppose I could pre-fetch two different tokens, one for each price, and store them to be ready for when and if the user clicks the payment button. But that seems like a lot of work. The promise sounds like the best approach because it would allow me to wait for the client to make the request, get the appropriate token for the selected payment, then bring up the payment screen. So I really think the promise should be the thing that needs to be fixed.

TSMMark commented 7 years ago

Waiting for promise resolve worked for me. See below.

@kraffslol It would be very nice if the docs mentioned this at all. I likely would have spent hours diagnosing this if I didn't see this issue first.

export class PaymentTest extends Component {
  props: Props
  static displayName = 'PaymentTest'

  _braintreeIsSetup = false

  showPaymentFormAfterClientSetup = () => {
    this._braintreeIsSetup = true

    const options = {}

    BTClient.showPaymentViewController(options).then((nonce) => {
      console.log("showPaymentViewController Success", nonce)

      this.props.handlePayment({
        payment_method_nonce: nonce
      })

    })
    .catch((error) => {
      // TODO: Handle error.
      console.log("showPaymentViewController Error", error)
    })
  }

  showPaymentForm = () => {
    const { data: { me: { braintree_client_token } } } = this.props

    if (this._braintreeIsSetup) {
      this.showPaymentFormAfterClientSetup()
    } else if (Platform.OS === 'ios') {
      BTClient.setupWithURLScheme(braintree_client_token, 'com.vydia.app-dev.payments').then(this.showPaymentFormAfterClientSetup)
    } else {
      BTClient.setup(braintree_client_token).then(this.showPaymentFormAfterClientSetup)
    }
  }
VansonLeung commented 7 years ago

I succeeded in avoiding this issue by adding a 3000ms timeout after 'setup'

  onClickTerms(clientToken, amount)
  {
    var self = this;
    if (Platform.OS === 'ios') {
      BTClient.setupWithURLScheme(clientToken, GLOBAL.url_scheme).then(() => {
        this.showPaymentViewController(amount);
      });
    } else {
      BTClient.setup(clientToken).then(() => {
        this.showPaymentViewController(amount);
      });
    }
  }

  showPaymentViewController(amount)
  {
    var self = this;
    setTimeout(() => {
      BTClient.showPaymentViewController()
          .then(function(nonce) {
            //payment succeeded, pass nonce to server
          })
          .catch(function(err) {
            //console.error(err);
          });
    }, 3000);
  }
StevePotter commented 7 years ago

@asciiman @VansonLeung, we suddenly experienced this error. I checked into the bridge's Android dependencies and one entry was compile 'com.braintreepayments.api:braintree:2.+'. I looked at the maven repo and there have been releases almost monthly. I went back through each release and found that it broke after 2.3.9. So I forked this repo to use that specific version.

The fix is available here: https://github.com/Vydia/react-native-braintree-xplat/commit/4fcfc68dc50583e8211daa42a7d276f79a93f993

Just change your package.json to read

    "react-native-braintree-xplat": "git@github.com:Vydia/react-native-braintree-xplat.git#4fcfc68dc50583e8211daa42a7d276f79a93f993",

and you're good. However I don't want to use old versions of the Braintree repo, since according to their release notes they've fixed quite a few bugs.

@kraffslol we should probably hardcode Android dependencies so things like this won't suddenly happen. Granted it's more maintenance work but it's worth it for the users.