So paid pages in my app, when a user click to open any of these pages i want to run a function to check if the product is purchased and if it's not then buy the product, I have found two packages which are react-native-iap and expo-in-app-purchase but did not find clear instructions on how to achieve the process i need..
const paidPage = async (productId , navTo) => {
// PASS THE PRODUCT ID AND A FUNCTION THAT NAVIGATE TO THE PAGE
// HERE I WANT TO CHECK IF THE PRODUCT IS PURCHASED, IF YES THEN NAVIGATE TO THE PAGE
// IF NO THEN PURCHASE THE PRODUCT
}
You have to check if you have already bought the product or not and check if it is still valid or not. Using react-native-iap package
RNIap.getPurchaseHistory().catch(() => {
console.log("error fetching purchase history")
}).then((res) => {
// here you will get all the purchase history
// filter with the product id for which you want to check
// if record exist that means you have to check its validity
})
checking validity is not straight forward. you need use code given bellow. However, receipt in receiptBody bellow can be any receipt since response does not shows its expiry date. Response has 'latest_receipt_info' that contain all the purchase with expiry date from where you can filter the one you need.
const receiptBody = {
"receipt-data" : receipt,
"password" : "" //password from the App-Specific Shared Secret in appstoreconnect in-app-purchase section for the app
}
await RNIap.validateReceiptIos(receiptBody, true).then((receipt) => {
try {
const renewalHistory = receipt.latest_receipt_info
// it has all purchase with expiry date
// filter for the one you are looking for
} catch (error) {}
})
Receipt can be any receipt in receiptBody above so you can get current receipt which is fast to implement like bellow
await RNIap.getReceiptIOS().then((res) => {
currentReceipt = res
})
You may want to consider using other third party libraries to handle receipt processing that provide clearer direction for how to handle in-app purchase.
One example is NamiML, which has a guide on how to check for active purchases here that has a react native example:
https://docs.namiml.com/docs/react-to-changes-to-entitlements
The code for what you want to check using the NamiML library looks like:
NativeModules.NamiEntitlementManagerBridge.isEntitlementActive("premium_access", (active) => {
if (active) {
// react to entitlement being active
}
});
For react native iap, if the user purchases the app but they already own it, you'll get a purchase error code of "E_ALREADY_OWNED". You can use async storage to store a variable to check if the page has already been purchased; if not, then go to the page and award it.
useEffect(() => {
if (currentPurchaseError) {
if (
currentPurchaseError.code === "E_ALREADY_OWNED" &&
!isPagePurchased
) {
setAndStorePagePurchase(true)
}
}
}, [currentPurchaseError])
See this example for how to implement this into a full simple app.
I've used RevenueCat with relative success recently. They're free if you make less than 10k(?) in a year through the app. With their sdk react-native-purchases I implemented something like this, with the consideration that I only have 1 IAP:
try {
const purchaserInfo = await Purchases.getPurchaserInfo();
if (purchaserInfo && Object.entries(purchaserInfo.entitlements.active).length) {
setActive(true); //There exists an entry for the purchase they made,
//meaning they're an active customer
} else {
setActive(false);
}
const offerings = await Purchases.getOfferings();
if (offerings && offerings.current) {
setPrice(offerings.all.fullVersionOffering.availablePackages[0].product.price_string);
} else {
setPrice('$1.99');
}
} catch (error) {
console.log(error);
}
Related
After I create the user using auth.createUserWithEmailAndPassword, I then need to create the relevant documents in Cloud Firestore.
Option 1: if the auth.createUserWithEmailAndPassword task is successful, call a Cloud Function from the app and pass it the username, UID, and email to create the relevant documents.
if (user.isNotEmpty() && email.isNotEmpty() && password.isNotEmpty()) {
fs.authCreateUser(email, password)
.addOnCompleteListener() { task ->
if (task.isSuccessful) {
Log.e(tag, "createUserWithEmailAndPassword task was successful")
fs.CFcreateUser(user, email)
.addOnCompleteListener() { task ->
if (task.isSuccessful) {
Log.e(tag, "CFcreateUser task was successful")
val result = task.result!!["result"]
val message = task.result!!["message"]
//If result = 1, go to Groups Activity
//Else, delete the Firebase user so that the list of authenticated users matches the list of users in Firestore
if (result == "1") {
val intent = Intent(this, ActivityGroups::class.java)
startActivity(intent)
finish()
} else {
}
} else {
Log.e(tag, "CFcreateUser task failed")
Log.e(tag, "CFcreateUser task: ${task.exception.toString()}")
}
}
} else {
Log.e(tag, "createUserWithEmailAndPassword task failed")
Log.e(tag, "createUserWithEmailAndPassword exception: ${task.exception.toString()}")
}
}
}
Option 2: if the auth.createUserWithEmailAndPassword task is successful, update the user's displayName with user!!.updateProfile (from the docs) and then somehow set up a background trigger to create the relevant documents using the displayname as the username.
//After the auth.createUserWithEmailAndPassword task runs, run the code below
val user = Firebase.auth.currentUser
val profileUpdates = userProfileChangeRequest {
displayName = "Jane Q. User"
photoUri = Uri.parse("https://example.com/jane-q-user/profile.jpg")
}
user!!.updateProfile(profileUpdates)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
Log.d(TAG, "User profile updated.")
}
}
Not sure which option is more secure, I'm new to Android dev.
Which is the more secure method between these two?
All requests that are made to Firebase Products like Cloud Firestore, Firebase Realtime Database, or Cloud Functions for Firebase and are coming from a backend SDK will bypass the security rules entirely. To also mention, that this also includes the Firebase Admin SDK. Security rules only apply to web and mobile clients. On the other hand, when using client-side code, you can secure your app using either, Cloud Firestore Security Rules or Firebase Realtime Database Security Rules.
That being said, in my opinion, that's not really about security as it is about efficiency. It's true that you can make the client app do the work, but there is a disadvantage because you can instead do it more efficiently using Cloud Functions. If the client app will do the work, then you'll don't have to pay the cost against the data plan, in terms of data usage and speed. In the case of Cloud Functions, everything is done internally.
Because you are using Firebase Authentication, you can create a Cloud Function to simply solve this problem. So you have to create a function that will be triggered every time something new happens. Here I'm talking about the trigger of a function on user creation.
That's also the same when we are talking about Cloud Storage triggers.
I want to log out from all devices on the button click for Firebase google login. Is this possible? If yes please share the doc or sample code.
Couldn't find any solution for this on stackoverflow.
Code for LogOut from current device
FirebaseAuth.getInstance().signOut()
This works only for current device & not all devices.
To revoke the refresh tokens for a given user (which will prevent clients requesting a new ID token), you can:
Change the user's password
Disable the user using a Firebase Server SDK and updating the disabled property. For Node, you would use the Auth#updateUser method of firebase-admin.
Revoke the tokens manually using a Firebase Server SDK. For Node, you would use the Auth#revokeRefreshTokens() method of firebase-admin.
It's important to note here, that these methods won't invalidate already issued tokens. These tokens will be considered valid up to as long as an hour unless you explicitly test if they have been revoked. You can deal with this somewhat by using FirebaseUser#getIdToken(true) when you detect the user has opened the app but is already signed in. This method tries to get a fresh ID token rather than just use the cached ID token - just make sure to handle the case where the refresh token has been invalidated.
Of the above options, I would recommend the last option and make it available using a Callable Cloud Function.
The server-side code, hosted on Cloud Functions:
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
export const revokeRefreshTokens = functions.https.onCall((data, context) => {
if (!context.auth) {
throw new functions.https.HttpsError(
"failed-precondition",
"The function must be called while authenticated."
);
}
// New optional feature: Firebase App Check
if (context.app === undefined) {
throw new functions.https.HttpsError(
"failed-precondition",
"The function must be called from an App Check verified app."
);
}
const uid = context.auth.uid;
return admin
.auth()
.revokeRefreshTokens(uid)
.then(() => {
return admin.auth().getUser(uid);
})
.then((userRecord) => {
// send back a response with the UID and time tokens were revoked
const timestamp = new Date(userRecord.tokensValidAfterTime).getTime();
return { uid, revokedAt: timestamp }
})
.catch((err) => {
// rethrow any errors as HttpsError for clients
throw new functions.https.HttpsError("unknown", err.code || err.message);
});
});
The client-side code:
public void revokeRefreshTokens() {
return FirebaseFunctions.getInstance()
.getHttpsCallable("revokeRefreshTokens")
.call(data)
.continueWith(new Continuation<HttpsCallableResult, Void>() {
#Override
public String then(#NonNull Task<HttpsCallableResult> task) throws Exception {
if (task.isSuccessful()) {
// revoked successfully, end this sign-in session
FirebaseAuth.getInstance().signOut()
} else {
// failed to revoke, rethrow error
throw task.getException();
}
}
});
}
I want to store doctors and users two different collections on the firestore I have created different collections one for users and one for doctors in registration activity. Now the problem is when someone wants to log in I don't know he is a user or doctor I need to call the collection in the main activity I want to display all the details of user or doctor how can i identify which collection i need to call
for example, when doctors want to log in I only know his email id and I want to show all the details of him
in main activity i dont know which collection i need to call because i don't know he is a doctor or a user
I have created two collections because I wanna show all the doctors list to the user
and users list to doctors
my app details
My first activity is login activity, if someone is new, they can register
now let's start with Register activity
this is my Register activity
FirebaseAuth.getInstance().createUserWithEmailAndPassword( binding.etEmail.text.toString().trim { it <= ' ' }, binding.etPassword.text.toString().trim { it <= ' ' })
.addOnCompleteListener { task ->
if (task.isSuccessful) {
val firebaseUser: FirebaseUser = task.result!!.user!!
val user = User(
firebaseUser.uid,
binding.etFirstName.text.toString().trim{it<=' '},
binding.etLastName.text.toString().trim{it<=' '},
binding.etEmail.text.toString().trim { it <= ' ' },
)
if (binding.rbUser.isChecked){
// registerUser method create a user collection on firebase
FirestoreClass().registerUser(this,user)
}else{
// // registerDoctors method create a doctor collection on firebase
FirestoreClass().registerDoctors(this, user)
}
} else {
showErrorSnackBar(task.exception!!.message.toString(), true)
}
}
I think i don't need to put FirestoreClass().registerUser(this,user) and FirestoreClass().registerDoctors(this, user) methods code all work fine now i have two collection
This is my firestore collections sreenshot
now someone wants to login I don't know he/she is a doctor or user
This is my login activity layout
// this is my login activity
val email = binding.etEmail.text.toString().trim{it <= ' '}
val password = binding.etPassword.text.toString().trim{it <= ' '}
FirebaseAuth.getInstance().signInWithEmailAndPassword(email,password)
.addOnCompleteListener {
task ->
if(task.isSuccessful){
// Now how can I identify This is A doctor or A user what should I put in if blocks please tell
if ( )
FirestoreClass().getDoctorDtails(this#LoginActivity)
else{
FirestoreClass().getUserDetails(this#LoginActivity)
}
}else{
// hideProgressDialog()
showErrorSnackBar(task.exception!!.message.toString(), true)
}
if your have any solution for this please tell me how can i know he/she is a doctor and i user
You can use Firebase Custom Claims. They are like roles assigned to the users. You need to use the Firebase Admin SDK for assigned custom claims.
Now it entirely depends on your sign up flow that how and when are you going to assign the custom claims.
Coming to the part when you need to know if the current user is a Doctor or normal user, you can do so with the follow code in Android (Kotlin):
user.getIdToken(false).addOnSuccessListener(new OnSuccessListener<GetTokenResult>() {
#Override
public void onSuccess(GetTokenResult result) {
boolean isDoctor = result.getClaims().get("doctor");
if (isDoctor) {
// Show doctor UI.
} else {
// Show regular user UI.
}
}
});
Then you can check if the logged in user is a doctor or no and then conditionally switch to relevant activities. Please let me know if you need help setting up Custom Claims in your sign up flow.
What I want
I want to integrate Google Play in app purchases into my flutter app, the product I want to sell is a consumable. Right now I am using the in_app_purchase: (0.3.4+16) package.
What I did
Create the product in the google play developer console
Set up the project according to the documentation of the in_app_purchase package
Implemented some logic to buy a consumable item (see below)
Built an alpha release and uploaded it to the alpha test track in order to test it
Created a new google account on my developer phone, registered it as tester and downloaded the alpha version
Purchased with a "test card, always approves"
Purchased with my PayPal account
Expected and actual result
I expect the payment to work and all api calls to return ok.
When I initiate a purchase on my phone the purchase flow starts and I can select the desired payment method. After I accept the payment the _listenToPurchaseUpdated(...) method is called, as expected.
However, the call to InAppPurchaseConnection.instance.completePurchase(p) returns a BillingResponse.developerError and I get the following debug messages:
W/BillingHelper: Couldn't find purchase lists, trying to find single data.
I/flutter: result: BillingResponse.developerError (Purchase is in an invalid state.)
This error comes with the "test card, always approves" and also when I start a real transaction using PayPal. For the PayPal purchase I got a confirmation Email, that the transaction was successful.
In the documentation it says:
Warning! Failure to call this method and get a successful response within 3 days of the purchase will result a refund on Android.
Summarized question
How can I get the call to InAppPurchaseConnection.instance.completePurchase(p) to return a successful result?
The purchase implementation
The code to setup in app purchases is implemented as shown in the documentation:
InAppPurchaseConnection.enablePendingPurchases();
Stream<List<PurchaseDetails>> purchaseUpdated = InAppPurchaseConnection.instance.purchaseUpdatedStream;
_subscription = purchaseUpdated.listen(_listenToPurchaseUpdated, onDone: () {
_subscription.cancel();
}, onError: (error) {
// handle error here.
});
...
Future<void> _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) async {
for (var p in purchaseDetailsList) {
// Code to validate the payment
if (!p.pendingCompletePurchase) continue;
var result = await InAppPurchaseConnection.instance.completePurchase(p);
if (result.responseCode != BillingResponse.ok) {
print("result: ${result.responseCode} (${result.debugMessage})");
}
}
}
To buy a consumable I have this method which queries the product details and calls buyConsumable(...)
Future<bool> _buyConsumableById(String id) async {
final ProductDetailsResponse response = await InAppPurchaseConnection
.instance
.queryProductDetails([id].toSet());
if (response.notFoundIDs.isNotEmpty || response.productDetails.isEmpty) {
return false;
}
List<ProductDetails> productDetails = response.productDetails;
final PurchaseParam purchaseParam = PurchaseParam(
productDetails: productDetails[0],
);
return await InAppPurchaseConnection.instance.buyConsumable(
purchaseParam: purchaseParam,
);
}
The solution is to not call the completePurchase(...) method for consumable purchases. By default the library consumes the purchase for you which implicitly acts as a call to completePurchase(...).
Background
The call to InAppPurchaseConnection.instance.buyConsumable(...) has an optional boolean parameter autoConsume which is always true. This means that, on android, the purchase is consumed right before the callback to the purchaseUpdatedStream.
The documentation of the completePurchase method says the following:
The [consumePurchase] acts as an implicit [completePurchase] on Android
Code to fix the problem
Future<void> _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) async {
for (var p in purchaseDetailsList) {
// Code to validate the payment
if (!p.pendingCompletePurchase) continue;
if (_isConsumable(p.productID)) continue; // Determine if the item is consumable. If so do not consume it
var result = await InAppPurchaseConnection.instance.completePurchase(p);
if (result.responseCode != BillingResponse.ok) {
print("result: ${result.responseCode} (${result.debugMessage})");
}
}
}
I have a Firebase user set up:
Then, based on the following, I get a custom token from the uid (MG76cXdqhDNrx3BJktUq62...) on the server.
FirebaseAuth.getInstance().createCustomToken(uid)
.addOnSuccessListener(new OnSuccessListener<String>() {
#Override
public void onSuccess(String customToken) {
// Send token back to client
}
});
Then, on the client, it returns the custom token. I then signInWithCustomToken as you can see below.
getUserWithToken(personModel: PersonModel): Promise<firebase.User> {
return new Promise<firebase.User>((resolve) => {
this.firebaseService.getCustomToken(personModel.uid).then((customToken: string) => {
this.fireAuth.signInWithCustomToken(customToken).then((data: FirebaseAuthState) => {
this.fireAuth.onAuthStateChanged((fireUser: firebase.User) => {
if (fireUser && fireUser.uid === personModel.uid) {
resolve(fireUser);
} else {
resolve(null);
}
});
}).catch((error) => {
console.error('Error getting user with CustomeToken login for ' + personModel.uid, error);
this.doAlert(error.message);
});
});
});
}
But, instead of signing in the with above (with the uid MG76cXdqhDNrx3BJktUq62...) user as expected, Firebase creates a new user with a different uid RoPBW0NKRoWEI1o8wgb9DD...:
It's as if it getting the uid of an old user and creating that user. However, I would expect it to just sign in with the user associated with the passed uid MG76cXdqhDNrx3BJktUq62....
Any advise appreciated.
SOLUTION
The issue was a silly mistake on my part.
The server code was caching an old custom token, because it was being saved on the class instance. Fixed by removing the custom token on each time the method is called.