I am trying to get Stripe server code working using Firestore. I found example server code that uses Firebase RTDB and am having trouble converting this code to use Firestore.
Original Firebase Function code using Real Time Database:
const functions = require('firebase-functions')
const admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase);
const stripe = require('stripe')(functions.config().stripe.testkey)
exports.stripeCharge = functions.database
.ref('/payments/{userId}/{paymentId}')
.onWrite(event => {
const payment = event.data.val();
const userId = event.params.userId;
const paymentId = event.params.paymentId;
// checks if payment exists or if it has already been charged
if (!payment || payment.charge) return;
return admin.database()
.ref(`/users/${userId}`)
.once('value')
.then(snapshot => {
return snapshot.val();
})
.then(customer => {
const amount = payment.amount;
const idempotency_key = paymentId; // prevent duplicate charges
const source = payment.token.id;
const currency = 'usd';
const charge = {amount, currency, source};
return stripe.charges.create(charge, { idempotency_key });
})
.then(charge => {
admin.database()
.ref(`/payments/${userId}/${paymentId}/charge`)
.set(charge)
})
});
My attempt to convert this to using Firestore:
const functions = require('firebase-functions')
const admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase)
const stripe = require('stripe')(functions.config().stripe.testkey)
exports.stripeCharge = functions.firestore
.document('/users/{userId}/payments/{paymentId}')
.onWrite(event => {
const payment = event.data
const userId = event.params.userId
const paymentId = event.params.paymentId
// checks if payment exists or if it has already been charged
if (!payment || payment.charge) return
return admin.firestore
.document(`/users/${userId}`)
.get()
.then(snapshot => {
return snapshot
})
.then(customer => {
const amount = payment.amount;
const idempotency_key = paymentId // prevent duplicate charges
const source = payment.token.id
const currency = 'usd'
const description = 'irl Map Fine Print'
const charge = {amount, currency, source}
return stripe.charges.create(charge, { idempotency_key })
})
.then(charge => {
admin.firestore
.fieldValue(`/users/${userId}/payments/${paymentId}/charge`)
.set(charge)
})
})
My version fails with error saying that admin.firestore.document is not a function.
This updated code will work for you! There were several issues with your code: Missing parentheses... incorrect use of fieldValue (though I am not sure how to use this correctly)... 'document' should be 'doc'
But this works:
const functions = require('firebase-functions')
const admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase)
const stripe = require('stripe')(functions.config().stripe.token)
exports.stripeCharge = functions.firestore
.document('/users/{userId}/payments/{paymentId}')
.onWrite(event => {
const payment = event.data.data()
const userId = event.params.userId
const paymentId = event.params.paymentId
// checks if payment exists or if it has already been charged
if (!payment || payment.charge) return
return admin.firestore()
.doc(`/users/${userId}`)
.get()
.then(snapshot => {
return snapshot
})
.then(customer => {
const amount = payment.price * 100 // amount must be in cents
const idempotency_key = paymentId // prevent duplicate charges
const source = payment.token.id
const currency = 'usd'
const description = 'irl Map Fine Print'
const charge = {amount, currency, source}
return stripe.charges.create(charge, { idempotency_key })
})
.then(charge => {
admin.firestore()
.doc(`/users/${userId}/payments/${paymentId}`)
.set({
charge: charge
}, { merge: true })
})
})
Related
I develop in iOS and this is the first time I'm coding in Typescript. I made my first Cloud Function that triggers when a new chat message is sent in RTDB and notifies the members that receive the message.
When I make the call to Firestore to get tokens of the user devices (userTokens) I get no errors and have the correct path, but the data returned from those promises don't show anything. When I log the "tokenList" it just says ["0"]. I think the error is when I push the promise to a list or resolve them, but I haven't managed to fix it.
The code:
import * as functions from "firebase-functions"
import * as admin from "firebase-admin"
admin.initializeApp()
export const newLastMessageDetected = functions.database
.ref('/ChatMessages/{chatID}/{messageID}/')
.onCreate(async (snap, context) => {
const values = snap.val()
const chatID = context.params.chatID
const messageID = context.params.messageID
const message = values.message
const fromID = values.fromID
const fromName = values.fromName
console.log( `LastMessage changed with chatID: ${chatID} and messageID ${messageID} `)
console.log( `Last message: ${message} by fromID: ${fromID} and by name ${fromName}`)
const payload = {
notification: {
title: fromName,
body: message
}
}
let membersSnapshotRef = admin.database().ref('/Members/' + chatID + '/')
return membersSnapshotRef.once('value')
.then(dataSnapshot => {
const promises = []
console.log('*** GOT SNAPSHOT ***')
dataSnapshot.forEach((element) => {
if (element.key != fromID && element.val() === true) {
const p = admin.firestore().collection('userTokens').doc(`${element.key}`).collection('devices').get()
console.log('*** GOT PROMISE ***')
console.log(`*** The recipientID: ${element.key} ***`)
console.log(`${p}`)
promises.push(p)
}
})
return Promise.all(promises).then(snapshot => {
console.log('*** GOT RETURNED PROMISES ***')
const tokenList = []
const data = snapshot.keys()
for (const token in data) {
console.log(`${token}`)
tokenList.push(token)
}
console.log(`${tokenList}`)
return admin.messaging().sendToDevice(tokenList, payload).then(result => {
console.log("Notification sent!");
return null;
})
})
.catch(error => {
console.log(`${error}`)
})
})
})
When you use Promise.all(), the result of the promise it returns is always going to be an array.
Promise.all(promises).then(snapshot => {
// snapshot is an array of results with one element for each of the promises
})
You need to iterate that array to find the results of all the promises you stored in the promises array. snapshot.keys() does not iterate that array - it is just giving you a list of numbers that are the indexes of those array. Try using snapshot.forEach() instead.
You might want to review some documentation for promise.all.
I actually really messed up because I tried to retrieve the data on the query;
didn't realize that the first loop was on the retrieved queries, so I had to do another on the documents retrieved. The device tokens are each of the documentIDs with the timestamp stored as the data.
The working code:
import * as functions from "firebase-functions"
import * as admin from "firebase-admin"
admin.initializeApp()
export const newLastMessageDetected = functions.database
.ref('/ChatMessages/{chatID}/{messageID}/')
.onCreate(async (snap, context) => {
const values = snap.val()
const chatID = context.params.chatID
const messageID = context.params.messageID
const message = values.message
const fromID = values.fromID
const fromName = values.fromName
console.log( `LastMessage changed with chatID: ${chatID} and messageID ${messageID} `)
console.log( `Last message: ${message} by fromID: ${fromID} and by name ${fromName}`)
const payload = {
notification: {
title: fromName,
body: message
}
}
let membersSnapshotRef = admin.database().ref('/Members/' + chatID + '/')
return membersSnapshotRef.once('value')
.then(dataSnapshot => {
const promises = []
// const docIDS = []
console.log('*** GOT SNAPSHOT ***')
dataSnapshot.forEach((element) => {
if (element.key != fromID && element.val() === true) {
const doc = admin.firestore().collection('userTokens').doc(`${element.key}`).collection('devices')
const p = doc.get()
// const docID = doc.id
console.log('*** GOT PROMISE ***')
console.log(`*** The recipientID: ${element.key} ***`)
// console.log(`*** The docID: ${docID} ***`)
promises.push(p)
// docIDS.push(docID)
}
})
return Promise.all(promises)
})
.then(async querySnapshot => {
console.log('*** GOT RETURNED PROMISES ***')
const tokenList = []
querySnapshot.forEach(snap => { // first here
console.log(`${snap.id} *** `)
console.log(`${snap} *** `)
snap.forEach(doc => { // then here
console.log(`${doc.id}`)
tokenList.push(doc.id)
})
})
await admin.messaging().sendToDevice(tokenList, payload)
console.log("Notification sent!")
return null
})
.catch(error => {
console.log(`${error}`)
})
})
I am attempting to retrieve the boolean child (notificationsOn) of an object stored as a Firestore document to see if the rest of a function should be executed.
The overall function works to completion without this portion, but adding the portion from let threadDoc to the if statement presents a "threadDoc.get is not a function" error. I think my syntax is wrong but I don't know how, as a similar function works in a later part of the function:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.sendDMNotification =functions.firestore.document('/dm_threads/{thread_id}/messages/{message_id}').onCreate((snapshot, context) => {
const newMessage = snapshot.data();
const senderName = newMessage.authorName;
const senderID = newMessage.authorUID;
const messageText = newMessage.message;
const recipientID = newMessage.recipientUID;
var notificationsOn = null;
let deviceTokenQuery = admin.firestore().collection(`/users/${recipientID}/device_tokens/`);
var idsToBeSorted = [senderID, recipientID];
idsToBeSorted.sort();
var threadID = idsToBeSorted[0] + idsToBeSorted[1];
console.log(recipientID);
console.log(threadID);
let threadDoc = admin.firestore().document(`users/${recipientID}/threads/${threadID}/`);
return threadDoc.get().then(doc => {
let notificationsOn = doc.data.notificationsOn;
console.log(notificationsOn);
if (notificationsOn !== false){
return deviceTokenQuery.get().then(querySnapshot => {
let tokenShapshot = querySnapshot.docs;
const notificationPromises = tokenShapshot.map(doc => {
let token_id = doc.data().tokenID;
const payload = {
data: {
title: senderName,
body: messageText,
senderID: senderID,
senderName: senderName
}
};
return admin.messaging().sendToDevice(token_id, payload).then(response => {
console.log("Notification sent: ", response);
})
.catch(error => {
console.log("Error sending message: ", error);
});
});
return Promise.all(notificationPromises);
});
}
return;
});
});
admin.firestore().document() was supposed to be admin.firestore().collection(...).doc(...)
This fixed my problem
I think you meant to say admin.firestore() instead of functions.firestore.
I'm working on a project with angular 6, Stripe elements and Google's Firebase (with Cloud Functions). All are new to me!
For the life of me, I'm unable to figure out how I can return 'something' which states that the payment has been successful. Stripe API docs, state that it only returns an error call if there is an 'error'...
I can see that the card is being charged successfully from the charge object in Firebase.
What can I use to query this and return the 'status: paid' value to my front-end...so I can use an *ngIf to display confirmation/failure message?
I know i'm missing something dead simple here...! I really appreciate any help with this guys.
index.js (cloud function)
const functions = require('firebase-functions');
const admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase);
const stripe = require('stripe')
(functions.config().stripe.testkey)
exports.stripeCharge = functions.database
.ref('/payments/{paymentId}')
.onWrite((change, context) => {
const payment = change.after.val();
const paymentId = context.params.paymentId;
// checks if payment exists or if it has already been charged
if (!payment || payment.charge) {
return
}
return admin.database()
.ref('/users/')
.once('value')
.then(snapshot => {
return snapshot.val()
})
.then(customer => {
const amount = payment.amount;
const idempotency_key = paymentId; // prevent duplicate charges
const source = payment.token.id;
const currency = 'gbp';
const charge = { amount, currency, source };
return stripe.charges.create(charge, { idempotency_key });
})
.then(charge => {
admin.database()
.ref(`/payments/${paymentId}/charge`)
.set(charge)
return true;
})
});
Payment.service.ts
import { Injectable } from '#angular/core';
import { AngularFireDatabase } from 'angularfire2/database';
#Injectable()
export class PaymentService {
constructor(private db: AngularFireDatabase) {}
// save the token to firebase, triggering the cloud function
processPayment(token: any, amount) {
const payment = { token, amount }
return this.db.list('/payments/').push(payment)
}
}
payment.component.ts (here's my onSubmit handler for the checkout)
async onSubmit(form: NgForm) {
//this.paymentProcess = true;
const { token, error } = await stripe.createToken(this.card, {
name: this.contactName,
email: this.contactEmail
});
if (error) {
console.log('Something is wrong:', error);
} else {
console.log('Success!', token);
this.paymentSvc.processPayment(token, this.amount);
}
this.card.clear();
}
You should modify your Cloud Function code as follows, in order to return the promise returned by the asynchronous .set(charge) method.
exports.stripeCharge = functions.database
.ref('/payments/{paymentId}')
.onWrite((change, context) => {
const payment = change.after.val();
const paymentId = context.params.paymentId;
// checks if payment exists or if it has already been charged
if (!payment || payment.charge) {
return
}
return admin.database()
.ref('/users/')
.once('value')
.then(snapshot => {
return snapshot.val()
})
.then(customer => {
const amount = payment.amount;
const idempotency_key = paymentId; // prevent duplicate charges
const source = payment.token.id;
const currency = 'gbp';
const charge = { amount, currency, source };
return stripe.charges.create(charge, { idempotency_key });
})
.then(charge => {
return admin.database() <-- Here return !!!!!!
.ref(`/payments/${paymentId}/charge`)
.set(charge);
//return true; <-- Here don't return !!!!!!
})
});
In order to "return the 'status: paid' value to the front-end..", just set a listener to the /payments/${paymentId}/charge path and as soon as the charge has the correct status value, update your front-end.
Finally, note that with:
...
.then(charge => {
admin.database()
.ref(`/payments/${paymentId}/charge`)
.set(charge)
return true;
})
you were returning the value true to the Cloud Function platform, (indicating that the Function can be terminated) before the set() asynchronous operation was completed.
I perform the following task, during the registration of users for the first few months we did not save images of users in Firebase Cloud Storage and took a link that was received from Facebook. Now faced with the problem that some links to images have become expired. Because of this, I decided to make the cloud function and run it once as a script, so that it went through to users who have only one link to the image (which means that this is the first link received from facebook), take the facebook user id and request current profile image. I got a json file with the given users from Firebase, then I get links for each user separately, if the user is deleted then I process this error in a separate catch so that it does not stop the work of other promises. But after running this cloud function, I ran into this error because of this, for almost all users this operation was not successful. Even I increased the memory size in cloud function to 2 gigabytes. Please tell me how it can be fixed?
{ Error: read ECONNRESET
at exports._errnoException (util.js:1018:11)
at TLSWrap.onread (net.js:568:26) code: 'ECONNRESET', errno: 'ECONNRESET', syscall: 'read' }
My function
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const account_file = require('../account_file.json');
var FB = require('fb');
const path = require('path');
const imageDownloader = require('image-downloader');
const os = require('os');
const shortid = require('shortid');
const imageManager = require('../../lib/Images/image-manager.js');
module.exports = functions.https.onRequest((req, res) => {
const token = req.header('X-Auth-Token');
var errorsCount = 0;
return admin.auth().verifyIdToken(token)
.then(function(decodedToken) {
const adminID = decodedToken.uid;
console.log('adminID is', adminID);
const users = account_file['users'];
var fixPhotoPromises = [];
users.forEach(function(user) {
const userID = user['localId'];
const fixPhotoPromise = fixPhoto(userID).catch(error => {
console.log(error);
errorsCount += 1;
});
fixPhotoPromises.push(fixPhotoPromise);
});
return Promise.all(fixPhotoPromises);
}).then(results => {
console.log('results.length', results.length, 'errorsCount', errorsCount);
console.log('success all operations');
const successJSON = {};
successJSON["message"] = "Success operation";
return res.status(200).send(successJSON);
}).catch(error => {
console.log(error);
const errorJSON = {};
errorJSON["error"] = error;
return res.status(error.code).send(errorJSON);
});
});
function fixPhoto(userID) {
var authUser = {};
var filename = '';
return new Promise((resolve, reject) => {
return admin.auth().getUser(userID)
.then(userModel => {
const user = userModel.toJSON();
const facebookID = user['providerData'][0]['uid'];
const userID = user['uid'];
authUser = {'userID' : userID, 'facebookID' : facebookID};
const userImagesPromise = admin.database().ref()
.child('userImages')
.child(userID)
.once('value');
return Promise.all([userImagesPromise])
}).then(results => {
const userImagesSnap = results[0];
if (userImagesSnap.val() !== null && userImagesSnap.val() !== undefined) {
const userProfileImagesDict = userImagesSnap.val()['userProfileImages'];
const keys = Object.keys(userProfileImagesDict);
var userProfileImages = [];
keys.forEach(function(key){
const userProfileImage = userProfileImagesDict[key];
userProfileImages.push(userProfileImage);
});
if (userProfileImages.length > 1) {
const status = 'user has more than one image';
return resolve(status);
}
}
const facebookAppID = functions.config().facebook.appid;
const facebookAppSecret = functions.config().facebook.appsecret;
const facebookAccessPromise = FB.api('oauth/access_token', {
client_id: facebookAppID,
client_secret: facebookAppSecret,
grant_type: 'client_credentials'
});
return Promise.all([facebookAccessPromise]);
}).then(results => {
const facebookResult = results[0];
const facebookAccessToken = facebookResult['access_token'];
const profileImageURL = 'https://graph.facebook.com/' + authUser.facebookID + '/picture?width=9999&access_token=' + facebookAccessToken;
const shortID = shortid.generate() + shortid.generate() + shortid.generate();
filename = shortID + ".jpg";
const tempLocalFile = path.join(os.tmpdir(), filename);
const options = {
url: profileImageURL,
dest: tempLocalFile // Save to /path/to/dest/image.jpg
};
const imageDownloaderPromise = imageDownloader.image(options);
return Promise.all([imageDownloaderPromise])
}).then(results => {
const imageDownloaderResult = results[0];
const userID = authUser.userID;
const localImagePath = imageDownloaderResult['filename'];
const imageManagerPromise = imageManager.saveUserImageToCloudStorage(localImagePath, filename, userID);
return Promise.all([imageManagerPromise]);
}).then(results => {
const result = results[0];
return resolve(result);
}).catch(function(error) {
reject(error)
})
});
}
exports.saveUserImageToCloudStorage = function saveUserImageToCloudStorage(localImagePath, filename, userID) {
const bucketName = functions.config().googlecloud.defaultbacketname;
const bucket = gcs.bucket(bucketName);
const profileImagePath = path.normalize(path.join('userImages', userID, 'profileImages', filename));
const profileImageFile = bucket.file(profileImagePath);
return new Promise((resolve, reject) => {
bucket.upload(localImagePath, {destination: profileImagePath})
.then(() => {
const config = {
action: 'read',
expires: '03-01-2500'
};
const userRefPromise = admin.database().ref()
.child('users')
.child(userID)
.once('value');
return Promise.all([profileImageFile.getSignedUrl(config), userRefPromise])
}).then(function(results) {
const url = results[0][0];
const userSnap = results[1];
if (userSnap.val() === null || userSnap.val() === undefined) {
return resolve('user was deleted from database');
}
const userModel = userSnap.val();
const userCheckID = userModel['id'];
if (userCheckID !== userID) {
return reject("WARNING userCheckID !== userID");
}
// save to database
const userImagesRef = admin.database().ref().child('userImages')
.child(userID)
.child('userProfileImages')
.push();
const timeStamp = timestamp.now();
const imageModelID = userImagesRef.key;
const userImagesRefPromise = userImagesRef.update({
'path': url,
'id': imageModelID,
'fileName': filename,
'timeStamp': timeStamp
});
const userRef = admin.database().ref()
.child('users')
.child(userID)
.child('currentProfileImage');
const userRefPromise = userRef.update({
'path': url,
'id': imageModelID,
'fileName': filename,
'timeStamp': timeStamp
});
return Promise.all([userImagesRefPromise, userRefPromise]);
}).then(() => {
const successJSON = {};
successJSON["message"] = "Success operation";
return resolve(successJSON);
}).catch(function(error) {
return reject(error);
});
});
};
I added this code when init google cloud storage and I did not have this error anymore.
var gcs = require('#google-cloud/storage')({keyFilename: "service-account-credentials.json"});
gcs.interceptors.push({
request: function(reqOpts) {
reqOpts.forever = false
return reqOpts
}
});
I am trying to fire a notification using Cloud Functions for Firebase. I can get it to console log stating a message has been fired, but can't actually get the notification to work in the browser. Can anyone see a flaw?
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.newMessageAlert = functions.database.ref('/messages/{message}').onWrite((event) => {
const message = event.data.val();
const getTokens = admin.database().ref('users').once('value').then((snapshot) => {
const tokens = [];
snapshot.forEach((user) => {
const token = user.child('token').val();
if (token) tokens.push(token);
});
return tokens;
});
const getAuthor = admin.auth().getUser(message.uid);
Promise.all([getTokens, getAuthor]).then(([tokens, author]) => {
const payload = {
notification: {
title: `Hot Take from ${author.displayName}`,
body: message.content,
icon: author.photoURL
}
};
admin.messaging().sendToDevice(tokens, payload).then((resp) =>{
console.log("IT WORKED", resp);
});
});
});