Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Meteor: Proper use of Meteor.wrapAsync on server

Background

I'm trying to integrate stripe payments into my site. I need to create a stripe user using my private stripe key. I'm storing this key on my server, and calling a server method to create the user. Maybe there's another way of accomplishing this? Here's the stripe api (copied below for convenience): https://stripe.com/docs/api/node#create_customer

//stripe api call
var Stripe = StripeAPI('my_secret_key');

Stripe.customers.create({
  description: 'Customer for [email protected]',
  card: "foobar" // obtained with Stripe.js
}, function(err, customer) {
  // asynchronously called
});

My attempts and results

I've been using the same client code with different server code. All attempts immediately give undefined on the client's console.log(...) but give the proper response on the server console.log(...):

//client
Meteor.call('stripeCreateUser', options, function(err, result) {
  console.log(err, result);
});

//server attempt 1
var Stripe = StripeAPI('my_secret_key');

Meteor.methods({
    stripeCreateUser: function(options) {  
        return Meteor.wrapAsync(Stripe.customers.create({
            description: 'Woot! A new customer!',
            card: options.ccToken,
            plan: options.pricingPlan
        }, function (err, res) {
            console.log(res, err);
            return (res || err);
        }));
    }
});

//server attempt 2
var Stripe = StripeAPI('my_secret_key');

Meteor.methods({
    stripeCreateUser: function(options) {  
        return Meteor.wrapAsync(Stripe.customers.create({
            description: 'Woot! A new customer!',
            card: options.ccToken,
            plan: options.pricingPlan
        }));
    }
});

I've also tried both without Meteor.wrapAsync.

EDIT - I'm also using this package: https://atmospherejs.com/mrgalaxy/stripe

like image 414
Adam Avatar asked Oct 06 '14 23:10

Adam


People also ask

How do I call Meteor method?

// Asynchronous call Meteor. call('foo', 1, 2, (error, result) => { ... }); If you do not pass a callback on the server, the method invocation will block until the method is complete. It will eventually return the return value of the method, or it will throw an exception if the method threw an exception.

What is Meteor API?

Meteor is a full-stack JavaScript platform for developing modern web and mobile applications. Meteor includes a key set of technologies for building connected-client reactive applications, a build tool, and a curated set of packages from the Node. js and general JavaScript community.


2 Answers

From the Meteor.wrapAsync http://docs.meteor.com/#meteor_wrapasync you can see that you need to pass it a function and optionally a context, whereas on your two attempts you are passing the RESULT of calling the async version of Stripe.customers.create.

Meteor.methods({
  stripeCreateUser: function(options) {
    // get a sync version of our API async func
    var stripeCustomersCreateSync=Meteor.wrapAsync(Stripe.customers.create,Stripe.customers);
    // call the sync version of our API func with the parameters from the method call
    var result=stripeCustomersCreateSync({
      description: 'Woot! A new customer!',
      card: options.ccToken,
      plan: options.pricingPlan
    });
    // do whatever you want with the result
    console.log(result);
  }
});

Meteor.wrapAsync transforms an async function into a convenient synchronous-looking function which allows to write sequentially looking code. (underhood everything is still executed within the asynchronous Node.js event loop).

We need to pass to Meteor.wrapAsync our API function (Stripe.customers.create) along with the function context, ie this inside the body of the API func, which in this case is Stripe.customers.

EDIT :

How to retrieve errors ?

Traditional node style API functions usually take a callback as last argument that will get called ultimately when the required task is completed. This callback takes 2 arguments : error and data, either one will be null according to the result of the call.

How do we access the error object using the synchronous wrapped functions returned by Meteor.wrapAsync ?

We have to rely on using try/catch blocks, because in case of error, it will be thrown by the sync function instead of being passed as first argument of the async function callback.

try{
  var result=syncFunction(params);
  console.log("result :",result);
}
catch(error){
  console.log("error",error);
}
// is the equivalent of :
asyncFunc(params,function(error,result){
  if(error){
    console.log("error",error);
    return;
  }
  console.log("result :",result);
});

why doesn't Stripe need to be passed?

JavaScript has no "namespace" concept, so API developers use the common trick of defining a global object acting as API namespace, properties defined on this object are "sub-modules" of the API. It means that Stripe.customers is a submodule of the Stripe API exposing customers-related funcs, and as such these funcs this context is Stripe.customers, not Stripe.

You can test it yourself by copy pasting this mocking code in your browser console :

Stripe={
  customers:{
    create:function(){
      console.log(this==Stripe.customers);
    }
  }
};

And then calling the stub function in your browser console like this :

> Stripe.customers.create();
true
like image 121
saimeunt Avatar answered Oct 11 '22 21:10

saimeunt


Another option is this package which achieves the similar goals.

meteor add meteorhacks:async

From the package README:

Async.wrap(function)

Wrap an asynchronous function and allow it to be run inside Meteor without callbacks.

//declare a simple async function
function delayedMessge(delay, message, callback) {
  setTimeout(function() {
    callback(null, message);
  }, delay);
}

//wrapping
var wrappedDelayedMessage = Async.wrap(delayedMessge);

//usage
Meteor.methods({
  'delayedEcho': function(message) {
    var response = wrappedDelayedMessage(500, message);
    return response;
  }
});
like image 11
FullStack Avatar answered Oct 11 '22 21:10

FullStack