How do I validate IAP in android? - java

I am finding it hard to find complete documentation online. So, atm I have a Cloud Firestore DB. A one Non-Consumable that unlocks a premium version. The short guide I did find recommends:
To validate purchase details on a trusted server, complete the following steps:
Ensure that the device-server handshake is secure.
Check the returned data signature and the orderId, and verify that the orderId is a unique value that you have not previously processed.
Verify that your app's key has signed the INAPP_PURCHASE_DATA that you process.
Validate purchase responses using the ProductPurchase resource (for in-app products) or the SubscriptionPurchase resource (for subscriptions) from the Google Play Developer API. This step is particularly useful because attackers cannot create mock responses to your Play Store purchase requests.
What server do I use? Is cloud fire-store sufficient to complete this validation?
How do I Verify that your app's key has signed the INAPP_PURCHASE_DATA that you process
Do I need to read every order ID every time a user makes a purchase? Couldn't this lead to thousands of reads on the DB very quickly?
Here is the onPurchasesUpdated code block where I am going to implement the validation:
#Override
public void onPurchasesUpdated(BillingResult billingResult, #Nullable List<Purchase> purchases) {
if (billingResult.getResponseCode() == BillingResponseCode.OK
&& purchases != null) {
int index = 0;
for (Purchase purchase : purchases) {
if(purchase.getSignature().equals( /*Somehow check igned the INAPP_PURCHASE_DATA HERE?*/) || purchase.getOrderId().equals(purchases.get(index).getOrderId())) {
//Invalid
getMessage("Invalid. Order cancelled");
return;
} else {
handlePurchase(purchase);
}
}
} else if (billingResult.getResponseCode() == BillingResponseCode.USER_CANCELED) {
getMessage("Payment Cancelled");
} else {
getMessage("Error. Try Again");
}
}
What steps should I take? What do you do to validate IAP's?
Why is the doumentation very poor on this.
Is it even worth the effort? I don't expect this app to vbe downloaded millions of times. Should I just skip it?

You can use a Firebase Cloud Function for receipt validation. You'd set up an HTTP triggered function that you pass the purchase token and have it return if the token is valid or not.
You don't need to store anything in your database (although you may want to). The receipt validation can be done in a function - and there are some well documented open-source examples of this online.
Is it worth the effort? That depends on how much time you wish to invest, and whether you think the cost of users with "fake" purchase tokens outweighs this investment.
Another alternative is to use a hosted solution for your in-app purchase server such as RevenueCat (Disclaimer: I work there).

Related

Android In-App billing library: doubts about unlocking logic

I have succeeded integrating the class BillingClient, starting the connection etc. I can, in fact, make a test purchase of the product from the App (it shows the payment form, buys the product etc.) I implemented it as it is described in this link that explains how to integrate the Google Play Billing Library.
It works, the purchase is performed... but I am clueless about how to implement the unlocking logic! For the record, it is only one product, non-consumable:
private void handlePurchase(Purchase purchase) {
AcknowledgePurchaseResponseListener acknowledgePurchaseResponseListener = new AcknowledgePurchaseResponseListener() {
#Override
public void onAcknowledgePurchaseResponse(#NonNull BillingResult billingResult) {
// TODO Ok, now what???
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
// TODO Handle the success of the "acknowledge"
// Unlock things etc...
}
}
};
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
if (!purchase.isAcknowledged()) {
AcknowledgePurchaseParams acknowledgePurchaseParams =
AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
billingClient.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener);
}
// TODO Unlock from here?
is_premium_unlocked = true;
}
}
I am not sure howto implement the unlock: but for keeping it simple, let's say that the variable is_premium_unlocked will do the job. Will this grant it to be true after closing and reopening the App? I don't think so... Even though this handlePurchase is triggered onPurchaseUpdated, when this event occurrs? Only in new purchase requests? Every time the BillingClient connection is successful? I wonder about all of these things...
So, summarizing: What does my business logic have to do about the "acknowledging"? I am aware that this is some kind of mechanism to prevent duplicated purchases by the same user and stuff like that... but what should my App do about it, beyond what the code samples suggest? But most importantly: how is the app supposed to know that the "unlock product" is already purchased while starting? Is this done by some of the callbacks implemented by documentation's code samples, or have I to implement it my own way? Is there a way of, looking at the ProductDetails to check if it is has been already purchased before, and proceed to execute unlocking logic right away? Is this up to my code, or up to Google Play Billing Service?
The point is, what I have already done following those docs, lets the app "trigger" the unlock just after the product is purchased (AKA is_premium_unlocked = true), but I don't know how is the billingClient supposed to check if the product was already purchased in future connections, or if billingClient is able to do so at all in the first place.
Finally, it was in the docs (I didn't read them hard enough). It looks like it must be done this way, by adding an async checker. So, if I trigger queryPurchasesAsync every time the billingClient connects successfully, I can trigger the unlock from there after app starts:
billingClient.queryPurchasesAsync(
QueryPurchasesParams.newBuilder()
.setProductType(ProductType.INAPP)
.build(),
new PurchasesResponseListener() {
public void onQueryPurchasesResponse(BillingResult billingResult, List purchases) {
is_premium_unlocked = true;
}
}
);

How To Charge Card In Stripe Using Java(Android)

After searching lot's of asked questions regarding the charging the card using stripe. Most of the question are not answer yet and not able to find the way to charge the payment.
Here what i did in my project:
I am able to successfully got the token from stripe server.
try {
carToSave = mCardInputWidget.getCard();
Log.d(TAG, carToSave.toString());
if (carToSave == null) {
Toast.makeText(this, "Please fill information", Toast.LENGTH_SHORT).show();
} else {
//get key from https://dashboard.stripe.com/account/apikeys
Stripe stripe = new Stripe(StripeActivity.this, "put key here");
stripe.createToken(carToSave, new TokenCallback() {
#Override
public void onError(Exception error) {
// Show localized error message
Log.d(TAG, "onError");
}
#Override
public void onSuccess(Token token) {
//do charge with token
Log.d(TAG, token.getId());//token
}
});
}
} catch (Exception e) {
e.printStackTrace();
}
I have spend lot's of hours on Creating Charges Official docs and in the official docs they were using Charge class to charge the card but this class is not included in latest docs.
Here are the link of previously asked question on stack-overflow but not contain any relevant answer that's why i am posting this answer.
Link 1
Link 2
3.I have followed the Official Stripe Github but left with empty hand.
Note: Most of docs are using Charge class but that class not included in latest sdk of stripe.
I also had the same issue, so I had a look at this post.
The Charge class and, Stripe.api key all available in its Java SDK.
implementation 'com.stripe:stripe-java:5.36.0'
You should never create a charge from within your Android mobile app. In order to create a charge, you need to use your secret API key. Your secret API should never ever be kept in a mobile application because someone could retrieve this key and they would be able to create charges and refunds on your Stripe account.
You can create charges in your server-side code like in this Stripe example backend:
https://github.com/stripe/example-ios-backend/blob/0adc94abc7e2e78464e027e510da3d99152b13e6/web.rb#L34
The same issue for me. The problem for me started today in the demo account after having managed to take the process to the end. This leads me to conclude that either is some stripe problem. The process of obtaining the token is as simple as that shown in the Suraj Bahadur code. In the Stripe dashboard, the token request is immediately indicated, but the token does not reach the device that requested it.
plz follow this I got it from stripe payment doc, here is the link :https://stripe.com/docs/charges
after getting the token from stripe server use as request param.
you use the String token = request.getParameter("token")

How to use cursor-based pagination with Android Facebook API

I am trying to retrieve items from my Facebook news feed using the graph API. The code (unfinished) I am using is below, which seems to only be returning a single news feed post. I have read the documentation on cursor based pagination but it does not explain how to implement it, nor have i found any other resources explaining this matter.
// Get items from users news feed
public void getFeed() {
Session s = Session.getActiveSession();
new Request(
s,
"/me/home",
null,
HttpMethod.GET,
new Request.Callback() {
public void onCompleted(Response response) {
/* handle the result */
JSONArray json = null;
JSONObject current = null;
try {
json = (JSONArray)response.getGraphObject().getProperty("data");
} catch(Exception e) {
// TODO
}
for(int i=0; i<5; i++) {
try {
current = json.getJSONObject(i);
Log.d("Value: ", current.get("message").toString());
} catch(Exception e) {
// Nothing
}
}
}
}
).executeAsync();
}
As I am still experimenting, I am just trying to pull down 5 news feed items and output the message JSON attribute. Could anybody advise me on how to properly implement the cursor based pagination to pull down an arbitrary number of feed posts at a time? Thanks
The person in your link is using FB-Andriod-SDK 3.0.1
Source
Which means it is using Graph v1.0
FB-Andriod-SDK 3.8 and above started using Graph v2.0
In Graph v2.0 and above the ability to use the "read_stream" permission became severely limited. So much so that unless you are a Facebook engineer you won't get it.
Limited Use
This permission is granted to apps building a Facebook-branded client on platforms where Facebook is not already available. For example, Android and iOS apps will not be approved for this permission. In addition, Web, Desktop, in-car and TV apps will not be granted this permission.
Source, Scroll down to "read_stream"
The user/home edge requires "read_stream" (user is a place holder for a User ID or "Me" keyword)
Permissions
A user access token with read_stream permission is required to view that person's news feed.
Source
This also extents to user/feed edge
Permissions
Any valid access token is required to view public links.
A user access token with read_stream permission is required.
Only posts whose authors have also granted read_stream permission to the app will be shown.
Source
As well as user/posts edge
This is a duplicate of the /feed edge which only shows posts published by the person themselves.
Source
On April 30, 2015 Graph v1.0 will be completely gone.
The current, latest version of the Graph API is v2.2. Apps calling v1.0 have until April 30, 2015 to upgrade to v2.0 or later.
Source, under "Staying up to date"
More insightful information about this can be found here.
Read all the comments Emil (FB Engineer) and Simon (Graph Product Manger) are the best sources
So what it boils down to is. You are attempting to do something that the Facebook developed app already does and they don't want you to do it.
First of all you can not use "me/home" as a call. It requires the "read_stream" permission and you'll never get that approved.
There is really no way to get this data on your own. You'll have to use one of FB's Media Partners
Here is more info.
Click here for more info

Dropbox Datastore API: Reliably determining if the user is authenticated

The Problem
I am using the Dropbox Datastore API to store information in my app. I am trying to figure out a reliable way to check if the user is authenticated with Dropbox.
Currently I am using this helper function:
public boolean isLoggedIn(Context context){
LogHelper logHelper = new LogHelper();
DbxAccount dropboxAcount = getDropboxAccountManager(context).getLinkedAccount();
if(dropboxAcount == null){
return false;
} else{
if(dropboxAcount.isLinked() && dropboxAcount.getAccountInfo() != null){
return true;
} else{
return false;
}
}
}
The problem with this is if a user becomes unauthenticated after they have logged in, for example, if the user goes to their dropbox settings and unlinks the app. When this happens the above function will detect that the user is logged in, only when you try to perform an action that requires authentication(Like writing to a datastore) does the dropbox api realize that the user is not authenticated.
The Question
Is there a reliable way to figure out if the user is authenticated with the Dropbox Datastore API?
From https://www.dropbox.com/developers/datastore/docs/android#com.dropbox.sync.android.DbxAccountManager.addListener:
void addListener(AccountListener l)
Adds an DbxAccountManager.AccountListener which will be called
whenever a new account is linked or an existing account is unlinked.
The listener will be called regardless of whether the account was
unlinked using DbxAccount.unlink() or by the user on the Dropbox
website.
This notification will still presumably only fire after some communication with the server, since that's the only way for the client to know that something happened outside of the app.

GWT RequestFactory-based authentication

I am experimenting with GWT RequestFactory (RF) for the first time and am trying to implement a simple sign-in screen and authentication system (not using anything fancy, just fiddling around with the basics here). The basic user experience I'm looking to achieve is pretty par for the course:
The user will be presented with a sign-in screen (email and password and "Sign In" button). When they click the button, I want to use RF to send their credentials to the server (using ValueProxy since these are not entities) and authenticate them. If the credentials were correct, they are now "signed in" to the system, and the GWT app will download a whole new module and they'll be redirected to their account's main menu. If the credentials were incorrect, I want to send back a String explaining that the email or password was incorrect, and they are still "signed out" of the app.
Regarding this question that I posted yesterday, I have now figured out how to use RF to expose a SignInOutService which has a signIn(SignIn) method for attempting to sign the user in, and a signOut(SignOut) method for signing the user out of the system. But now I'm actuallly trying to implement that service, and here's what I have so far:
public class DefaultSignInOutService {
// Try to sign the user into the system.
public String signIn(SignIn signIn) {
// The SignIn object contains the email/hashed password the user tried
// signing-in with, as well as other metadata I'm looking to store for
// security purposes (IP address, user agent, etc.).
String email = signIn.getEmail();
String hashedPassword = signIn.getHashedPassword();
// This will be set to a non-null value if the sign-in attempt fails.
// Otherwise (on successful sign-in) it will stay NULL. The client-side
// handler will know what to do with the UI based on this value.
String failReason = null;
// For this simple example, the password is "12345" and below is it's MD5 hash.
// Hey! That's the combination on my luggage!
if(!"skroob#spaceballs.example.com".equals(email) || !"827ccb0eea8a706c4c34a16891f84e7b".equals(hashedPassword))
failReason = "Login failed; incorrect email or password.";
else {
// Log the user into the system...
// TODO: How?
}
return failReason;
}
// Sign the user out of the system.
public void signOut(SignOut signOut) {
// The SignOut object should reference the user attempting to sign out, as well as a reason
// for why the sign out is occurring: the user manually requested to be signed out, or they
// "expired" due to inactivity or navigating the browser away from the app, and so the system
// auto-signed them out, etc.
// TODO: How?
return;
}
}
So now, I've implemented my super-simple email/password check, and I'm ready to write the code that somehow signs the user into the app (so that they're not presented with a login screen over and over again). And I'm choking on what to do next.
Issues I'm trying to find solutions for:
Is GWT RF somehow session- or token-based? If so, under the commented line "Log the user into the system...", what code can I write that says "this user is now authenticated, set some cookie or session variable to make it so!"? I ask this because once they sign in and are routed to the new module and main menu, GWT will need a way to authenticate every subsequent RF request thereafter.
What does the signOut() method need to reset/clear/nullify in order to clear these cookies/session vars? In other words, how do I actually sign the user out, so if they try to go to the URL for their main menu (which again is only accessible if they're signed in), they'll be redirected to the sign-in screen?
How could I implement a 15-min inactivity timeout, where the user is automatically signed out of the app after a certain length of time? I think this answer will become more obvious once I see how questions #1 and #2 above work.
I was told that I may need to have two servlets and/or filters: one for handling unauthenticated RF requests (while a user is signed out or has not yet signed in), and one for handling authenticated RF requests (once the user is actively signed in). But I can't see how they fit into the overall picture here.
The easiest way is to store your authentication details in session.
public String signIn(SignIn signIn) {
...
if(!"skroob#spaceballs.example.com".equals(email) || !"827ccb0eea8a706c4c34a16891f84e7b".equals(hashedPassword))
failReason = "Login failed; incorrect email or password.";
else {
RequestFactoryServlet.getThreadLocalRequest().getSession().setAttribute("auth", signIn);
}
return failReason;
}
public void signOut(SignOut signOut) {
RequestFactoryServlet.getThreadLocalRequest().getSession().removeAttribute("auth");
return;
}
On every request you can check if SignIn object is still present in session:
SignIn signIn = null;
final Object userObject = RequestFactoryServlet.getThreadLocalRequest().getSession().getAttribute("auth");
if (userObject != null && userObject instanceof SignIn) {
signIn = (SignIn) userObject;
}
In case of absence of this object you should cancel the request and redirect user to login page.

Categories