lykhonis / flutter_billing

Flutter plugin to enable billing on iOS and Android.
Apache License 2.0
94 stars 22 forks source link

How to get receipt data for validation? #6

Closed opensourcegps closed 6 years ago

opensourcegps commented 6 years ago

In order to validate purchases server side, receipt data must be used. How to get it with this plugin?

purchase() only returns bool, however fake purchases are very frequent and server side validation is important.

Here is server code used for validation: https://gist.github.com/menny/1985010

Code implemented in cordova plugin that might be possible to use:

For Android: https://github.com/j3k0/cordova-plugin-purchase/blob/master/src/android/com/smartmobilesoftware/inappbilling/InAppBillingPlugin.java

purchase.getSignature()

For iOS: https://github.com/j3k0/cordova-plugin-purchase/blob/master/src/ios/InAppPurchase.m

Would be great if purchase() returned array of data, including status and signature/receipt for validation server side.

alamboley commented 6 years ago

Hey, just saying I'm currently working on this.

opensourcegps commented 6 years ago

Hi, I've made it working for Android with the code below. I bit ugly solution for pull request, requires requesting list of purchases just after purchase was made.

Added new function in BillingPlugin.java:


private void fetchPurchasesFull(final Result result) {
    executeServiceRequest(new Request() {
        @Override
        public void execute() {
            final Purchase.PurchasesResult purchasesResult = billingClient.queryPurchases(SkuType.INAPP);
            final int responseCode = purchasesResult.getResponseCode();

            if (responseCode == BillingResponse.OK) {
                result.success(getIdentifiersFull(purchasesResult.getPurchasesList()));
            } else {
                result.error("ERROR", "Failed to query purchases with error " + responseCode, null);
            }
        }

        @Override
        public void failed() {
            result.error("UNAVAILABLE", "Billing service is unavailable!", null);
        }
    });
}

private List<Object> getIdentifiersFull(List<Purchase> purchases) {
        if (purchases == null) return Collections.emptyList();

        //final List<String> identifiers = new ArrayList<>(purchases.size());
        final List<Object> identifiers = new ArrayList<>(purchases.size());

        for (Purchase purchase : purchases) {

            //debug edited - works
            final List<String> purchase_single = new ArrayList<>(2);

            String signature = purchase.getSignature();   //! important for verification
            String OriginalJson = purchase.getOriginalJson();   //! important for verification
            String sku = purchase.getSku();

            purchase_single.add(sku);
            purchase_single.add(OriginalJson);
            purchase_single.add(signature);

            //add more stuff about this purchase
            //https://developer.android.com/reference/com/android/billingclient/api/Purchase

            identifiers.add(purchase_single);

        }

        return identifiers;
    }

And in Dart side:


Future<Set<Object>> getPurchasesFull() {
  if (_purchasesFetchedFull) {
    return new Future.value(new Set.from(_purchasedProductsFull));
  }
  return synchronized(this, () async {
    try {
      final List purchases = await _channel.invokeMethod('fetchPurchasesFull');
      _purchasedProductsFull.addAll(purchases.cast());
    //  _purchasesFetchedFull = true;  //always refresh purchases list
      return _purchasedProductsFull;
    } catch (e) {
      if (_onError != null) _onError(e);
      return new Set.identity();
    }
  });
}
alamboley commented 6 years ago

@VolodymyrLykhonis ok, so I think for implemented it correctly, we need to review some logic. At the moment when a purchase is done, on the native side, you send an array with all purchases (as string identifiers) and check if result contains the given param identifier.

For iOS there is no problem because we can grab the receipt with a simple function call. But for Android this is an other story since it's linked to a purchase. And it seems, we are not able to grab it later.

I think we should unifiate the API in all cases. purchase method on native side should return a Dictionnary/Hash/json string with those args: success and receipt (and probably more informations for Android). Then on dart side, returns also this object.

Finally, I sent a commit with the logic for grabbing the receipt on iOS. We can merge easily this code with what I said above. For Android it seems to be a bit more complicated since receipt are attached to product.

It's a bit complicated to contribute correctly, since with my fork I don't have the Xcode & Android projects coming, so no real API auto completion... feeling in JS land :)

alamboley commented 6 years ago

Thanks for the code example @opensourcegps, a bit ugly but... it does the job quickly!

opensourcegps commented 6 years ago

@alamboley can you share your version for iOS verification please?

lykhonis commented 6 years ago

Agree, we can unify, always can bump to version to v0.3.0. Though I would suggest to define objects to represent data rather than arbitrary sets or maps of data. I left a comment on this PR with example of how to define a Receipt for both Android/iOS.

alamboley commented 6 years ago

@opensourcegps you can find it here https://github.com/VolodymyrLykhonis/flutter_billing/pull/10/commits/7b23c9aaf87859253beca08eb1df880c29891bc9.