node.jsfirebasegoogle-cloud-platformgoogle-cloud-firestoregoogle-cloud-functions

Accessing Firestore via Cloud Function


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);
    }
  });
});

Solution

  • 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;
          });
      }
    });