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 test@example.com',
  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

upvote
  flag
oh hey, googled this question and it's nearly identical to my code – corvid

4 Answers 11

up vote 72 down vote accepted

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
1 upvote
  flag
Thanks. The documentation wasn't as clear to me as your answer. I also don't completely comprehend your last paragraph. I'll have to study that bit a little bit more. For example, why doesn't Stripe need to be passed? Thanks again! – Adam
2 upvote
  flag
Adressed it in my latest edit. – saimeunt
upvote
  flag
Wow, SO is amazing. Thanks again! – Adam
1 upvote
  flag
how do you get an error? I saw elsewhere you can still provide a callback that will receive (error, data), but in the synchronous response will an error object be there instead of data if it failed? – faceyspacey.com
1 upvote
  flag
This is addressed in my latest edit, thanks for pointing this out. – saimeunt
1 upvote
  flag
@saimeunt How do you get the error back to the client if you use Meteor.call from the client? If you set a callback on the client (you have to if you don't use a stub) there appears to be no way of returning traditional-style (err, result) only result can be returned. Any light on this would be gratefully received. – mwarren
1 upvote
  flag
@saimeunt I seem to have solved my particular problem of sending the error back. I'll put it as an answer if anybody is interested – mwarren
upvote
  flag
@saimeunt yes please share how to sent the data, error back to the client. – timebandit
upvote
  flag
I am having the same problem with Stripe.charges.create, I have followed what you suggest but I want to send a value back to client side method call, this could be the error and result. I have posted my issuere here //allinonescript.com/questions/36167076/… – timebandit
upvote
  flag
For anyone else who has trouble getting the import syntax right in Meteor 1.3 or overlooks where secret key needs to be passed, this is what worked for me: var Stripe = require("stripe")(Meteor.settings.stripe.secretKey); – Elijah Lofgren

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;
  }
});
1 upvote
  flag
Huh, I wonder about wrapping a Meteor.call method itself. Meteor.call is supposed to have a callback when called from the client. So maybe we could wrap that method. – Alexander Mills
2 upvote
  flag
Yes, you can do that. Note that Meteor callbacks are asynchronous. See docs.meteor.com/#/full/meteor_call – FullStack
upvote
  flag
Ok, so for sync method calls don't give it a callback – panw

First of all, thanks to @saimeunt for his answer, which makes some difficult concepts clear. However I had the problem of wanting a classic async callback(err, result) showing both the error and the result back on the client, so that I can give informative messages in the browser.

I solved it this way:

Server code:

var Stripe = StripeAPI(STRIPE_SECRET_KEY);

Meteor.methods({
    createCust: Meteor.wrapAsync(Stripe.charges.create, Stripe.charges)
});

Client code:

var stripeCallOptions = {
    description: 'Woot! A new customer!',
    card: ccToken,
    plan: pricingPlan
};


Meteor.call('createCust', stripeCallOptions, function(error, result){
    console.log('client error', error);
    console.log('client result', result);
});

Looks neat. However unfortunately wrapAsync has an open bug, (see https://github.com/meteor/meteor/issues/2774) because it doesn't restore the proper error to the caller. A genius called Faceyspacey has written a replacement called Meteor.makeAsync() which you will find on the bug page I mentioned, which however returns either the result OR the error to the 'result' variable, leaving the 'error' variable undefined. That's fine by me for now, at least I've got a hook on the proper error object.

If you use makeAsync() you will need to import Futures like this:

Meteor.startup(function () {
    //this is so that our makeAsync function works
    Future = Npm.require('fibers/future');
});

Since you are going to need almost every function to be wraped in Async what you should do is use this package https://atmospherejs.com/copleykj/stripe-sync it prewraps all stripe functions with WrapAsync making your life easier and code cleaner.

1 upvote
  flag
life easier and code cleaner .. you shouldn't assume he has a dirty life :p – a20
upvote
  flag
lol thanks, I've just updated my post – Patrick Mencias-lewis

Not the answer you're looking for? Browse other questions tagged or ask your own question.