dooboolab-community/flutter_inapp_purchase

Slow test card is approved but should decline

DevonTomatoSandwich opened this issue · 10 comments

Version of flutter_inapp_purchase

flutter_inapp_purchase: ^2.0.5

Platforms you faced the error (IOS or Android or both?)

Android

Expected behavior

When clicking "Slow test card, declines after a few minutes" I'm expecting my app to wait until there is a result, in this case declined. I'm testing a non-consumable iap.

Actual behavior

It seems the purchase is accepted as the user has access to purchased goods immediately after clicking "Slow test card, declines after a few minutes". Not seeing any update to the purchase after several minutes (i.e. from the debug console after several minutes)

Tested environment (Emulator? Real Device?)

Real Device

Steps to reproduce the behavior

When user clicks the button in the app to open the purchase dialog for the item

void openPurchaseScreen() { 
  print('openPurchaseScreen (Next was pushed)');
  asyncPurchaseScreen();
}

asyncPurchaseScreen() async {
  if(!isFiapDone){ // if iap was not initialised (e.g. if internet off when launching) set up now
    await initStorePurchases(); // initialise billing
  }
  await FlutterInappPurchase.instance.requestPurchase('premium14'); // opens purchase screen for item
}

To initialise billing:

Future<bool> initStorePurchases() async {
  print('initStorePurchases');

  var result = await FlutterInappPurchase.instance.initConnection;
  print('result: $result');

  _conectionSubscription = FlutterInappPurchase.connectionUpdated.listen((connected) {
    print('connected: $connected');
  });

  _purchaseUpdatedSubscription = FlutterInappPurchase.purchaseUpdated.listen((purchasedItem) {
    print('purchase-updated: $purchasedItem');
    setFullSolutionOn();
  });

  _purchaseErrorSubscription = FlutterInappPurchase.purchaseError.listen((purchaseError) {
    print('purchase-error: $purchaseError');
  });

  await _getProduct();
  await _getPurchases();

  return true;
}

When "Slow test card. Declines after a few minutes" is pressed the console reads

I/flutter ( 5641): purchase-updated: productId: premium14, transactionId: GPA.3345-4499-1762-10765, transactionDate: 2019-12-28T10:32:39.795, transactionReceipt: {"orderId":"GPA.3345-4499-1762-10765","packageName":"com.jfspackage.watsuppbreh.stiffnessmethodsolver","productId":"premium14","purchaseTime":1577489559795,"purchaseState":4,"purchaseToken":"nokoohicmcnigldkfekndaoe.AO-J1OyZ7Hy1FyyPQzckkUIksPYBlPQaMrcslIp950J_XTjk235mJ4UVP1u5WQSC466XLJbPe1_UkZ1JFOOd871F23Jc27xG0uZ8UX0m-AC3LAua7Zmym0HDnZ62lCxo6TClIed1iWd8mmFrc8qFAS-CHo5umpGK7g","acknowledged":false}, purchaseToken: nokoohicmcnigldkfekndaoe.AO-J1OyZ7Hy1FyyPQzckkUIksPYBlPQaMrcslIp950J_XTjk235mJ4UVP1u5WQSC466XLJbPe1_UkZ1JFOOd871F23Jc27xG0uZ8UX0m-AC3LAua7Zmym0HDnZ62lCxo6TClIed1iWd8mmFrc8qFAS-CHo5umpGK7g, orderId: GPA.3345-4499-1762-10765, dataAndroid: {"orderId":"GPA.3345-4499-1762-10765","packageName":"com.jfspackage.watsuppbreh.stiffnessmethodsolver","productId":"premium14","purchaseTime":1577489559795,"purchaseState":4,"purchaseToken":"nokoohicmcnigldkfekndaoe.AO-J1OyZ7
I/flutter ( 5641): setFullSolutionOn
I/flutter ( 5641): FileState didChangeAppLifecycleState
I/flutter ( 5641): didChangeAppLifecycleState AppLifecycleState.resumed
I/flutter ( 5641): StartState build

Notice that purchase-updated is called which runs the code in the stream:
FlutterInappPurchase.purchaseUpdated.listen
In my code in this stream i call setFullSolutionOn which saves the purchase in my local database and sets the UI so the user knows the item is purchased. But this happens immediately. How can i wait till the purchase is processed?

I don't see any later calls to purchase error i.e.
FlutterInappPurchase.purchaseError.listen
but i'm not sure if this is supposed to run.

This issue looks like related to handling pending purchases. Currently, I'm not sure what to do with it.

The issue #199 you mentioned is about whether you can see the 4 testing options. It was closed as the OP eventually saw the 4 options later.

My issue is different as it is about the unexpected result when pushing "Slow test card. Declines after a few minutes"

Um. I think I am still confused about your expectations. Could you record some gifs for me to understand the problem for others who would wish to help you out?

So as of this issue i've updated my listener from

_purchaseUpdatedSubscription = FlutterInappPurchase.purchaseUpdated.listen((purchasedItem) {
  print('purchase-updated: $purchasedItem');
  setFullSolutionOn();
});

To

_purchaseUpdatedSubscription = FlutterInappPurchase.purchaseUpdated.listen((purchasedItem) async {
  print('purchase-updated: $purchasedItem');
      
  String result = await FlutterInappPurchase.instance.finishTransaction(
    purchasedItem, 
    developerPayloadAndroid: purchasedItem.developerPayloadAndroid, 
    isConsumable: false
  );
  print('  result (from finishTransaction) = $result');

  setFullSolutionOn();
});

But when one of the 2 "Slow test card..." options are selected
I get this error:

Exception has occurred.
PlatformException (PlatformException(acknowledgePurchase, E_DEVELOPER_ERROR, Google is indicating that we have some issue connecting to payment.))

Googling the error leads me to a similar issue from the react-native-iap
I think this is supposed to happen with a pending purchase due to this answer. It might be good if the user is alerted to why the app has frozen though. I'm thinking you should catch the error and alert the user saying that the connection is slow like this:

_purchaseUpdatedSubscription = FlutterInappPurchase.purchaseUpdated.listen((purchasedItem) async {
  print('purchase-updated: $purchasedItem');
  
  try {

    String result = await FlutterInappPurchase.instance.finishTransaction(
      purchasedItem, 
      developerPayloadAndroid: purchasedItem.developerPayloadAndroid, 
      isConsumable: false
    );
    print('  result (from finishTransaction) = $result');

    setFullSolutionOn(); // sets the purchase state and saves the purchase in a local database

  } on PlatformException { // slow connection
    print('PlatformException (slow connection)');

    // dialog lets user know there is a slow connection
    showDialog( context: context, builder: (_) => MyErrorDialog(title: 'Slow Connection',
      message: 'Connection with the store is delayed possibly due to ' +
      'slow connection to the store or a slow card.',),);
    
  }

});

The problem is if the "Slow test card. Declines after a few minutes" is selected and the user quits and resumes the app sometime during these "several minutes", the awaiting purchase is recognised as a successful purchase in _getPurchases() which is called when resuming. Here is my code for _getPurchases():

Future _getPurchases() async {
    List<PurchasedItem> items = await FlutterInappPurchase.instance.getAvailablePurchases();
    print('_getPurchases: (Short description)');
    for (var item in items) {
      print('productId=${item.productId}, transactionId=${item.transactionId}, transactionDate=${item.transactionDate}');
      this._purchases.add(item);
    }
    print('_getPurchases:');
    for (var item in items) {
      print('${item.toString()}');
    }

    setState(() {
      this._items = [];
      this._purchases = items;
    });
  }

To be clearer, I've made a repo here if anyone wants to try to reproduce this issue.

The repo has its own issue here which is similar to this one.

The app has a list of non-consumables you can purchase (smiley faces) check out the readme for more details and screenshots. The readme also has detailed instructions on how to set up.

This issue is stale because it has been open 90 days with no activity. Leave a comment or this will be closed in 7 days.

Does anyone have a solution? This issue also happened on official flutter in-app purchase package

@KazakovKirill
I published my app with these issues and I'm still not sure if people are scamming me with faulty test cards. Might try flutter's official package later this year but not sure if that will help either

Um. I think I am still confused about your expectations. Could you record some gifs for me to understand the problem for others who would wish to help you out?

I think the delayed confirmation should be handled with 2 results on listener: 1st a timeout (duration as an optional parameter) and 2nd the usual return (success or error). This timeout result could be handled in the App to show slow connection.

Should the App be closed before the 2nd (delayed result), getPastPurchases would get unfinished transactions.

I think I might be running into this problem. On Android, I made a purchase a couple days ago with the slow decline card. getPurchaseHistory() is now always giving me a purchase with transactionId == null. When I make a successful purchase I see a transaction with a transactionId != null via the purchase subscription, but when I reload the app and call getPurchaseHistory() I only see the transactionId == null value. I opened this issue to try and ask if this is normal.

Is this the behavior you all are seeing?