Hello stack overflow so when I am testing a stripe transaction with testing credit cards. I get a "bot precheckout timeout" error and cannot succeed with the tested transaction. After the stripe payment I would like to see a success method. Here is my source code.
require("dotenv").config();
const express = require("express");
const axios = require("axios");
const bodyParser = require("body-parser");
const invoiceDescription = require('./constants');
const { TOKEN, SERVER_URL, BOTTOKEN } = process.env;
const TELEGRAM_API = `https://api.telegram.org/bot${TOKEN}`;
const URI = `/webhook/${TOKEN}`;
const WEBHOOK_URL = SERVER_URL + URI;
const app = express();
app.use(bodyParser.json());
const init = async () => {
const res = await axios.get(`${TELEGRAM_API}/setWebhook?url=${WEBHOOK_URL}`);
console.log(res.data);
};
app.post(URI, async (req, res) => {
console.log(req.body)
let text = "", chatId = "", userObjectForTable = {};
if(req.body.message?.chat?.id && req.body.message?.text){
chatId = req.body.message.chat.id;
text = req.body.message.text;
await axios.post(`${TELEGRAM_API}/sendMessage`, {
chat_id: chatId,
text: text,
});
}
if (text === "/pay") {
const message = await axios.post(`${TELEGRAM_API}/sendInvoice`, {
chat_id: chatId,
title: `FinnessinPips`,
description: invoiceDescription,
payload: "Invoice for payment",
provider_token: BOTTOKEN,
currency: "USD",
prices: [{ label: "tax", amount: 4000 }],
});
userObjectForTable = {
date: new Date(message.data.result.date),
username: message.data.result.from.username
}
}
if(req.body.pre_checkout_query?.id){
//save to sqlite
console.log("saving to sqlite", req.body)
await axios.post(`${TELEGRAM_API}/sendMessage`, {
chat_id: req.body.pre_checkout_query?.from.id,
text: "Successfully Payed private url is",
});
}
return res.send();
});
app.listen(process.env.PORT || 5050, async () => {
console.log("🚀 app running on port", process.env.PORT || 5050);
await init();
});
Take a look at "7. Pre-Checkout" section in the Payment API here: https://core.telegram.org/bots/payments#7-pre-checkout
More specifically :
Your bot must reply using answerPrecheckoutQuery within 10 seconds after receiving this update or the transaction is canceled.
Related
Platform: React Native (IOS target)
Hi,
I am trying to build a way to send push notifications ( it's basically an app simillar to any datting app where you click a user and it sends them a notification to let them know I want to connect).
The notifications are being received fine on Expo Go but I don't know how to target a specific individual? Do I need backend for this?
This is my code:
const ProfilesScreen: React.FC<Props> = ({ navigation, route }) => {
const [expoPushToken, setExpoPushToken] = useState("");
const [notification, setNotification] = useState(false);
const [notificationRes, setNotifcationRes] = useState([]);
const notificationListener = useRef();
const responseListener = useRef();
const [data, setData] = useState<any>([]);
const [notificationsSentUids, setNotificationSentUids] = useState<any>([]);
const [notificationsCount, setNotificationsCount] = useState<number>(0);
const [activeIndex, setActiveIndex] = useState<number>(0);
const [storageData, setStorageData] = useState({});
async function sendPushNotification(expoPushToken) {
const message = {
to: expoPushToken,
sound: "default",
title: "My message",
body: storageData
? `${storageData?.name} wants to connect with you`
: null,
data: { someData: "goes here" },
};
await fetch("https://exp.host/--/api/v2/push/send", {
method: "POST",
headers: {
Accept: "application/json",
"Accept-encoding": "gzip, deflate",
"Content-Type": "application/json",
},
body: JSON.stringify(message),
});
}
async function registerForPushNotificationsAsync() {
let token;
if (Device.isDevice) {
const { status: existingStatus } =
await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== "granted") {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== "granted") {
alert("Failed to get push token for push notification!");
return;
}
token = (await Notifications.getExpoPushTokenAsync()).data;
setExpoPushToken(token);
} else {
// alert("Must use physical device for Push Notifications");
}
return token;
}
useEffect(() => {
registerForPushNotificationsAsync().then(token => setExpoPushToken(token));
// This listener is fired whenever a notification is received while the app is foregrounded
notificationListener.current =
Notifications.addNotificationReceivedListener(notification => {
setNotification(notification);
});
// This listener is fired whenever a user taps on or interacts with a notification (works when app is foregrounded, backgrounded, or killed)
responseListener.current =
Notifications.addNotificationResponseReceivedListener(response => {
setNotifcationRes(response);
return () => {
Notifications.removeNotificationSubscription(
notificationListener.current
);
Notifications.removeNotificationSubscription(responseListener.current);
};
}, []);
return ....
};
Hello I am trying to integrate stripe payment method in react native app everything work fine except for last step in which presentPaymentSheet has to dismiss and payment should be successfull in stripe account but it says incomplete in stripe account.
Client Side Code
const { initPaymentSheet, presentPaymentSheet } = useStripe();
const { paymentIntent, ephemeralKey, customer } =
await fetchPaymentSheetParams();
const { error } = await initPaymentSheet({
customerId: customer,
customerEphemeralKeySecret: ephemeralKey,
paymentIntentClientSecret: paymentIntent.client_secret,
});
if (!error) {
setPaymentSheetEnabled(true);
}
const openPaymentSheet = async () => {
if (!clientSecret) {
return;
}
setLoading(true);
const { error } = await presentPaymentSheet();
console.log({error});
}
Server Side Code
const functions = require("firebase-functions");
const stripe = require("stripe")(functions.config().stripe.secret);
console.log(stripe);
const admin = require("firebase-admin");
admin.initializeApp(functions.config().firebase);
exports.paymentSheet = functions.https.onRequest(async (req, res) => {
const db = admin.firestore();
const uid = db.collection("place_order").doc("id");
const id = await uid.get();
const cityRef = db.collection("orders").doc(id.data().id);
const doc = await cityRef.get();
const price = doc.data().order;
const code = db.collection("settings").doc("currency");
const currencyCode = await code.get();
const cCode = currencyCode.data().code;
const customer = await stripe.customers.create();
const ephemeralKey = await stripe.ephemeralKeys.create(
{customer: customer.id},
{apiVersion: "2022-08-01"},
);
const paymentIntent = await stripe.paymentIntents.create({
amount: price[price.length - 1].order_data[0].price * 100,
currency: cCode,
customer: customer.id,
automatic_payment_methods: {
enabled: true,
},
});
res.json({
paymentIntent: paymentIntent,
ephemeralKey: ephemeralKey.secret,
customer: customer.id,
});
});
I have the following Function that:
Listens for document (text message) creation
Grab IDs of members of a group chat
Get the FCM Tokens for each member
With a for-loop, send messages to group members
exports.sendChatMessage = functions.firestore
.document("chats/{mealID}/messages/{messageID}")
.onCreate((snap, context) => {
const data = snap.data();
const mealID = context.params.mealID;
const senderID = data.senderID;
const senderName = data.senderName;
const messageContent = data.content;
var docRef = db.collection("chats").doc(mealID);
docRef
.get()
.then((doc) => {
if (doc.exists) {
const docData = doc.data();
const mealName = docData.name;
const userStatus = docData.userStatus;
var users = docData.to;
var eligibleUsers = users.filter(
(user) => userStatus[user] == "accepted"
);
eligibleUsers.push(docData.from);
// get fcmTokens from eligibleUsers and send the messagme
db.collection("users")
.where("uid", "in", eligibleUsers)
.get()
.then((snapshot) => {
var fcmTokens = [];
var thumbnailPicURL = "";
// get thumbnailpic of the sender and collect fcmTokens
snapshot.forEach((doc) => {
if (doc.data().uid == senderID) {
thumbnailPicURL =
doc.data().thumbnailPicURL == null
? "https://i.imgur.com/8wSudUk.png"
: doc.data().thumbnailPicURL;
} else {
fcmTokens.push(doc.data().fcmToken);
}
});
// send the message fcmTokens
fcmTokens.forEach((token) => {
if (token != "") {
const fcmMessage = {
message: {
token: token,
notification: {
title: mealName,
body: senderName + ": " + messageContent,
image: thumbnailPicURL,
},
apns: {
payload: {
aps: {
category: "MESSAGE_RECEIVED",
},
MEAL_ID: mealID,
},
},
},
};
tokenManger.sendFcmMessage(fcmMessage);
}
});
return true;
});
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
return false;
}
})
.catch((error) => {
console.log("Error getting document:", error);
return false;
});
return true;
});
My send function comes from a helper file that uses the HTTP V1 protocol to build the send-request:
const { google } = require("googleapis");
const https = require("https");
const MESSAGING_SCOPE = "https://www.googleapis.com/auth/firebase.messaging";
const SCOPES = [MESSAGING_SCOPE];
const PROJECT_ID = MY_PROJECT_ID;
const HOST = "fcm.googleapis.com";
const PATH = "/v1/projects/" + PROJECT_ID + "/messages:send";
exports.getAccessToken = () => {
return new Promise(function (resolve, reject) {
const key = require("./service-account.json");
var jwtClient = new google.auth.JWT(
key.client_email,
null,
key.private_key,
SCOPES,
null
);
jwtClient.authorize(function (err, tokens) {
if (err) {
reject(err);
return;
}
resolve(tokens.access_token);
});
});
};
//send message
exports.sendFcmMessage = (fcmMessage) => {
this.getAccessToken().then(function (accessToken) {
var options = {
hostname: HOST,
path: PATH,
method: "POST",
headers: {
Authorization: "Bearer " + accessToken,
},
// … plus the body of your notification or data message
};
var request = https.request(options, function (resp) {
resp.setEncoding("utf8");
resp.on("data", function (data) {
console.log("Message sent to Firebase for delivery, response:");
console.log(data);
});
});
request.on("error", function (err) {
console.log("Unable to send message to Firebase");
console.log(err);
});
request.write(JSON.stringify(fcmMessage));
request.end();
});
};
It worked all fine in the emulator but once deployed, there're significant delays (~3 mins):
I also noticed that the console says the cloud function finishes execution BEFORE sendFcmMessage logs success messages.
I did some research online, it appears that it might have something to do with the usage of Promise but I wasn't sure if that's the sole reason or it has something to do with my for-loop.
The Problem
To summarize the issue, you are creating "floating promises" or starting other asynchronous tasks (like in sendFcmMessage) where you aren't returning a promise because they use callbacks instead.
In a deployed function, as soon as the function returns its result or the Promise chain resolves, all further actions should be treated as if they will never be executed as documented here. An "inactive" function might be terminated at any time, is severely throttled and any network calls you make (like setting data in database or calling out to FCM) may never be executed.
An indicator that you haven't properly chained the promises is when you see the function completion log message ("Function execution took...") before other messages you are logging. When you see this, you need to look at the code you are running and confirm whether you have any "floating promises" or are using callback-based APIs. Once you have changed the callback-based APIs to use promises and then made sure they are all chained together properly, you should see a significant boost in performance.
The fixes
Sending the message data to FCM
In your tokenManger file, getAccessToken() could be reworked slightly and sendFcmMessage should be converted to return a Promise:
exports.getAccessToken = () => {
return new Promise(function (resolve, reject) {
const key = require("./service-account.json");
const jwtClient = new google.auth.JWT(
key.client_email,
null,
key.private_key,
SCOPES,
null
);
jwtClient.authorize(
(err, tokens) => err ? reject(err) : resolve(tokens.access_token)
);
});
};
//send message
exports.sendFcmMessage = (fcmMessage) => {
// CHANGED: return the Promise
return this.getAccessToken().then(function (accessToken) {
const options = {
hostname: HOST,
path: PATH,
method: "POST",
headers: {
Authorization: "Bearer " + accessToken,
},
// … plus the body of your notification or data message
};
// CHANGED: convert to Promise:
return new Promise((resolve, reject) => {
const request = https.request(options, (resp) => {
resp.setEncoding("utf8");
resp.on("data", resolve);
resp.on("error", reject);
});
request.on("error", reject);
request.write(JSON.stringify(fcmMessage));
request.end();
});
});
};
However, the above code was built for googleapis ^52.1.0 and google-auth-library ^6.0.3. The modern versions of these modules are v92.0.0 and v7.11.0 respectively. This means you should really update the code to use these later versions like so:
// Import JWT module directly
const { JWT } = require('google-auth-library');
// FIREBASE_CONFIG is a JSON string available in Cloud Functions
const PROJECT_ID = JSON.parse(process.env.FIREBASE_CONFIG).projectId;
const FCM_ENDPOINT = `https://fcm.googleapis.com/v1/projects/${PROJECT_ID}/messages:send`;
const FCM_SCOPES = ["https://www.googleapis.com/auth/firebase.messaging"];
exports.sendFcmMessage = (fcmMessage) => {
const key = require("./service-account.json"); // consider moving outside of function (so it throws an error during deployment if its missing)
const client = new JWT({
email: key.client_email,
key: key.private_key,
scopes: FCM_SCOPES
});
return client.request({ // <-- this uses `gaxios`, Google's fork of `axios` built for Promise-based APIs
url: FCM_ENDPOINT,
method: "POST",
data: fcmMessage
});
}
Better yet, just use the messaging APIs provided by the Firebase Admin SDKs that handle the details for you. Just feed it the message and tokens as needed.
import { initializeApp } from "firebase-admin/app";
import { getMessaging } from "firebase-admin/messaging";
initializeApp(); // initializes using default credentials provided by Cloud Functions
const fcm = getMessaging();
fcm.send(message) // send to one (uses the given token)
fcm.sendAll(messagesArr) // send to many at once (each message uses the given token)
fcm.sendMulticast(message) // send to many at once (uses a `tokens` array instead of `token`)
The Cloud Function
Updating the main Cloud Function, you'd get:
exports.sendChatMessage = functions.firestore
.document("chats/{mealID}/messages/{messageID}")
.onCreate((snap, context) => {
const mealID = context.params.mealID;
const { senderID, senderName, content: messageContent } = snap.data();
const docRef = db.collection("chats").doc(mealID);
/* --> */ return docRef
.get()
.then((doc) => {
if (!doc.exists) { // CHANGED: Fail fast and avoid else statements
console.log(`Could not find "chat:${mealID}"!`);
return false;
}
const { userStatus, to: users, name: mealName, from: fromUser } = doc.data();
const eligibleUsers = users.filter(
(user) => userStatus[user] == "accepted"
);
eligibleUsers.push(fromUser);
// get fcmTokens from eligibleUsers and send the message
/* --> */ return db.collection("users")
.where("uid", "in", eligibleUsers) // WARNING: This will only work for up to 10 users! You'll need to break it up into chunks of 10 if there are more.
.get()
.then(async (snapshot) => {
const fcmTokens = [];
let thumbnailPicURL = "";
// get thumbnailpic of the sender and collect fcmTokens
snapshot.forEach((doc) => {
if (doc.get("uid") == senderID) {
thumbnailPicURL = doc.get("thumbnailPicURL"); // update with given thumbnail pic
} else {
fcmTokens.push(doc.get("fcmToken"));
}
});
const baseMessage = {
notification: {
title: mealName,
body: senderName + ": " + messageContent,
image: thumbnailPicURL || "https://i.imgur.com/8wSudUk.png", // CHANGED: specified fallback image here
},
apns: {
payload: {
aps: {
category: "MESSAGE_RECEIVED",
},
MEAL_ID: mealID,
},
}
}
// log error if fcmTokens empty?
// ----- OPTION 1 -----
// send the message to each fcmToken
const messagePromises = fcmTokens.map((token) => {
if (!token) // handle "" and undefined
return; // skip
/* --> */ return tokenManger
.sendFcmMessage({
message: { ...baseMessage, token }
})
.catch((err) => { // catch the error here, so as many notifications are sent out as possible
console.error(`Failed to send message to "fcm:${token}"`, err);
})
});
await Promise.all(messagePromises); // wait for all messages to be sent out
// --------------------
// ----- OPTION 2 -----
// send the message to each fcmToken
await getMessaging().sendAll(
fcmTokens.map((token) => ({ ...baseMessage, token }))
);
// --------------------
return true;
})
.catch((error) => {
console.log("Error sending messages:", error);
return false;
});
})
.catch((error) => {
console.log("Error getting document:", error);
return false;
});
});
I found out that the culprit is my queries to db. Like #samthecodingman commented, I was creating floating Promises.
Originally, I have codes like:
db.collection("users")
.where("uid", "in", eligibleUsers)
.get()
.then((snapshot) => {...}
All I needed to do is to return that call:
return db.collection("users")
.where("uid", "in", eligibleUsers)
.get()
.then((snapshot) => {...}
Although it's still not instant delivery, it's much faster now.
I am using a function in Firebase to send an email every time there is a new record in Firestore, this worked fine, but it seems that SendGrid has been updated to a new version of transactional templates.
What I had before in the body of my SendGrid transaction template was:
nombre: {{nombre}}
email: {{email}}
celular: {{celular}}
valorPropiedad: {{valorPropiedad}}
This worked correctly, that is, it sent the mail (every time there was a new record in Firestore) with the data of the new record, but now it only sends the mail but arrives without any data. I think something has changed in SendGrid? or is it a theme of my function?
Before I used Angular 5 I am now using version 6.
Here the code of my function index.js:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const SENDGRID_API_KEY = functions.config().sendgrid.key
const sgMail = require('#sendgrid/mail');
sgMail.setApiKey(SENDGRID_API_KEY);
exports.firestoreEmail2 = functions.firestore
.document('domiciliarios/{domiciliarioId}')
.onCreate((snap, context) => {
const domiciliarioId = context.params.domiciliarioId;
const db = admin.firestore()
return db.collection('domiciliarios').doc(domiciliarioId)
.get()
.then(doc => {
const domiciliario = doc.data();
const msg = {
from: 'mail1#mail.com',
to: 'mail2#mail.com',
subject: 'mySubject',
templateId: 'myTemplateId',
substitutionWrappers: ['{{', '}}'],
substitutions: {
nombre: domiciliario.nombre,
email: domiciliario.email,
celular: domiciliario.celular,
valorPropiedad: `US$ ${domiciliario.valorPropiedad}`,
}
};
return sgMail.send(msg)
})
.then(() => console.log('email sent!'))
.catch(err => console.log(err))
});
After some brute force testing this combination worked.
exports.firestoreEmail = functions.firestore
.document( 'members/{memberId}' )
.onCreate( ( snap, context ) => {
const member = snap.data();
mail.setApiKey( SENDGRID_API_KEY );
const msg = {
to: member.email,
from: "hello#angularfirebase.com",
subject: "Welcome",
templateId: "xxx",
substitutions: {
name: member.name,
email: member.email,
membershipId: member.memberId
},
dynamic_template_data:
{
name: member.name,
email: member.email,
membershipId: member.memberId
}
};
return mail.send( msg ).then( () => console.log( 'email sent to receiver2!' ) )
.catch( err => console.log( err ) );
} );
'use strict'
const functions = require('firebase-functions');
const admin=require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotification=functions.database.ref('/notifications/{user_id}/{notification_id }').onWrite((change,context) =>{
const user_id=context.params.user_id;
const notification_id=context.params.notification_id;
console.log('The user ID is :',user_id);
if(!change.after.val()){
return console.log('A notification has been deleted from database:',notification_id);
}
const fromUser=admin.database().ref(`/notifications/${user_id}/${notification_id}`).once('value');
return fromUser.then(fromUserResult=>{
const from_user_id=fromUserResult.val().from;
console.log('You have new notification from: : ', from_user_id);
const userQuery=admin.database().ref(`users/${from_user_id}/name`).once('value');
return userQuery.then(userResult=>{
const userName=userResult.val();
const deviceToken=admin.database().ref(`/users/${user_id}/device_token`).once('value');
return deviceToken.then(result =>{
const token_id=result.val();
const payload={
notification:{
title:"Friend Request",
body:`${userName} has sent you request`,
icon:"default"
}
};
return admin.messaging().sendToDevice(token_id, payload);
});
});
});
});
TypeError: Cannot read property 'from' of null
at fromUser.then.fromUserResult (/user_code/index.js:22:47)
at process._tickDomainCallback (internal/process/next_tick.js:135:7)
The only line of code where you're accessing a property called from is here:
const from_user_id=fromUserResult.val().from;
Therefore, fromUserResult.val() must be returning null.
fromUserResult is a DataSnapshot type object. According to the API documentation for the val() method, it can return null if there is no data at the location of the query. So, you will have to check for that case in your code.
I have achieved sending a notification with sender's name using this code:
'use strict'
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotification = functions.database.ref('/Notifications/{receiver_user_id}/{notification_id}')
.onWrite((data, context) =>
{
const receiver_user_id = context.params.receiver_user_id;
const notification_id = context.params.notification_id;
if(!data.after.val())
{
console.log('A notification has been deleted :' , notification_id);
return null;
}
const sender_user_id = admin.database().ref(`/Notifications/${receiver_user_id}/${notification_id}`).once('value');
return sender_user_id.then(fromUserResult =>
{
const from_sender_user_id = fromUserResult.val().from;
const userQuery = admin.database().ref(`/Users/${from_sender_user_id}/name`).once('value');
return userQuery.then(userResult =>
{
const senderUserName = userResult.val();
console.log('You have notification from :' , senderUserName);
const DeviceToken = admin.database().ref(`/Users/${receiver_user_id}/device_token`).once('value');
console.log('Checkpoint2');
return DeviceToken.then(result =>
{
const token_id = result.val();
const payload =
{
notification:
{
//from_sender_user_id : from_sender_user_id,
title: "New Chat Request",
body: `${senderUserName} wants to connect with you`,
icon: "default"
}
};
return admin.messaging().sendToDevice(token_id, payload).then(response =>
{
console.log('This was the notification Feature');
return null;
}).catch(error => {
console.error(error);
res.error(500);
});
});
});
});
});