Skip to content

Commit b523b96

Browse files
author
Robot Media
committed
Added isPurchasedNet and countPurchasesNet. Closes robotmedia#3
1 parent e0957ce commit b523b96

File tree

8 files changed

+414
-74
lines changed

8 files changed

+414
-74
lines changed

AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java

Lines changed: 90 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,6 @@ public static enum BillingStatus {
4949
UNKNOWN, SUPPORTED, UNSUPPORTED
5050
}
5151

52-
private static BillingStatus status = BillingStatus.UNKNOWN;
53-
5452
/**
5553
* Used to provide on-demand values to the billing controller.
5654
*/
@@ -72,6 +70,8 @@ public interface IConfiguration {
7270
public String getPublicKey();
7371
}
7472

73+
private static BillingStatus status = BillingStatus.UNKNOWN;
74+
7575
private static Set<String> automaticConfirmations = new HashSet<String>();
7676
private static IConfiguration configuration = null;
7777
private static boolean debug = false;
@@ -149,8 +149,9 @@ private static void confirmNotifications(Context context, String[] notifyIds) {
149149
}
150150

151151
/**
152-
* Returns the number of purchases for the specified item. Refunded
153-
* purchases are not subtracted.
152+
* Returns the number of purchases for the specified item. Refunded and
153+
* cancelled purchases are not subtracted. See
154+
* {@link #countPurchasesNet(Context, String)} if they need to be.
154155
*
155156
* @param context
156157
* @param itemId
@@ -163,6 +164,34 @@ public static int countPurchases(Context context, String itemId) {
163164
return TransactionManager.countPurchases(context, itemId);
164165
}
165166

167+
/**
168+
* Returns the number of purchases for the specified item, minus the number
169+
* of cancellations and refunds.
170+
*
171+
* @param context
172+
* @param itemId
173+
* id of the item whose purchases will be counted.
174+
* @return number of net purchases for the specified item.
175+
*/
176+
public static int countPurchasesNet(Context context, String itemId) {
177+
final List<Transaction> transactions = BillingController.getTransactions(context);
178+
int count = 0;
179+
for (Transaction t : transactions) {
180+
switch (t.purchaseState) {
181+
case PURCHASED:
182+
count++;
183+
break;
184+
case CANCELLED:
185+
count--;
186+
break;
187+
case REFUNDED:
188+
count--;
189+
break;
190+
}
191+
}
192+
return count;
193+
}
194+
166195
/**
167196
* Requests purchase information for the specified notification. Immediately
168197
* followed by a call to
@@ -181,33 +210,45 @@ private static void getPurchaseInformation(Context context, String notifyId) {
181210
}
182211

183212
/**
184-
* Lists all transactions stored locally, including cancellations and refunds.
213+
* Gets the salt from the configuration and logs a warning if it's null.
214+
*
215+
* @return salt.
216+
*/
217+
private static byte[] getSalt() {
218+
byte[] salt = null;
219+
if (configuration == null || ((salt = configuration.getObfuscationSalt()) == null)) {
220+
Log.w(TAG, "Can't (un)obfuscate purchases without salt");
221+
}
222+
return salt;
223+
}
224+
225+
/**
226+
* Lists all transactions stored locally, including cancellations and
227+
* refunds.
185228
*
186229
* @param context
187230
* @return list of transactions.
188231
*/
189232
public static List<Transaction> getTransactions(Context context) {
190233
List<Transaction> transactions = TransactionManager.getTransactions(context);
191-
final byte[] salt = getSalt();
192-
if (salt != null) {
193-
for (Transaction p : transactions) {
194-
unobfuscate(context, p);
195-
}
196-
}
234+
unobfuscate(context, transactions);
197235
return transactions;
198236
}
199237

200238
/**
201-
* Gets the salt from the configuration and logs a warning if it's null.
239+
* Lists all transactions of the specified item, stored locally.
202240
*
203-
* @return salt.
241+
* @param context
242+
* @param itemId
243+
* id of the item whose transactions will be returned.
244+
* @return list of transactions.
204245
*/
205-
private static byte[] getSalt() {
206-
byte[] salt = null;
207-
if (configuration == null || ((salt = configuration.getObfuscationSalt()) == null)) {
208-
Log.w(TAG, "Can't (un)obfuscate purchases without salt");
209-
}
210-
return salt;
246+
public static List<Transaction> getTransactions(Context context, String itemId) {
247+
final byte[] salt = getSalt();
248+
itemId = salt != null ? Security.obfuscate(context, salt, itemId) : itemId;
249+
List<Transaction> transactions = TransactionManager.getTransactions(context, itemId);
250+
unobfuscate(context, transactions);
251+
return transactions;
211252
}
212253

213254
/**
@@ -227,6 +268,19 @@ public static boolean isPurchased(Context context, String itemId) {
227268
return TransactionManager.isPurchased(context, itemId);
228269
}
229270

271+
/**
272+
* Returns true if there have been purchases for the specified item and the
273+
* number is greater than the number of cancellations and refunds.
274+
*
275+
* @param context
276+
* @param itemId
277+
* item id
278+
* @return true if there are net purchases for the item, false otherwise.
279+
*/
280+
public static boolean isPurchasedNet(Context context, String itemId) {
281+
return countPurchasesNet(context, itemId) > 0;
282+
}
283+
230284
/**
231285
* Notifies observers of the purchase state change of the specified item.
232286
*
@@ -260,7 +314,7 @@ private static void notifyPurchaseStateChange(String itemId, Transaction.Purchas
260314
* purchase to be obfuscated.
261315
* @see #unobfuscate(Context, Transaction)
262316
*/
263-
private static void obfuscate(Context context, Transaction purchase) {
317+
static void obfuscate(Context context, Transaction purchase) {
264318
final byte[] salt = getSalt();
265319
if (salt == null) {
266320
return;
@@ -367,14 +421,8 @@ protected static void onPurchaseStateChanged(Context context, String signedData,
367421
// refunds.
368422
addManualConfirmation(p.productId, p.notificationId);
369423
}
370-
371-
// Save itemId and purchaseState before obfuscation for later use
372-
final String itemId = p.productId;
373-
final Transaction.PurchaseState purchaseState = p.purchaseState;
374-
obfuscate(context, p);
375-
TransactionManager.addTransaction(context, p);
376-
377-
notifyPurchaseStateChange(itemId, purchaseState);
424+
storeTransaction(context, p);
425+
notifyPurchaseStateChange(p.productId, p.purchaseState);
378426
}
379427
if (!confirmations.isEmpty()) {
380428
final String[] notifyIds = confirmations.toArray(new String[confirmations.size()]);
@@ -542,15 +590,27 @@ public static void startPurchaseIntent(Activity activity, PendingIntent purchase
542590
}
543591
}
544592

593+
static void storeTransaction(Context context, Transaction t) {
594+
final Transaction t2 = t.clone();
595+
obfuscate(context, t2);
596+
TransactionManager.addTransaction(context, t2);
597+
}
598+
599+
static void unobfuscate(Context context, List<Transaction> transactions) {
600+
for (Transaction p : transactions) {
601+
unobfuscate(context, p);
602+
}
603+
}
604+
545605
/**
546-
* Unobfuscated the specified purchase.
606+
* Unobfuscate the specified purchase.
547607
*
548608
* @param context
549609
* @param purchase
550610
* purchase to unobfuscate.
551611
* @see #obfuscate(Context, Transaction)
552612
*/
553-
private static void unobfuscate(Context context, Transaction purchase) {
613+
static void unobfuscate(Context context, Transaction purchase) {
554614
final byte[] salt = getSalt();
555615
if (salt == null) {
556616
return;

AndroidBillingLibrary/src/net/robotmedia/billing/model/BillingDB.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ public Cursor queryTransactions() {
6868
null, null, null, null);
6969
}
7070

71+
public Cursor queryTransactions(String productId) {
72+
return mDb.query(TABLE_TRANSACTIONS, TABLE_TRANSACTIONS_COLUMNS, COLUMN_PRODUCT_ID + " = ?",
73+
new String[] {productId}, null, null, null);
74+
}
75+
7176
public Cursor queryTransactions(String productId, PurchaseState state) {
7277
return mDb.query(TABLE_TRANSACTIONS, TABLE_TRANSACTIONS_COLUMNS, COLUMN_PRODUCT_ID + " = ? AND " + COLUMN_STATE + " = ?",
7378
new String[] {productId, String.valueOf(state.ordinal())}, null, null, null);

AndroidBillingLibrary/src/net/robotmedia/billing/model/Transaction.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,51 @@ public Transaction(String orderId, String productId, String packageName, Purchas
8181
this.developerPayload = developerPayload;
8282
}
8383

84+
public Transaction clone() {
85+
return new Transaction(orderId, productId, packageName, purchaseState, notificationId, purchaseTime, developerPayload);
86+
}
87+
88+
@Override
89+
public boolean equals(Object obj) {
90+
if (this == obj)
91+
return true;
92+
if (obj == null)
93+
return false;
94+
if (getClass() != obj.getClass())
95+
return false;
96+
Transaction other = (Transaction) obj;
97+
if (developerPayload == null) {
98+
if (other.developerPayload != null)
99+
return false;
100+
} else if (!developerPayload.equals(other.developerPayload))
101+
return false;
102+
if (notificationId == null) {
103+
if (other.notificationId != null)
104+
return false;
105+
} else if (!notificationId.equals(other.notificationId))
106+
return false;
107+
if (orderId == null) {
108+
if (other.orderId != null)
109+
return false;
110+
} else if (!orderId.equals(other.orderId))
111+
return false;
112+
if (packageName == null) {
113+
if (other.packageName != null)
114+
return false;
115+
} else if (!packageName.equals(other.packageName))
116+
return false;
117+
if (productId == null) {
118+
if (other.productId != null)
119+
return false;
120+
} else if (!productId.equals(other.productId))
121+
return false;
122+
if (purchaseState != other.purchaseState)
123+
return false;
124+
if (purchaseTime != other.purchaseTime)
125+
return false;
126+
return true;
127+
}
128+
129+
130+
84131
}

AndroidBillingLibrary/src/net/robotmedia/billing/model/TransactionManager.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ public synchronized static int countPurchases(Context context, String itemId) {
5252
public synchronized static List<Transaction> getTransactions(Context context) {
5353
BillingDB db = new BillingDB(context);
5454
final Cursor c = db.queryTransactions();
55+
final List<Transaction> transactions = cursorToList(c);
56+
db.close();
57+
return transactions;
58+
}
59+
60+
private static List<Transaction> cursorToList(final Cursor c) {
5561
final List<Transaction> transactions = new ArrayList<Transaction>();
5662
if (c != null) {
5763
while (c.moveToNext()) {
@@ -60,6 +66,13 @@ public synchronized static List<Transaction> getTransactions(Context context) {
6066
}
6167
c.close();
6268
}
69+
return transactions;
70+
}
71+
72+
public synchronized static List<Transaction> getTransactions(Context context, String itemId) {
73+
BillingDB db = new BillingDB(context);
74+
final Cursor c = db.queryTransactions(itemId);
75+
final List<Transaction> transactions = cursorToList(c);
6376
db.close();
6477
return transactions;
6578
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package net.robotmedia.billing;
2+
3+
import java.util.List;
4+
5+
import net.robotmedia.billing.model.BillingDB;
6+
import net.robotmedia.billing.model.BillingDBTest;
7+
import net.robotmedia.billing.model.Transaction;
8+
import net.robotmedia.billing.model.TransactionTest;
9+
import android.test.AndroidTestCase;
10+
import android.test.suitebuilder.annotation.MediumTest;
11+
12+
public class BillingControllerTest extends AndroidTestCase {
13+
14+
private BillingDB mData;
15+
16+
@Override
17+
protected void tearDown() throws Exception {
18+
super.tearDown();
19+
BillingDBTest.deleteDB(mData);
20+
}
21+
22+
@Override
23+
protected void setUp() throws Exception {
24+
super.setUp();
25+
mData = new BillingDB(getContext());
26+
}
27+
28+
@MediumTest
29+
public void testIsPurchased() throws Exception {
30+
assertFalse(BillingController.isPurchased(getContext(), TransactionTest.TRANSACTION_1.productId));
31+
BillingController.storeTransaction(getContext(), TransactionTest.TRANSACTION_1);
32+
assertTrue(BillingController.isPurchased(getContext(), TransactionTest.TRANSACTION_1.productId));
33+
BillingController.storeTransaction(getContext(), TransactionTest.TRANSACTION_1_REFUNDED);
34+
assertTrue(BillingController.isPurchased(getContext(), TransactionTest.TRANSACTION_1.productId));
35+
}
36+
37+
@MediumTest
38+
public void testIsPurchasedNet() throws Exception {
39+
assertFalse(BillingController.isPurchasedNet(getContext(), TransactionTest.TRANSACTION_1.productId));
40+
BillingController.storeTransaction(getContext(), TransactionTest.TRANSACTION_1);
41+
assertTrue(BillingController.isPurchasedNet(getContext(), TransactionTest.TRANSACTION_1.productId));
42+
BillingController.storeTransaction(getContext(), TransactionTest.TRANSACTION_1_REFUNDED);
43+
assertFalse(BillingController.isPurchasedNet(getContext(), TransactionTest.TRANSACTION_1.productId));
44+
}
45+
46+
@MediumTest
47+
public void testCountPurchases() throws Exception {
48+
assertEquals(BillingController.countPurchases(getContext(), TransactionTest.TRANSACTION_1.productId), 0);
49+
BillingController.storeTransaction(getContext(), TransactionTest.TRANSACTION_1);
50+
assertEquals(BillingController.countPurchases(getContext(), TransactionTest.TRANSACTION_1.productId), 1);
51+
BillingController.storeTransaction(getContext(), TransactionTest.TRANSACTION_1_REFUNDED);
52+
assertEquals(BillingController.countPurchases(getContext(), TransactionTest.TRANSACTION_1.productId), 1);
53+
}
54+
55+
@MediumTest
56+
public void testCountPurchasesNet() throws Exception {
57+
assertEquals(BillingController.countPurchasesNet(getContext(), TransactionTest.TRANSACTION_1.productId), 0);
58+
BillingController.storeTransaction(getContext(), TransactionTest.TRANSACTION_1);
59+
assertEquals(BillingController.countPurchasesNet(getContext(), TransactionTest.TRANSACTION_1.productId), 1);
60+
BillingController.storeTransaction(getContext(), TransactionTest.TRANSACTION_1_REFUNDED);
61+
assertEquals(BillingController.countPurchasesNet(getContext(), TransactionTest.TRANSACTION_1.productId), 0);
62+
}
63+
64+
@MediumTest
65+
public void testGetTransactions() throws Exception {
66+
final List<Transaction> transactions0 = BillingController.getTransactions(getContext());
67+
assertEquals(transactions0.size(), 0);
68+
BillingController.storeTransaction(getContext(), TransactionTest.TRANSACTION_1);
69+
final List<Transaction> transactions1 = BillingController.getTransactions(getContext());
70+
assertEquals(transactions1.size(), 1);
71+
BillingController.storeTransaction(getContext(), TransactionTest.TRANSACTION_2);
72+
final List<Transaction> transactions2 = BillingController.getTransactions(getContext());
73+
assertEquals(transactions2.size(), 2);
74+
}
75+
76+
@MediumTest
77+
public void testGetTransactionsString() throws Exception {
78+
final List<Transaction> transactions0 = BillingController.getTransactions(getContext());
79+
assertEquals(transactions0.size(), 0);
80+
BillingController.storeTransaction(getContext(), TransactionTest.TRANSACTION_1);
81+
final List<Transaction> transactions1 = BillingController.getTransactions(getContext(), TransactionTest.TRANSACTION_1.productId);
82+
assertEquals(transactions1.size(), 1);
83+
BillingController.storeTransaction(getContext(), TransactionTest.TRANSACTION_2);
84+
final List<Transaction> transactions2 = BillingController.getTransactions(getContext(), TransactionTest.TRANSACTION_1.productId);
85+
assertEquals(transactions2.size(), 1);
86+
}
87+
}

0 commit comments

Comments
 (0)