0

So i have 2 Cloud Functions within the same file:

exports.Auth = functions.region('europe-west1').https.onRequest((req, res) =>

and

exports.IPN = functions.region('europe-west1').https.onRequest((req, res) =>

When adding the following code right at the start of my Auth function it adds a new document to the Firestore as expected, however, when i add the same code at the start of my IPN function, which is currently being called via Paypal's IPN Simulator, it does nothing, no errors.

  let pin = RandomPIN(10, '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');
  var userRef = db.collection('Users').doc(pin);
  var setWithOptions = userRef.set({ Activated: false }, { merge: true });
  console.log("PIN: "+pin);

What on earth is going on, i must be missing something?

Thanks in advance.

Update:

Here are the logs, first with the 2 middle lines commented and then uncommented enter image description here It seems to be silently failing, i'm just not sure what is causing it.

Update with Complete function:

exports.IPN = functions.region('europe-west1').https.onRequest((req, res) =>
{
  console.log("IPN Notification Event Received");

  let pin = RandomPIN(10, '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');
  var userRef = db.collection('Users').doc(pin);
  var setWithOptions = userRef.set({ Activated: false }, { merge: true });
  console.log("PIN: "+pin);

  if (req.method !== "POST")
  {
    console.error("Request method not allowed.");
    res.status(405).send("Method Not Allowed");
  }
  else
  {
    console.log("IPN Notification Event received successfully.");
    res.status(200).end();
  }

  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",
    uri: getPaypalURI(),
    body: verificationBody,
  };

  // POST verification IPN data to paypal to validate.
  request(options, function callback(error, response, body)
  {
    if(!error && response.statusCode === 200)
    {
      if(body === "VERIFIED")
      {
        console.log(`Verified IPN: IPN message for Transaction ID: ${ipnTransactionMessage.txn_id} is verified.`);
        SendPIN(ipnTransactionMessage.payer_email, pin);
      }
      else if(body === "INVALID")
        console.error(`Invalid IPN: IPN message for Transaction ID: ${ipnTransactionMessage.txn_id} is invalid.`);
      else
        console.error("Unexpected reponse body.");
    }
    else
    {
      console.error(error);
      console.log(body);
    }
  });
});
7
  • "which is currently being called via Paypal's IPN Simulator" Are you sure the function is actually being called? Commented May 1, 2019 at 14:07
  • Yup, it verifies simulated transactions just fine if i comment out the middle 2 firestore lines Commented May 1, 2019 at 14:18
  • Please add this information to your question, so we can clean up the comments trail. Commented May 1, 2019 at 14:28
  • Do you return the Promise returned by the set() method? You should add the entire code of the function to your question, it may be because you don't return and chain the different promises. Commented May 1, 2019 at 14:33
  • Please edit the question to show the entire function that doesn't work the way you expect, not just a few lines of it. Commented May 1, 2019 at 15:27

1 Answer 1

1

Indeed it is a problem of Promises chaining and also a problem due to the request library: request supports callback interfaces natively but does not return a promise, which is what you must do within a Cloud Function.

I would suggest that you watch these official Firebase videos from Doug : https://www.youtube.com/watch?v=7IkUgCLr5oA&t=28s and https://www.youtube.com/watch?v=652XeeKNHSk which explain this key concept.

You can use request-promise (https://github.com/request/request-promise) and the rp() method which "returns a regular Promises/A+ compliant promise".

It is not clear what SendPIN() is doing. Let's make the assumption it returns a Promise. If this is true, you could adapt your code along the following lines:

//....
const rp = require('request-promise');
//....

exports.IPN = functions.region('europe-west1').https.onRequest((req, res) => {
  console.log('IPN Notification Event Received');

  let pin = RandomPIN(
    10,
    '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
  );
  var userRef = db.collection('Users').doc(pin);

  if (req.method !== 'POST') {
    console.error('Request method not allowed.');
    res.status(405).send('Method Not Allowed');
  } else {
    let ipnTransactionMessage;
    userRef
      .set({ Activated: false }, { merge: true })
      .then(() => {
        console.log('PIN: ' + pin);
        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',
          uri: getPaypalURI(),
          body: verificationBody
        };
        // POST verification IPN data to paypal to validate.
        return rp(options);
      })
      .then(response => {
        //Not sure what you will get within the response object...
        console.log(
          `Verified IPN: IPN message for Transaction ID: ${
            ipnTransactionMessage.txn_id
          } is verified.`
        );
        return SendPIN(ipnTransactionMessage.payer_email, pin); //It is not clear what SendPIN is doing, let's make the assumption it returns a Promise...
      })
      .then(() => {
        res.send('Success');
        return null;
      })
      .catch(err => {
        console.error(
          `Invalid IPN: IPN message for Transaction ID: ${
            ipnTransactionMessage.txn_id
          } is invalid.`
        );
        res
          .status(500)
          .send(
            'Error: ' +
              err +
              ` - Invalid IPN: IPN message for Transaction ID: ${
                ipnTransactionMessage.txn_id
              } is invalid.`
          );
        return null;
      });
  }
});
Sign up to request clarification or add additional context in comments.

8 Comments

Thanks for the links and your help Renaud, SendPIN is just a void function that uses nodemailer to email the pin. I'm not sure if it's related but i get an error @ res.send('Success'); "error Each then() should return a value or throw promise/always-return"
Is SendPIN asynchronous and returning a Promise?
No, it's just a synchronous void function function SendPIN(email, pin) it just calls nodemailer.createTransport and then transporter.sendMail
Are you sure it is not asynchronous? See nodemailer.com/usage: "If callback argument is not set then the method returns a Promise object. Nodemailer itself does not use Promises internally but it wraps the return into a Promise for convenience."
I've modified the answer by adding return null; after res.send('Success');. However, I am not 100% sure it is the best way to solve the problem you mentioned. It would be interesting to have the opinion of Doug or Frank on this point.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.