diff --git a/.travis.yml b/.travis.yml index 8a63149..2a25986 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,5 +7,4 @@ gemfile: - Gemfile script: - bundle install - - bundle exec rake clean - bundle exec rake spec diff --git a/README.md b/README.md index 1c176c3..c4c6d1b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,10 @@ # ProMotion-iap +[![Gem Version](https://badge.fury.io/rb/ProMotion-iap.svg)](http://badge.fury.io/rb/ProMotion-iap) +[![Build Status](https://travis-ci.org/clearsightstudio/ProMotion-iap.svg)](https://travis-ci.org/clearsightstudio/ProMotion-iap) + ProMotion-iap is in-app purchase notification support for the -popular RubyMotion gem [ProMotion](https://github.com/clearsightstudio/ProMotion). + popular RubyMotion gem [ProMotion](https://github.com/clearsightstudio/ProMotion). ## Installation @@ -11,7 +14,79 @@ gem 'ProMotion-iap' ## Usage -### AppDelegate +### 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). + +```ruby +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: , + formatted_price: "$0.99", + price_locale: , + downloadable: false, + download_content_lengths: , # TODO: ? + download_content_version: , # TODO: ? + product: + } + end + + product.purchase 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 + + product.restore do |status, product| + if status == :restored + # Update your UI, notify the user + 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. @@ -22,7 +97,7 @@ class PurchaseScreen < PM::Screen def on_load - retrieve_iaps([ "productid1", "productid2" ]) do |products, error| + retrieve_iaps [ "productid1", "productid2" ] do |products, error| # products looks something like the following [{ product_id: "productid1", @@ -54,7 +129,7 @@ class PurchaseScreen < PM::Screen end end - restore_iaps do |status, products| + restore_iaps "productid" do |status, products| if status == :restored # Update your UI, notify the user end @@ -65,19 +140,42 @@ class PurchaseScreen < PM::Screen end ``` -#### purchase_iap(*product_ids, &callback) +#### 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. + -TODO -```ruby -``` Find the Product ID here: ![product id](http://clrsight.co/jh/2015-02-11-d8xw6.png?+) +## Authors +| Contributor | Twitter | +| Jamon Holmgren | [@jamonholmgren](http://twitter.com/jamonholmgren) | +| Kevin VanGelder | [@kevinvangelder](http://twitter.com/kevin_vangelder) | + +## Inspired By +- [Helu](https://github.com/ivanacostarubio/helu) +- [Mark Rickert's Code Example](https://github.com/OTGApps/TheShowCloser/blob/master/app/helpers/iap_helper.rb) + ## Contributing diff --git a/lib/ProMotion-iap.rb b/lib/ProMotion-iap.rb index 49fac20..d167c51 100644 --- a/lib/ProMotion-iap.rb +++ b/lib/ProMotion-iap.rb @@ -7,5 +7,6 @@ Motion::Project::App.setup do |app| lib_dir_path = File.dirname(File.expand_path(__FILE__)) app.files << File.join(lib_dir_path, "ProMotion/iap.rb") + app.files << File.join(lib_dir_path, "ProMotion/product.rb") app.frameworks << "StoreKit" end diff --git a/lib/ProMotion/iap.rb b/lib/ProMotion/iap.rb index aa2f96e..fd1bcfb 100644 --- a/lib/ProMotion/iap.rb +++ b/lib/ProMotion/iap.rb @@ -2,9 +2,9 @@ module ProMotion module IAP attr_accessor :completion_handlers - def purchase_iaps(product_id, &callback) + def purchase_iaps(*product_ids, &callback) iap_setup - retrieve_iaps product_id do |products| + retrieve_iaps product_ids do |products| products.each do |product| self.completion_handlers["purchase-#{product[:product_id]}"] = callback payment = SKPayment.paymentWithProduct(product[:product]) @@ -14,9 +14,9 @@ def purchase_iaps(product_id, &callback) end alias purchase_iap purchase_iaps - def restore_iaps(product_id, &callback) + def restore_iaps(*product_ids, &callback) iap_setup - retrieve_iaps product_id do |products| + retrieve_iaps product_ids do |products| products.each do |product| self.completion_handlers["restore-#{product[:product_id]}"] = callback SKPaymentQueue.defaultQueue.restoreCompletedTransactions diff --git a/lib/ProMotion/product.rb b/lib/ProMotion/product.rb new file mode 100644 index 0000000..32536fa --- /dev/null +++ b/lib/ProMotion/product.rb @@ -0,0 +1,27 @@ +module ProMotion + class IAP::Product + include PM::IAP + + attr_reader :product_id + + def initialize(product_id) + @product_id = product_id + end + + def retrieve(&callback) + retrieve_iaps(product_id) do |products, error| + callback.call products.first, error + end + end + + def purchase(&callback) + purchase_iaps(product_id, &callback) + end + + def restore(&callback) + restore_iaps(product_id) do |status, products| + callback.call status, products.find{|p| p[:product_id] == product_id } + end + end + end +end \ No newline at end of file diff --git a/spec/iap_product_spec.rb b/spec/iap_product_spec.rb new file mode 100644 index 0000000..5401e70 --- /dev/null +++ b/spec/iap_product_spec.rb @@ -0,0 +1,35 @@ +describe PM::IAP::Product do + + + it "#retrieve" do + subject = PM::IAP::Product.new("retrieveid") + subject.mock!(:retrieve_iaps) do |product_ids, &callback| + product_ids.should.include "retrieveid" + end + subject.retrieve do |products, error| + end + + end + + it "#purchase" do + subject = PM::IAP::Product.new("purchaseid") + subject.mock!(:purchase_iaps) do |product_ids, &callback| + product_ids.should.include "purchaseid" + end + subject.purchase do |status, transaction| + end + end + + it "#restore" do + subject = PM::IAP::Product.new("restoreid") + subject.mock!(:restore_iaps) do |product_ids, &callback| + product_ids.should.include "restoreid" + callback.call(:restored, [{product_id: "restoreid2"}, {product_id: "restoreid"}, {product_id: "restoreid4"}]) + end + subject.restore do |status, product| + status.should == :restored + product.should == { product_id: "restoreid" } + end + end + +end \ No newline at end of file