I am using stripe for payments in my app, I want to create a receipt document in my own database after a succesful transaction
My code:
Meteor.methods({
makePurchase: function(tabId, token) {
check(tabId, String);
tab = Tabs.findOne(tabId);
Stripe.charges.create({
amount: tab.price,
currency: "USD",
card: token.id
}, function (error, result) {
console.log(result);
if (error) {
console.log('makePurchaseError: ' + error);
return error;
}
Purchases.insert({
sellerId: tab.userId,
tabId: tab._id,
price: tab.price
}, function(error, result) {
if (error) {
console.log('InsertionError: ' + error);
return error;
}
});
});
}
});
However this code returns an error:
Error: Meteor code must always run within a Fiber. Try wrapping callbacks that you pass to non-Meteor libraries with Meteor.bindEnvironment.
I am not familiar with Fibers, any idea as to why this is?
The problem here is that the callback function which you pass to Stripe.charges.create is called asynchronously (of course), so it's happening outside the current Meteor's Fiber.
One way to fix that is to create your own Fiber, but the easiest thing you can do is to wrap the callback with Meteor.bindEnvironment, so basically
Stripe.charges.create({
// ...
}, Meteor.bindEnvironment(function (error, result) {
// ...
}));
Edit
As suggested in the other answer, another and probably better pattern to follow here is using Meteor.wrapAsync helper method (see docs), which basically allows you to turn any asynchronous method into a function that is fiber aware and can be used synchronously.
In your specific case an equivalent solution would be to write:
let result;
try {
result = Meteor.wrapAsync(Stripe.charges.create, Stripe.charges)({ /* ... */ });
} catch(error) {
// ...
}
Please note the second argument passed to Meteor.wrapAsync. It is there to make sure that the original Stripe.charges.create will receive the proper this context, just in case it's needed.
You might want to take a look at the docs for http://docs.meteor.com/#/full/meteor_wrapasync.
Related
I’m using meteor with check() and audit-check-arguments package.
When I use a meteor method using async/await and pass a parameter, even though I use check() to validate the function parametrs, the audit package still throws an exception indicating that not all input parameters have been checked. If I remove the async/await implementation, the package does not crib. What am I missing?
Example:
Meteor.methods({
test: async function(param1){
check(param1, String);
...
await ....
}
});
Throws an exception:
=> Client modified -- refreshing
I20200513-10:43:27.978(5.5)? Exception while invoking method 'test' Error: Did not check() all arguments during call to 'test'
I20200513-10:43:27.979(5.5)? at ArgumentChecker.throwUnlessAllArgumentsHaveBeenChecked (packages/check/match.js:515:13)
Whereas this traditional meteor method does not throw any exceptions
Meteor.methods({
test: function(param1){
check(param1, String);
...
}
});
I know for sure that I am passing exactly one parameter.
It looks like audit-argument-checks only works for synchronous functions.
I don't have this issue because we use mdg:validated-method, which uses requires you to specify an argument validator for each method.
It shuts up the argument checker by wrapping the method function with this:
// Silence audit-argument-checks since arguments are always checked when using this package
check(args, Match.Any);
The simplest solution I can think of, is to separate the check from the async function. You could use a wrapper function to do this:
function checkAndRun(check, run) {
return function(...args) {
check.apply(this, args);
return run.apply(this, args);
}
}
Meteor.methods({
'example': checkAndRun(
function(exampleID){
check(exampleID, String);
},
async function(exampleID) {
const result = await doSomethingAsync(exampleID);
SomeDB.update({ _id: exampleID }, { $set: { someKey: result.value } });
return result.status;
}
}
});
or you could even do it inline with an async IIFE:
Meteor.methods({
example(exampleID) {
check(exampleID, String);
return (async () => {
const result = await doSomethingAsync(exampleID);
SomeDB.update({ _id: exampleID }, { $set: { someKey: result.value } });
return result.status;
})()
}
});
Which, come to think of it, is much simpler than the simplest solution I could think of at first 🙃
You just want to separate the sync check from the async method body somehow
In case you're curious, let diving through the source to see where it's called. When the method is called (in ddp-server/livedata-server), we end up here, a sync method call for the first reference of audit-argument-checks:
https://github.com/meteor/meteor/blob/master/packages/ddp-server/livedata_server.js#L1767-L1770
Which takes us into check/Match for another sync call here: https://github.com/meteor/meteor/blob/71f67d9dbac34aba34ceef733b2b51c2bd44b665/packages/check/match.js#L114-L123
Which uses the strange Meteor.EnvironmentVariable construct, which under the hood has another sync call: https://github.com/meteor/meteor/blob/master/packages/meteor/dynamics_nodejs.js#L57
i'm having issues with wrapAsync + method + sessions.
How do I implement the WrapAsync correctly?
I want, in a template to know if the user has at least one item created by him. And then define whether or not he can create another item.
Now i'm getting this error:
W20141013-15:04:43.237(-3)? (STDERR) Error: Can't wait without a fiber
But, I could not find Fiber at Documentation. And for implementing this, is it really necessary?
On the client side I want something like:
//pagina.js
Template.pagina.helpers{
userHasItem: return Session.get('userHasItem');
}
//pagina.js
Meteor.call('userHasItem', Meteor.userId(), function (error,result) {
Session.set('userHasItem', result);
});
//at server side:
if(Meteor.isServer){
Meteor.startup(function () {
var userHasItemAsync = function (userId) {
setTimeout(function () {
if (Items.findOne({'userId': userId})) {
return true;
} else {
return false;
}
}, 4000);
};
Meteor.methods({
userHasItem: function(userId) {
var userHasItemSync = Meteor.wrapAsync(userHasItemAsync),
result;
try {
userHasItemSync(userId);
console.log(result);
return result;
}catch (e) {
console.log('erreur', e.message);
throw new Meteor.Error(500, e);
}
},
}
});
}
Can't get your error to reproduce based on the existing code.
Still, userHasItemAsync is not available because you've defined it locally in the Meteor.startup function. But the error you should get in this case is userHasItemAsync is undefined.
Also the code you've entered here has multiple errors (i guess you typed it in not copy / pasted from your project): template instead of Template, Template it's defined outside of isClient (probably it's in a file available for the client) etc. Because of that it's hard to reproduce your exact case.
There is no need to call a server method to see if the item exists (assuming you have set up the proper publications/subscriptions), nor any need to call wrapAsync. In fact, what you want to achieve doesn't even require a session. All of the code can be ultimately distilled to this:
Template.pagina.helpers{
userHasItem: return Items.find({ userId: Meteor.userId() }).count() > 0;
}
The cursor returned by Items.find is reactive in itself, so there is no need for using a Session.
I'm using balanced-payments and their version 1.1 of balanced.js within Meteor.
I'm trying to create a new customer using
balanced.marketplace.customers.create(formData);
Here is my CheckFormSubmitEvents.js file
Template.CheckFormSubmit.events({
'submit form': function (e, tmpl) {
e.preventDefault();
var recurringStatus = $(e.target).find('[name=is_recurring]').is(':checked');
var checkForm = {
name: $(e.target).find('[name=name]').val(),
account_number: $(e.target).find('[name=account_number]').val(),
routing_number: $(e.target).find('[name=routing_number]').val(),
recurring: { is_recurring: recurringStatus },
created_at: new Date
}
checkForm._id = Donations.insert(checkForm);
Meteor.call("balancedCardCreate", checkForm, function(error, result) {
console.log(result);
// Successful tokenization
if(result.status_code === 201 && result.href) {
// Send to your backend
jQuery.post(responseTarget, {
uri: result.href
}, function(r) {
// Check your backend result
if(r.status === 201) {
// Your successful logic here from backend
} else {
// Your failure logic here from backend
}
});
} else {
// Failed to tokenize, your error logic here
}
// Debuging, just displays the tokenization result in a pretty div
$('#response .panel-body pre').html(JSON.stringify(result, false, 4));
$('#response').slideDown(300);
});
}
});
Here is my Methods.js file
var wrappedDelayedFunction = Async.wrap(balanced.marketplace.customers.create);
Meteor.methods({
balancedCardCreate: function (formData) {
console.log(formData);
var response = wrappedDelayedFunction(formData);
console.log(response);
return response;
}
});
I get nothing back when I submit the form, except that on the server console I do see the log of the form data.
I'm sure I'm not calling some of these async functions correctly. The hard part for me here is that the balanced function are async, but I don't know if they fit into the same mold as some of the examples I've seen.
I've tried to follow this example code.
http://meteorhacks.com/improved-async-utilities-in-meteor-npm.html
Is there a specific change that needs to be done in regard to working with balanced here? Does anyone have any tips for working with Async functions or see something specific about my code that I've done wrong?
Thanks
The NPM utilities Async.wrap does the same thing as the undocumented Meteor function Meteor._wrapAsync, in that it takes an asynchronous function with the last argument function(err, result) {} and turns it into a synchronous function which takes the same arguments, but either returns a result or throws an error instead of using the callback. The function yields in a Fiber until the asynchronous callback returns, so that other code in the event loop can run.
One pitfall with this is that you need to make sure that the function you wrap is called with the correct context. So if balanced.marketplace.customers.create is a prototype method that expects this to be set to something, it will not be set properly unless you bind it yourself, using function.bind or any of the other various library polyfills.
For more information, see https://stackoverflow.com/a/21542356/586086.
What I ended up doing was using a future. This works great, I just need to do better at catching errors. Which will be a question for a pro I think ; - )
Credit should go to user3374348 for answering another similar question of mine, which solved both of these.
https://stackoverflow.com/a/23777507/582309
var Future = Npm.require("fibers/future");
function extractFromPromise(promise) {
var fut = new Future();
promise.then(function (result) {
fut["return"](result);
}, function (error) {
fut["throw"](error);
});
return fut.wait();
}
Meteor.methods({
createCustomer: function (data) {
balanced.configure(Meteor.settings.balancedPaymentsAPI);
var customerData = extractFromPromise(balanced.marketplace.customers.create({
'name': data.fname + " " + data.lname,
"address": {
"city": data.city,
"state": data.region,
"line1": data.address_line1,
"line2": data.address_line2,
"postal_code": data.postal_code,
},
'email': data.email_address,
'phone': data.phone_number
}));
var card = extractFromPromise(balanced.marketplace.cards.create({
'number': data.card_number,
'expiration_year': data.expiry_year,
'expiration_month': data.expiry_month,
'cvv': data.cvv
}));
var associate = extractFromPromise(card.associate_to_customer(customerData.href).debit({
"amount": data.total_amount*100,
"appears_on_statement_as": "Trash Mountain" }));
});
As Andrew mentioned, you need to set the context for the method.
Here's the way you can do that with Async.wrap
Async.wrap(balanced.marketplace.customers, "create");
I'm quite new to dojo, but I have a certain error dealing with the Deferred API I can't help
my invoking code is
function openEditor(id, fieldName) {
editOptionsDialog_fetchData(id, fieldName).then(function(data){
console.log("done!");
});
console.log("leaving openEditor!");
}
which calls this function
function editOptionsDialog_fetchData(id, fieldName) {
require(["dojo/ready", "dojo/data/ObjectStore", "dojo/Deferred"], function(ready, ObjectStore, Deferred) {
var store;
var def;
switch (fieldName) {
case "degree":
store = new degreeStore();
def = store.getJsonData();
break;
case "faculty":
store = new facultyStore();
def = store.getJsonData();
break;
default:
console.log("error in editOptionsDialog_fetchData: " + fieldName);
}
return def.then(function(data){
store.data = data.items;
editOptionsDialog_select.setStore(new ObjectStore({ objectStore : store }));
editOptionsDialog_select.store.query({ "id" : id });
editOptionsDialog_select.startup();
});
});
}
where store.getJsonData() creates a Deferred, which I want to use for chaining the Deferred resolving (see additional code after the main text).
The error I receive is
editOptionsDialog_fetchData(id, fieldName).then(function(data)...) is undefined
Since the error message appears right after accessing the openEditor function, it is clear, that the value of the function call has to be undefined, since the callback is not yet done.
My question is, where this misunderstanding of the Deferred API must be within my code, since the purpose is to evaluate the function call of editOptionsDialog AS SOON AS the async call is done and called backed, AND NOT BEFORE this call has finished (in the state where the function call still leads to undefined, but I thought this is the purpose of the then-return).
Thx for your help
--- additional code for getJsonData() ---
getJsonData: function() {
return xhr(this.handlerUrl, {
handleAs: "json",
method: "POST",
data: {
action: "getJsonData"
}
}).then(function(data){
return data;
}, function(err){
alert("Data cannot be fetched in degreeStore.getJsonData! " + err);
});
}
I created a fiddle to demonstrate what you are trying to do.
Basically I am returning the Deferred to the editOptionsDialog_fetchData method from getJsonData. editOptionsDialog_fetchData creates another Deferred that it returns to openEditor. In editOptionsDialog_fetchData, I connect the resolving of the first Deferred to the resolve the second Deferred.
http://jsfiddle.net/cswing/yUTT8/
Resolved the problem by restructuring the code. In principle, the Deferred-object seems not to be able to be returned to a distinct require-clause. Therefore, this worked for me:
var editMultilanguageDialog_Startup;
// one require-clause, wrapping all the Deferred-sensitive statements
require(["dojo/request/xhr", "dojo/json", "dojo/Deferred", "dojo/store/Memory", "dojox/timing", "dojo/domReady!"], function(xhr, json, Deferred, Memory, timing, domReady) {
function editMultilanguageDialog_Startup(id, fieldName) {
// code from former mainForm -> moved into this require-clause
//[...]
// here, the invocation is no longer undefined and waits for the resolve
editMultilanguageDialog_fetchData(id, fieldName).then(function(data){
//[...]
editMultilanguageDialog.show();
});
}
function editMultilanguageDialog_fetchData(id, fieldName) {
// unchanged, returns Deferred
}
});
This is a workaround, I don't know if this feature is on purpose by dojo.
I am working with Mocha and am trying to test an API that I am in the process of building.
I am having trouble understanding where to place the done() function.
If I place it where it is now, it doesn't execute the callback function of User.findOne().
If I place the done at the bottom of the callback function of User.findOne(), then it creates a timeout.
I am relatively new to async and this done function, so can someone help explain why these two cases happen, and how to fix the code so that it will test correctly in Mocha?
describe('POST /signup', function() {
before(checkServerIsRunning); // Need to implement
it('create a new user if username is unique', function(done) {
httpReq({
method : 'POST',
url : url + '/signup',
json : true,
body : JSON.stringify({
username : 'test',
first : 'first',
last : 'last' })
},
function (err, res, body) {
if (err) {
done(err);
}
else {
res.statusCode.should.be.equal(201);
User.findOne( { username: 'test' }, function(err, user) {
user.should.have.property('username', 'testy');
user.should.have.property('firstName', 'first');
user.should.have.property('lastName', 'last');
usersToRemove.push(user);
});
done();
}
}
);
});
});
You should place done() inside the called to findOne.
If you're finding that it times out then either findOne is never calling its callback (which is an error!) or it's taking too long to execute.
In this case you could up the timeout by sticking something like this.timeout(5000) at the beginning of the test (which increases the timeout to 5 seconds).
In general you wouldn't usually want tests that slow though, so maybe try and figure out why it takes so long.