Giter VIP home page Giter VIP logo

promotion-iap's Introduction

ProMotion-iap

Gem Version Build Status

ProMotion-iap is in-app purchase notification support for the popular RubyMotion gem ProMotion. It also works as a stand-alone gem if you are not using ProMotion.

Read the introductory blog post here: http://jamonholmgren.com/iap/

ProMotion-iap was created by Infinite Red, a web and mobile development company based in Portland, OR and San Francisco, CA. While you're welcome to use it, please note that we rely on the community to maintain it. We are happy to merge pull requests and release new versions but are no longer driving primary development.

Installation

gem 'ProMotion-iap'

Usage

PM::IAP::Product Class

The Product class is an abstraction layer that provides a simpler interface when working with a single IAP product. If you are dealing with multiple products you will want to use the IAP Module directly (documented below).

class PurchaseScreen < PM::Screen

  def on_load

    product = PM::IAP::Product.new("productid")

    product.retrieve do |product, error|
      # product looks something like the following
      {
        product_id:               "productid1",
        title:                    "title",
        description:              "description",
        price:                    <BigDecimal 0.99>,
        formatted_price:          "$0.99",
        price_locale:             <NSLocale>,
        downloadable:             false,
        download_content_lengths: <?>, # TODO: ?
        download_content_version: <?>, # TODO: ?
        product:                  <SKProduct>
      }
    end

    product.purchase do |status, data|
      case status
      when :in_progress
        # Usually do nothing, maybe a spinner
      when :deferred
        # Waiting on a prompt to the user
      when :purchased
        # Notify the user, update any affected UI elements
      when :canceled
        # They just canceled, no big deal.
      when :error
        # Failed to purchase
        data[:error].localizedDescription # => error message
      end
    end

    product.restore do |status, data|
      if status == :restored
        # Update your UI, notify the user
        # data is a hash with :product_id, :error, and :transaction
      else
        # Tell the user it failed to restore
      end
    end

  end
end

Product.new(product_id)

Stores the product_id for use in the instance methods.

retrieve(&callback)

Retrieves the product.

purchase(&callback)

Begins a purchase of the product.

restore(&callback)

Begins a restoration of the previously purchased product.

IAP Module

Include PM::IAP to add some in-app purchase methods to a screen, app delegate, or other class.

# app/screens/purchase_screen.rb
class PurchaseScreen < PM::Screen
  include PM::IAP

  def on_load

    retrieve_iaps [ "productid1", "productid2" ] do |products, error|
      # products looks something like the following
      [{
        product_id:               "productid1",
        title:                    "title",
        description:              "description",
        price:                    <BigDecimal 0.99>,
        formatted_price:          "$0.99",
        price_locale:             <NSLocale>,
        downloadable:             false,
        download_content_lengths: <?>, # TODO: ?
        download_content_version: <?>, # TODO: ?
        product:                  <SKProduct>
      }, {...}]
    end

    purchase_iaps [ "productid" ], username: User.current.username do |status, transaction|
      case status
      when :in_progress
        # Usually do nothing, maybe a spinner
      when :deferred
        # Waiting on a prompt to the user
      when :purchased
        # Notify the user, update any affected UI elements
      when :canceled
        # They just canceled, no big deal.
      when :error
        # Failed to purchase
        transaction.error.localizedDescription # => error message
      end
    end

    restore_iaps [ "productid" ], username: User.current.username do |status, data|
      if status == :restored
        # Update your UI, notify the user
        # This is called once for each product you're trying to restore.
        data[:product_id] # => "productid"
      elsif status == :error
        # Update your UI to show that there was an error.
        # This will only be called once, no matter how many products you are trying to restore.
        # You'll get an NSError in your `data` hash.
        data[:error].localizedDescription # => description of error
      end
    end


  end
end

retrieve_iaps(*product_ids, &callback)

Retrieves in-app purchase products in an array of mapped hashes. The callback method should accept products and error.

purchase_iaps(*product_ids, &callback)

Prompts the user to login to their Apple ID and complete the purchase. The callback method should accept status and transaction. The callback method will be called several times with the various statuses in the process. If more than one product_id is provided the callback method will be called several times per product with the applicable transaction.

restore_iaps(*product_ids, &callback)

Restores a previously purchased IAP to the user (for example if they have upgraded their device). This relies on the Apple ID the user enters at the prompt. Unfortunately, if there is no purchase to restore for the signed-in account, no error message is generated and will fail silently.

Important Note

In order for this library to work correctly, you must provide your, app.identifier to the Rakefile.

Find the Product ID here:

product id

Authors

| Contributor | Twitter | | Jamon Holmgren | @jamonholmgren | | Kevin VanGelder | @kevinvangelder |

Inspired By

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Make some specs pass
  5. Push to the branch (git iap origin my-new-feature)
  6. Create new Pull Request

promotion-iap's People

Contributors

dam13n avatar jamonholmgren avatar kevinvangelder avatar mborromeo avatar ryanlntn avatar serialbandicoot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

promotion-iap's Issues

Documentation should include app.identifier

Just a note, it might be worth adding to the ReadMe that you will require app.identifier to be included otherwise nothing will happen and your products will be nil.

Great library though ๐Ÿ‘

Lacking test and well tested app

Hello @jamonholmgren!

When creating Helu I struggled to test the integration points with Apple. In a effort to learn how to test this type of code better, I forked this repo and commented the key pieces of code to make In App Purchases ( ivanacostarubio@4347641). After that last commit, the library is not able to make in app purchases, yet none of the tests are red.

What is the proper way to test the integration with apple?

If a library can't make IAP, but all the tests are green is it well tested?

uninitialized constant ProMotion::IAP (NameError)

This looks like a very useful gem :-)

I just added this gem to my project and when I run

bundle exec rake clean && bundle exec rake

the simulator starts and exits with the following error message:

uninitialized constant ProMotion::IAP (NameError)

This is the git diff in my project. I basically just added the gem and ran bundle install.

diff --git a/Gemfile b/Gemfile
index 3d13d05..3c9792e 100644
--- a/Gemfile
+++ b/Gemfile
@@ -8,3 +8,5 @@ gem "bubble-wrap", "~> 1.7.1", require: ['bubble-wrap/core']
 gem 'dotenv', '~> 0.9.0'

 gem 'sugarcube', require: [ 'sugarcube-localized' ]
+
+gem 'ProMotion-iap', github: 'clearsightstudio/ProMotion-iap'
\ No newline at end of file
diff --git a/Gemfile.lock b/Gemfile.lock
index 7f15582..ed63432 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,4 +1,10 @@
 GIT
+  remote: git://github.com/clearsightstudio/ProMotion-iap.git
+  revision: fe579ab697f139676b709185471096378b317a11
+  specs:
+    ProMotion-iap (0.2.0)
+
+GIT
   remote: git://github.com/clearsightstudio/ProMotion.git
   revision: 77b86eaf33295a86adfdfa040e20f9737ce16844
   specs:
@@ -22,6 +28,7 @@ PLATFORMS

 DEPENDENCIES
   ProMotion!
+  ProMotion-iap!
   bubble-wrap (~> 1.7.1)
   dotenv (~> 0.9.0)
   rake

Am I missing a step in the installation? Any pointers on how to debug this problem would be greatly appreciated :-)

Segmentation Fault

Calling this twice (e.g. on viewDidLoad and user dismisses the view then reloads it):

product = PM::IAP::Product.new("productid")
product.retrieve do |product, error|
  # stuff
end

Then call this:

product.purchase do |status, transaction|
  # stuff
end

The result is this rather cryptic error (and crash):

Service exited due to signal: Segmentation fault: 11
Feb 13 17:49:46 macbook.local SpringBoard[10156]: Application 'UIKitApplication:com.company.App[0x7c01]' crashed.

(I realise I can't test this IAP process properly directly from OS X, but with only a single call to the first part, the simulator gracefully returns an error to the block).

Canceling a purchase

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!

Restore purchase bug

When i try to restore purchase:

13  StoreKit                        0x2c011010 -[SKPaymentQueue _notifyObserversAboutChanges:sendUpdatedDownloads:] + 124
14  StoreKit                        0x2c011922 -[SKPaymentQueue _processUpdates:trimUnmatched:sendUpdatedDownloads:] + 1070
15  StoreKit                        0x2c012252 -[SKPaymentQueue _updatePaymentsForMessage:] + 118
16  StoreKit                        0x2c010f2a __44-[SKPaymentQueue _handleMessage:connection:]_block_invoke + 138

If i try to bye same inapp after cancel restore it (i can cancel restore purchase if i'm not login on device), i get this

product.rb:23:in `block in restore': undefined method `find' for #<SKPaymentTransaction:0x12b13170> (NoMethodError)

Gem naming

Including ProMotion in the name of the gem makes it seem like this library requires using ProMotion, when it really can be used in any RubyMotion app.

It's just ProMotion-inspired, right?

Will Not Allow a CDQ Model named Product

It appears that some sort of conflict happens between PM::IAP and CDQ if a CDQ model named Product exists. To reproduce:
Create a fresh ProMotion app.
Create a CDQ schema and model named Product
Enable the promotion-iap gem by uncommenting it in the Gemfile
Run rake to go into the simulator, and create a product

(main)> product=Product.create(title: "Test product")
=> #NSException:0x110d8ec80

A spec sheds a little more light. This creates a product. It prints the ancestors of class Product before doing so.

  • creates a product[Product, CDQManagedObject, CDQ, CoreDataQueryManagedObjectBase, NSManagedObject, NSObject, Kernel]
    [ERROR: NSException - #NSException:0x10bee56e0]
    NSInternalInconsistencyException: "Product" is not a subclass of NSManagedObject.

And removing PM::IAP causes everything to work.

(main)> product=Product.create(title: "Test product")
=> <Product: 0x10bd5e7d0> (entity: Product; id: 0x10bd9ff20 x-coredata:///Product/t5E48D422-4DA9-4ED2-9768-B4B2EAFB58D02 ; data: {
price = 0;
title = "Test product";
})

Hangs or crashes app if iTunes endpoints are down

I was testing purchasing in my app in the simulator and on device, and it suddenly stopped working. Noticed if I tried to hit the iTunes receipt endpoint (503 error) or access iTunes Connect, they weren't responding. It was temporary and not sure how frequent this is, but could be good to catch.

Implementation of SKPaymentQueue causes bugs with payments

There is an undocumented flow as part of the StoreKit framework that means some payments don't trigger the callback for a successful payment. I'll do my best to explain but for more information see this thread and specifically the post by Rich from Apple: https://forums.developer.apple.com/thread/6431

Per Apple's Best Practices for In App Payments document (https://developer.apple.com/library/ios/technotes/tn2387/_index.html) the observer should be added to SKPaymentQueue when the app loads. There are two reasons for this:

  1. The "StoreKit Flow" from the above linked thread, means that some users (those required to update payment details or agree to new T&Cs on the store) will be taken out of the app to update their details, and when the app is backgrounded the observer is removed.
  2. As part of the "StoreKit Flow", two statuses will be sent to the observer, one for a cancellation and one for a success (the cancellation once the user leaves the app, the success if they complete the payment), therefore it is desirable to leave the observer in place throughout the lifecycle of the application.

ProMotion-iap is a really nice gem and I did attempt to rework the logic to fix our issue relating to the StoreKit Flow however it proved quite difficult and would mean a reworking of the basic functionality of the gem which wasn't something that I felt comfortable doing on a PR without discussion first.

Hope that helps and do ask if anything isn't clear, I've spent the best part of a week working on this :)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.