Search code examples
ember.jsbraintreebraintree-sandbox

braintree hosted payment fields client undefined Ember 3.25


Ember and Braintree Hosted Fields are not a good mix so far, Braintree Support are out of ideas on this one. When the form renders on the page it calls the action to create the client. The client is undefined.

picture-this-44ac48bef9f8df633632a4d202da2379.js:57 Uncaught TypeError: Cannot read property 'client' of undefined

enter image description here

component hbs

<script src="https://js.braintreegateway.com/web/3.81.0/js/client.min.js"></script>
<script src="https://js.braintreegateway.com/web/3.81.0/js/hosted-fields.min.js"></script>
<div class="demo-frame" {{did-insert this.setupBraintreeHostedFields}}>
  <form action="/" method="post" id="cardForm" >
    <label class="hosted-fields--label" for="card-number">Card Number</label>
    <div id="card-number" class="hosted-field"></div>

    <label class="hosted-fields--label" for="expiration-date">Expiration Date</label>
    <div id="expiration-date" class="hosted-field"></div>

    <label class="hosted-fields--label" for="cvv">CVV</label>
    <div id="cvv" class="hosted-field"></div>

    <label class="hosted-fields--label" for="postal-code">Postal Code</label>
    <div id="postal-code" class="hosted-field"></div>

    <div class="button-container">
    <input type="submit" class="button button--small button--green" value="Purchase" id="submit"/>
    </div>
  </form>
</div>

component class

import Component from '@glimmer/component';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import { tracked } from '@glimmer/tracking';
import { braintree } from 'braintree-web';

export default class CardPaymentComponent extends Component {

  @action
  setupBraintreeHostedFields() {

    alert('booh');
    var form = document.querySelector('#cardForm');
    var authorization = 'sandbox_24nzd6x7_gyvpsk2myght4c2p';

    braintree.client.create({
      authorization: authorization
    }, function(err, clientInstance) {
      if (err) {
        console.error(err);
        return;
      }
      createHostedFields(clientInstance);
    });

    function createHostedFields(clientInstance) {
      braintree.hostedFields.create({
        client: clientInstance,
        styles: {
          'input': {
            'font-size': '16px',
            'font-family': 'courier, monospace',
            'font-weight': 'lighter',
            'color': '#ccc'
          },
          ':focus': {
            'color': 'black'
          },
          '.valid': {
            'color': '#8bdda8'
          }
        },
        fields: {
          number: {
            selector: '#card-number',
            placeholder: '4111 1111 1111 1111'
          },
          cvv: {
            selector: '#cvv',
            placeholder: '123'
          },
          expirationDate: {
            selector: '#expiration-date',
            placeholder: 'MM/YYYY'
          },
          postalCode: {
            selector: '#postal-code',
            placeholder: '11111'
          }
        }
      }, function (err, hostedFieldsInstance) {
        var tokenize = function (event) {
          event.preventDefault();

          hostedFieldsInstance.tokenize(function (err, payload) {
            if (err) {
              alert('Something went wrong. Check your card details and try again.');
              return;
            }

            alert('Submit your nonce (' + payload.nonce + ') to your server here!');
          });
        };

        form.addEventListener('submit', tokenize, false);
      });
    }

  }

}

package.json

...
"ember-cli": "^3.25.2",
"braintree-web": "^3.81.0",
...

** Final Solution **

NPM braintree-web not required. Component class does not have access to the Braintree Window object. Move the tags to the app/index.html as outlined in the accepted answer.

component hbs

<article class="rental">
  <form action="/" method="post" id="cardForm">
    <label class="hosted-fields--label" for="card-number">Cardholder Name</label>
    <div id="card-holder-name" class="hosted-field payment"></div>

    <label class="hosted-fields--label" for="card-number">Email</label>
    <div id="email" class="hosted-field payment"></div>

    <label class="hosted-fields--label" for="card-number">Card Number</label>
    <div id="card-number" class="hosted-field payment"></div>

    <label class="hosted-fields--label" for="expiration-date">Expiration Date</label>
    <div id="expiration-date" class="hosted-field payment"></div>

    <label class="hosted-fields--label" for="cvv">CVV</label>
    <div id="cvv" class="hosted-field payment"></div>

    <label class="hosted-fields--label" for="postal-code">Postal Code</label>
    <div id="postal-code" class="hosted-field payment"></div>

    <div class="button-container">
    <input type="submit" class="button" value="Purchase" id="submit"/>
    </div>
  </form>
</article>

<script>
var form = document.querySelector('#cardForm');
var authorization = 'sandbox_24nzd6x7_gyvpsk2myght4c2p';

braintree.client.create({
  authorization: authorization
}, function(err, clientInstance) {
  if (err) {
    console.error(err);
    return;
  }
  createHostedFields(clientInstance);
});

function createHostedFields(clientInstance) {
  braintree.hostedFields.create({
    client: clientInstance,
    styles: {
      'input': {
        'font-size': '1.2em',
        'font-family': 'courier, monospace',
        'font-weight': 'lighter',
        'color': '#ccc'
      },
      ':focus': {
        'color': 'black'
      },
      '.valid': {
        'color': '#8bdda8'
      }
    },
    fields: {
      number: {
        selector: '#card-number',
        placeholder: '4111 1111 1111 1111'
      },
      cvv: {
        selector: '#cvv',
        placeholder: '123'
      },
      expirationDate: {
        selector: '#expiration-date',
        placeholder: 'MM/YYYY'
      },
      postalCode: {
        selector: '#postal-code',
        placeholder: '11111'
      }
    }
  }, function (err, hostedFieldsInstance) {
    var tokenize = function (event) {
      event.preventDefault();

      hostedFieldsInstance.tokenize(function (err, payload) {
        if (err) {
          alert('Something went wrong. Check your card details and try again.');
          return;
        }

        alert('Submit your nonce (' + payload.nonce + ') to your server here!');
      });
    };

    form.addEventListener('submit', tokenize, false);
  });
}
</script>

Solution

  • You can use Braintree SDK via either the direct script tag or using the npm module with the help of ember-auto-import. In your case, you are using both.

    For simplicity, let's use the script tag to inject the SDK. The issue in your snippet is that you are trying to load the script tag inside a component handlebar file. the handlebars (.hbs file) cannot load scripts using a <script> tag. We need to move the script tag to the index.html file present inside the app folder. This will load the SDK properly to be used inside a component.

    app/index.html:

    <body>
      ...
    
    
      <script src="https://js.braintreegateway.com/web/3.81.0/js/client.min.js"></script>
      <script src="https://js.braintreegateway.com/web/3.81.0/js/hosted-fields.min.js"></script>
    
      {{content-for "body-footer"}}
    </body>
    

    Once you inject the SDK properly, you can use the braintree window object without any issue.