1

On firebase function I need to get data from Paypal and do 4 things :

1. returns an empty HTTP 200 to them.
2. send the complete message back to PayPal using `HTTPS POST`.
3. get back "VERIFIED" message from Paypal.
4. *** write something to my Firebase database only here.

What I do now works but i am having a problem with (4).

   exports.contentServer = functions.https.onRequest((request, response) => {
....

                       let options = {
                          method: 'POST',
                          uri: "https://ipnpb.sandbox.paypal.com/cgi-bin/webscr",
                          body: verificationBody
                           };

                           // ** say 200 to paypal
                           response.status(200).end();

                           // ** send POST to paypal back using npm request-promise
                           return rp(options).then(body => {

                            if (body === "VERIFIED") {

                              //*** problem is here!
                                   return admin.firestore().collection('Users').add({request.body}).then(writeResult => {return console.log("Request completed");});
                                }
                                return console.log("Request completed");
                              })
                              .catch(error => {
                                      return console.log(error);
                              })

As you can see when I get final VERIFIED from Paypal I try to write to the db with admin.firestore().collection('Users')..

I get a warning on compile :

Avoid nesting promises 

for the write line.

How and where should I put this write at that stage of the promise ?

Renaud Tarnec
  • 79,263
  • 10
  • 95
  • 121
hkrlys
  • 361
  • 1
  • 13

1 Answers1

2

I understand that this HTTPS Cloud Function is called from Paypal.

By doing response.status(200).end(); at the beginning of your HTTP Cloud Function you are terminating it, as explained in the doc:

Important: Make sure that all HTTP functions terminate properly. By terminating functions correctly, you can avoid excessive charges from functions that run for too long. Terminate HTTP functions with res.redirect(), res.send(), or res.end().

This means that in most cases the rest of the code will not be executed at all or the function will be terminated in the middle of the asynchronous work (i.e. the rp() or the add() methods)

You should send the response to the caller only when all the asynchronous work is finished. The following should work:

exports.contentServer = functions.https.onRequest((request, response) => {

    let options = {
        method: 'POST',
        uri: "https://ipnpb.sandbox.paypal.com/cgi-bin/webscr",
        body: verificationBody
    };

    // ** send POST to paypal back using npm request-promise
    return rp(options)
        .then(body => {

            if (body === "VERIFIED") {
                //*** problem is here!
                return admin.firestore().collection('Users').add({ body: request.body });
            } else {
                console.log("Body is not verified");
                throw new Error("Body is not verified");
            }

        })
        .then(docReference => {
            console.log("Request completed");
            response.send({ result: 'ok' }); //Or any other object, or empty
        })
        .catch(error => {
            console.log(error);
            response.status(500).send(error);
        });


});

I would suggest you watch the official Video Series on Cloud Functions from Doug Stevenson (https://firebase.google.com/docs/functions/video-series/) and in particular the first video on Promises titled "Learn JavaScript Promises (Pt.1) with HTTP Triggers in Cloud Functions".

Renaud Tarnec
  • 79,263
  • 10
  • 95
  • 121
  • Sorry but i just don't get this. (thanks and sorry to be harsh). I read here everywhere that the .end just finish the RESONSE and NOT the function. Moreover- people are keep saying this here everywhere (that it terminates the function), and out of maybe 100 calls to this specific code, 0 (!) times it was terminated and ALL the other code was executed perfectly. So - what's really the truth here ? there is another guy from Firebase that wrote here "response just finish the request and the function is definitely stay alive" - which seems true because i have tried around 500 times already. – hkrlys Oct 17 '19 at 01:20
  • Adding to that - i can not change the order of things as you suggested. As i said - PayPal ask to first send the 200 OK, and later send the other message. So you changed the order of my needs, and i can't do it. (again but thanks a lot). – hkrlys Oct 17 '19 at 01:22
  • Sorry for another comment - did watched ALL of Doug Stevenson videos and must say(as an engineer) that there is a real mess with those functions, and people write different opinions about termination of a function that doesn't fit REALITY. (our function had never been terminated after respond/end/return and the rest of the code was ALWAYS executed. – hkrlys Oct 17 '19 at 01:25
  • There you go - (sorry can't edit my comments) this guy is highly voted and says exactly this on the answer. https://stackoverflow.com/questions/16180502/why-can-i-execute-code-after-res-send - "Sure end ends the HTTP response, but it doesn't do anything special to your code." – hkrlys Oct 17 '19 at 01:32
  • Well, things are not "black or white" if I can say it like that.. As explained in the doc by sending a response (`res.redirect()`, `res.send()` or `res.end()`) it means that you terminate the HTTP function. AFAIK it doesn't mean that the serverless system will **immediately** kill the Cloud Function but that it is informed that it can do so. Depending on several elements that are totally out of our control the system may sometimes decide to keep it running and kill it later on but there is no guarantee for that. – Renaud Tarnec Oct 17 '19 at 07:45
  • Have a look at this answer from Doug (https://stackoverflow.com/questions/49746980/continue-execution-after-sending-response-cloud-functions-for-firebase), which exposes the situation very clearly and also gives you the solution if you need to first send back an answer to Paypal, then do something else: "kick off a second function to continue where the HTTP function left off by using a pub/sub". – Renaud Tarnec Oct 17 '19 at 07:46
  • Having said that I would appreciate if you could accept and upvote my answer above, which (IMHO) answers your question about the "Avoid nesting promises" warning. Thanks in advance! – Renaud Tarnec Oct 17 '19 at 07:47
  • your answer works and thanks for that, but your comments "its not back and white" are a strange thing to say about software. You all say "system may sometimes decide to keep it running", and this is like ignoring the truth, because me and others already tested hundreds of times, and it NEVER terminated. It's not your fault or something but it's time that Google will address their server problems clearly and declare how exactly it works. It's software not gambling. – hkrlys Oct 18 '19 at 00:50
  • Should I ignore Josh's answer or yours ? because one of them is wrong - https://stackoverflow.com/questions/16180502/why-can-i-execute-code-after-res-send – hkrlys Oct 18 '19 at 01:03