So I created a function that will handle Paypal Instant Payment Notification(IPN). All goes well in receiving the notification and I also got "VERIFIED" message when requesting back to Paypal for verification. The problem is that cloud function is not or stops executing inside verification function callback.
exports.ipnHandler = functions.https.onRequest((req, res) => {
console.log("IPN Notification Event Received");
if (req.method !== "POST") {
console.error("Request method not allowed.");
res.status(405).send("Method Not Allowed");
} else {
// Return empty 200 response to acknowledge IPN post success.
console.log("IPN Notification Event received successfully.");
// JSON object of the IPN message consisting of transaction details.
let ipnTransactionMessage = req.body;
// Convert JSON ipn data to a query string since Google Cloud Function does not expose raw request data.
let formUrlEncodedBody = querystring.stringify(ipnTransactionMessage);
// Build the body of the verification post message by prefixing 'cmd=_notify-validate'.
let verificationBody = `cmd=_notify-validate&${formUrlEncodedBody}`;
console.log(`Verifying IPN: ${verificationBody}`);
let options = {
method: "POST",
url: getPaypalURI(),
body: verificationBody,
};
requestPromise(options)
.then(body => {
// This will log "VERIFIED"
console.log(body); // Cloud function stops here
if (body === "VERIFIED") {
console.log("Manage user subscription");
const transactionType = ipnTransactionMessage.txn_type;
const invoice = ipnTransactionMessage.invoice;
const docRef = firestore.collection("users");
console.log("Transaction type: " + transactionType);
console.log("Invoice " + invoice);
if (transactionType === "subscr_payment") {
console.log("About to subscribe user: " + invoice);
return docRef.where("invoice", "==", invoice).get();
}
}
return console.log("Request completed");
})
.then(snapshots => {
return console.log("Return snapshots " + snapshots);
})
.catch(error => {
console.log(error);
})
res.status(200).end();
}
It starts misbehaving when I added this line return docRef.where("invoice", "==", invoice).get(); Supposedly it will return a snapshot data in the callback below.
.then(snapshots => {
return console.log("Return snapshots " + snapshots);
})
I have solved the problem. It seems that function reached timeout which is 60 seconds in default. I just changed timeout to 5 minutes.
Related
Good day, I am trying to process payment using Firestore Cloud Function and a payment Gateway called Yoco. My payment code works and returns an object of failure or success. When I do logger.log I can view the object but the problem is when I try returning the result to the Client. I keep getting undefined. My methods are async.
CLIENT SIDE
I want to response from server and update my UI
const handlePay = () => {
if (amount > 100) {
yoco.showPopup({
amountInCents: amount, // 2 decimal places begining from left
currency: "ZAR",
name: "Student Angel",
description: "Helping students deal with student debt",
callback: function (result) {
// This function returns a token that your server can use to capture a payment
if (result.error) {
const errorMessage = result.error.message;
alert("error occured: " + errorMessage);
} else {
paymentMethod({ token: result.id, amount: amount });
}
// In a real integration - you would now pass this chargeToken back to your
// server along with the order/basket that the customer has purchased.
},
});
} else {
toast.error(
"Due to high transaction charges, We can only process R100 and above. Thank you"
);
}
};
const paymentMethod = ({ token, amount }) => {
const handlePayments = httpsCallable(functions, "handlePayments");
handlePayments({ token, amount })
.then((result) => {
console.log(result);
})
.catch((result) => {
console.log(result);
});
};
THIS IS CLOUD FUNCTION
exports.handlePayments = functions.https.onCall(async (data, context) => {
// Using a testing key Not Production Key
const SECRET_KEY = "sk_test_5ca384cdyerp8kY188a435e95fbe";
// check auth state
if (!context.auth) {
throw new functions.https.HttpsError(
"unauthenticated",
"You are not authenticated"
);
}
const token = data.token;
const amount = data.amount;
await axios // I have tried this return await axios.post but nothing
.post(
"https://online.yoco.com/v1/charges/",
{
token: token,
amountInCents: amount,
currency: "ZAR",
},
{
headers: {
"X-Auth-Secret-Key": SECRET_KEY,
},
}
)
.then((res) => {
// res.status will contain the HTTP status code
// res.data will contain the response body
functions.logger.log(res);
return res;
})
.catch((error) => {
return error;
});
});
I removed the await on my axios call and replaced it with return. on my callbacks .then and catch instead of returning
return res
or
return error
I access the data property like this
return res.data
It also got rid of the Error I was getting in my cloud functions logs Maximum call stack size exceeded
Seems rather like an unwanted requirement for a piece of code but in my case this is exactly what I need. I have an api (API-1) that interacts with a third party service. This third party service instead of directly giving me a response that I can forward back to frontend is giving me response on API-2 (With a webhook listener endpoint). I'm saving this API-2 response that I get by listening to the webhook in my database. Now I somehow need this response which is now sitting idol in my database in my API-1 so that I can forward it back to the frontend. If I query the database right away during the flow of the API-1 (Just after consume the third party service API), I'll get null as API-2 is getting the response asynchronously with a webhook (Mostly a gap of 1-2 seconds). So I somehow need to figure out an easy way to await/hold the API-1 flow till the database does not return null/returns back the response I saved from API-2 in the database. I'm not sure if the gap will always be 1-2 seconds hence I can't be using setTimeout for this.
//API-1
const sendPaymentRequest = async (req, res) => {
try {
const payment_reponse = await axios.post(url, body, config);
const { data } = payment_reponse;
console.log("Payment request => ", data);
//Check result i.e response from http listener
const webhookResponse = await MpesaModel.findOne({
conversationId: data.ConversationID
});
console.log('Webhook response => ', webhookResponse); //This is null
res.status(200).json({ message: "Send money request", data });
} catch (error) {
console.log("Error while making a payment request", error);
res
.status(400)
.json({ message: "Error while send payment request", error: error.data });
}
};
//API-2 - This is the webhook which receives the response
const saveWebhookB2C = async (req, res) => {
const { Result } = req.body;
//console.log('Mpesa webhook data received => ', Result);
let saveResponse = new MpesaModel({
...Result,
});
const result = await saveResponse.save();
console.log('B2c mpesa to kenya saved in db => ', result);
res.status(200).send();
};
Just wait until the response is different than null:
let webhookResponse = null;
while (response === null) {
await sleep(1000);
webhookResponse = await MpesaModel.findOne({
conversationId: data.ConversationID,
});
}
The sleep function should be fairly simple (and we use it only to wait one second until the next query):
const sleep = (timeInMilliseconds) => {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(), timeInMilliseconds);
});
};
Note: the while loop will run forever if the answer is always null, so probably you want to add another condition to the while in case the result is always null (maybe a maximum number of tries?).
async function getNotNullResponse({conversationId}){
const webhookResponse = await MpesaModel.findOne({conversationId});
return webhookResponse || getNotNullResponse({conversationId});
}
//API-1
const sendPaymentRequest = async (req, res) => {
try {
const payment_reponse = await axios.post(url, body, config);
const { data } = payment_reponse;
console.log("Payment request => ", data);
//Check result i.e response from http listener
const webhookResponse = await getNotNullResponse({
conversationId: data.ConversationID
});
console.log('Webhook response => ', webhookResponse); //This is not null
res.status(200).json({ message: "Send money request", data });
} catch (error) {
console.log("Error while making a payment request", error);
res
.status(400)
.json({ message: "Error while send payment request", error: error.data });
}
};
//API-2 - This is the webhook which receives the response
const saveWebhookB2C = async (req, res) => {
const { Result } = req.body;
//console.log('Mpesa webhook data received => ', Result);
let saveResponse = new MpesaModel({
...Result,
});
const result = await saveResponse.save();
console.log('B2c mpesa to kenya saved in db => ', result);
res.status(200).send();
};
I need help to integrate a few actions inside a function.
I get a call from PayPal, then i need to :
Access my DB to compare.
If ok send 200 OK
Send the request body back.
Currently - I do (3) only, and it somehow works without (2).
exports.contentServer = functions.https.onRequest((request, response) => {
....
....
if(request.path === paid)
{
if (request.method !== "POST")
response.status(405).send("Method Not Allowed");
else {
let ipnTransactionMessage = request.body;
let formUrlEncodedBody = querystring.stringify(ipnTransactionMessage);
let verificationBody = `cmd=_notify-validate&${formUrlEncodedBody}`;
//______________
//** at this point i need to read a firebase collection and return 200OK , ONLY THEN DO THE POST BELOW
var docRef = admin.firestore().collection('All').doc(ipnTransactionMessage.custom);
docRef.once('value').then(function(snapshot) {
console("our data to compare",snapshot);
res.status(200); // ?
});
//**** how do i do the next only after the previous ?
let options = {
method: 'POST',
uri: "https://ipnpb.sandbox.paypal.com/cgi-bin/webscr",
body: verificationBody
};
return rp(options)
.then(body => {
if (body === "VERIFIED") {
//** we are done here - and this actually works already
})
.then(docReference => {
console.log("Request completed");
return response.send({ result: 'ok' });
})
.catch(error => {
console.log(error);
return response.status(500).send(error);
});
See the comment in the code. After reading my DB and respond with 200 OK , only then i would like to send back the body as I do.
As Doug mentioned the code needs to go in the then clause:
I took a part of your code to shaow where the code to get executed only when firestore answers can go.
//** at this point i need to read a firebase collection and return 200OK , ONLY THEN DO THE POST BELOW
var docRef = admin.firestore().collection('All').doc(ipnTransactionMessage.custom);
docRef.once('value').then(function(snapshot) {
console("our data to compare",snapshot);
res.status(200); //
if(snapshot.exists){
// ---------------------------------
// The code that you want to get executed only
// firestore answers Goes Here
// ---------------------------------
}
});
I have a function running on the creation of a document.
When I send this information to an external API Firebase returns on 'ok' message before the API call is complete.
const functions = require('firebase-functions');
const request = require('request');
const admin = require('firebase-admin');
const rp = require('request-promise');
const port = '****';
const ip = '***.***.***.***';
admin.initializeApp(functions.config().firebase);
exports.sendUser = functions.firestore
.document('user/{userId}')
.onCreate((snap, context) => {
const data = snap.data();
const options = {
method: 'POST',
uri: 'http://' + ip + ':' + port + '/user',
body: data,
json: true,
};
rp(options)
.then(function (parsedBody) {
console.log('TEN ', parsedBody);
return parsedBody;
})
.catch(function (err) {
console.log('ERR ', err);
return err;
});
});
As you can see from my function it is not doing anything special apart from sending the data to an external source.
The API look like the following:-
app.post('/user', function (req, res) {
fs.exists(path, function(exists) {
if (exists === true) {
console.log('Currently Printing Different User Info');
fs.unlinkSync(path);
res.status(404).json({errorCode: 404, errorMessage: 'Currently Printing Different User.'});
return;
} else {
fs.writeFile(path, '', () => { console.log('File Created'); });
fs.unlinkSync(path);
res.status(200).json({statusCode: 200, statusMessage: 'Here we go'});
return;
}
});
})
How can I get Firebase to recognise the returned 404 as a failed call, and also wait until the call is complete before returning ok or failed.
The API is behaving correctly with Postman but not when data is posted via Firebase.
Has anyone encountered this before, or can anybody see what I am doing wrong?
The data is being parse over to the serve but only once Firebase has returned with 'ok' even if I purposely trigger a fail.
I need this in place to be able to use the Firebase Cloud Function retry function.
Images can be seen # https://imgur.com/a/1qYxrci
The Cloud Function returns the result before the call is complete because you don't return the Promise returned by the request-promise call.
Changing your code as follows should do the trick (at least for this problem):
exports.sendUser = functions.firestore
.document('user/{userId}')
.onCreate((snap, context) => {
const data = snap.data();
const options = {
method: 'POST',
uri: 'http://' + ip + ':' + port + '/user',
body: data,
json: true,
};
return rp(options) // <-- See the change here
.then(function (parsedBody) {
console.log('TEN ', parsedBody);
return parsedBody;
})
.catch(function (err) {
console.log('ERR ', err);
return err;
});
});
I would suggest you watch the official Video Series (https://firebase.google.com/docs/functions/video-series/) which explain very well this point about returning Promises for background functions (in particular the ones titled "Learn JavaScript Promises").
I'm making a bot that searches restaurants based on location. Can anyone help me why this doesnt show up in FB messenger?:
restaurants(result.getMemory('location').raw)
.then(res=>{
message.addReply(res);
message.reply();
});
}
The call to the restaurants function returns the results from a YELP API call (an array of restaurants) but when I add it as a reply to message, nothing happens in FB messenger.
Here is the full code for message.js:
const recastai = require('recastai');
const restaurants = require('./restaurants');
// This function is the core of the bot behaviour
const replyMessage = (message) => {
// Instantiate Recast.AI SDK, just for request service
const request = new recastai.request(process.env.REQUEST_TOKEN,
process.env.LANGUAGE);
// Get text from message received
const text = message.content;
console.log('I receive: ', text);
// Get senderId to catch unique conversation_token
const senderId = message.senderId;
// Call Recast.AI SDK, through /converse route
request.converseText(text, { conversationToken: senderId })
.then(result => {
//Recast takes text analyses that, returns a result object, generates replies adds messages to reply stack and then sends the replies
//Call Yelp API with when the intent is Location. When Yelp returns result we add it to the result.replies array.
//Then we add everything in result.replies to the messaging queue that sends the responses to FB
if (result.action) {
console.log('The conversation action is: ', result.action.slug);
}
// If there is not any message return by Recast.AI for this current conversation
if (!result.replies.length) {
message.addReply({ type: 'text', content: 'I don\'t have the reply to this yet :)' });
} else {
// Add each reply received from API to replies stack
result.replies.forEach(replyContent => message.addReply({ type: 'text', content: replyContent }));
}
// Send all replies
message.reply()
//send initial reply generated by Recast first
.then(() => {
//call restaurant function that returns a list of results from API
//if the action is location and done
if(result.action && result.action.slug === 'location' && result.action.done){
restaurants(result.getMemory('location').raw)
.then(res=>{
console.log(res);
message.addReply(res);
message.reply();
});
}
})
.catch(err => {
console.error('Error while sending message to channel', err);
});
})
.catch(err => {
console.error('Error while sending message to Recast.AI', err);
});
};
module.exports = replyMessage;
And here is my restaurants.js code that is imported into the message.js file for the bot behavior:
const rp = require('request-promise');
// Load configuration
require('./config');
const restaurants = (location) => {
return Promise.all([
yelpCall(location)
]).then(result => {
//result contains the return value from Yelp call
return result;
});
};
const yelpCall = (location) => {
const auth = {
method: 'POST',
url: 'https://api.yelp.com/oauth2/token?grant_type=client_credentials&client_id='+ process.env.YELP_APP_ID +'&client_secret='+process.env.APP_SECRET
};
return rp(auth)
.then(result => {
const tokens = JSON.parse(result);
return tokens;
})
.then(result=>{
const options = {
url: 'https://api.yelp.com/v3/businesses/search?location=' + location + "&term=thai",
headers: {Authorization: "Bearer " + result.access_token}
};
return rp(options).then(findings =>{
return findings;
});
});
};
module.exports = restaurants;
A few thoughts :
message.reply is thenable, therefore return message.reply() in two places.
request.converseText() is thenable, therefore return request.converseText(...).
restaurants is thenable, therefore return restaurants(...).
in message.js, message.addReply() is passed object of the form {type:..., content:...} in two places but finally just res. Is that correct?
in restaurants.js, Promise.all() appears to be unnecessary. It will cause its result to be wrapped in an array. module.exports = location => yelpCall(location); seems more appropriate.