I'm implementing cordova-plugin-purchase in app for Android, I have working all but I have a one thing to fix.
In my app when I make a purchase for subscription always get the same purchase token, in my first test this token was a valid but now when I unsubscribed and subscribed again in my app get the same purchasetoken.
When I put this purchaseToken in my iap server validator the status always is 0 and is logic because I made this purchase on 18th of January.
I need made something for https://github.com/j3k0/cordova-plugin-purchase generate a new purchaseToken?
Always I get this in app when I subscribed:
{
"data": "{\"packageName\":\"com.rubeapp.padres\",\"productId\":\"premium\",\"purchaseTime\":1453157722465,\"purchaseState\":0,\"purchaseToken\":\"pdocnplgcbeoafhgimgkdmhj.AO-J1OxGqkmERQbgjQpcBZy6iaG8UCMzz-lHIYOc0fPwRFJLdAiVljZ31S-x904LUYFrUrQ-40qhIyGHRjIPhECB3e6VMIQukoVtbFLMrmsIpGfAmsImfjs\",\"autoRenewing\":false}",
"signature":"jRTGPHwIX8WCspBMZGIk0PHEIFavKJ0NjXHh6MiHHOl4ZDtbAvHCXrKhP6j99fRtNzpynt5gxDsdI9schL4ed2G2pUJvVIwD/0Lf9p90gt/wuIaKrYxTe+A35i/4smafnYQTikhFv8F5c5/ckVL3ihdDwHLtd1ihOJpvF3z2t+vhNvFZ+f6ZRa2gWO5ucfLWvDV3rA/KK1PS3vXtW10NL+K73IbySXiFycqW8jK4N93eNnrifVGxgM1tkGv0nRSjqqZSD8Imb68LGb0GyPd1EOffMNgLHHEJ4iRR0+LN/ZgONK1dLxrGsmN+49OqJooyDKAWhCwMNCJGecuSc0ahaA=="
}
And this is test response in my iap validator:
{"response":{"packageName":"com.rubeapp.padres","productId":"premium","purchaseTime":1453157722465,"purchaseState":0,"purchaseToken":"pdocnplgcbeoafhgimgkdmhj.AO-J1OxGqkmERQbgjQpcBZy6iaG8UCMzz-lHIYOc0fPwRFJLdAiVljZ31S-x904LUYFrUrQ-40qhIyGHRjIPhECB3e6VMIQukoVtbFLMrmsIpGfAmsImfjs","autoRenewing":false,"status":0,"service":"google"}}
Fixed the problem is purchase have a time of life, while the suscription is live in playstore you get always the same purchasetoken.
In test you must wait the suscription expired to get a new purchasetoken.
Related
We have a unique problem where some users have tried to purchase a sale bundle in our app, but after they finish a generic "Sale" iAP, we have under rare circumstances lost which bundle they were attempting to purchase.
Our bad logic aside, is there a way to "Decline" a purchase instead of acknowledging and consuming the iAP. I know that after 3 days, the purchase gets refunded and cancelled, but it seems like there should be a way to do this code side to avoid needing to wait in these circumstances.
So instead of something like
mBillingClient.consumeAsync(consumeParams, new ConsumeResponseListener() { ...
it would be
mBillingClient.cancelAsync(consumeParams, new ConsumeResponseListener() {
I created an Android app that supports in-app-purchase.
In order to develop in app purchase function, I need to setup in-app-purchase in GooglePlay console.
In order to create in-app-purchase in GooglePlay console, I need to upload app first. However, my app needs in-app-purchase to complete the development.
Therefore, I uploaded my incomplete app to GooglePlay in closed Alpha track. From some research, I learnt I also need to publish the app as otherwise in app purchase won't work.
However, the situation is Google takes long time to review the app and the status of the app is stuck in "being reviewed" for over 3 days. I don't know if the review will be passed as the app is incomplete and it is not functional.
OK, after some research, I learnt that I can use test in-app-purchase item such as
android.test.purchase and android.test.canceled
Therefore, I started testing my in-app-purchase function where my mobile app calls my backend to validate the purchase result and then I start receiving 400 error from Google on my server side.
At the following is my server side code that validates the purchase that received from mobile app:
let performGooglePlayValidationLogic = async function(userid, sku, purchaseToken) {
let auth = new google.auth.GoogleAuth({
credentials: {
client_email: '4655XXXXXXXX-compute#developer.gserviceaccount.com',
private_key: '-----BEGIN PRIVATE KEY-----XXXXXXXXXXXXX-----END PRIVATE KEY-----\n'
},
scopes: ['https://www.googleapis.com/auth/androidpublisher'],
});
const authClient = await auth.getClient();
google.options({ auth: authClient });
let purchaseResponse = {};
let purchases = google.androidpublisher({version: 'v3',}).purchases;
try {
//products
purchaseResponse = await purchases.products.get({
packageName: 'com.mycompany.myapp',
productId: sku,
token: purchaseToken,
});
if (purchaseResponse.data.purchaseState !== 0) {
throw new BadRequestException('Purchase is either Pending or Cancelled!');
} else if (purchaseResponse.data.consumptionState !== 0) {
throw new BadRequestException('Purchase is already consumed!');
} else {
At the following is the parameter that I passed into this method (The value are received from mobile app)
sku: android.test.purchased
purchaseToken: inapp:com.mycompany.myapp:android.test.purchased
Then I received exception at line purchaseResponse = await purchases.products.get({:
status:400
statusText:'Bad Request'
url: https://androidpublisher.googleapis.com/androidpublisher/v3/applications/com.mycompany.myapp/purchases/products/android.test.purchased/tokens/inapp%3Acom.mycompany.myapp%3Aandroid.test.purchased'
I am not sure if this 400 error is caused by my app is still under review so package name com.mycompany.myapp might yet be available, or it is because I did something wrong?
From my research it looks like 400 error is most likely cause by an invalid purchase token, which in my case it is inapp:com.mycompany.myapp:android.test.purchased I do feel it looks suspicious but it is received from my mobile app where is issued by GooglePlay.
ok after 2 day's investigation, it has been discovered that:
The purchasetoken (inapp:com.mycompany.myapp:android.test.purchased) generated by inapppurchase item android.test.purchased cannot be used on server side validation as androidpublisher.googleapis.com will return error 400 for such purchasetoken.
I hope my 2 day's work can help others with saving their time.
I'am using subscriptions in my application and it is working perfectly during testing. However, I didn't find a way to get user subscription history for all transactions.
Example:
-User subscribed to product id "sub1" for 3 months. (purchaseToken : "X")
-User canceled subscription for same product id "sub1"
-User resubscribed for same product id (purchaseToken : "Y")
In this scenario when querying queryPurchaseHistoryAsync() function it is returning only latest purchase. Also when using this [API][https://developers.google.com/android-publisher/api-ref/purchases/subscriptions/get] it returns only information of a specific purchase token ("Y" retreived from queryPurchaseHistoryAsync()).
Is there any other way to get user subscriptions history (Detailed transactions)
?
Any help would be greatly appreciated
After a lot of searching it turned out that there is no API that returns detailed transactions of specific subscription.
I am developed an app in which user purchases will registered on the server. And on the server side OrderId of Purchase class is a primary key.
In android documentation, I have read that order Id will be blank for test purchases. However, my server will not accept blank order id. So I tried this workaround:
public static final boolean IS_TEST_PURCHASES = true;
// Before updating to server
if (IS_TEST_PURCHASES && Util.isBlank(purchaseId))
purchaseId = "Test." + TimeUtils.getCurrentUnixTime();
This solved my problem for some time. But, in Beta mode the same problem appeared as I cannot release APK with IS_TEST_PURCHASES = true.
I checked the JSON retrned after purchase in which only OrderId is blank and rest of the feilds like signature, token are available. I was thinking if I can make a condition like:
if OrderId is empty and Signature or Token is not empty, then this
purchase is a test purchase and assign some dummy order id to it.
But I am confused whether this condition will cause any future problems.
I need to differentiate real purchase and test purchase so that I can set some dummy OrderId if and only if purchase is made through sandbox testing.
Any help is appreciated.
I don't know if this will help you, but what I did to differentiate between a test purchase and a real purchase was to use both the orderId and the purchaseToken as keys.
If it is a real purchase, there will be an order ID present that follows the pattern of something like GPA.ORDER_ID. If it's a test purchase, you are right there will be no order ID, but there will still be a purchase token that is a long string of random numbers / letters. So I know in my DB if it was a real purchase, the order ID will have GPA.ORDER_ID. If it doesn't follow that pattern, then it was a test purchase.
I have an android app with renewable monthly subscriptions. In this app I want to notify user some info when his subcription continues in next month.
As I can see renewals in merchant center(orderId ends with eg. ..0, ..1), but when querying the inventory my purchase orderId is same as befor eq.
{
"orderId": "GPA.XXXX-YYYY-XXXX-ZZZZZ",
"packageName": "my.packageName",
"productId": "my.sku",
"purchaseTime": 1456398623654,
"purchaseState": 0,
"developerPayload": "mypayload",
"purchaseToken": "token",
"autoRenewing": true
}
What bothers me more is that purchaseTime also doesn't change.
So my question is: If there is any way to detect in app that renewal occured?
Edit:
I'm using Google Play Developer API to get subscription info and then calculate number of renewals myself.
Order id for all recurrences are returned in orderId field of the INAPP_PURCHASE_DATA JSON field (in V3) with each recurring transaction appended by an integer.
Subscription order numbers
To help you track transactions relating to a given subscription,
Google payments provides a base Merchant Order Number for all
recurrences of the subscription and denotes each recurring transaction
by appending an integer as follows:
GPA.1234-5678-9012-34567 (base order number)
GPA.1234-5678-9012-34567..0 (first recurrence orderID)
GPA.1234-5678-9012-34567..1 (second recurrence orderID)
GPA.1234-5678-9012-34567..2 (third recurrence orderID) ...
But due to local caching you might not get the latest information. So try clearing cache from application manager to first see if you get correct purchase information.
Since purchase query this way is not reliable, it makes more sense to call Google Play Developer Purchases.subscriptions: get API from a backend to get Purchases.subscriptions resource which will return expiryTimeMillis of current subscription.
{
"kind": "androidpublisher#subscriptionPurchase",
"startTimeMillis": long,
"expiryTimeMillis": long,
"autoRenewing": boolean,
"priceCurrencyCode": string,
"priceAmountMicros": long,
"countryCode": string,
"developerPayload": string,
"paymentState": integer,
"cancelReason": integer
}
The purchase data for subscriptions is returned only when the subscription is active.
If the subscription expires then you won't get this purchase data when you query the inventory.
Excerpt from the link
If a recurring payment fails (for example, because the customer’s
credit card has become invalid), the subscription does not renew. The
getPurchases() method does not return failed or expired subscriptions.
You can serve in your DB the expiration date, and every time, when you are getting the data, you can check the expiration date with your db's value, and if it is later, then the subscription was renewed))
I struggled to find a solution for the exact implementation of #random's suggestion. It seems to indeed be the only way to have a solid implementation for renewal tracking on Android, but I couldn't find a good approach online on how to do it. For those who want to save some time (cost me 6 hours today), please find my answer below:
1. First step is to add the dependencies:
implementation "com.google.apis:google-api-services-androidpublisher:v3-rev142-1.25.0" // Update based on latest release
implementation "com.google.auth:google-auth-library-oauth2-http:1.12.1" // Update based on latest release
2. Follow these steps to link the Google Play Console with Google Play Developer API (choose the "Use a service account", not "Use OAuth clients" and follow until "Additional information").
3. Download the services JSON file from your Google Cloud service account (click on the account that you set up in the previous step). You can find/create this file under the "Manage Keys" action or the "Keys" tab. Add the exported JSON file in your assets folder in Android
4. Then you can call the Google Play Developer API to query subscriptions like this (important to call from a Thread, didn't work from the UI thread, not sure why):
new Thread(() -> {
InputStream inputStream = context.getAssets().open("service_account_google_play.json"); // JSON file from step 3
GoogleCredentials credentials = GoogleCredentials.fromStream(inputStream)
.createScoped(AndroidPublisherScopes.ANDROIDPUBLISHER);
AndroidPublisher androidPublisher = new AndroidPublisher(
new NetHttpTransport(),
JacksonFactory.getDefaultInstance(),
new HttpCredentialsAdapter(credentials)
);
SubscriptionPurchase purchase = androidPublisher.purchases().subscriptions().get(
context.getPackageName(), subscriptionId, purchaseToken
).execute();
// Check the orderId or check the expiryTimeMillis for renewal check, e.g. purchase.getOrderId();
}).start();
At the risk of being overly descriptive, the subscriptionId is the ID of your subscription in the Play Console (e.g. subscription_monthly or whatever you called it), and the purchaseToken is the token you get from the Purchase token after querying the BillingClient (querying subscriptions is explained in detail here).