I have been trying to implement 'Login via Facebook' functionality in my application. I'm using Parse as backend. The problem arises due to the fact that Parse creates a new entry in the User Table with authData and random username, even if there's already a user exists with the same email in the User Table. So what I'm doing for now is ask user to connect Facebook via Facebook SDK, and get his email. Then I call this cloud code from Android that will search the user associated with that email in the Parse User Table. If a user exists, I'm trying to update the authData field. It can update any column but authData. It gives the following error while updating authData from cloud code.
Uncaught Tried to save an object with a pointer to a new, unsaved object
Following is the cloud code I'm using:
Parse.Cloud.define("facebookLogin", function(request, response){
Parse.Cloud.useMasterKey();
var email = request.params.email;
var userQuery = new Parse.Query(Parse.User);
userQuery.equalTo("email", email);
userQuery.first({
success: function(user) {
if (user) {
Parse.Cloud.useMasterKey();
var authData = util.createFBAuthData(request.params.id, request.params.access_token, request.params.expiration_date);
console.log(authData);
user.set("authData", authData);
user.save(null, {
success: function(rest) {
response.success(rest);
},
error: function(error) {
console.log(error);
response.error(messageLog.getErrorMessage());
}
});
} else {
createNewUserEntry(request);
}
},
error: function() {
response.error(messageLog.getErrorMessage());
}
});
})
I don't understand why Parse isn't allowing to edit/update authData using cloud code. I'm able to manually insert (Copy/Paste) the json into the authData field in the User Table from the Parse Dashboard. Anybody who has faced this issue?
Related
Update At Bottom
I am trying to build a signup page in my Android app that signs users up for a subscription through Stripe. What I am stuck on is adding a payment source from Android, through a cloud function, and receive a token from Stripe.
I currently have solved, automatically adding a newly created User to Stripe. As well creating the subscription when (/users/{userId}/membership/token) is written to, or changed.
On Android I am able to obtain the credit card data through the input..
PaymentMethodCreateParams.Card card = cardInputWidget.getPaymentMethodCard();
I next need to submit this to my cloud function by using..
mFunctions = FirebaseFunctions.getInstance();
mFunctions.getHttpsCallable("addPaymentSource")
.call()
.addOnCompleteListener(task -> {
...
Being I am having trouble finding information on this, here is all I have for this cloud function (Javascript)
exports.addPaymentSource = functions.https.onCall((data, context) =>{
const pm = await stripe.paymentMethods.attach('pm_678', {customer: 'cus_123'});
return admin.firestore().collection('users').doc(user.uid).get('membership').set({token: token});
}
I need to obtain the customer number which is saved at - /users/{user.uid}/customerId'. As well pass the payment method through my http data call, and pass/obtain the user_id (which would have been created long before all this).
I got this far watching this youtube video and converting my code over. Subscription Payments with Stripe, Angular, and Firebase
I also referenced Stripe's Cloud Function examples quite a bit. The one issue is everyone seems to be using this code (below), which doesn't work in my implementation. With most guides/examples not being used for Subscriptions.
// Add a payment source (card) for a user by writing a stripe payment source token to Cloud Firestore
exports.addPaymentSource = functions.firestore.document('/stripe_customers/{userId}/tokens/{pushId}').onCreate(async (snap, context) => {
const source = snap.data();
const token = source.token;
if (source === null){
return null;
}
try {
const snapshot = await admin.firestore().collection('stripe_customers').doc(context.params.userId).get();
const customer = snapshot.data().customer_id;
const response = await stripe.customers.createSource(customer, {source: token});
return admin.firestore().collection('stripe_customers').doc(context.params.userId).collection("sources").doc(response.fingerprint).set(response, {merge: true});
} catch (error) {
await snap.ref.set({'error':userFacingMessage(error)},{merge:true});
return reportError(error, {user: context.params.userId});
}
});
Update:
Made some small changes to get try and get this to work..
exports.addPaymentSource = functions.https.onCall((data, context) =>{
///users/{userId}/membership/token
// Create Payment Method
const paymentMethod = stripe.paymentMethods.create(
{
type: 'card',
card: {
number: '4242424242424242',
exp_month: 5,
exp_year: 2021,
cvc: '314',
},
}).then(pm => {
console.log('paymentMethod: ', paymentMethod.id);
return stripe.paymentMethods.attach(paymentMethod.id, { customer: 'cus_HCQNxmI5CSlIV5' })
.then(pm => {
return admin.firestore().collection('users').doc(user.uid).get('membership').set({token: pm.id});
});
});
});
I am getting close, the problem is paymentMethod.id is 'undefined'
While I'm not a Firebase expert, on your Android side, you want to call your cloud function with parameters of the Customer ID and PaymentMethod ID in order to pass them to your cloud function.
Passing parameters shown here: https://stackoverflow.com/a/56298213/10654456
Then in your cloud function, you want to attach the PaymentMethod to the Customer (as you are doing using stripe-node) and make it the Customer's default for Subscriptions, as shown here: https://stripe.com/docs/billing/subscriptions/payment#signup-3
Then, you should create a Subscription on the Customer for a particular Plan, again using stripe-node, as shown here https://stripe.com/docs/billing/subscriptions/payment#signup-4
Here I have my functioning code. (I used some placeholder data to fill in the variables)
exports.addPaymentSource = functions.https.onCall((data, context) =>{
// Create Payment Method
stripe.paymentMethods.create( {
type: 'card',
card: {
number: '4242424242424242',
exp_month: 5,
exp_year: 2021,
cvc: '314',
},
})
.then(pm => {
return stripe.paymentMethods.attach(pm.id, { customer: 'cus_HCCNMAAwRhNM3c' })
})
.then(pm => {
console.log('final step');
console.log('paymentMethod: ', pm.id);
admin.firestore().collection('users').doc('LzgbQBtk0QSZi7QISIbV').set({token: pm.id});
return admin.firestore().collection('users').doc(user.uid).collection('membership').set({token: pm.id});
})
.catch(error => { return null });
});
So I manually pasted in some variables to confirm my features were functioning. The CustomerID and card details need to be passed in from the Android app. These card details are the only ones I should need for a subscription
'pm' is the returned Payment Method object, in which id is the variable that needs to be attached to the user.
Finally pm.id is the token that must be saved inside into the firestore. Doing this triggers my subscription setup cloud function(not displayed).
The code displayed shows how to avoid nested then statements, and Android firestore direct function calling. While also not shown, the data field can call upon any variable's key word "data.cardnbr".
The method avoids any use of SetupIntents. While this is incredibly effective for Subscription based charging, it might not be best practice for direct charges.
So I realize that, since version 12.0, you can call Firebase Functions directly from an Android app... this makes sense with the given example for sending messages:
private Task<String> addMessage(String text) {
// Create the arguments to the callable function.
Map<String, Object> data = new HashMap<>();
data.put("text", text);
data.put("push", true);
return mFunctions
.getHttpsCallable("addMessage")
.call(data)
.continueWith(new Continuation<HttpsCallableResult, String>() {
#Override
public String then(#NonNull Task<HttpsCallableResult> task) throws Exception {
// This continuation runs on either success or failure, but if the task
// has failed then getResult() will throw an Exception which will be
// propagated down.
String result = (String) task.getResult().getData();
return result;
}
});
}
...where you're sending text to a function.
exports.addMessage = functions.https.onCall((data, context) => {
// [START_EXCLUDE]
// [START readMessageData]
// Message text passed from the client.
const text = data.text;
// [END readMessageData]
// [START messageHttpsErrors]
// Checking attribute.
if (!(typeof text === 'string') || text.length === 0) {
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('invalid-argument', 'The function must be called with ' +
'one arguments "text" containing the message text to add.');
}
// Checking that the user is authenticated.
if (!context.auth) {
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' +
'while authenticated.');
}
But I'm not exactly sure what I should be sending for something like the sendFollowerNotification example:
https://github.com/firebase/functions-samples/tree/master/fcm-notifications
exports.sendFollowerNotification = functions.database.ref('/followers/{followedUid}/{followerUid}')
.onWrite((change, context) => {
const followerUid = context.params.followerUid;
const followedUid = context.params.followedUid;
// If un-follow we exit the function.
if (!change.after.val()) {
return console.log('User ', followerUid, 'un-followed user', followedUid);
}
I mean... assuming the users are logged-in and have firebase UIDs and are in the database (my app automatically creates a firebase user when someone logs in)... it would appear that sendFollowerNotification just gets everything from the realtime database.
So what do I put under?:
.call(data)
And how am I retrieving the UID for the user that I'm trying to follow? For one that's logged in and using the app... I obviously already have that user's UID, token, and everything else... but I'm unsure of how to retrieve that info for the user who's about to be followed... if that makes any sense.
I've googled all over the internet and have never found an example of this particular kind of function call being used from within an android app using the new post 12.0.0 method. So I'm curious to know what the proper syntax should be.
Ok! This one really enraged me trying to figure it out... It turns out you don't need to call "sendFollowerNotification" at all.. All it does is it listens for changes to the Firebase Realtime Database. If you make changes in the syntax where sendFollowerNotification is looking... it automatically sends out the notification.
There's no call at all in "sendFollwerNotification" for writing users to the Realtime Database. I actually handle this at login:
private DatabaseReference mDatabase; //up top
mDatabase = FirebaseDatabase.getInstance().getReference(); //somewhere in "onCreate"
final String userId = mAuth.getUid();
String refreshedToken = FirebaseInstanceId.getInstance().getToken();
mDatabase.child("users").child(userId).child("displayName").setValue(name);
mDatabase.child("users").child(userId).child("notificationTokens").child(refreshedToken).setValue(true);
mDatabase.child("users").child(userId).child("photoURL").setValue(avatar);
Then when one user follows another I just write that to the realtime database as well:
mDatabase.child("followers").child(user_Id).child(follower_id).setValue(true);
And that's it! The second a new follower is added to the RealTime Database... sendFollwerNotification will automatically send out a notification. You just need to setup a listener in your app for receiving messages and where it should redirect your users once they tap a message that's been received and you're done.
I'm about to begin working on a recipe Android app for my college final project, and I want users to be able to add recipes to the database. However, I don't want the data to be added right away, but I'd like to receive a notification whenever someone wants to add a recipe, so I can confirm it myself. I'm using back{4}app by the way.
How can I do such a thing in a not-so-complicated way? I was thinking to create an admin account for myself in the app itself, but is there any way to send the notifications to said account? I also want to be able to confirm recipe addition with a simple "Confirm" button from within the app, so will this require me to create an additional class for pending recipes? Will I need an admin account in any case?
all this can be achieve by using cloud code.
Parse.cloud.define("addRecipe", function(request, response) {
const query = new Parse.Query("recipe");
query.set("name", "name");
query.save({
success function(result) {
response(result);
//call push notification function from client or from cloud code when the error is nil
},
error: function(result, error) {
response(error);
}
});
});
this is an example of push notifications using cloud code.
push notification are not allow anymore from the client due to secure reason.
you should be subscribe to this channel
Parse.Cloud.define("pushsample", function (request, response) {
Parse.Push.send({
channels: ["channelName"],
data: {
title: "Hello!",
message: "Hello from the Cloud Code",
}
}, {
success: function () {
// Push was successful
response.sucess("push sent");
},
error: function (error) {
// Push was unsucessful
response.sucess("error with push: " + error);
},
useMasterKey: true
});
});
you should also implement some logic to your app in order to display recipes confirm by admin.
var recipe = Parse.Object.extend("recipe");
var query = new Parse.Query(recipe);
query.equalTo("confirm", true);
query.find({
success: function(results) {
//it will display recipes confirmed
},
error: function(error) {
alert("Error: " + error.code + " " + error.message);
});
you should also setup a admin system in your app or a website
I'm totally lost as to how to do this. I want to be able to authenticate a user with their username and password only--so I have to use a customAuth from Firebase.
I created a server (node.js) that handles the generation of tokens (runs on Heroku):
var express = require('express')
var Firebase = require('firebase')
var app = express()
app.set('port', (process.env.PORT || 5000))
app.use(express.static(__dirname + '/public'))
app.listen(app.get('port'), function() {
console.log("Node app is running at localhost:" + app.get('port'))
})
var SECRET = "numbers would be here";
var tokenGenerator = new FirebaseTokenGenerator(SECRET);
var AUTH_TOKEN = tokenGenerator.createToken({
uid: "arbitrary",
data: "blahblahblah"});
console.log(AUTH_TOKEN);
var ref = new Firebase("null");
ref.authWithCustomToken(AUTH_TOKEN, function(error, authData) {
if (error) {
console.log("Login Failed!", error);
} else {
console.log("Login Succeeded!", authData);
}
});
Now I have an Android app in which I want to authenticate a user. If I have something like,
Firebase mRef = new Firebase("myFirebaseUrl");
mRef.authWithCustomToken(String token, AuthResultHandler handler); //issue
I don't know how to get the token. Furthermore, I'm not sure I understand how it matters if the token is always the same.
You'll need to come up with a secure way to communicate the username and password from your Android client to the node.js server and to subsequently communicate the resulting token (or any error codes) back from the node.js server to the client.
While this is definitely possible (it's pretty much how Firebase email+password authentication works), it is definitely too broad a topic to cover in a StackOverflow answer. It's a project, rather than a question.
What you can consider is using Firebase email+password auth and then stubbing out the email domain. So if a user signs up with username Nxt3 and password, you simply append a dummy domain to the username and register them as Nxt3#dummydomain.com.
I am using parse for my application
I have one fragment on user which have to write article and save on parse but with not approve
when admin approve that user filed I want to send push notification automatically to that user with particular article is approved message
How can I implement this things..?
Parse.Cloud.afterSave("Data", function(request) {var dirtyKeys = request.object.dirtyKeys();for (var i = 0; i < dirtyKeys.length; ++i) {
var dirtyKey = dirtyKeys[i];
if (dirtyKey === "name") {
//Get value from Data Object
var username = request.object.get("name");
//Set push query
var pushQuery = new Parse.Query(Parse.Installation);
pushQuery.equalTo("name",username);
//Send Push message
Parse.Push.send({
where: pushQuery,
data: {
alert: "Name Updated",
sound: "default"
}
},{
success: function(){
response.success('true');
},
error: function (error) {
response.error(error);
}
});
return; } } response.success();});
If this is a matter of just hitting "approve", I would create a cloud code function for "Approve article" that is called by the admin tapping an approve button. This function sets the approve status and then calls a function (still in cloud code) for sending the push message to the user.
More on cloud code functions: https://parse.com/docs/cloudcode/guide#cloud-code-cloud-functionshttps://parse.com/docs/cloudcode/guide#cloud-code-cloud-functions
Alternatively the approve button can change status and save the document, and then an afterSave() function in cloud code can handle the push. This is less clear, though, since the afterSave() function will always be called when the record is saved, and it would need to check for status and only send push if the article has been approved.