How to use Mollie Api with firebase cloud functions - firebase

I could need some help setting up the node.js api from mollie with firebase cloud functions. I tried to use parts of the cloudfunction setting up paypal guide but didn't get it to work yet. I'm new to cloud functions and node.js so i have a hard time getting it done. I am using hard coded payment properties for testing purposes. I am using a blaze subscribtion to be able to do requests to non-Google services The code i have so far:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
Mollie = require("mollie-api-node");
mollie = new Mollie.API.Client;
mollie.setApiKey("test_GhQyK7Gkkkkkk**********");
querystring = require("querystring");
fs = require("fs");
exports.pay = functions.https.onRequest((req, res) => {
console.log('1. response', res)
mollie.payments.create({
amount: 10.00,
method: Mollie.API.Object.Method.IDEAL,
description: "My first iDEAL payment",
redirectUrl: "https://dummyse-afgerond",
webhookUrl: "https://us-c90e9d.cloudfunctions.net/process",
testmode: true
}, (payment)=> {
if (payment.error) {
console.error('errrr' , payment.error);
return response.end();
}
console.log('3. payment.getPaymentUrl()', payment.getPaymentUrl());
res.redirect(302, payment.getPaymentUrl());
});
});
exports.process = functions.https.onRequest((req, res) => {
let _this = this;
this.body = "";
req.on("data", (data)=> {
console.log('_this.body += data', _this.body += data)
return _this.body += data;
});
req.on("end", ()=> {
console.log('hier dan?')
let mollie, _ref;
_this.body = querystring.parse(_this.body);
if (!((_ref = _this.body) !== null ? _ref.id : void 0)) {
console.log('res.end()', res.end())
return res.end();
}
})
mollie.payments.get(
_this.body.id
, (payment) => {
if (payment.error) {
console.error('3a. err', payment.error);
return response.end();
}
console.log('4a. payment', payment);
console.log('5a. payment.isPaid()', payment.isPaid());
if (payment.isPaid()) {
/*
At this point you'd probably want to start the process of delivering the
product to the customer.
*/
console.log('6a. payment is payed!!!!!!!!!!')
} else if (!payment.isOpen()) {
/*
The payment isn't paid and isn't open anymore. We can assume it was
aborted.
*/
console.log('6a. payment is aborted!!!!!!!!!!')
}
res.end();
});
});
this is mollies api guide:
https://github.com/mollie/mollie-api-node
this is paypal cloud function guide:
https://github.com/firebase/functions-samples/tree/master/paypal
UPDATE:
I updated the code. The error i get now is that all properties of the payment variable are undefined in the procces function (webhook). and the payment.isPaid() function says false while it should say true.

I did the same when I first tried to get mollie working in firebase cloud functions, this:
let _this = this;
this.body = "";
req.on("data", (data)=> {
console.log('_this.body += data', _this.body += data)
return _this.body += data;
});
req.on("end", ()=> {
console.log('hier dan?')
let mollie, _ref;
_this.body = querystring.parse(_this.body);
if (!((_ref = _this.body) !== null ? _ref.id : void 0)) {
console.log('res.end()', res.end())
return res.end();
}
})
is not necessary.
my webhook endpoint is much simpler, directly using the request & response that functions provides:
exports.paymentsWebhook = functions.https.onRequest((request, response) => {
// ...
console.log("request.body: ", request.body);
console.log("request.query: ", request.query);
mollie.payments.get(request.body.id, function (payment) {
console.log("payment", payment);
if (payment.error) {
console.error('payment error: ', payment.error);
response.end();
}
//checking/processing the payment goes here...
response.end();
});
});

Related

Setting up a firebase cloud function call

I am trying to set up cloud functions with firebase and I am having a slightly difficult time getting it set up.
I want to set up a function that gets called by an HTTP request. The function would take the information provided, double-check if those values are indeed the same values as the ones found in my firestorm
and then execute some Javascript code before responding; this is my code:
// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require("firebase-functions");
// The Firebase Admin SDK to access Firestore.
const admin = require('firebase-admin');
admin.initializeApp();
// [START trigger]
exports.buyCrypto = functions.https.onRequest((request, res) =>
{
// [END trigger]
// [START sendError]
// Forbidding PUT requests.
if (request.method === 'PUT') {
return res.status(403).send('Forbidden!');
}
// [END sendError]
// [START readQueryParam]
const uid = request.body.uid
const crypto = request.body.crypto
const amount = request.body.amount
const docRef = admin.firestore().collection("users").doc(uid);
docRef.get().then((doc) => {
if (doc.exists) {
if(crypto === "BTC")
{
if(doc.data.btc <= amount)
{
//execute buy
return res.status(200).send("Sucess");
}
}
if(crypto === "ETH")
{
if(doc.data.btc <= amount)
{
//execute buy
return res.status(200).send("Sucess");
}
}
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}).catch((error) => {
console.log("Error getting document:", error);
});
// Push the new message into Firestore using the Firebase Admin SDK.
//const writeResult = await admin.firestore().collection('messages').add({original: original});
// Send back a message that we've successfully written the message
// [START sendResponse]
const formattedResponse = "IDK"
return res.status(403).send("Failed");
// [END sendResponse]
});
Unfortunatly I cannot seem to find a great deal of documentation for firebase functions and when I try to test it with the emulator through a web browser it goes into infinite loading and does not display an error message so I am finding it impossible to debug anything.
Thank you for your time
You are calling return res.status(403).send("Failed"); outside of the then() block, so this line will be called before the asynchronous call to the get() method is completed and the Promise returned by this method is fulfilled. Result: your Cloud Function always sends back an error to its caller.
In addition, you do doc.data.btc instead of doc.data().btc. See the doc for the DocumentSnapshot, data() is a method.
Also, note that you don't need to use return in an HTTPS Cloud Function. Just send back a response with res.redirect(), res.send(), or res.end(). You may watch this video: https://www.youtube.com/watch?v=7IkUgCLr5oA.
The following should therefore do the trick:
exports.buyCrypto = functions.https.onRequest((request, res) => {
if (request.method === 'PUT') {
return res.status(403).send('Forbidden!');
}
const uid = request.body.uid
const crypto = request.body.crypto
const amount = request.body.amount
const docRef = admin.firestore().collection("users").doc(uid);
docRef.get().then((doc) => {
if (doc.exists) {
if (crypto === "BTC") {
if (doc.data().btc <= amount) {
//execute buy
res.status(200).send("Success");
} else {
// send a 200 response or throw an error res.status(200).send("....");
// Depends on your functional requirements
}
} else if (crypto === "ETH") {
if (doc.data.btc <= amount) {
//execute buy
return res.status(200).send("Success");
} else {
// send a 200 response or throw an error res.status(200).send("....");
// Depends on your functional requirements
}
} else {
// send a 200 response or throw an error res.status(200).send("....");
// Depends on your functional requirements
}
} else {
console.log("No such document!");
// send a 200 response or throw an error res.status(200).send("....");
}
}).catch((error) => {
console.log("Error getting document:", error);
res.status(500).send(error);
});
});

Unity Firebase functions.https.onRequest call error is always Internal with error message: Response is not valid JSON object

I'm trying to handle Firebase cloud function errors in Unity but I cannot always receive an error with error code "Internal" and error message "Response is not valid JSON object."
After reading through the Firebase documentation and some other stack overflow questions I understand that there is a difference between functions.https.onCall and functions.https.onRequest. We are currently using onRequest because we need to access these functions from web, in addition to android and iOS.
The problematic cloud code:
app.post("/test", (req, res) => {
res.status(500).send("This message will never appear in the editor");
});
exports.app = functions.https.onRequest(app);
exports.ThisTestWorks = functions.https.onCall((data, context) => {
throw new functions.https.HttpsError('invalid-argument', 'This message will appear in the editor');
});
Unity code:
FirebaseProcess retVal = new FirebaseProcess();
FunctionInst.GetHttpsCallable("app/test").CallAsync(request).ContinueWith(t => {
if (t.IsFaulted || t.IsCanceled) {
foreach (var inner in t.Exception.InnerExceptions) {
if (inner is FunctionsException) {
var e = (FunctionsException) inner;
Debug.Log("e.ErrorCode: " + e.ErrorCode + ", e.Message: " + e.Message);
} else {
Debug.Log("inner.Message: " + inner.Message);
}
}
Debug.Log("t.Exception.Message " + t.Exception.Message);
Debug.Log("t.Status " + t.Status);
} else {
retVal.Success = true;
retVal.ResultJSON = t.Result.Data as string;
}
retVal.IsRunning = false;
});
This code will return the following logs:
e.ErrorCode: Internal, e.Message: Response is not valid JSON object.
t.Exception.Message One or more errors occurred.
t.Status Faulted
Full (relevant) cloud code:
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const cors = require('cors')({origin: true});
const express = require('express');
const cookieParser = require('cookie-parser')();
const app = express();
admin.initializeApp();
const validateFirebaseIdToken = async (req, res, next) => {
if ((!req.headers.authorization || !req.headers.authorization.startsWith('Bearer ')) &&
!(req.cookies && req.cookies.__session)) {
console.error('No Firebase ID token was passed as a Bearer token in the Authorization header.',
'Make sure you authorize your request by providing the following HTTP header:',
'Authorization: Bearer <Firebase ID Token>',
'or by passing a "__session" cookie.');
res.status(403).send('Unauthorized');
return;
}
let idToken;
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
// Read the ID Token from the Authorization header.
idToken = req.headers.authorization.split('Bearer ')[1];
} else if(req.cookies) {
// Read the ID Token from cookie.
idToken = req.cookies.__session;
} else {
// No cookie
res.status(403).send('Unauthorized');
return;
}
try {
const decodedIdToken = await admin.auth().verifyIdToken(idToken);
req.user = decodedIdToken;
next();
return;
} catch (error) {
console.error('Error while verifying Firebase ID token:', error);
res.status(403).send('Unauthorized');
return;
}
};
app.use(cors);
app.use(cookieParser);
app.use(validateFirebaseIdToken);
app.post("/test", (req, res) => {
res.status(500).send("This message will never appear in the editor");
});
exports.ThisTestFails = functions.https.onRequest(app);
exports.ThisTestWorks = functions.https.onCall((data, context) => {
throw new functions.https.HttpsError('invalid-argument', 'This message will appear in the editor');
});
Is there a reason that this doesn't return a custom error in the Unity editor? What am I missing here?
Thanks in advance!

Firebase Callable function with JWT Authentication and Google Sheets V4 API

I want to implement firebase callable function with JWT Authentication and fetching data from Google Sheet, using Google Sheets V4 API.
For test I tried to use Example Spreadsheet but Sheets API not activated for that Spreadsheet and I cloned it on my own drive and use it for testing.
References:
My code based on solution described in this question How to use Google sheets API while inside a google cloud function and Accessing Google APIs using Service account in Node.JS
Also I have got two important information: "Service Account".json and API Key. I save API Key in api_key.json but didn't find examples how to use it with Google Sheets V4 API:
{
key: "xxxxxx"
}
test() callable function which doesn't require any authentication works fine:
exports.test = functions.https.onCall((data, context) => {
return { text: data.text };
});
Calling test() function somewhere on client (in Browser):
function getTest() {
console.log("clicked getTest()");
var test = firebase.functions().httpsCallable('test');
test({text: '12345'}).then(function(result) {
console.log(result);
}).catch(function(error) {
console.log(error.code);
console.log(error.message);
});
}
Calling getData() somewhere on client (in Browser):
function requestData() {
console.log("clicked requestData()");
//https://firebase.google.com/docs/functions/callable
//getData() function described in functions/index.js
var getData = firebase.functions().httpsCallable('getData');
getData(null).then(function (result) {
// Read result of the Cloud Function.
console.log(result); //<------- Expected rows from Spreadsheet????
}).catch(function(error) {
console.log(error.code);
console.log(error.message);
});
}
**Thank you, F10. I corrected code.
index.js:
'use strict'
const functions = require('firebase-functions');
const { google } = require('googleapis');
var serviceAccount = require("./credentials/owner-service-account-gcloud.json");
function getJwt() {
// Define the required scopes.
var scopes = [
'https://www.googleapis.com/auth/spreadsheets'
];
return new google.auth.JWT(
serviceAccount.client_email,
null,
serviceAccount.private_key,
scopes
);
}
function getSpreadsheetDate(jwt) {
return new Promise((resolve, reject) => {
jwt.authorize((error, access_token) => {
if (error) {
console.log('Error in jwt.authorize: ' + error);
reject(error);
} else {
// access_token ready to use to fetch data and return to client
const sheets = google.sheets({ version: 'v4', access_token });
// set auth as a global default:
google.options({ auth: jwt }); //<----------------------
const request = {
auth: jwt,
spreadsheetId: 'xxxx',
range: 'Class Data!A2:E', //'Class Data!A2:E',
}
sheets.spreadsheets.values.get(request, (err, response) => {
console.log("inside: sheets.spreadsheets.values.get() -------------------------------");
if (err) {
console.log('The Sheets API returned an error: ' + err);
//The API returned an error: Error: API key not valid. Please pass a valid API key.
reject(err);
};
try {
var numRows = response.data.values ? response.data.values.length : 0;
console.log('%d rows retrieved.', numRows);
console.log("response.data:-------------------------------");
console.log(response.data.values);
resolve(response.data.values);
} catch (err) {
console.log("Error processing Sheets API response: " + err);
reject(err);
}
})
}
})
})
}
exports.getData = functions.https.onCall((data, context) => {
console.log("getData()---------------------------");
if (!context.auth) {
throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' + 'while authenticated.');
} else {
console.log("context.auth ------------ OK");
const uid = context.auth.uid;
console.log(uid);
var jwt = getJwt();
console.log("getJwt() --------------- OK");
return getSpreadsheetDate(jwt); //<------------ Requested Spreadsheet's Data
}
})
exports.test = functions.https.onCall((data, context) => {
return { text: data.text };
});
There's a solution that uses googleapis instead of the auth library to do the authentication with JWT. Regarding your token inquiries, you could check the OAuth 2.0 for client-side web applications documentations, which explains the steps to do the authentication.

How do you unit test a firebase function wrapped with express?

With firebase functions, you can utilize express to achieve nice functionality, like middleware etc. I have used this example to get inspiration of how to write https firebase functions, powered by express.
However, my issue is that the official firebase documentation on how to do unit testing, does not include a https-express example.
So my question is how to unit test the following function (typescript)?:
// omitted init. of functions
import * as express from 'express';
const cors = require('cors')({origin: true});
const app = express();
app.use(cors);
// The function to test
app.get('helloWorld, (req, res) => {
res.send('hello world');
return 'success';
});
exports.app = functions.https.onRequest(app);
This works with Jest
import supertest from 'supertest'
import test from 'firebase-functions-test'
import sinon from 'sinon'
import admin from 'firebase-admin'
let undertest, adminInitStub, request
const functionsTest = test()
beforeAll(() => {
adminInitStub = sinon.stub(admin, 'initializeApp')
undertest = require('../index')
// inject with the exports.app methode from the index.js
request = supertest(undertest.app)
})
afterAll(() => {
adminInitStub.restore()
functionsTest.cleanup()
})
it('get app', async () => {
let actual = await request.get('/')
let { ok, status, body } = actual
expect(ok).toBe(true)
expect(status).toBeGreaterThanOrEqual(200)
expect(body).toBeDefined()
})
You can use supertest paired with the guide from Firebase. Below is a very basic example of testing your app, however, you can make it more complex/better by integrating mocha.
import * as admin from 'firebase-admin'
import * as testFn from 'firebase-functions-test'
import * as sinon from 'sinon'
import * as request from 'supertest'
const test = testFn()
import * as myFunctions from './get-tested' // relative path to functions code
const adminInitStub = sinon.stub(admin, 'initializeApp')
request(myFunctions.app)
.get('/helloWorld')
.expect('hello world')
.expect(200)
.end((err, res) => {
if (err) {
throw err
}
})
Would something like mock-express work for you? It should allow you to test paths without actually forcing you to make the express server.
https://www.npmjs.com/package/mock-express
I've gotten this to work using firebase-functions-test and node-mocks-http.
I have this utility class FunctionCaller.js :
'use strict';
var httpMocks = require('node-mocks-http');
var eventEmitter = require('events').EventEmitter;
const FunctionCaller = class {
constructor(aYourFunctionsIndex) {
this.functions_index = aYourFunctionsIndex;
}
async postFunction(aFunctionName,aBody,aHeaders,aCookies) {
let url = (aFunctionName[0]=='/') ? aFunctionName : `/${aFunctionName}`;
let options = {
method: 'POST',
url: url,
body: aBody
};
if (aHeaders)
options.headers = aHeaders;
if (aCookies) {
options.cookies = {};
for (let k in aCookies) {
let v = aCookies[k];
if (typeof(v)=='string') {
options.cookies[k] = {value: v};
} else if (typeof(v)=='object') {
options.cookies[k] = v;
}
}
}
var request = httpMocks.createRequest(options);
var response = httpMocks.createResponse({eventEmitter: eventEmitter});
var me = this;
await new Promise(function(resolve){
response.on('end', resolve);
if (me.functions_index[aFunctionName])
me.functions_index[aFunctionName](request, response);
else
me.functions_index.app(request, response);
});
return response;
}
async postObject(aFunctionName,aBody,aHeaders,aCookies) {
let response = await this.postFunction(aFunctionName,aBody,aHeaders,aCookies);
return JSON.parse(response._getData());
}
async getFunction(aFunctionName,aParams,aHeaders,aCookies) {
let url = (aFunctionName[0]=='/') ? aFunctionName : `/${aFunctionName}`;
let options = {
method: 'GET',
url: url,
query: aParams // guessing here
};
if (aHeaders)
options.headers = aHeaders;
if (aCookies) {
options.cookies = {};
for (let k in aCookies) {
let v = aCookies[k];
if (typeof(v)=='string') {
options.cookies[k] = {value: v};
} else if (typeof(v)=='object') {
options.cookies[k] = v;
}
}
}
var request = httpMocks.createRequest(options);
var response = httpMocks.createResponse({eventEmitter: eventEmitter});
var me = this;
await new Promise(function(resolve){
response.on('end', resolve);
if (me.functions_index[aFunctionName])
me.functions_index[aFunctionName](request, response);
else
me.functions_index.app(request, response);
});
return response;
}
async getObject(aFunctionName,aParams,aHeaders,aCookies) {
let response = await this.getFunction(aFunctionName,aParams,aHeaders,aCookies);
return JSON.parse(response._getData());
}
};
module.exports = FunctionCaller;
and my app is mounted as app :
exports.app = functions.https.onRequest(expressApp);
and my firebase.json contains :
"rewrites": [
:
:
:
{
"source": "/path/to/function", "function": "app"
}
]
In my test file at the top I do :
const FunctionCaller = require('../FunctionCaller');
let fire_functions = require('../index');
const fnCaller = new FunctionCaller(fire_functions);
and then in the test I do :
let response = await fnCaller.postFunction('/path/to/function',anObject);
and it calls my function with anObject as request.body and returns the response object.
I'm using node 8 on Firebase to get async/await etc.
For local & no-network unit tests, you could refactor the app.get("helloWorld", ...) callback into a separate function and call it with mock objects.
A general approach would be something like this:
main.js:
// in the Firebase code:
export function helloWorld(req, res) { res.send(200); }
app.get('helloWorld', helloWorld);
main.spec.js: using jasmine & sinon
// in the test:
import { helloWorld } from './main.js';
import sinon from 'sinon';
const reqMock = {};
const resMock = { send: sinon.spy() };
it('always responds with 200', (done) => {
helloWorld(reqMock, resMock);
expect(resMock.send.callCount).toBe(1);
expect(resMock.send).toHaveBeenCalledWith(200);
});
Testing is about building up confidence or trust.
I would start by Unit Testing the function in FireBase. Without more requirements defined, I would follow the documentation. Once those Unit Tests pass you can consider what type of testing you want at the Express level. Keeping in mind that you have already tested the function, the only thing to test at the Express level is if the mapping is correct. A few tests at that level should be sufficient to ensure that the mappings have not become "stale" as a result of some set of changes.
If you want to test the Express level and above without having to involve the DB, then you would look at a mocking framework to act like the database for you.
Hope this helps you think about what tests you need.
You can use postman application for unit test.
Enter following url with your project name
https://us-central1-your-project.cloudfunctions.net/hello
app.get('/hello/',(req, res) => {
res.send('hello world');
return 'success';
});

Cloud Functions for Firebase onWrite() promise always times out

I'm trying to run a cloud function and change a value on the database, but every time I return a promise with or without 'firebase-admin' module the function times out after 60 seconds.
Here is my code:
var functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.handleDisconnection = functions.database.ref('/pages/{pageId}/listeners/disconnection')
.onWrite(event => {
const eventSnapshot = event.data;
const isDisconnected = eventSnapshot.val();
const pageId = event.params.pageId;
console.log('there is a new disconnection on page ' + pageId + ', isDisconnected: ' + isDisconnected);
if (eventSnapshot.changed() && isDisconnected) {
console.log('is disconnected true');
return admin.database().ref('/pages/'+pageId+'/listeners/disconnection').set({ disconnection: false }).then(() => {
console.log('Write succeeded!'); // this never triggers
});
}
});
if (eventSnapshot.changed() && isDisconnected) {
console.log('is disconnected true');`
return admin.database.ref('/...')
.set({ disconnection: false }, err => {
if(!err) // No error
{ console.log("Set Updated"); }
});
set Methods Has a callback which passes in err as object
you can use err to get the operations status.
Turns out the problem was not in the code.
Solved it updating Node.js & npm
Use the "Current" version https://nodejs.org/en/

Resources