Skip to content

Commit 5c4ae65

Browse files
committed
allow instant consuming of IAP for test purposes. fix purchaseWithReceipt call. now returns a Purchse object
1 parent 541f059 commit 5c4ae65

File tree

2 files changed

+80
-24
lines changed

2 files changed

+80
-24
lines changed

android/src/main/java/com/lykhonis/flutterbilling/BillingPlugin.java

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,13 @@
1515
import com.android.billingclient.api.SkuDetails;
1616
import com.android.billingclient.api.SkuDetailsParams;
1717
import com.android.billingclient.api.SkuDetailsResponseListener;
18+
import com.android.billingclient.api.ConsumeResponseListener;
19+
1820

1921
import java.util.ArrayList;
2022
import java.util.Collections;
2123
import java.util.HashMap;
24+
import java.util.Iterator;
2225
import java.util.List;
2326
import java.util.Map;
2427

@@ -34,6 +37,7 @@ public final class BillingPlugin implements MethodCallHandler {
3437
private final Activity activity;
3538
private final BillingClient billingClient;
3639
private final Map<String, Result> pendingPurchaseRequests;
40+
private final Map<String, Boolean> requestsToConsume;
3741

3842
private boolean billingServiceConnected;
3943

@@ -46,6 +50,7 @@ private BillingPlugin(Activity activity) {
4650
this.activity = activity;
4751

4852
pendingPurchaseRequests = new HashMap<>();
53+
requestsToConsume = new HashMap<>();
4954

5055
billingClient = BillingClient.newBuilder(activity)
5156
.setListener(new BillingListener())
@@ -87,8 +92,11 @@ public void onMethodCall(MethodCall methodCall, Result result) {
8792
} else if ("fetchPurchases".equals(methodCall.method)) {
8893
fetchPurchases(result);
8994
} else if ("purchase".equals(methodCall.method)) {
90-
purchase(methodCall.<String>argument("identifier"), result);
91-
} else if ("fetchSubscriptions".equals(methodCall.method)) {
95+
purchase(
96+
methodCall.<String>argument("identifier"),
97+
methodCall.<Boolean>argument("consume"),
98+
result);
99+
} else if ("fetchSubscriptions".equals(methodCall.method)) {
92100
fetchSubscriptions(result);
93101
} else if ("subscribe".equals(methodCall.method)) {
94102
subscribe(methodCall.<String>argument("identifier"), result);
@@ -153,7 +161,10 @@ public void failed() {
153161
});
154162
}
155163

156-
private void purchase(final String identifier, final Result result) {
164+
private void purchase(final String identifier, final Boolean consume, final Result result) {
165+
166+
requestsToConsume.put(identifier, consume);
167+
157168
executeServiceRequest(new Request() {
158169
@Override
159170
public void execute() {
@@ -165,6 +176,7 @@ public void execute() {
165176
.build());
166177

167178
if (responseCode == BillingResponse.OK) {
179+
Log.d(TAG, "purchase(): result: " + result.toString());
168180
pendingPurchaseRequests.put(identifier, result);
169181
} else {
170182
result.error("ERROR", "Failed to launch billing flow to purchase an item with error " + responseCode, null);
@@ -259,7 +271,7 @@ static Map<String, Object> convertSkuDetailToMap(SkuDetails detail) {
259271
return product;
260272
}
261273

262-
List<Map<String, Object>> convertPurchasesToListOfMaps(List<Purchase> purchases) {
274+
private List<Map<String, Object>> convertPurchasesToListOfMaps(List<Purchase> purchases) {
263275
if (purchases == null) {
264276
return Collections.emptyList();
265277
}
@@ -271,7 +283,7 @@ List<Map<String, Object>> convertPurchasesToListOfMaps(List<Purchase> purchases)
271283
return list;
272284
}
273285

274-
static Map<String, Object> convertPurchaseToMap(Purchase purchase) {
286+
private static Map<String, Object> convertPurchaseToMap(Purchase purchase) {
275287
final Map<String, Object> product = new HashMap<>();
276288
product.put("orderId", purchase.getOrderId());
277289
product.put("packageName", purchase.getPackageName());
@@ -325,11 +337,34 @@ private void executeServiceRequest(Request request) {
325337
}
326338

327339
final class BillingListener implements PurchasesUpdatedListener {
340+
328341
@Override
329342
public void onPurchasesUpdated(int resultCode, List<Purchase> purchases) {
343+
344+
if (purchases != null) {
345+
Log.d(TAG, "onPurchasesUpdated(): num: " + purchases.size());
346+
347+
for (Purchase p : purchases) {
348+
Boolean consume = requestsToConsume.remove(p.getSku());
349+
if (consume != null && consume == true) {
350+
Log.d("BillingPlugin", "onPurchasesUpdated(): consuming. token " + p.getPurchaseToken());
351+
352+
billingClient.consumeAsync(p.getPurchaseToken(), new ConsumeResponseListener() {
353+
@Override
354+
public void onConsumeResponse(int responseCode, String purchaseToken) {
355+
Log.d("BillingPlugin", "onConsumeResponse(): consumed: " + purchaseToken);
356+
}
357+
});
358+
}
359+
}
360+
}
361+
330362
if (resultCode == BillingResponse.OK && purchases != null) {
363+
331364
final List<Map<String, Object>> identifiers = convertPurchasesToListOfMaps(purchases);
332365
for (final Map<String, Object> next : identifiers) {
366+
Log.d("BillingPlugin", "onPurchasesUpdated(): id: " + next);
367+
333368
final Result result = pendingPurchaseRequests.remove(next.get("identifier"));
334369
if (result != null) {
335370
result.success(identifiers);

lib/flutter_billing.dart

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ class Purchase {
9595
@override
9696
String toString() {
9797
return 'Purchase{order: $orderId, package: $packageName, sku: $identifier, '
98-
'token: $purchaseToken, purchaseTime: $purchaseTime, expiresTime: $expiresTime, autorenewal: $autorenewal';
98+
'purchaseToken: $purchaseToken, purchaseTime: $purchaseTime, expiresTime: $expiresTime, autorenewal: $autorenewal';
9999
}
100100
}
101101

@@ -158,7 +158,10 @@ class Billing {
158158
///
159159
/// Returns products identifiers that are already purchased.
160160
Future<Set<Purchase>> getPurchases() {
161+
print("FLUTTER_BILLING: getPurchases():");
162+
161163
if (_purchasesFetched) {
164+
print("FLUTTER_BILLING: getPurchases(): already fetched");
162165
return new Future.value(new Set.from(_purchasedProducts));
163166
}
164167
return synchronized(this, () async {
@@ -167,6 +170,7 @@ class Billing {
167170
key: (purchase) => purchase['orderId'],
168171
value: (purchase) => _convertToPurchase(purchase),
169172
);
173+
print("FLUTTER_BILLING: getPurchases(): fetched: ${purchases.values.length} products");
170174
_purchasedProducts.addAll(purchases.values);
171175
_purchasesFetched = true;
172176
return _purchasedProducts;
@@ -176,29 +180,43 @@ class Billing {
176180
/// Validate if a product is purchased.
177181
///
178182
/// Returns true if a product is purchased, otherwise false.
183+
///
179184
Future<bool> isPurchased(String identifier) async {
180185
assert(identifier != null);
181186
final Set<Purchase> purchases = await getPurchases();
182187
return purchases.where((Purchase purchase) => purchase.identifier == identifier).isNotEmpty;
183188
}
184189

190+
/// Validate if a product is purchased.
191+
///
192+
/// Returns a [Purchase] if a product is purchased, otherwise null.
193+
///
194+
Future<Purchase> getPurchase(String identifier) async {
195+
assert(identifier != null);
196+
final Set<Purchase> purchases = await getPurchases();
197+
return purchases.where((Purchase purchase) => purchase.identifier == identifier).first;
198+
}
199+
185200
/// Purchase a product.
186201
/// [identifier] id of the product to purchase.
187202
/// [appSharedSecret] Apples app shared secret. Only for Apple, ignored for Android.
188203
///
189204
/// This would trigger platform UI to walk a user through steps of purchasing the product.
190205
/// Returns updated list of product identifiers that have been purchased.
191-
Future<bool> purchase(String identifier) async {
206+
Future<bool> purchase(String identifier, bool consume) async {
192207
assert(identifier != null);
193208

209+
print("FLUTTER_BILLING: purchase(): id: ${identifier}");
210+
194211
final bool purchased = await isPurchased(identifier);
195212
if (purchased) {
213+
print("FLUTTER_BILLING: purchase(): id: ${identifier} already purchased");
196214
return new Future.value(true);
197215
}
198216

199217
return synchronized(this, () async {
200218
final Map<String, Purchase> purchases = new Map.fromIterable(
201-
await _channel.invokeMethod('purchase', {'identifier': identifier}),
219+
await _channel.invokeMethod('purchase', {'identifier': identifier, 'consume': consume}),
202220
key: (purchase) => purchase['orderId'],
203221
value: (purchase) => _convertToPurchase(purchase),
204222
);
@@ -211,32 +229,29 @@ class Billing {
211229

212230
/// Purchase a product. And retrieve the receipt.
213231
/// Use this when you need the receipt data to do serverside validation
214-
Future<Purchase> purchaseWithReceipt(String identifier) async {
232+
///
233+
Future<Purchase> purchaseWithReceipt(String identifier, bool consume) async {
215234
assert(identifier != null);
216235

217-
print("FLUTTER_BILLIING: purchaseWithReceipt(): productId: $identifier");
236+
print("FLUTTER_BILLING: purchaseWithReceipt(): productId: $identifier");
218237

219238
final bool purchased = await isPurchased(identifier);
239+
220240
if (purchased) {
221-
print("FLUTTER_BILLIING: purchaseWithReceipt(): already puchased");
222-
// return new Future.value(null);
241+
print("FLUTTER_BILLING: purchaseWithReceipt(): already puchased");
242+
//return new Future.value(null);
223243
}
224244

225245
return synchronized(this, () async {
226246
final Map<String, Purchase> purchases = new Map.fromIterable(
227-
await _channel.invokeMethod('purchase', {'identifier': identifier}),
228-
key: (purchase) => purchase['orderId'],
247+
await _channel.invokeMethod('purchase', {'identifier': identifier, 'consume': consume}),
248+
key: (purchase) => purchase['identifier'],
229249
value: (purchase) => _convertToPurchase(purchase),
230250
);
231-
232-
purchases.forEach((k,v) {
233-
if (k == identifier) {
234-
print("FLUTTER_BILLIING: purchaseWithReceipt(): got receipt");
235-
return v;
236-
}
237-
});
238-
// Something went wrong
239-
return null;
251+
_purchasedProducts.addAll(purchases.values);
252+
_purchasesFetched = true;
253+
final Purchase purchase = await getPurchase(identifier);
254+
return purchase;
240255
});
241256
}
242257

@@ -322,10 +337,12 @@ BillingProduct _convertToBillingProduct(Map<dynamic, dynamic> product) {
322337
}
323338

324339
Purchase _convertToPurchase(Map<dynamic, dynamic> purchase) {
340+
print("_convertToPurchase(): $purchase");
325341
assert(purchase != null);
326342
final num purchaseTime = purchase['purchaseTime'];
327343
final num expiresTime = purchase['expiresTime'];
328-
return new Purchase(
344+
345+
Purchase ret = new Purchase(
329346
orderId: purchase['orderId'],
330347
packageName: purchase['packageName'],
331348
identifier: purchase['identifier'],
@@ -334,4 +351,8 @@ Purchase _convertToPurchase(Map<dynamic, dynamic> purchase) {
334351
expiresTime: expiresTime?.toInt() ?? 0,
335352
autorenewal: purchase['autorenewal'],
336353
);
354+
355+
print("_convertToPurchase(): $ret");
356+
357+
return ret;
337358
}

0 commit comments

Comments
 (0)