4

I'm trying to get Braintree Payments working in a Meteor app. I'm stuck at trying to return the result of generating a token (server side, via a Meteor Method) to be used client side.

I've tried this:

/server/braintree.js

Meteor.methods({
  createClientToken: function() {

    var token = gateway.clientToken.generate({
        customerId: this.userId
      }, function(err, response) {
          clientToken = response.clientToken
          return clientToken
        }
      )

    console.log(token)
    return token
  }
})

which returns true.

I've also tried this:

Meteor.methods({
  createClientToken: function() {

    var clientToken
    gateway.clientToken.generate({
        customerId: this.userId
      }, function(err, response) {
          clientToken = response.clientToken
        }
      )

    console.log(clientToken)
    return clientToken
  }
})

Which returns undefined.

The function(err, response) is being called asynchronously, yes? If so, that would be the explanation of the problem. Seems that trying to return a value from an asynchronous function is a bit of a pain point in Javascript. I've read a number of SO answers on it (like this one, this one and this one) but none have seemed to lead me in the right direction.

Also, I believe I may need to be using Meteor's wrapAsync method, correct? I've tried this (and found this and this relevant SO questions on it), but still can't seem to get things right.

Grateful for any feedback.

Update:

For a working approach to integrating Braintree with Meteor, check out the example repo (many thanks @Nick Tomlin for this)

Community
  • 1
  • 1
Isaac Gregson
  • 1,325
  • 1
  • 14
  • 27
  • 1
    I can confirm that the callback `function(err, response)` for `gateway.clientToken.generate` is called asynchronously. I cannot vouch for Meteor specific workarounds, however :( – mrak Dec 05 '14 at 20:01

1 Answers1

6

Disclaimer: I work for Braintree :)

I'm not familiar with Meteor, but as @mrak noted clientToken.generate is asynchronous and you will definitely handle that appropriately in your method.

In your current code, clientToken is undefined because console.log(clientToken) executes immediately, before you receive a clientToken from the callback for clientToken.generate. Asynchronous programming can take a while to wrap your head around if you are used to coding in a synchronous matter, but there are many resources out there to help you (here is one).

It appears that Meteor.wrapAsync will indeed provide what you need, here is an untested example implementation.

Meteor.methods({
  createClientToken: function() {
    var createToken = Meteor.wrapAsync(gateway.clientToken.generate, gateway.clientToken);

    var response = createToken({});

    return response.clientToken;
  }
});

Update

I've created a very basic Braintree + Meteor application that may be of some use to you (if it is not, please file an issue on the GH repo to help improve it!)

Nick Tomlin
  • 25,904
  • 10
  • 55
  • 84
  • Thanks for the help, @Nick Tomlin. With the above, I get a server 500 error, which reports `Exception while invoking method 'createClientToken' TypeError: Object [object Object] has no method 'validateParams'`. It seems that `createClientToken` doesn't want an object, which I assume it's being given by passing `this` along with the `generateToken` wrapAsync method? – Isaac Gregson Dec 06 '14 at 06:51
  • @IsaacGregson I may have led you astray with the context passed as the second argument to `wrapAsync`. Change the call to: `Meteor.wrapAsync(gateway.clientToken.generate);` and you should be fine. I've edited my answer as well. – Nick Tomlin Dec 06 '14 at 13:35
  • Hmm... I had already tried that and it returns the same server error (500), only this time it reads: `TypeError: Object # has no method 'validateParams'`. ideas? – Isaac Gregson Dec 08 '14 at 09:39
  • Following the answer to [this SO question](http://stackoverflow.com/questions/26226583/meteor-proper-use-of-meteor-wrapasync-on-server) I tried adjusting the call to `Meteor.wrapAsync(gateway.clientToken.generate, gateway.clientToken)` which returns a true response but `undefined` for the client token. So it seems the problem is in what's being passed along (or not) in the second argument to wrapAsync. Closer... but still not working. – Isaac Gregson Dec 08 '14 at 10:22
  • If I remove the creation of a customerId in the generateToken function then it creates the token (no more undefined) but it's still simply returning `true` for the response variable, not the clientToken. – Isaac Gregson Dec 08 '14 at 10:29
  • For understanding context in Meteor's wrapAsync, see ["a bit of a bind sidenote here](https://www.discovermeteor.com/blog/wrapping-npm-packages/). I'm just still not sure how to return the client token from the method in order to use it client side. – Isaac Gregson Dec 08 '14 at 13:04
  • @IsaacGregson excellent sleuthing, and thanks for the pointer on wrapAsync (i've once again updated my answer). It seems like there should be an easy way to to update your client template from within your callback; I am going to try and get a meteor example app up and running today because I'm not familiar enough with it yet to provide a more educated answer (obviously ^^). Feel free to reach out to our [support](https://support.braintreepayments.com/) if you need more dedicated assistance. – Nick Tomlin Dec 08 '14 at 14:13
  • Great! I'll look forward to seeing the upcoming meteor example app. Thanks for your input here. – Isaac Gregson Dec 08 '14 at 14:21
  • @IsaacGregson also, if you wanted to create an isolated test case and stick it up on Github that would be awesome. – Nick Tomlin Dec 08 '14 at 14:44
  • I'll see what I can do... today's pretty busy, but hopefully in a day or two. – Isaac Gregson Dec 08 '14 at 15:47
  • @IsaacGregson updated with a link to an example application. Please let me know if it is helpful (or if it even works at all ^^) – Nick Tomlin Dec 12 '14 at 19:34
  • Works! Many thanks. There's only one thing that's still unclear: customerId is for *existing* customers, correct? Or is it for *creating* a customer id to later be used? I had initially understood it as the later, but this approach (simply using this.userId for trying to create a customerId) returns an authorization error. So the former must be correct (that customerId is for *existing* customers). Also, there's a discrepancy between your answer and the github... the github approach works (and passing an options' object is much cleaner), while the above doesn't. – Isaac Gregson Dec 13 '14 at 10:42
  • PS: Once you get a change to update your answer with the approach in the example repo I'll accept the answer (in order to prevent possible confusion in the meantime :) – Isaac Gregson Dec 13 '14 at 10:44
  • @IsaacGregson you are correct, you pass a client id for _existing_ customers. I've updated my answer. Also, feel free to submit a PR to the repo when you finish your integration, it would be nice to have an example of recurring billing that actually uses the DB, and i'm sure there are a lot of others things that could be made nicer. – Nick Tomlin Dec 13 '14 at 14:52
  • Hello Nick. Could you extend your example to include subscriptions via the Drop-In as well? I couldn't find any other helpful examples online. – Cos May 20 '16 at 19:09