libgdx/gdx-pay

These in-app purchases can’t be promoted on the App Store because your latest approved binary doesn’t include the SKPaymentTransactionObserver method.

gagbaghdas opened this issue · 44 comments

Hi guys.

I see, that this issue should be resolved in gdx-pay-0.11.3-SNAPSHOT.

But I receive this warning message from Apple now.

I'm using 0.12.1-SNAPSHOT version.

Maybe I missed adding some part of implementation? But it seems no 😃 .

So, please, could you look into it?

Why don't you use 0.12.0? Are you using RoboVM or MOE?

Hi @MrStahlfelge. Thanks for the answer. I used 0.12.0, then, after seeing this issue, trying to use 0.12.1-SNAPSHOT, but nothing changed. So there is no difference between 0.12.0 and 0.12.1-SNAPSHOT regarding this issue.

Are you using RoboVM or MOE?

I'm using RoboVM(MobiVM's fork).

P.S., I tested with

itms-services://?action=purchaseIntent&bundleId=[my game bundle id]&productIdentifier=[sku]

and it didn't work ) so , I think there is a problem and it isn't on Apple's side)

Hi @MrStahlfelge , @gagbaghdas,

For #175 I implemented a required method for promoting IAP in App Store. @Glyfad never confirmed whether it worked. So it has not been tested so far.

I have never read through the developer guide of Apple, of how to implement it, I just followed instructions provided by @Glyfad. Changes made for the issue: 92ad0b7

So it is perfectly possible that it is not yet working well. We are willing to accept PRs to do additional required code changes.

@gagbaghdas Apple's error message says the last approved binary is concerned. I doubt that you were able to test the new versions that fast, in my experiences it takes at least a day until a new version is approved.

Please also state how we can reconstruct a problem. A link to App Store Connect does not work, please tell us how to go to this error message.

@MrStahlfelge I'm seeing this message for about a week already. So, there is no problem with the version approval.

I implemented PurchaseManageriOSApple in my game. In-app purchases work well, so, there is no problem with the implementation in general. I didn't do anything special for the app store promotions. So, you can just implement the PurchaseManageriOSApple, as you do it for the purchases and try to upload to the app store connect.

Here is the screenshot, where I'm seeing the message.

Screen Shot 2019-04-11 at 4 12 57 PM

P.S., "SKPaymentTransactionObserver method" in the message has a link, and when you click on it, you go to the
https://developer.apple.com/documentation/storekit/skpaymenttransactionobserver/2877502-paymentqueue

I'll check later if I have this warning in my app store connect with an approved and released 0.12.1-SNAPSHOT.

Thanks @MrStahlfelge ) I'll continue trying to understand what's going on.

P.S. @MrStahlfelge, you can test it with the following call.

Click on your ios device on the following link.

itms-services://?action=purchaseIntent&bundleId=[my game bundle id]&productIdentifier=[sku]

In my case, when I click on this link , it opens my game, but nothing happened regarding buying the product with the sku.

So, I think , you can test it without uploading to the app store connect.

@MrStahlfelge from the logs, I found the following message.

[SKPaymentQueue]: Tried to send purchase intent: [product_sku] to delegate, delegate does not respond to method paymentQueue:shouldAddStorePayment:forProduct

[product_sku] - is the SKU, of the in-app purchase, that in the

itms-services://?action=purchaseIntent&bundleId=[my game bundle id]&productIdentifier=[sku]

Any ideas?

@MrStahlfelge I'm closing this issue, for now, As I was able to successfully test flow on a simple app with hardcoded skus. Thanks for your help.

Where you able to test with gdxpay or just plain robovm?

With gdx-pay)

@keesvandieren @MrStahlfelge I have the following problem.

I initialize my PurchaseManager in the top of the didFinishLaunching method, but, actually, it isn't inited in most of the cases (I mean, sometimes it inited in time, and the flow works well, sometimes, no), when Apple wants to call it for the iaps, when you click on the link.

It works well When it already inited and, for example, my game in the background, and I click on the link.

I tried to init my PurchaseManager in createApplication method, but the result is the same.

So, maybe you have any suggestions?

IMPORTANT UPDATE.

It works well when I add the following code in the didFinishLaunching method.

SKPaymentTransactionObserverAdapter workaround = new SKPaymentTransactionObserverAdapter() {
            @Override
            public boolean shouldAddStorePayment(SKPaymentQueue queue, SKPayment payment, SKProduct product) {
                return true;
            }
        };
        SKPaymentQueue.getDefaultQueue().addTransactionObserver(workaround);
        SKPaymentQueue.getDefaultQueue().addStrongRef(workaround);

Thanks to Demyan Kimitsa for help :)

Yes. The problem cause is that PurchaseManageriOSApple installs the transaction observer after fetching the products, which is too late. I don't know if this is in purpose. The transaction observer itself does not access the products, but I just did a quick check and I am not sure if I missed side effects.

@MrStahlfelge Thanks a lot. Please, let me know if I can help with anything. ))

Found it. The observer needs product access in updatedTransactions.

@gagbaghdas With your work-around, what happens with the purchased items when your game starts cold: Is the purchase reliably reported to your PurchaseObserver#handlePurchase method?

Yes))

Good to hear, then we can integrate the same workaround into PurchaseManageriOSApple. Are you able to test a snapshot this weekend?

Sure . I'll wait for your confirmation. Thank you))

Published a new 0.12.1-SNAPSHOT with the workaround. Please let me know if this works for you. (Please check if you really got the latest snapshot, refreshing snapshots is sometimes fiddly)

Ah, please wait... I saw a copy/paste mistake :(

Okay, fixed. :)

@MrStahlfelge hi, thank you.
It works :)

That's great to hear! I will publish release 0.12.1 within the next two weeks. If you experience any problems in the meantime, please let us know.

Sure @MrStahlfelge , Thanks again.

P.S., I have 2 questions.

  1. Is there any way to install the PurchaseManager and change config later? Because I get sku's from a backend and don't have them in didFinishLaunching (
  2. Is there any way to hold the call of handlePurchase or something like this? The problem is that for this feature, Apple call purchase as soon as game starts, so there is still loading screen(

Thanks.

@MrStahlfelge , Apple says , that

If your app needs to defer transaction, you return false. For example, you may need to defer a transaction if the user is in the middle of onboarding, and continue it after onboarding is completed.

To defer a transaction:

  1. Save the payment to use when the app is ready. The payment already contains information about the product. Do not create a new SKPayment with the same product.
  2. Return false from shouldAddStorePayment method
  3. After the user is finished with onboarding or other actions that required a deferral, send the saved payment to the payment queue, the same way you would with a normal in-app purchase.

I'm not sure what they mean by saying "Save the payment to use when the app is ready. ". Could you please help me. How can I do it with gdxpay?

Thanks.

  1. You can dispose and reinstall the PurchaseObserver at any time.
  2. you said you get the callback to your purchaseobserver reiliably. so you can process the payment however you want

@MrStahlfelge what do you think about the following idea?

In PurchaseManageriOSApple.

  1. add the following variable.
private SKPayment promotionalPayment;
  1. Change the following code to
    private class PromotionTransactionObserver extends SKPaymentTransactionObserverAdapter {
        @Override
        public boolean shouldAddStorePayment(SKPaymentQueue queue, SKPayment payment, SKProduct product) {
         if(!addStorePayments){
               promotionalPayment = payment;
          }
            return addStorePayments;
        }
    }
  1. add the following method
public void purchasePromotionalProduct(){
     if(promotionalPayment != null){
          SKPaymentQueue.getDefaultQueue().addPayment(promotionalPayment);
          promotionalPayment = null;
      }
}

What do you think?

P.S. I see in the PurchaseManageriOSApple you wrote

if (this.appleObserver == null) {
                this.startupTransactionObserver = new PurchaseManageriOSApple.PromotionTransactionObserver();
....

Why do you check for null the appleObserver during startupTransactionObserver init?

Sorry, if I misunderstand anything )

P.S. 2. I tried to implement 2 observers, and it worked, when the game closed. But when the game opened and in the background and I try to test the flow, the same transaction purchases 2 times ) And after the 2nd purchase, Apple says, that "This In-App purchase has already been bought. It will be restored for free". ( Any ideas why the same transaction is being completed 2 times?

Thanks.

I don't think a change to the core interface is needed, as you said the purchase is reported to your observer anyway. You can store this purchase in an own variable, do your work to start up your game and then process the purchase in your gui.

I don't unterstand the second issue with two observers.

EDIT: The nullcheck of appleObserver is needed if the install method is called more than once.

@MrStahlfelge I tried to create MyPurchaseManageriOSApple and add the code mentioned above and it works as expected.

The problem with "You can store this purchase in an own variable, do your work to start up your game and then process the purchase in your gui." is that if you return true from shouldAddStorePayment, the payment is being added to the defaultQueue automatically and I'm seeing the Apple's prompt about purchase during loading screen. But if you return false, and from shouldAddStorePayment and serve the Payment object for later, it works as expected. e.g. you have control when to add to the defaultQueue the promotional Payment object and when show Apple's prompt about the purchase to the User.

About 2nd issue, I'm trying to reproduce now why the same transaction is being completed twice.

@MrStahlfelge FOUND THE 2ND ISSUE.

  1. I have added the following variable to the MyPurchaseManageriOSApple.
    private boolean startupTransactionObserverInstalled;

  1. And changed the following code to
 if (!startupTransactionObserverInstalled) {
                startupTransactionObserverInstalled = true;
                // Installing intermediate observer to handle App Store promotions
                ....
            }

And the bug with double transactions was fixed. It seems the problem was in the following

 if (appleObserver == null) {
                // Installing intermediate observer to handle App Store promotions
.....

As the new (2nd) PromotionTransactionObserver was installed during appleObserver install.

Are you going to add these 2 fixes (the above one and this one) to the 0.12.1-SNAPSHOT or no?
Because with these 2 fixes the feature is worked perfectly for all cases)
Please, let me know, if you are going to add these fixes to the SNAPSHOT, I'll remove MyPurchaseManageriOSApple and will use PurchaseManageriOSApple from the build.

Thank you for your help.

This problem indicates you really call install() more than once.

I would prefer to use this condition:

 if (appleObserver == null && startupTransactionObserver == null)

This should be the same, but please test this.

@MrStahlfelge Thanks.
Yes, I call install() twice, but before the second install() I call the dispose().

Ok, Will test now and let you know how it works.

Thanks.

Yes @MrStahlfelge you are right. The result is same :)

So, are you going to add these 2 changes to the SNAPSHOT?

Thanks.

Which is the second change?

Regarding promotionalPayment object.
Please, see above.

Thanks.

@MrStahlfelge , Please, check here "Deferring or Canceling a Transaction" section .

I implemented the same, as I mentioned here.

Please, let me know , if you are going to include these changes too.

Thank you.

Hi, I commited a fix for issue 2.

I also made it possible to override the behaviour on how the promotional purchases are processed. This way, if you use the PurchaseManager without any changes, the purchase is processed as soon as possible. But if you need other behaviour as you do, you can override this in an own subclass by overriding the method shouldProcessPromotionalStorePayment. You can do your own logic you suggested in this slim subclass without needing to copy the whole iOS purchase manager. I think that's the best solution and this way the code for the different use cases is seperated.

@MrStahlfelge Thanks. I'm closing the issue.

Just in case, if anyone needed.

import com.badlogic.gdx.pay.ios.apple.PurchaseManageriOSApple;

import org.robovm.apple.storekit.SKPayment;
import org.robovm.apple.storekit.SKPaymentQueue;
import org.robovm.apple.storekit.SKProduct;

public class MyPurchaseManageriOSApple extends PurchaseManageriOSApple {

    private SKPayment promotionalPayment;
    private boolean shouldProcessPromotionalStorePayment = true;

    @Override
    protected boolean shouldProcessPromotionalStorePayment(SKPaymentQueue queue, SKPayment payment, SKProduct product) {
        if (!shouldProcessPromotionalStorePayment) {
            promotionalPayment = payment;
        }
        return shouldProcessPromotionalStorePayment;
    }

    // Call this method to process saved promotional product later
    public void purchasePromotionalProduct(){
        if(promotionalPayment != null){
            SKPaymentQueue.getDefaultQueue().addPayment(promotionalPayment);
            promotionalPayment = null;
        }
    }
    
    public void enablePromotionalStorePayment() {
        shouldProcessPromotionalStorePayment = true;
    }

    public void disablePromotionalStorePayment() {
        shouldProcessPromotionalStorePayment = false;
    }
}

Thanks to all for help.

Hello, what is the final resolution for this?

We're using gdx Pay 0.12.1 and facing the same issue

We modify the IOS Purcharse Manager per your suggestion in this comment #192 (comment)

However we're not sure if this is good enough or we need to modify didFinishLaunching as well per this comment #192 (comment)