Skip to content

Commit f358431

Browse files
syndarinserggl
authored andcommittedJul 23, 2019
Support for GetPurchaseHistory (#414)
* New API for BillingProcessor: isRequestBillingHistorySupported - to check if request purchase history supported getPurchaseHistory - to request purchase history * Updated code according to checkstyle * Updated README to cover new API with getPurchaseHistory.
1 parent 5bf9620 commit f358431

File tree

7 files changed

+300
-0
lines changed

7 files changed

+300
-0
lines changed
 

‎README.md

+19
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,25 @@ public final Date purchaseTime;
238238
public final PurchaseInfo purchaseInfo;
239239
```
240240

241+
## Getting Purchase History
242+
You can request most recent purchases using `getPurchaseHistory` method. Pass required type as "inapp" for one-time purchases and "subs" for subscriptions
243+
or use `Constants.PRODUCT_TYPE_MANAGED` and `Constants.PRODUCT_TYPE_SUBSCRIPTION` respectively.
244+
```java
245+
public List<BillingHistoryRecord> getPurchaseHistory(String type, Bundle extraParams)
246+
```
247+
As a result you will get a `List` of `BillingHistoryRecord` objects with following fields:
248+
```java
249+
public final String productId;
250+
public final String purchaseToken;
251+
public final long purchaseTime;
252+
public final String developerPayload;
253+
public final String signature;
254+
```
255+
Please keep in mind that this API requires `billing API` of version 6 or higher, so you should check if it is supported beforehand:
256+
```java
257+
public boolean isRequestBillingHistorySupported(String type)
258+
```
259+
241260
## Handle Canceled Subscriptions
242261

243262
Call `bp.getSubscriptionTransactionDetails(...)` and check the `purchaseInfo.purchaseData.autoRenewing` flag.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.anjlab.android.iab.v3;
2+
3+
import android.os.Parcel;
4+
5+
import com.anjlab.android.iab.v3.util.ResourcesUtil;
6+
7+
import org.json.JSONException;
8+
import org.junit.Before;
9+
import org.junit.Test;
10+
import static junit.framework.Assert.assertEquals;
11+
12+
public class BillingHistoryRecordTest
13+
{
14+
15+
private String historyResponseJson;
16+
17+
@Before
18+
public void setup()
19+
{
20+
historyResponseJson = ResourcesUtil.loadFile("purchase_history_response.json");
21+
}
22+
23+
@Test
24+
public void testCreatesFromJsonCorrectly() throws JSONException
25+
{
26+
BillingHistoryRecord record = new BillingHistoryRecord(historyResponseJson, "signature");
27+
28+
assertEquals("sample-product-id", record.productId);
29+
assertEquals("sample-purchase-token", record.purchaseToken);
30+
assertEquals(1563441231403L, record.purchaseTime);
31+
assertEquals("sample-developer-payload", record.developerPayload);
32+
assertEquals("signature", record.signature);
33+
}
34+
35+
@Test
36+
public void testParcelizesCorrectly() throws JSONException
37+
{
38+
BillingHistoryRecord record = new BillingHistoryRecord(historyResponseJson, "signature");
39+
40+
Parcel parcel = Parcel.obtain();
41+
record.writeToParcel(parcel, 0);
42+
parcel.setDataPosition(0);
43+
44+
BillingHistoryRecord restoredRecord = BillingHistoryRecord.CREATOR.createFromParcel(parcel);
45+
assertEquals("sample-product-id", restoredRecord.productId);
46+
assertEquals("sample-purchase-token", restoredRecord.purchaseToken);
47+
assertEquals(1563441231403L, restoredRecord.purchaseTime);
48+
assertEquals("sample-developer-payload", restoredRecord.developerPayload);
49+
assertEquals("signature", restoredRecord.signature);
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"productId":"sample-product-id",
3+
"purchaseToken":"sample-purchase-token",
4+
"purchaseTime":1563441231403,
5+
"developerPayload":"sample-developer-payload"
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.anjlab.android.iab.v3;
2+
3+
public class BillingCommunicationException extends Exception
4+
{
5+
6+
public BillingCommunicationException(Throwable cause)
7+
{
8+
super(cause);
9+
}
10+
11+
public BillingCommunicationException(String message)
12+
{
13+
super(message);
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package com.anjlab.android.iab.v3;
2+
3+
import android.os.Parcel;
4+
import android.os.Parcelable;
5+
6+
import org.json.JSONException;
7+
import org.json.JSONObject;
8+
9+
public class BillingHistoryRecord implements Parcelable
10+
{
11+
12+
public final String productId;
13+
public final String purchaseToken;
14+
public final long purchaseTime;
15+
public final String developerPayload;
16+
public final String signature;
17+
18+
public BillingHistoryRecord(String dataAsJson, String signature) throws JSONException
19+
{
20+
this(new JSONObject(dataAsJson), signature);
21+
}
22+
23+
public BillingHistoryRecord(JSONObject json, String signature) throws JSONException
24+
{
25+
productId = json.getString("productId");
26+
purchaseToken = json.getString("purchaseToken");
27+
purchaseTime = json.getLong("purchaseTime");
28+
developerPayload = json.getString("developerPayload");
29+
this.signature = signature;
30+
}
31+
32+
public BillingHistoryRecord(String productId, String purchaseToken, long purchaseTime,
33+
String developerPayload, String signature)
34+
{
35+
this.productId = productId;
36+
this.purchaseToken = purchaseToken;
37+
this.purchaseTime = purchaseTime;
38+
this.developerPayload = developerPayload;
39+
this.signature = signature;
40+
}
41+
42+
protected BillingHistoryRecord(Parcel in)
43+
{
44+
productId = in.readString();
45+
purchaseToken = in.readString();
46+
purchaseTime = in.readLong();
47+
developerPayload = in.readString();
48+
signature = in.readString();
49+
}
50+
51+
@Override
52+
public void writeToParcel(Parcel dest, int flags)
53+
{
54+
dest.writeString(productId);
55+
dest.writeString(purchaseToken);
56+
dest.writeLong(purchaseTime);
57+
dest.writeString(developerPayload);
58+
dest.writeString(signature);
59+
}
60+
61+
@Override
62+
public int describeContents()
63+
{
64+
return 0;
65+
}
66+
67+
public static final Creator<BillingHistoryRecord> CREATOR = new Creator<BillingHistoryRecord>()
68+
{
69+
@Override
70+
public BillingHistoryRecord createFromParcel(Parcel in)
71+
{
72+
return new BillingHistoryRecord(in);
73+
}
74+
75+
@Override
76+
public BillingHistoryRecord[] newArray(int size)
77+
{
78+
return new BillingHistoryRecord[size];
79+
}
80+
};
81+
82+
@Override
83+
public String toString()
84+
{
85+
return "BillingHistoryRecord{" +
86+
"productId='" + productId + '\'' +
87+
", purchaseToken='" + purchaseToken + '\'' +
88+
", purchaseTime=" + purchaseTime +
89+
", developerPayload='" + developerPayload + '\'' +
90+
", signature='" + signature + '\'' +
91+
'}';
92+
}
93+
}

‎library/src/main/java/com/anjlab/android/iab/v3/BillingProcessor.java

+114
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
import com.android.vending.billing.IInAppBillingService;
3636

37+
import org.json.JSONException;
3738
import org.json.JSONObject;
3839

3940
import java.util.ArrayList;
@@ -489,6 +490,42 @@ public boolean isOneTimePurchaseWithExtraParamsSupported(Bundle extraParams)
489490
return isOneTimePurchaseExtraParamsSupported;
490491
}
491492

493+
/**
494+
* Checks if API supports version 6 which required to request purchase history
495+
* @param type product type, accepts either {@value Constants#PRODUCT_TYPE_MANAGED}
496+
* or {@value Constants#PRODUCT_TYPE_SUBSCRIPTION}
497+
* @return {@code true} if feature supported {@code false} otherwise
498+
*/
499+
public boolean isRequestBillingHistorySupported(String type) throws BillingCommunicationException
500+
{
501+
if (!type.equals(Constants.PRODUCT_TYPE_MANAGED) && !type.equals(Constants.PRODUCT_TYPE_SUBSCRIPTION))
502+
{
503+
throw new RuntimeException("Unsupported type " + type);
504+
}
505+
506+
IInAppBillingService billing = billingService;
507+
508+
if (billing != null)
509+
{
510+
511+
try
512+
{
513+
int response = billing.isBillingSupported(Constants.GOOGLE_API_REQUEST_PURCHASE_HISTORY_VERSION,
514+
contextPackageName, type);
515+
return response == Constants.BILLING_RESPONSE_RESULT_OK;
516+
}
517+
catch (RemoteException e)
518+
{
519+
throw new BillingCommunicationException(e);
520+
}
521+
522+
}
523+
else
524+
{
525+
throw new BillingCommunicationException("Billing service isn't connected");
526+
}
527+
}
528+
492529
/**
493530
* Change subscription i.e. upgrade or downgrade
494531
*
@@ -1021,4 +1058,81 @@ private void reportBillingError(int errorCode, Throwable error)
10211058
eventHandler.onBillingError(errorCode, error);
10221059
}
10231060
}
1061+
1062+
/**
1063+
* Returns the most recent purchase made by the user for each SKU, even if that purchase is expired, canceled, or consumed.
1064+
*
1065+
* @param type product type, accepts either {@value Constants#PRODUCT_TYPE_MANAGED} or
1066+
* {@value Constants#PRODUCT_TYPE_SUBSCRIPTION}
1067+
* @param extraParams a Bundle with extra params that would be appended into http request
1068+
* query string. Not used at this moment. Reserved for future functionality.
1069+
*
1070+
* @return @NotNull list of billing history records
1071+
* @throws BillingCommunicationException if billing isn't connected or there was an error during request execution
1072+
*/
1073+
public List<BillingHistoryRecord> getPurchaseHistory(String type, Bundle extraParams) throws BillingCommunicationException
1074+
{
1075+
1076+
if (!type.equals(Constants.PRODUCT_TYPE_MANAGED) && !type.equals(Constants.PRODUCT_TYPE_SUBSCRIPTION))
1077+
{
1078+
throw new RuntimeException("Unsupported type " + type);
1079+
}
1080+
1081+
IInAppBillingService billing = billingService;
1082+
1083+
if (billing != null)
1084+
{
1085+
1086+
try
1087+
{
1088+
1089+
List<BillingHistoryRecord> result = new ArrayList<>();
1090+
int resultCode;
1091+
String continuationToken = null;
1092+
1093+
do
1094+
{
1095+
1096+
Bundle resultBundle = billing.getPurchaseHistory(Constants.GOOGLE_API_REQUEST_PURCHASE_HISTORY_VERSION,
1097+
contextPackageName, type, continuationToken, extraParams);
1098+
resultCode = resultBundle.getInt(Constants.RESPONSE_CODE);
1099+
1100+
if (resultCode == Constants.BILLING_RESPONSE_RESULT_OK)
1101+
{
1102+
1103+
List<String> purchaseData = resultBundle.getStringArrayList(Constants.INAPP_PURCHASE_DATA_LIST);
1104+
1105+
List<String> signatures = resultBundle.getStringArrayList(Constants.INAPP_DATA_SIGNATURE_LIST);
1106+
1107+
if (purchaseData != null && signatures != null)
1108+
{
1109+
1110+
for (int i = 0, max = purchaseData.size(); i < max; i++)
1111+
{
1112+
String data = purchaseData.get(i);
1113+
String signature = signatures.get(i);
1114+
1115+
BillingHistoryRecord record = new BillingHistoryRecord(data, signature);
1116+
result.add(record);
1117+
}
1118+
1119+
continuationToken = resultBundle.getString(Constants.INAPP_CONTINUATION_TOKEN);
1120+
}
1121+
}
1122+
1123+
} while (continuationToken != null && resultCode == Constants.BILLING_RESPONSE_RESULT_OK);
1124+
1125+
return result;
1126+
1127+
} catch (RemoteException | JSONException e)
1128+
{
1129+
throw new BillingCommunicationException(e);
1130+
}
1131+
1132+
}
1133+
else
1134+
{
1135+
throw new BillingCommunicationException("Billing service isn't connected");
1136+
}
1137+
}
10241138
}

‎library/src/main/java/com/anjlab/android/iab/v3/Constants.java

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public class Constants
2020
public static final int GOOGLE_API_VERSION = 3;
2121
public static final int GOOGLE_API_SUBSCRIPTION_CHANGE_VERSION = 5;
2222
public static final int GOOGLE_API_VR_SUPPORTED_VERSION = 7;
23+
public static final int GOOGLE_API_REQUEST_PURCHASE_HISTORY_VERSION = 6;
2324

2425
public static final String PRODUCT_TYPE_MANAGED = "inapp";
2526
public static final String PRODUCT_TYPE_SUBSCRIPTION = "subs";
@@ -59,6 +60,7 @@ public class Constants
5960
public static final String BUY_INTENT = "BUY_INTENT";
6061
public static final String INAPP_PURCHASE_DATA_LIST = "INAPP_PURCHASE_DATA_LIST";
6162
public static final String INAPP_PURCHASE_DATA = "INAPP_PURCHASE_DATA";
63+
public static final String INAPP_CONTINUATION_TOKEN = "INAPP_CONTINUATION_TOKEN";
6264
public static final String RESPONSE_INAPP_SIGNATURE = "INAPP_DATA_SIGNATURE";
6365
public static final String INAPP_DATA_SIGNATURE_LIST = "INAPP_DATA_SIGNATURE_LIST";
6466
public static final String RESPONSE_ORDER_ID = "orderId";

0 commit comments

Comments
 (0)
Please sign in to comment.