Is there any way to unsubscribe from all topics at once?
I'm using Firebase Messaging to receive push notification from some topics subscribed, and somehow I need to unsubscribe from all topics without unsubscribing one by one. Is that possible?
You can use Instance API to query all the available topics subscribed to given token and than call multiple request to unsubscribe from all the topics.
However, if you want to stop receiving from all the topics and then the token is not useful at all, you can call FirebaseInstanceId.getInstance().deleteInstanceId() (reference: deleteInstanceId() and surround with a try/catch for a potential IOException) that will reset the instance id and than again you can subscribe to new topics from the new instance id and token.
Hope this helps someone.
If you want to avoid deleting InstanceId and moreover, to avoid missing some of the subscribed topics when saved in a local database or a remote database due to highly probable buggy implementation.
First get all subscribed topics:
var options = BaseOptions(headers: {'Authorization':'key = YOUR_KEY'});
var dio = Dio(options);
var response = await dio.get('https://iid.googleapis.com/iid/info/' + token,
queryParameters: {'details': true});
Map<String, dynamic> subscribedTopics = response.data['rel']['topics'];
Get your key here:
Firebase console -> your project -> project settings -> cloud messaging -> server key
Get your token as:
var firebaseMessaging = FirebaseMessaging();
String token;
firebaseMessaging.getToken().then((value) {
token = value;
});
Now unsubscribe from all topics:
Future<void> unsubscribeFromAllTopics() async {
for (var entry in subscribedTopics.entries) {
await Future.delayed(Duration(milliseconds: 100)); // throttle due to 3000 QPS limit
unawaited(firebaseMessaging.unsubscribeFromTopic(entry.key)); // import pedantic for unawaited
debugPrint('Unsubscribed from: ' + entry.key);
}
}
All code is in Dart.
For more information about instance id:
https://developers.google.com/instance-id/reference/server
For Java users:
If you want to do it topic wise, refer others answers and If you want to stop recieving FCM push notification, do below:
new Thread(new Runnable() {
#Override
public void run() {
try {
FirebaseInstanceId.getInstance().deleteInstanceId();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
I have placed deleteInstanceId() in a separate thread to stop java.io.IOException: MAIN_THREAD W/System.err and wrapped with try / catch to handle IOException.
I know this is not the best way but it works!
You can store list of all topics in Database and then unsubscribe from all topics when user sign-outs
final FirebaseMessaging messaging= FirebaseMessaging.getInstance();
FirebaseDatabase.getInstance().getReference().child("topics").addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
String topic = dataSnapshot.getValue(String.class);
messaging.unsubscribeFromTopic(topic);
}...//rest code
Keep a private list of subscribed topics in Preferences.
It's not that hard. Here's what I do:
public class PushMessagingSubscription {
private static SharedPreferences topics;
public static void init(ApplicationSingleton applicationSingleton) {
topics = applicationSingleton.getSharedPreferences("pushMessagingSubscription", 0);
}
public static void subscribeTopic(String topic) {
if (topics.contains(topic)) return; // Don't re-subscribe
topics.edit().putBoolean(topic, true).apply();
// Go on and subscribe ...
}
public static void unsubscribeAllTopics() {
for (String topic : topics.getAll().keySet()) {
FirebaseMessaging.getInstance().unsubscribeFromTopic(topic);
}
topics.edit().clear().apply();
// FirebaseInstanceId.getInstance().deleteInstanceId();
}
}
We can use the unsubcribeAllTopics in server side as below.
Example,
interface GetTopics {
rel: {topics: {[key: string]: any}}
}
/**
* Unsubcribe all topics except one topic
*
* Example response of `https://iid.googleapis.com/iid/info/${fcmToken}?details=true`
* {
"applicationVersion": "...",
"application": "...",
"scope": "*",
"authorizedEntity": "...",
"rel": {
"topics": {
"TOPIC_KEY_STRING": { // topic key
"addDate": "2020-12-23"
}
}
},
"appSigner": "...",
"platform": "ANDROID"
}
*/
export const unsubcribeAllTopics = async (
fcmTokens: string | string[],
exceptTopic?: string,
) => {
const headers = {
'Content-Type': 'application/json',
Authorization: `key=${process.env.FCM_SERVER_KEY}`,
}
const url = `https://iid.googleapis.com/iid/info/${fcmTokens}?details=true`
try {
const response = await fetch(url, {method: 'GET', headers: headers})
const result: GetTopics = await response.json()
const keys = Object.keys(result.rel.topics)
keys.forEach(key => {
key !== exceptTopic &&
messaging()
.unsubscribeFromTopic(fcmTokens, key)
.catch(error => {
console.error('error', {data: error})
})
})
} catch (error) {
console.error('error', {data: error})
}
}
https://gist.github.com/JeffGuKang/62c280356b5632ccbb6cf146e2bc4b9d
if you are using flutter if you want to unsubscribe from all topics use-
final FirebaseMessaging _fcm = FirebaseMessaging();
_fcm.deleteInstanceID().then((value){
print('deleted all'+value.toString());
});
Firebase.messaging.deleteToken()
Is actual answer for 2021.
try {
FirebaseInstallations.getInstance().delete()
} catch (e: IOException) {
}
Related
This question already has answers here:
Why does my function that calls an API or launches a coroutine return an empty or null value?
(4 answers)
Closed 1 year ago.
What I am trying to do: Simply return data from Firebase Cloud Function.
The function is used to create a payment order in the payment gateway's server.
My required data about the order's details are present in the function(err,data) (see below), but I need this data sent back to my Android app.
Problem I faced: I could see the data printed in the Firebase console's log but it doesn't return to my Android app.
My Firebase Cloud Function:
const functions = require("firebase-functions");
exports.order = functions.https.onCall((amnt, response) => {
const Ippopay = require('node-ippopay');
const ippopay_instance = new Ippopay({
public_key: 'YOUR_PUBLIC_KEY',
secret_key: 'YOUR_SECRET_KEY',
});
ippopay_instance.createOrder({
amount: amnt,
currency: 'DOLLAR',
payment_modes: "cc,dc,nb,cheque",
customer: {
name: "Test",
email: "test#gmail.com",
phone: {
country_code: "42",
national_number: "4376543210"
}
}
}, function (err, data) {
return data.order.order_id;
});
});
My Android client-side code:
public class Payment extends AppCompatActivity implements IppoPayListener {
Button pay;
EditText amount;
private FirebaseFunctions mFunctions;
TextView order_data;
String data;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_payment);
}
#Override
protected void onPostCreate(#Nullable Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
pay=findViewById(R.id.pay_button);
amount=findViewById(R.id.user_amount);
order_data=findViewById(R.id.data_text);
pay.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d("PAY Button clicked", "yes");
mFunctions = FirebaseFunctions.getInstance("us-central1");
mFunctions.getHttpsCallable("order").call(5).continueWith(new Continuation<HttpsCallableResult, Object>() {
#Override
public Object then(#NonNull Task<HttpsCallableResult> task) throws Exception {
HttpsCallableResult result=task.getResult();
if(result !=null)
{
data=result.getData().toString();
return result.getData().toString();
}
return null;
}
});
order_data.setText(data);
onPaymentClick();
}
});
}
/* ... */
}
I'm a Beginner so there's a high possibility of some dead silly mistakes. :)
Based on what your code looks like at the moment, you have a mix of code from a Callable Cloud Function and the older HTTP Request Cloud Function.
To return data from a callable Cloud Function, you should return a Promise, a method of running asynchronous code that returns a value. Older JavaScript and many other languages use callbacks instead, which is what you have here.
In it's simplest form, this callback-based method:
someModule.doSomething(input, function (err, result) {
// check for errors and handle result
});
would be converted to use Promises using:
new Promise((resolve, reject) => {
someModule.doSomething(
input,
(err, result) => err ? reject(err) : resolve(result) // short form of "if error, reject with an error, otherwise resolve (succeed) with result"
)
});
For errors to be handled correctly by clients, you need to wrap any errors in a functions.https.HttpsError.
Combining this together gives:
const functions = require("firebase-functions");
exports.order = functions.https.onCall((amnt, context) => {
const Ippopay = require('node-ippopay');
return new Promise((resolve, reject) => {
const ippopay_instance = new Ippopay({
public_key: 'YOUR_PUBLIC_KEY',
secret_key: 'YOUR_SECRET_KEY',
});
ippopay_instance.createOrder({
amount: amnt,
currency: 'DOLLAR',
payment_modes: "cc,dc,nb,cheque",
customer: {
name: "Test",
email: "test#gmail.com",
phone: {
country_code: "42",
national_number: "4376543210"
}
}
}, function (err, data) {
if (err) {
// something went wrong, send error back to caller
reject(new functions.https.HttpsError('unknown', 'Ippopay threw an unexpected error', err));
return;
}
// successful, send data back to caller
resolve(data.order.order_id);
});
});
});
You should also make sure you make use of context.auth to restrict access to this function. You wouldn't want to bill the wrong customer.
(Sorry for bad English)
I'm looking for a way to make user to user notifications in Android.
The system have to catch a new child event in the Database, read the data and send the notification to destination.
The struct of the DB is this:
notificationRequests
$pushid
message: "You have a new request! Open the app"
userId: "sadbijasuobru112u4124u21b" //user destination id
By doing some researches in the web I've found the possibility to use topic messages.
So, i've added this in my LoginActivity befor calling the MainActivity:
FirebaseMessaging.getInstance().subscribeToTopic("users_topic$uid")
.addOnCompleteListener {
Log.d("LoginActivity", "User registered")
}
The code works. I can send notifications from the console
Like I said before, i need automatic messages.
I've found this code on the web, but it doesn't work.
var firebase = require('firebase-admin');
var request = require('request');
var API_KEY = "Firebase Cloud Messaging Server API key"
// Fetch the service account key JSON file contents
var serviceAccount = require("./serviceAccountKey.json");
// Initialize the app with a service account, granting admin privileges
firebase.initializeApp({
credential: firebase.credential.cert(serviceAccount),
databaseURL: "https://appname.firebaseio.com/"
});
ref = firebase.database().ref();
function listenForNotificationRequests() {
var requests = ref.child('notificationRequests');
requests.on('child_added', (requestSnapshot) => {
var request = requestSnapshot.val();
sendNotificationToUser(
request.userId,
request.message,
() => {
requestSnapshot.ref.remove();
}
);
}, (error) => {
console.error(error);
});
}
function sendNotificationToUser(userID, message, onSuccess) {
request({
url: 'https://fcm.googleapis.com/fcm/send',
method: 'POST',
headers: {
'Content-Type' :' application/json',
'Authorization': 'key='+API_KEY
},
body: JSON.stringify({
notification: {
title: message
},
to : '/topics/users_topic'+userID
})
}, (error, response, body) => {
if (error) { console.error(error); }
else if (response.statusCode >= 400) {
console.error('HTTP Error: '+response.statusCode+' - '+response.statusMessage);
}
else {
onSuccess();
}
});
}
// start listening
listenForNotificationRequests();
I've implemented this code in the index.js for deployng. I've also write the entire file instead of requiring it, but it's always the same.
const functions = require('firebase-functions');
exports.sendNotifications = functions.https.onRequest((request, response) => {
require('./sendNotifications.js')
})
Any suggestions?
Thanks for the kelp!
EDIT
Does this need a billing account configurated? If yes, how can i make it with the free plan?
Does this need a billing account configurated? If yes, how can i make it with the free plan?
See https://firebase.google.com/support/faq#functions-runtime, but in short: from Node 10 you will need to enter billing information to use Cloud Functions.
You can use Node 8 until early next year without entering billing information.
Note that entering billing information does not necessarily mean you'll have to pay, as Cloud Functions has a pretty significant free tier.
The problem was that I was calling an external site. I solved it doing this:
const functions = require('firebase-functions');
var firebase = require('firebase-admin');
// Fetch the service account key JSON file contents
var serviceAccount = require("./serviceAccountKey.json");
// Initialize the app with a service account, granting admin privileges
firebase.initializeApp({
credential: firebase.credential.cert(serviceAccount),
databaseURL: "https://appname.firebaseio.com/"
});
exports.sendNotifications = functions.database.ref('/notificationRequest/{pushId}')
.onCreate( (change, context) => {
var uid = change.child('userId').val()
var notificationMessage = change.child('message').val()
var userTopic = 'users_topic'+uid
var payload = {
data: {
message: notificationMessage
}
};
var options = {
priority: 'high',
timeToLive: 60 * 60 * 24,
collapseKey: 'notification'
};
firebase.messaging().sendToTopic(userTopic, payload, options)
// eslint-disable-next-line promise/always-return
.then((response) => {
console.log('Done', response);
})
.catch((error) => {
console.log('Error: ', error);
});
return change.ref.remove();
});
I am trying to call a Google cloud function from an Android app that does not work (First call just after deployment works 90 % of the times but subsequent calls fails, nothing is displayed on firebase log console either).
public Task<String> myCloudFunction() {
return FirebaseFunctions.getInstance()
.getHttpsCallable("createUser")
.call(data)
.continueWith(task -> {
String result = (String) task.getResult().getData();
return result;
});
}
Endpoint in Functions Dashboard
https://us-central1-xyz:555.cloudfunctions.net/createUser
This is how I call it.
public void callCloudFunction() {
createFirebaseUserAccount.myCloudFunction()
.addOnCompleteListener(new OnCompleteListener<String>() {
#Override
public void onComplete(#NonNull Task<String> task) {
if (!task.isSuccessful()) {
Exception e = task.getException();
if (e instanceof FirebaseFunctionsException) {
FirebaseFunctionsException ffe = (FirebaseFunctionsException) e;
FirebaseFunctionsException.Code code = ffe.getCode();
Object details = ffe.getDetails();
} else {
Timber.d(task.getResult());
}
}
}
});
}
Here is the cloud function:
$GOOGLE_APPLICATION_CREDENTIALS is pointing to service_key.json file which contains the private key.
admin.initializeApp({
credential: admin.credential.applicationDefault(),
databaseURL: "https://XYZ.firebaseio.com"
});
exports.createUser = functions.https.onCall((data, context) => {
const callerEmail = data.email;
const callerPassword = data.password;
const callerDisplayName = data.displayName;
return admin.auth().createUser({
email: callerEmail,
emailVerified: false,
password: callerPassword,
displayName: callerDisplayName,
disabled: false
}).then(userRecord => {
// See the UserRecord reference doc for the contents of userRecord.
console.log("Successfully created new user:", userRecord.uid);
return userRecord.uid;
}).catch(error => {
console.log("Error creating new user ", error);
return error;
});
});
Thanks for reading! :)
You're not returning a promise from the the function that contains the data to send to the client. Actually, you're not passing anything at all. You should instead return the promise chain from your async work:
return admin.auth().createUser({
email: callerEmail,
emailVerified: false,
password: callerPassword,
displayName: callerDisplayName,
disabled: false
}).then(userRecord => {
// See the UserRecord reference doc for the contents of userRecord.
console.log("Successfully created new user:", userRecord.uid);
return userRecord.uid;
}).catch(error => {
console.log("Error creating new user ", error);
return error;
});
Note the new return before the whole thing. You should definitely take some time to learn about how JavaScript promises work in order to make effective use of Cloud Functions, and they will not work correctly without observing their rules and conventions.
I'm creating a chat application and i want to use fcm to send notification if the person has a new message, but i don't know how to proceed. All the tutorials i found use to send the message from firebase. But i want to send it automatically when there is a new message for the person
A possible workaround if you use firebase should be like this:
You need to store each firebase FCM token for a specific user (need to take in account here that a user can log in at the same time on his account from multiple devices) so you can store the userId and his deviceUniqueId on flutter you can get it from device_info https://pub.dev/packages/device_info:
String identifier;
final DeviceInfoPlugin deviceInfoPlugin = new DeviceInfoPlugin();
try {
if (Platform.isAndroid) {
var build = await deviceInfoPlugin.androidInfo;
identifier = build.id.toString();
} else if (Platform.isIOS) {
var data = await deviceInfoPlugin.iosInfo;
identifier = data.identifierForVendor;//UUID for iOS
}
} on PlatformException {
print('Failed to get platform version');
}
and after that to get the unique CFM token that Firebase provide for each device, you can get it using Firebase firebase_messaging plugin (https://pub.dev/packages/firebase_messaging) getToken() and insert the token to firestore (or an other database you want to store it)
FirebaseMessaging firebaseMessaging = new FirebaseMessaging();
firebaseMessaging.requestNotificationPermissions(
const IosNotificationSettings(sound: true, badge: true, alert: true));
firebaseMessaging.onIosSettingsRegistered
.listen((IosNotificationSettings settings) {
print("Settings registered: $settings");
});
firebaseMessaging.getToken().then((token){
print('--- Firebase toke here ---');
Firestore.instance.collection(constant.userID).document(identifier).setData({ 'token': token});
print(token);
});
After that you can insert one or more FCM token connected to multiple device for one user. 1 user ... n devices , 1 device ... 1 unique token to get push notifications from Firebase.
send it automatically when there is a new message for the person : now you need to call the Firestore API(is very fast indeed but need to be careful about the plan limit that you are using here) or another API call if you store the token to another db, in order to get the token/tokens for each user and send the push notifications.
To send the push notification from flutter you can use a Future async function.
P.s: I'm passing as argument a List here in order to use "registration_ids" instead of "to" and send the push notification to multiple tokens if the user has been logged in on multiple devices.
Future<bool> callOnFcmApiSendPushNotifications(List <String> userToken) async {
final postUrl = 'https://fcm.googleapis.com/fcm/send';
final data = {
"registration_ids" : userToken,
"collapse_key" : "type_a",
"notification" : {
"title": 'NewTextTitle',
"body" : 'NewTextBody',
}
};
final headers = {
'content-type': 'application/json',
'Authorization': constant.firebaseTokenAPIFCM // 'key=YOUR_SERVER_KEY'
};
final response = await http.post(postUrl,
body: json.encode(data),
encoding: Encoding.getByName('utf-8'),
headers: headers);
if (response.statusCode == 200) {
// on success do sth
print('test ok push CFM');
return true;
} else {
print(' CFM error');
// on failure do sth
return false;
}
}
You can also check the post call from postman in order to make some tests. POST request
On Headers add the:
key Authorization with value key=AAAAO........ // Project Overview -> Cloud Messaging -> Server Key
key Content-Type with value application/json
And on the body add
{
"registration_ids" :[ "userUniqueToken1", "userUniqueToken2",... ],
"collapse_key" : "type_a",
"notification" : {
"body" : "Test post",
"title": "Push notifications E"
}
}
"registration_ids" to send it to multiple tokens (same user logged in to more than on device at the same time)
"to" in order to send it to a single token (one device per user / or update always the user token that is connected with his device and have 1 token ... 1 user)
I'm making an edit to the response, in order to add that is very important to add the FCM Server Key on a trusted environment or server!
I'll list here a few related questions which I have participated with answers. I guess you'll find a lot of relevant info on using firebase cloud messaging (FCM) in a chat app.
Is FCM the only way to build a chat app ?
Suggested approach to use FCM in a chat app
Is using topics a better solution then using the fcmToken in a chat app?
Problems with FCM onMessage while app is in background
Problem: after logoff, user continues to receive notifications
Good luck!
//Notification Sending Side Using Dio flutter Library to make http post request
static Future<void> sendNotification(receiver,msg)async{
var token = await getToken(receiver);
print('token : $token');
final data = {
"notification": {"body": "Accept Ride Request", "title": "This is Ride Request"},
"priority": "high",
"data": {
"click_action": "FLUTTER_NOTIFICATION_CLICK",
"id": "1",
"status": "done"
},
"to": "$token"
};
final headers = {
'content-type': 'application/json',
'Authorization': 'key=AAAAY2mZqb4:APA91bH38d3b4mgc4YpVJ0eBgDez1jxEzCNTq1Re6sJQNZ2OJvyvlZJYx7ZASIrAj1DnSfVJL-29qsyPX6u8MyakmzlY-MRZeXOodkIdYoWgwvPVhNhJmfrTC6ZC2wG7lcmgXElA6E09'
};
BaseOptions options = new BaseOptions(
connectTimeout: 5000,
receiveTimeout: 3000,
headers: headers,
);
try {
final response = await Dio(options).post(postUrl,
data: data);
if (response.statusCode == 200) {
Fluttertoast.showToast(msg: 'Request Sent To Driver');
} else {
print('notification sending failed');
// on failure do sth
}
}
catch(e){
print('exception $e');
}
}
static Future<String> getToken(userId)async{
final Firestore _db = Firestore.instance;
var token;
await _db.collection('users')
.document(userId)
.collection('tokens').getDocuments().then((snapshot){
snapshot.documents.forEach((doc){
token = doc.documentID;
});
});
return token;
}
//Now Receiving End
class _LoginPageState extends State<LoginPage>
with SingleTickerProviderStateMixin {
final Firestore _db = Firestore.instance;
final FirebaseMessaging _fcm = FirebaseMessaging();
StreamSubscription iosSubscription;
//this code will go inside intiState function
if (Platform.isIOS) {
iosSubscription = _fcm.onIosSettingsRegistered.listen((data) {
// save the token OR subscribe to a topic here
});
_fcm.requestNotificationPermissions(IosNotificationSettings());
}
_fcm.configure(
onMessage: (Map<String, dynamic> message) async {
print("onMessage: $message");
showDialog(
context: context,
builder: (context) => AlertDialog(
content: ListTile(
title: Text(message['notification']['title']),
subtitle: Text(message['notification']['body']),
),
actions: <Widget>[
FlatButton(
child: Text('Ok'),
onPressed: () => Navigator.of(context).pop(),
),
],
),
);
},
onLaunch: (Map<String, dynamic> message) async {
print("onLaunch: $message");
// TODO optional
},
onResume: (Map<String, dynamic> message) async {
print("onResume: $message");
// TODO optional
},
);
//saving token while signing in or signing up
_saveDeviceToken(uid) async {
// FirebaseUser user = await _auth.currentUser();
// Get the token for this device
String fcmToken = await _fcm.getToken();
// Save it to Firestore
if (fcmToken != null) {
var tokens = _db
.collection('users')
.document(uid)
.collection('tokens')
.document(fcmToken);
await tokens.setData({
'token': fcmToken,
'createdAt': FieldValue.serverTimestamp(), // optional
'platform': Platform.operatingSystem // optional
});
}
}
I am using firebase cloud functions as serverside for Paypal payment. Documentations are not obvious to understand.
when I am trying to send an object from android app to firebase cloud functions, nothing has happened. I think I added it wrong. so how can I pass an object from android app to the function??
public void payout(String PayerID,String paymentId) {
// Create the arguments to the callable function.
JSONObject postData = new JSONObject();
try {
postData.put("PayerID", PayerID);
postData.put("paymentId",paymentId);
} catch (JSONException e) {
e.printStackTrace();
}
mFunctions
.getHttpsCallable("payout")
.call(postData)
.continueWith(new Continuation<HttpsCallableResult, Object>() {
#Override
public Object then(#NonNull Task<HttpsCallableResult> task)
throws Exception {
return null;
}
});
}
///////////////////////////////////////////
exports.payout=functions.https.onRequest((req,res)=>{
const sender_batch_id = Math.random().toString(36).substring(9);
const payReq=JSON.stringify({
sender_batch_header: {
sender_batch_id: sender_batch_id,
email_subject: "You have a nice payment"
},
items: [
{
recipient_type: "EMAIL",
amount: {
value: 0.90,
currency: "USD"
},
receiver: "amrmahmoudM#app.com",
note: "Thank you very much.",
sender_item_id: "item_3"
}
]
});
paypal.payout.create(payReq,(error, payout)=>{
if (error) {
console.warn(error.res);
res.status('500').end();
throw error;
}else{
console.info("payout created");
console.info(payout);
res.status('200').end();
}
});
});
exports.process = functions.https.onRequest((req, res) => {
const paymentId = req.body.paymentId;
var payerId = {
payer_id: req.body.PayerID
};
return paypal.payout.execute(paymentId, payerId, (error, payout) => {
if (error) {
console.error(error);
} else {
if (payout.state === 'approved') {
console.info('payment completed successfully, description: ',
payout.transactions[0].description);
const ref=admin.firestore().collection("Users").doc(payerId);
ref.set({'paid': true});
} else {
console.warn('payment.state: not approved ?');
}
}
}).then(r =>
console.info('promise: ', r));
});
The problem comes from the fact that in your Android app you call an HTTPS Callable Function (via mFunctions.getHttpsCallable("payout")) but your Cloud Function is not an HTTPS Callable Function but a "simple" HTTPS Function.
HTTPS Callable Functions are written like:
exports.payout = functions.https.onCall((data, context) => {
// ...
});
while HTTPS Functions are written like:
exports.payout = functions.https.onRequest((req,res)=> {
// ...
})
So you should adapt the code of your Cloud Function according to the documentation: https://firebase.google.com/docs/functions/callable
Note that another option could be to write to the database (Real Time database or Firestore) and trigger the Cloud Function with an onWrite or onCreate trigger. The advantage of this approach is that you directly save the information of the payment in the database.