26829

Wrapping Stripe create customer callbacks in Fibers in Meteor

Question:

I'm having trouble getting Stripe.js to work when creating a new customer. Here is their Node.js code in their tutorial:

// Set your secret key: remember to change this to your live secret key in production // See your keys here https://dashboard.stripe.com/account/apikeys var stripe = require("stripe")("sk_test_9999999999999999999999"); // (Assuming you're using express - expressjs.com) // Get the credit card details submitted by the form var stripeToken = request.body.stripeToken; stripe.customers.create({ source: stripeToken, description: 'payinguser@example.com' }).then(function(customer) { return stripe.charges.create({ amount: 1000, // amount in cents, again currency: "usd", customer: customer.id }); }).then(function(charge) { saveStripeCustomerId(user, charge.customer); });

This is my attempt. I wrapped all the callbacks in Meteor.bindEnvironment because async callbacks need to run in a fiber. I get an error in the server console:

Exception while invoking method 'submitOrder' Error: Stripe: Unknown arguments (function (/* arguments */) {

Can anyone point me in the right direction for wrapping this in fibers? Or utilizing Meteor.wrapAsync?

var createStripeCustomer = function(ShoppingCartObject){ check(ShoppingCartObject, Object); var stripe = Stripe("sk_test_9999999999999999"); // (Assuming you're using express - expressjs.com) // Get the credit card details submitted by the form var stripeToken = ShoppingCartObject.charge.token; stripe.customers.create( { source: stripeToken, description: ShoppingCartObject._id, email: ShoppingCartObject.customerInfo.agentEmail, }, Meteor.bindEnvironment(function(customer){ return stripe.charges.create({ amount: ShoppingCartObject.totalPrice, // amount in cents, again currency: "usd", customer: customer.id }); }), Meteor.bindEnvironment(function(charge){ ShoppingCartObject.charge.customer = charge.customer; submitOrder(ShoppingCartObject); }) ); }; var submitOrder = function(ShoppingCartObject){ check(ShoppingCartObject, Object); var data = _.omit(ShoppingCartObject, '_id'); var setHash = { $set: data }; ShoppingCarts.update({_id: ShoppingCartObject._id}, setHash); };

Answer1:

Here is a simplified version of an approach that has worked for me. I basically create a function for each Stripe call that returns a Future.

// Server var Future = Npm.require('fibers/future'); function createCustomer(token){ var future = new Future; Stripe.customers.create({ card: token.id, email: token.email }, function(error, result){ if (error){ future.return(error); } else { future.return(result); } }); return future.wait(); } Meteor.methods({ purchase: function(token){ check(token: Object); try { var customer = createCustomer(token); } catch(error) { // error handle } // create charge, etc. repeating same pattern } });

Answer2:

I decided to work out everything using Meteor.wrapAsync(). The following code:

<strong>EDIT</strong>

After thinking about this, wrapAsync() seems to have some serious error-handling limitations, and especially for Stripe, where errors will be very common, my implementation below might be less-than-desireable.

Relevant discussion here: <a href="https://github.com/meteor/meteor/issues/2774" rel="nofollow">https://github.com/meteor/meteor/issues/2774</a>

User faceyspacey has this code to create a "better" wrapAsync that deals with errors more intuitively, although I haven't tried it yet.

Meteor.makeAsync = function(fn, context) { return function (/* arguments */) { var self = context || this; var newArgs = _.toArray(arguments); var callback; for (var i = newArgs.length - 1; i >= 0; --i) { var arg = newArgs[i]; var type = typeof arg; if (type !== "undefined") { if (type === "function") { callback = arg; } break; } } if(!callback) { var fut = new Future(); callback = function(error, data) { fut.return({error: error, data: data}); }; ++i; } newArgs[i] = Meteor.bindEnvironment(callback); var result = fn.apply(self, newArgs); return fut ? fut.wait() : result; }; };

<strong>Original Code Below</strong>

<ol><li>

ShoppingCartObject has the details of the order and also the cardToken that is generated by Stripe.js when you feed it to the customer's credit card details.

</li> <li>

A new customer is created with the cardToken saved, essentially saving their CC info for later use.

</li> <li>

Lastly, a charge is created on the customer using their CC.

</li> </ol>

<strong>code below</strong>

var createStripeCustomerAsync = function(ShoppingCartObject, callback){ var stripe = Stripe("sk_test_999999999999999999"); stripe.customers.create({ // this is the token generated on the client side from the CC info source: ShoppingCartObject.charge.cardToken, email: ShoppingCartObject.customer.email }, function(err, customer) { callback(err, customer); }); }; var createStripeCustomerSync = Meteor.wrapAsync(createStripeCustomerAsync); var createStripeChargeAsync = function(customer, ShoppingCartObject, callback){ var stripe = Stripe("sk_test_999999999999999999"); stripe.charges.create({ amount: ShoppingCartObject.totalPrice, // amount in cents, again currency: "usd", customer: customer.id }, function(error, charge){ callback(error, charge); }); }; var createStripeChargeSync = Meteor.wrapAsync(createStripeChargeAsync); var submitOrder = function(ShoppingCartObject){ check(ShoppingCartObject, Object); var customer = createStripeCustomerSync(ShoppingCartObject); console.log("customer: ", customer); var charge = createStripeChargeSync(customer, ShoppingCartObject); console.log("charge: ", charge); var data = _.omit(ShoppingCartObject, '_id'); var setHash = { $set: data }; ShoppingCarts.update({_id: ShoppingCartObject._id}, setHash); };

Recommend

  • MeteorJS realtime feedback from server stream
  • Fibers over Threads in D
  • How to get current user in custom route?
  • Got duplicated data when subscribe multiple times
  • Can't get VideoCapture property as the property identifier are not defined
  • Accessing a csv file with D3 inside meteor
  • What is the variable scope in Meteor client side?
  • Meteor.js mongodb version
  • Invoking a controller's action by button in View without redirecting to any view
  • Calling a constructor through reflection in scala 2.10
  • If I am creating a simple client server application in IntelliJ, how should this work?
  • is there a way to update filter with async data
  • Authentication failed with Azure Active Directory in Windows Phone
  • In loopback documentation what does variable 'cb' stands for?
  • ThreadStatic in asynchronous ASP.NET Web API
  • Web.config system.webserver errors
  • Converting a WriteableBitmap image ToArray in UWP
  • PHP buffered output depending on server setting?
  • Custom validator control occupying space even though display set to dynamic
  • Meteor helpers not available in Angular template
  • ImageMagick, replace semi-transparent white with opaque white
  • Why is an OPTIONS request sent to the server?
  • Is there any way to access browser form field suggestions from JavaScript?
  • Resize panoramic image to fixed size
  • Volusion's generic SQL folder, functionality
  • Upload files with Ajax and Jquery
  • How to pass list parameters for each object using Spring MVC?
  • SVN: Merging two branches together
  • Hibernate gives error error as “Access to DialectResolutionInfo cannot be null when 'hibernate.
  • Android Studio and gradle
  • Proper way to use connect-multiparty with express.js?
  • How do you join a server to an Active Directory (domain)?
  • coudnt use logback because of log4j
  • FormattedException instead of throw new Exception(string.Format(…)) in .NET
  • How to CLICK on IE download dialog box i.e.(Open, Save, Save As…)
  • Can Visual Studio XAML designer handle font family names with spaces as a resource?
  • IndexOutOfRangeException on multidimensional array despite using GetLength check
  • Linking SubReports Without LinkChild/LinkMaster
  • XCode 8, some methods disappeared ? ex: layoutAttributesClass() -> AnyClass
  • How can i traverse a binary tree from right to left in java?