-
Notifications
You must be signed in to change notification settings - Fork 536
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for GetPurchaseHistory #414
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package com.anjlab.android.iab.v3; | ||
|
||
import android.os.Parcel; | ||
|
||
import com.anjlab.android.iab.v3.util.ResourcesUtil; | ||
|
||
import org.json.JSONException; | ||
import org.junit.Before; | ||
import org.junit.Test; | ||
import static junit.framework.Assert.assertEquals; | ||
|
||
public class BillingHistoryRecordTest | ||
{ | ||
|
||
private String historyResponseJson; | ||
|
||
@Before | ||
public void setup() | ||
{ | ||
historyResponseJson = ResourcesUtil.loadFile("purchase_history_response.json"); | ||
} | ||
|
||
@Test | ||
public void testCreatesFromJsonCorrectly() throws JSONException | ||
{ | ||
BillingHistoryRecord record = new BillingHistoryRecord(historyResponseJson, "signature"); | ||
|
||
assertEquals("sample-product-id", record.productId); | ||
assertEquals("sample-purchase-token", record.purchaseToken); | ||
assertEquals(1563441231403L, record.purchaseTime); | ||
assertEquals("sample-developer-payload", record.developerPayload); | ||
assertEquals("signature", record.signature); | ||
} | ||
|
||
@Test | ||
public void testParcelizesCorrectly() throws JSONException | ||
{ | ||
BillingHistoryRecord record = new BillingHistoryRecord(historyResponseJson, "signature"); | ||
|
||
Parcel parcel = Parcel.obtain(); | ||
record.writeToParcel(parcel, 0); | ||
parcel.setDataPosition(0); | ||
|
||
BillingHistoryRecord restoredRecord = BillingHistoryRecord.CREATOR.createFromParcel(parcel); | ||
assertEquals("sample-product-id", restoredRecord.productId); | ||
assertEquals("sample-purchase-token", restoredRecord.purchaseToken); | ||
assertEquals(1563441231403L, restoredRecord.purchaseTime); | ||
assertEquals("sample-developer-payload", restoredRecord.developerPayload); | ||
assertEquals("signature", restoredRecord.signature); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"productId":"sample-product-id", | ||
"purchaseToken":"sample-purchase-token", | ||
"purchaseTime":1563441231403, | ||
"developerPayload":"sample-developer-payload" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package com.anjlab.android.iab.v3; | ||
|
||
public class BillingCommunicationException extends Exception | ||
{ | ||
|
||
public BillingCommunicationException(Throwable cause) | ||
{ | ||
super(cause); | ||
} | ||
|
||
public BillingCommunicationException(String message) | ||
{ | ||
super(message); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
package com.anjlab.android.iab.v3; | ||
|
||
import android.os.Parcel; | ||
import android.os.Parcelable; | ||
|
||
import org.json.JSONException; | ||
import org.json.JSONObject; | ||
|
||
public class BillingHistoryRecord implements Parcelable | ||
{ | ||
|
||
public final String productId; | ||
public final String purchaseToken; | ||
public final long purchaseTime; | ||
public final String developerPayload; | ||
public final String signature; | ||
|
||
public BillingHistoryRecord(String dataAsJson, String signature) throws JSONException | ||
{ | ||
this(new JSONObject(dataAsJson), signature); | ||
} | ||
|
||
public BillingHistoryRecord(JSONObject json, String signature) throws JSONException | ||
{ | ||
productId = json.getString("productId"); | ||
purchaseToken = json.getString("purchaseToken"); | ||
purchaseTime = json.getLong("purchaseTime"); | ||
developerPayload = json.getString("developerPayload"); | ||
this.signature = signature; | ||
} | ||
|
||
public BillingHistoryRecord(String productId, String purchaseToken, long purchaseTime, | ||
String developerPayload, String signature) | ||
{ | ||
this.productId = productId; | ||
this.purchaseToken = purchaseToken; | ||
this.purchaseTime = purchaseTime; | ||
this.developerPayload = developerPayload; | ||
this.signature = signature; | ||
} | ||
|
||
protected BillingHistoryRecord(Parcel in) | ||
{ | ||
productId = in.readString(); | ||
purchaseToken = in.readString(); | ||
purchaseTime = in.readLong(); | ||
developerPayload = in.readString(); | ||
signature = in.readString(); | ||
} | ||
|
||
@Override | ||
public void writeToParcel(Parcel dest, int flags) | ||
{ | ||
dest.writeString(productId); | ||
dest.writeString(purchaseToken); | ||
dest.writeLong(purchaseTime); | ||
dest.writeString(developerPayload); | ||
dest.writeString(signature); | ||
} | ||
|
||
@Override | ||
public int describeContents() | ||
{ | ||
return 0; | ||
} | ||
|
||
public static final Creator<BillingHistoryRecord> CREATOR = new Creator<BillingHistoryRecord>() | ||
{ | ||
@Override | ||
public BillingHistoryRecord createFromParcel(Parcel in) | ||
{ | ||
return new BillingHistoryRecord(in); | ||
} | ||
|
||
@Override | ||
public BillingHistoryRecord[] newArray(int size) | ||
{ | ||
return new BillingHistoryRecord[size]; | ||
} | ||
}; | ||
|
||
@Override | ||
public String toString() | ||
{ | ||
return "BillingHistoryRecord{" + | ||
"productId='" + productId + '\'' + | ||
", purchaseToken='" + purchaseToken + '\'' + | ||
", purchaseTime=" + purchaseTime + | ||
", developerPayload='" + developerPayload + '\'' + | ||
", signature='" + signature + '\'' + | ||
'}'; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,6 +34,7 @@ | |
|
||
import com.android.vending.billing.IInAppBillingService; | ||
|
||
import org.json.JSONException; | ||
import org.json.JSONObject; | ||
|
||
import java.util.ArrayList; | ||
|
@@ -489,6 +490,42 @@ public boolean isOneTimePurchaseWithExtraParamsSupported(Bundle extraParams) | |
return isOneTimePurchaseExtraParamsSupported; | ||
} | ||
|
||
/** | ||
* Checks if API supports version 6 which required to request purchase history | ||
* @param type product type, accepts either {@value Constants#PRODUCT_TYPE_MANAGED} | ||
* or {@value Constants#PRODUCT_TYPE_SUBSCRIPTION} | ||
* @return {@code true} if feature supported {@code false} otherwise | ||
*/ | ||
public boolean isRequestBillingHistorySupported(String type) throws BillingCommunicationException | ||
{ | ||
if (!type.equals(Constants.PRODUCT_TYPE_MANAGED) && !type.equals(Constants.PRODUCT_TYPE_SUBSCRIPTION)) | ||
{ | ||
throw new RuntimeException("Unsupported type " + type); | ||
} | ||
|
||
IInAppBillingService billing = billingService; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we really need a local var here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, |
||
|
||
if (billing != null) | ||
{ | ||
|
||
try | ||
{ | ||
int response = billing.isBillingSupported(Constants.GOOGLE_API_REQUEST_PURCHASE_HISTORY_VERSION, | ||
contextPackageName, type); | ||
return response == Constants.BILLING_RESPONSE_RESULT_OK; | ||
} | ||
catch (RemoteException e) | ||
{ | ||
throw new BillingCommunicationException(e); | ||
} | ||
|
||
} | ||
else | ||
{ | ||
throw new BillingCommunicationException("Billing service isn't connected"); | ||
} | ||
} | ||
|
||
/** | ||
* Change subscription i.e. upgrade or downgrade | ||
* | ||
|
@@ -1021,4 +1058,81 @@ private void reportBillingError(int errorCode, Throwable error) | |
eventHandler.onBillingError(errorCode, error); | ||
} | ||
} | ||
|
||
/** | ||
* Returns the most recent purchase made by the user for each SKU, even if that purchase is expired, canceled, or consumed. | ||
* | ||
* @param type product type, accepts either {@value Constants#PRODUCT_TYPE_MANAGED} or | ||
* {@value Constants#PRODUCT_TYPE_SUBSCRIPTION} | ||
* @param extraParams a Bundle with extra params that would be appended into http request | ||
* query string. Not used at this moment. Reserved for future functionality. | ||
* | ||
* @return @NotNull list of billing history records | ||
* @throws BillingCommunicationException if billing isn't connected or there was an error during request execution | ||
*/ | ||
public List<BillingHistoryRecord> getPurchaseHistory(String type, Bundle extraParams) throws BillingCommunicationException | ||
{ | ||
|
||
if (!type.equals(Constants.PRODUCT_TYPE_MANAGED) && !type.equals(Constants.PRODUCT_TYPE_SUBSCRIPTION)) | ||
{ | ||
throw new RuntimeException("Unsupported type " + type); | ||
} | ||
|
||
IInAppBillingService billing = billingService; | ||
|
||
if (billing != null) | ||
{ | ||
|
||
try | ||
{ | ||
|
||
List<BillingHistoryRecord> result = new ArrayList<>(); | ||
int resultCode; | ||
String continuationToken = null; | ||
|
||
do | ||
{ | ||
|
||
Bundle resultBundle = billing.getPurchaseHistory(Constants.GOOGLE_API_REQUEST_PURCHASE_HISTORY_VERSION, | ||
contextPackageName, type, continuationToken, extraParams); | ||
resultCode = resultBundle.getInt(Constants.RESPONSE_CODE); | ||
|
||
if (resultCode == Constants.BILLING_RESPONSE_RESULT_OK) | ||
{ | ||
|
||
List<String> purchaseData = resultBundle.getStringArrayList(Constants.INAPP_PURCHASE_DATA_LIST); | ||
|
||
List<String> signatures = resultBundle.getStringArrayList(Constants.INAPP_DATA_SIGNATURE_LIST); | ||
|
||
if (purchaseData != null && signatures != null) | ||
{ | ||
|
||
for (int i = 0, max = purchaseData.size(); i < max; i++) | ||
{ | ||
String data = purchaseData.get(i); | ||
String signature = signatures.get(i); | ||
|
||
BillingHistoryRecord record = new BillingHistoryRecord(data, signature); | ||
result.add(record); | ||
} | ||
|
||
continuationToken = resultBundle.getString(Constants.INAPP_CONTINUATION_TOKEN); | ||
} | ||
} | ||
|
||
} while (continuationToken != null && resultCode == Constants.BILLING_RESPONSE_RESULT_OK); | ||
|
||
return result; | ||
|
||
} catch (RemoteException | JSONException e) | ||
{ | ||
throw new BillingCommunicationException(e); | ||
} | ||
|
||
} | ||
else | ||
{ | ||
throw new BillingCommunicationException("Billing service isn't connected"); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❤️