infinitered / ProMotion-iap

In-app purchases for ProMotion!
19 stars 7 forks source link

Canceling a purchase #13

Closed jayroh closed 9 years ago

jayroh commented 9 years ago

Hey everyone!

First off - excellent gem. So so so much cleaner and more sane than the generic StoreKit API. Awesome work :).

The issue at hand - I've set up a small IAP manager class to contain most of the logic. It contains the following:

class IAPManager
  attr_reader :in_app_purchase_id

  def initialize(in_app_purchase_id)
    @in_app_purchase_id = in_app_purchase_id
  end

  def purchase
    @purchaser = PM::IAP::Product.new(in_app_purchase_id)

    @purchaser.purchase do |status, transaction|
      case status
      when :in_progress then show_spinner
      when :deferred    then hide_spinner
      when :purchased   then complete_transaction
      when :canceled    then go_away
      when :error       then error(transaction.error.localizedDescription)
      end
    end
  end

  def show_spinner
    CCHUD.showWithStatus('Purchasing course')
  end

  def hide_spinner
    CCHUD.dismiss
  end

  def complete_transaction
    CCHUD.showSuccessWithStatus('Course purchased')
  end

  def error(message)
    CCHUD.showErrorWithStatus(message)
  end

  def go_away
    CCHUD.dismiss
  end
end

This is all instantiated with a little IAPManager.new('my_iap_id').purchase. When I fire this up in the simulator, click the purchase button, and dismiss the IAP dialog by hitting "cancel" the app crashes.

The crash log looks like this:

After doing a little googling I found this - http://aplus.rs/2012/a-single-bug-in-my-storekit-code-that-lost-me-90-of-iap-sales/. And that looks quite a bit similar, no? I dug through the Promotion-iap source and see that there is a private iap_shutdown method. However, it is not used anywhere in the library. Is that intentional, or possibly an oversight? If that needs to be called somewhere - where would it be?

Thanks so much for your time!

jayroh commented 9 years ago

Wellp, I figured out how to stop it from within my manager but I have a feeling this might be needed in the gem regardless. The important part is below in #go_away.

class IAPManager
  attr_reader :in_app_purchase_id

  def initialize(in_app_purchase_id)
    @in_app_purchase_id = in_app_purchase_id
  end

  def purchase
    @purchaser = PM::IAP::Product.new(in_app_purchase_id)

    @purchaser.purchase do |status, transaction|
      case status
      when :in_progress then show_spinner
      when :deferred    then hide_spinner
      when :purchased   then complete_transaction
      when :canceled    then go_away
      when :error       then error(transaction.error.localizedDescription)
      end
    end
  end

  # <snip>

  def go_away
    CCHUD.dismiss
    @purchaser.send(:iap_shutdown)
  end
end

... remove the observer after the canceled operation.

Having said that, I'm going on a hunch and think maybe this shutdown should happen across all operations (maybe?) somewhere around line #162? Or expose the private method as public?

I'm not entirely sure - but the symptom of trying to perform an operation twice (whether it's "cancel" or not) is something I would guess would cause the same sort of crash on the 2nd attempt.

kevinvangelder commented 9 years ago

@jayroh Thanks for the feedback, glad you like our API.

It's been a while since I last worked on this gem, but it does appear to be an oversight however we would only want to call shutdown on completed, canceled, or failed. I'm working on a fix now.