How to call firebase cloud function to verify a purchase - android

I've been thinking of using Google API directly to reward users (update user data on firestore) after verifying purchase through:
GET https://www.googleapis.com/androidpublisher/v3/applications/packageName/purchases/products/productId/tokens/token
But the authorization step is somewhat tricky. How do I achieve it with this Cloud Function I have got from one of you:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const {google} = require("googleapis");
const publisher = google.androidpublisher('v2');
const authClient = new google.auth.JWT({
email: 'Service Account Email',
key: 'BEGIN PRIVATE KEY*************END PRIVATE KEY',
scopes: ['https://www.googleapis.com/auth/androidpublisher']
});
admin.initializeApp();
exports.validatePurchases = functions.database
.ref('/purchases/{uId}/{orderId}')
.onCreate((event, context) => {
const purchase = event.val();
if (purchase.is_processed === true) {
console.log('Purchase already processed!, exiting');
return null;
}
const orderId = context.params.orderId;
const dbRoot = event.ref.root;
const package_name = purchase.package_name;
const sku = purchase.sku;
const my_token = purchase.token;
authClient.authorize((err, result) => {
if (err) {
console.log(err);
}
publisher.purchases.products.get({
auth: authClient,
packageName: package_name,
productId: sku,
token: my_token
}, (err, response) => {
if (err) {
console.log(err);
}
// Result Status must be equals to 200 so that the purchase is valid
if (response.status === 200) {
return event.ref.child('is_validated').set(true);
} else {
return event.ref.child('is_validated').set(false);
}
});
});
return null;
});
Please spot errors and identify if it'll work. How do I authorize and deploy the function?
Thanks

Related

Flutter Firebase Authentication requires 2 clicks for sign in

I just connected my app with firebase authentication but when i'm trying to click login button : it says
W/System ( 6835): Ignoring header X-Firebase-Locale because its value was null.
And here is my code about firebase authentication :
`
void signIn() async{
final User? user = (await auth.signInWithEmailAndPassword(email: mailController.text, password: passController.text)).user;
final uid = user!.uid;
if(uid != null){
if(this.mounted){
setState((){
success = 2;
userMail = user.email!;
userName = userMail;
userName = userMail.replaceFirst(userName[0],"");
userName = userName.substring(0,userName.indexOf("#"));
userName = userName.replaceFirst(userName[0],userName[0].toUpperCase());
});
}}
else{
setState((){
success = 3;
});
}
}
`
if(success == 2){
if(userMail.startsWith("1")){
Navigator.pushReplacement(context,MaterialPageRoute(builder:(context) => OgrenciPage(mail:userName)));
}
``
I've tried to creating sha-1 key, closing bluetooth connection but it never works.
I also face this problem earlier then solve this by making the function Future asyn{} and by using try catch block.
In my case i created the login class model where I perform all All login/signup functionalities
Here is the example code where you can see how to manage this:
class Login {
FirebaseAuth firebaseAuth = FirebaseAuth.instance;
Future<void> signInUser(
String email, String password, BuildContext context) async {
try {
await firebaseAuth.signInWithEmailAndPassword(
email: email, password: password);
} on FirebaseAuthException catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
e.toString(),
),
backgroundColor: Colors.redAccent,
duration: const Duration(seconds: 2),
),
);
} catch (error) {
rethrow;
}
}
}

How read document property inside Firestore Trigger

I am trying to send notifications to an Android app's users with firebase cloud messaging. I am using cloud firestore triggers but when trying to access a user node's properties, they are undefined.
Here is my index.js :
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.notifyNewMessage = functions.firestore
.document('conversations/{conversation}/messages/{message}')
.onCreate((docSnapshot, context) => {
const message = docSnapshot.data();
const recipientId = message['recipientId'];
const senderName = message['senderName'];
return admin.firestore().collection("users").doc(recipientId).get().then(userDoc => {
const registrationTokens = userDoc.registrationTokens;
console.log("registrationTokens = "+ registrationTokens);
const notificationBody = message['message'];
const payload = {
notification : {
title : senderName + " sent you a message,",
body: notificationBody,
clickAction: "ConversationActivity"
},
data : {
contactName : senderName,
userId : message['senderId']
}
}
return admin.messaging().sendToDevice(registrationTokens, payload).then(response => {
const stillRegisteredTokens = registrationTokens;
response.results.forEach((result, index) => {
const error = result.error;
if (error){
const failedRegistrationToken = stillRegisteredTokens['index'];
if (error.code === 'messaging/invalid-registration-token'
|| error.code === 'messaging/registration-token-not-registered') {
const failedIndex = stillRegisteredTokens.indexOf(failedRegistrationToken)
if (failedIndex > -1) {
stillRegisteredTokens.splice(failedIndex, 1);
}
}
}
})
return admin.firestore().doc("users/" + recipientId).update({
registrationTokens: stillRegisteredTokens
})
})
})
})
Because of that I get an error "sendDevice() argument must be non-empty array or non null string"
UPDATE
registrationTokens were undefined because I called userDoc instead of userDoc.data()
Now registrationTokens is not null nor empty but I still get the error :
Registration token(s) provided to sendToDevice() must be a non-empty string or a non-empty array.

Firebase cloud function gets triggered twice. First time 'error', second time 'ok'

Just trying to impletment Stripe Payment into my Android App.
The trouble i have is that my cloud function is triggered twice when i enter a credit card in my app. the first trigger returns an "error" status and the second trigger returns an "ok" status
Here is the code i use to save the Stripe token to my firebase realtime database:
if (cardToSave != null) {
stripe.createToken(
cardToSave,
object:TokenCallback {
override fun onSuccess(token: Token?) {
val currentUser = FirebaseAuth.getInstance().currentUser?.uid
val database = FirebaseDatabase.getInstance()
val pushId = database.getReference("stripe_customers/$currentUser/sources/").push().key
val ref = database.getReference("stripe_customers/$currentUser/sources/$pushId/token/")
//save the token id from the "token" object we received from Stripe
ref.setValue(token?.id)
.addOnSuccessListener {
Log.d(TAG, "Added Stripe Token to database successfully")
}
.addOnFailureListener {
Log.d(TAG, "Failed to add Token to database")
}
}
...
Here is the cloud function i copied straight from Stripe's example in their github repo:
// Add a payment source (card) for a user by writing a stripe payment source token to Realtime database
exports.addPaymentSource = functions.database
.ref('/stripe_customers/{userId}/sources/{pushId}/token').onWrite((change, context) => {
const source = change.after.val();
if (source === null){
return null;
}
return admin.database().ref(`/stripe_customers/${context.params.userId}/customer_id`)
.once('value').then((snapshot) => {
return snapshot.val();
}).then((customer) => {
return stripe.customers.createSource(customer, {source});
}).then((response) => {
return change.after.ref.parent.set(response);
}, (error) => {
return change.after.ref.parent.child('error').set(userFacingMessage(error));
}).then(() => {
return reportError(error, {user: context.params.userId});
});
});
Any help would be appreciated!
EDIT:
index.js
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const logging = require('#google-cloud/logging');
//functions.config() is firebase's environment variables
const stripe = require('stripe')(functions.config().stripe.token);
const currency = functions.config().stripe.currency || 'USD';
// [START chargecustomer]
// Charge the Stripe customer whenever an amount is written to the Realtime database
exports.createStripeCharge = functions.database.ref('/stripe_customers/{userId}/charges/{id}')
.onCreate((snap, context) => {
const val = snap.val();
// Look up the Stripe customer id written in createStripeCustomer
return admin.database().ref(`/stripe_customers/${context.params.userId}/customer_id`)
.once('value').then((snapshot) => {
return snapshot.val();
}).then((customer) => {
// Create a charge using the pushId as the idempotency key
// protecting against double charges
const amount = val.amount;
const idempotencyKey = context.params.id;
const charge = {amount, currency, customer};
if (val.source !== null) {
charge.source = val.source;
}
return stripe.charges.create(charge, {idempotency_key: idempotencyKey});
}).then((response) => {
// If the result is successful, write it back to the database
return snap.ref.set(response);
}).catch((error) => {
// We want to capture errors and render them in a user-friendly way, while
// still logging an exception with StackDriver
return snap.ref.child('error').set(userFacingMessage(error));
}).then(() => {
return reportError(error, {user: context.params.userId});
});
});
// [END chargecustomer]]
// When a user is created, register them with Stripe
exports.createStripeCustomer = functions.auth.user().onCreate((user) => {
return stripe.customers.create({
email: user.email,
}).then((customer) => {
return admin.database().ref(`/stripe_customers/${user.uid}/customer_id`).set(customer.id);
});
});
// Add a payment source (card) for a user by writing a stripe payment source token to Realtime database
exports.addPaymentSource = functions.database
.ref('/stripe_customers/{userId}/sources/{pushId}/token').onWrite((change, context) => {
const source = change.after.val();
if (source === null){
return null;
}
return admin.database().ref(`/stripe_customers/${context.params.userId}/customer_id`)
.once('value').then((snapshot) => {
return snapshot.val();
}).then((customer) => {
return stripe.customers.createSource(customer, {source:source});
}).then((response) => {
return change.after.ref.parent.set(response);
}, (error) => {
return change.after.ref.parent.child('error').set(userFacingMessage(error));
}).then(() => {
return reportError(error, {user: context.params.userId});
});
});
// When a user deletes their account, clean up after them
exports.cleanupUser = functions.auth.user().onDelete((user) => {
return admin.database().ref(`/stripe_customers/${user.uid}`).once('value').then(
(snapshot) => {
return snapshot.val();
}).then((customer) => {
return stripe.customers.del(customer.customer_id);
}).then(() => {
return admin.database().ref(`/stripe_customers/${user.uid}`).remove();
});
});
// To keep on top of errors, we should raise a verbose error report with Stackdriver rather
// than simply relying on console.error. This will calculate users affected + send you email
// alerts, if you've opted into receiving them.
// [START reporterror]
function reportError(err, context = {}) {
// This is the name of the StackDriver log stream that will receive the log
// entry. This name can be any valid log stream name, but must contain "err"
// in order for the error to be picked up by StackDriver Error Reporting.
const logName = 'errors';
const log = logging.log(logName);
// https://cloud.google.com/logging/docs/api/ref_v2beta1/rest/v2beta1/MonitoredResource
const metadata = {
resource: {
type: 'cloud_function',
labels: {function_name: process.env.FUNCTION_NAME},
},
};
// https://cloud.google.com/error-reporting/reference/rest/v1beta1/ErrorEvent
const errorEvent = {
message: err.stack,
serviceContext: {
service: process.env.FUNCTION_NAME,
resourceType: 'cloud_function',
},
context: context,
};
// Write the error log entry
return new Promise((resolve, reject) => {
log.write(log.entry(metadata, errorEvent), (error) => {
if (error) {
return reject(error);
}
return resolve();
});
});
}
// [END reporterror]
// Sanitize the error message for the user
function userFacingMessage(error) {
return error.type ? error.message : 'An error occurred, developers have been alerted';
}

Notification sent by using firebase functions is not received on Android phone

My application is simply order app and I want admin get notification when new order is placed.
So to do that I send notification to device with token Id by using firebase functions.
'use-strict'
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotification = functions.database.ref('admin/{user_id}/notification/{notification_id}').onCreate((snapshot, context) => {
console.log("hehehehehehehehe");
const user_id=context.params.user_id;
const getDeviceTokensPromise = admin.database().ref(`admin/${user_id}`).once('value');
//let tokensSnapshot;
//let tokes;
return Promise.all([getDeviceTokensPromise]).then(result => {
//const registrationToken= result[0].tokenid.val();
//console.log(registrationToken);
const payload = {
notification: {
title: 'You have a new follower!',
body: 'Yeni sifaris var',
}
};
return admin.messaging().sendToDevice('fOCU86Rhhb4:APA91bHwJZInZYGp9cIDc7PyCw48QcEvvMOMuFepYcCTvkxlCJp8_Ieq1Ikwd9xNoU2rfTA9paRqCTLAuhUlZgF952AvpBstGdGRWMK8lCR2MHgHn6xzbvyxFEu-auRYexnPYmOnlTB1',payload).then((response) => {
return console.log('Successfully sent message:', response);
}).catch((error) => {
return console.log('Error sending message:', error);
});
});
});
Although I got the success response message in console log, my android phone couldn't get any notification.
Successfully sent message: { results: [ { messageId: '0:1542910779596746%095eb9af095eb9af' } ],
canonicalRegistrationTokenCount: 0,
failureCount: 0,
successCount: 1,
multicastId: 6551370257673400000 }
What is wrong with my code. I am new to firebase. I need your help.

How to create firestore transaction where target document needs to be found

I am looking for a way to create a firestore transaction where i find a document from a query and then modify this document in a transaction.
Something along those lines (kotlin):
firestore.runTransaction { transaction ->
val snapshot = transaction.get(db.collection("document")
.whereEqualTo("someField", null)
.orderBy("creationDate", ASCENDING)
.limit(1L))
val myObject = snapshot.toObject(MyObject::class.java)
myObject.someFiled = "123"
transaction.set(snapshot.reference, myObject)
}
The problem here is that the query returned by the .limit(1) method is not a DocumentReference, which is the only type the transaction accepts. Therefore my question is, how can such a transaction be achieved in java/kotlin?
I have seen something similar in this blog post using the admin sdk:
return trs.get(db.collection('rooms')
.where('full', '==', false)
.where('size', '==', size)
.limit(1));
After investigation looks like you can't do that in Kotlin/Java it is not supported. You will have to create cloud function and do something similar to:
exports.executeTransaction = functions.https.onRequest(async (req, res) => {
const db = admin.firestore();
const query = db
.collection('collection')
.where('name', '==', 'name')
.limit(1);
db.runTransaction(transaction => {
return transaction
.get(query)
.then((querySnapshot) => {
const gameDocSnapshot = querySnapshot.docs[0];
const gameData = gameDocSnapshot.data();
transaction.update(gameDocSnapshot.ref, { name: 'change' });
return gameData;
})
})
.then((gameData) => {
res.send(gameData);
console.log('Transaction successfully committed!', gameData);
})
.catch((error) => {
res.send('Transaction failed:' + error);
console.log('Transaction failed:', error);
});
});
I don't know about java/kotlin but here is how I did it in TypeScript/JavaScript in a Cloud Function.
const beerTapIndex: number = parseInt(req.params.beerTapIndex);
const firestore: FirebaseFirestore.Firestore = admin.firestore();
firestore
.runTransaction((transaction: FirebaseFirestore.Transaction) => {
const query: FirebaseFirestore.Query = firestore
.collection('beerOnTap')
.where('tapIndexOrder', '==', beerTapIndex)
.limit(1);
return transaction
.get(query)
.then((snapshot: FirebaseFirestore.QuerySnapshot) => {
const beerTapDoc: FirebaseFirestore.QueryDocumentSnapshot = snapshot.docs[0];
const beerTapData: FirebaseFirestore.DocumentData = beerTapDoc.data();
const beerTapRef: FirebaseFirestore.DocumentReference = firestore
.collection('beerOnTap')
.doc(beerTapDoc.id);
transaction.update(beerTapRef, {enabled: !beerTapData.enabled});
return beerTapData;
})
})
.then((beerTapData: FirebaseFirestore.DocumentData) => {
console.log('Transaction successfully committed!', beerTapData);
})
.catch((error: Error) => {
console.log('Transaction failed:', error);
});
Plan JavaScript Version
const beerTapIndex = parseInt(req.params.beerTapIndex);
const firestore = admin.firestore();
firestore
.runTransaction((transaction) => {
const query = firestore
.collection('beerOnTap')
.where('tapIndexOrder', '==', beerTapIndex)
.limit(1);
return transaction
.get(query)
.then((snapshot) => {
const beerTapDoc = snapshot.docs[0];
const beerTapData = beerTapDoc.data();
const beerTapRef = firestore
.collection('beerOnTap')
.doc(beerTapDoc.id);
transaction.update(beerTapRef, {enabled: !beerTapData.enabled});
return beerTapData;
})
})
.then((beerTapData) => {
console.log('Transaction successfully committed!', beerTapData);
})
.catch((error) => {
console.log('Transaction failed:', error);
});
Found my answer here: https://medium.com/#feloy/building-a-multi-player-board-game-with-firebase-firestore-functions-part-1-17527c5716c5
Async/Await Version
private async _tapPouringStart(req: express.Request, res: express.Response): Promise<void> {
const beerTapIndex: number = parseInt(req.params.beerTapIndex);
const firestore: FirebaseFirestore.Firestore = admin.firestore();
try {
await firestore.runTransaction(async (transaction: FirebaseFirestore.Transaction) => {
const query: FirebaseFirestore.Query = firestore
.collection('beerOnTap')
.where('tapIndexOrder', '==', beerTapIndex)
.limit(1);
const snapshot: FirebaseFirestore.QuerySnapshot = await transaction.get(query);
const beerTapDoc: FirebaseFirestore.QueryDocumentSnapshot = snapshot.docs[0];
const beerTapData: FirebaseFirestore.DocumentData = beerTapDoc.data();
const beerTapRef: FirebaseFirestore.DocumentReference = firestore
.collection('beerOnTap')
.doc(beerTapDoc.id);
transaction.update(beerTapRef, {enabled: !beerTapData.enabled});
const beerTapModel = new BeerTapModel({
...beerTapData,
tapId: beerTapDoc.id,
});
res.send(beerTapModel);
});
} catch (error) {
res.send(error);
}
}

Categories

Resources