How to process the results of wrapAsync before they are returned.?


#1

I am using Meteor.wrapAsync in a Method to process a stripe credit card payment. It works, but I do not want to return the entire response from theStripeAPI.charges.create to the client. How can I process the results of the wrapAsync function and return a new object from the method.

Meteor.methods({
  createStripeCharge: function(params){
     var charge = {amount: params.amount, currency:'usd', source: params.token.id}
     var StripeAPI = stripe(<myprivatestriplekey>);

     let chargeStripeCard = Meteor.wrapAsync( StripeAPI.charges.create, StripeAPI.charges );
     let payment = chargeStripeCard( charge );
     return payment;
  }
});

#2

Just do whatever processing you want before the return and then return something different. Or have I misunderstood?


#3

So this is what I did :

 let payment = chargeStripeCard( charge, function (err,res){
       return res.id
} );

But when I do that the client does not receive the return value - id


#4

Yes - that’s expected. Your return is in the callback, not the Method. The Method returns undefined before the callback is executed - standard async JavaScript behaviour.

So, use wrapAsync as you’ve done (the syntax is correct - but I’ve not checked the Stripe API). The thread will be suspended until the (now hidden) callback resolves and you can work with payment before the return.


#5

No - I am using wrapAsync - I know the method returns before the API call returns. I was giving you a snippet based on the original code posting . To make it clearer I will post the whole method here:

Meteor.methods({
  createStripeCharge: function(params){
     var charge = {amount: params.amount, currency:'usd', source: params.token.id}
     var StripeAPI = stripe(<myprivatestriplekey>);

     let chargeStripeCard = Meteor.wrapAsync( StripeAPI.charges.create, StripeAPI.charges );
     let payment = chargeStripeCard( charge,  function (err,res) { return res.id} );
     return payment;
  }
});

#6

Ah - that’s incorrect. Your first code snippet was better. This may be closer to what you’re looking for:

Meteor.methods({
  createStripeCharge: function(params){
     var charge = {amount: params.amount, currency:'usd', source: params.token.id}
     var StripeAPI = stripe(<myprivatestriplekey>);

     let chargeStripeCard = Meteor.wrapAsync( StripeAPI.charges.create, StripeAPI.charges );
     let payment = chargeStripeCard( charge );
     return payment.id;
  }
});

#7

Does this mean that I could also do
return {id:payment.id, amount:payment.amount, status:payment.status};

would that not run the wrapped function 3 times ?

My goal is to return an object containing a sub-selection of the object properties returned by stripe.

Meteor.methods({
  createStripeCharge: function(params){
     var charge = {amount: params.amount, currency:'usd', source: params.token.id}
     var StripeAPI = stripe(<myprivatestriplekey>);

     let chargeStripeCard = Meteor.wrapAsync( StripeAPI.charges.create, StripeAPI.charges );
     let payment = chargeStripeCard( charge,  function (err,res) { console.log( {id:res.id,amount:res.amount} ); return {id:res.id,amount:res.amount}; } );
     return payment;
  }
});

When i run the method like this - the server logs the object containing the id and amount - but the object does not get returned to the client.


#8

As I said earlier - that’s wrong. You don’t use the callback at all when it’s wrapped.

Yes - but check my example and comment - don’t use the callback. You will get the result as the return value of the wrapped function.


#9

Thank you for all your help. I just want to verify that this would be the correct way to pass back an object;

Meteor.methods({
  createStripeCharge: function(params){
     var charge = {amount: params.amount, currency:'usd', source: params.token.id}
     var StripeAPI = stripe(<myprivatestriplekey>);

     let chargeStripeCard = Meteor.wrapAsync( StripeAPI.charges.create, StripeAPI.charges );
     let payment = chargeStripeCard( charge );
     return {id: payment.id, amount: payment.amount, status: payment.status};
  }
});

#10

**

ok - tested and works !

**


#11

Wow - i think i must be having a slow day. I was thinking the chargeStripeCard function only ran on the return - it runs before the return, and return waits for the response.

Thank you so much for all your help.


#12

A follow on question : how would you handle a situation where you have multiple embedded external API calls in a method, and you only want to RETURN your method once the final API call has returned.

Would you wrapAsync each API call, assuming the Stripe API does not handle promises?, and if so - which wrapAsync identifier do you pass to the return value of the method.

Example :

'createStripeSubscription': function (params){
	//create a subscription based on a $ amount decided by the user
		 // check if a subscription plan exists for the chosen $ amount
		 // if a plan does not exist then create a plan for that $ amount
		 // turn the user into a customer based on their credit-card token.id
		 // subscribe the customer to the new plan / or existing plan. 

    var self = this;	 
    import stripe from 'stripe';
    let StripeAPI = stripe(Meteor.settings.private.stripe.testSecretKey);
	
	//************************
	//Here is where I am not certain how to handle the wrapAsync.  I am just pasting as if it were a sync function below, without any async conversions
	//************************
	
	//****** API CALL 1 ********
	StripeAPI.plans.retrieve( "monthsub_" + params.amount, checkIfPlanExists );	 
	
	var checkIfPlanExists = function(err, plan) {	
		  if (err){ //ie a stripe subscription plan with this id does not exist
			  //****** API CALL 2 ********			
			  let planDetails = { amount: params.amount, interval: "month",  name: "monthsub_" + params.amount, currency: "usd", id: "monthsub_" + params.amount};
			  StripeAPI.plans.create(planDetails, createCustomer);
		  }  
		  else{
		      self.planID = plan.id;
			  let CustomerDetails = {source: params.token.id};				  
			  StripeAPI.customers.create( customerDetails, subscribeCustomerToPlan);
		  }

	}
	
	var createCustomer = function(err, plan) {
		  if (err) { //return an error message };
		  
		  //****** API CALL 3 ********
          self.planID = plan.id;
		  let CustomerDetails = {source: params.token.id};				  
		  StripeAPI.customers.create( customerDetails, subscribeCustomerToPlan);
	}
	
	var subscribeCustomerToPlan = function(err, customer) {
		  if (err) { //return an error message };
		  
		  //****** API CALL 4 ********
		  let subscriptionDetails = { customer: customer.id, plan: "monthsub_" + params.amount } ;					  
		  StripeAPI.subscriptions.create(subscriptionDetails, completeSubscription);
	}
	
	var completeSubscription = function(err, subscription) {
		  if (err) { //return an error message };
		  if (subscription){
			  console.log(subscription)
			  //log in localDB
			  //return Method to Meteor.call with subscription details
		  }
	}	
}

#13

I solved it !!

And the really tidy part was wrapping all my API functions at the outset.

'createStripeSubscription': function (params){
	if (Meteor.isServer) {
		  import stripe from 'stripe';
		  var self = this;
		  
		  const StripeAPI = stripe(Meteor.settings.private.stripe.testSecretKey);
		  const checkForExistingPlan = Meteor.wrapAsync(StripeAPI.plans.retrieve, StripeAPI.plans);
		  const createPlan = Meteor.wrapAsync(StripeAPI.plans.create, StripeAPI.plans);
		  const createCustomer = Meteor.wrapAsync(StripeAPI.customers.create, StripeAPI.customers);
		  const createSubscription = Meteor.wrapAsync(StripeAPI.subscriptions.create, StripeAPI.subscriptions);

		  const planName = "monthsub_" + params.amount;

		  try {
			  const planExistsResult = checkForExistingPlan(planName);
			  self.planID = planExistsResult.id;
		  }
		  catch (err) {
		      //if the plan does not exist an error is thrown
			  let planDetails = { amount: params.amount, interval: "month", name: planName, currency: "usd", id: planName };
			  const planCreateResult = createPlan(planDetails);
			  self.planID = planCreateResult.id
		  }

		  let customerDetails = {source: params.token.id};
		  const customerCreateResult = createCustomer(customerDetails);

		  let subscriptionDetails = {customer: customerCreateResult.id, plan: planName};
		  let subscriptionCreateResult = createSubscription(subscriptionDetails);

		  return subscriptionCreateResult;
   }
}