rm_vendor 0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YzI2MTI0ZmVkMTIzYjk2Y2IwODg5ZjA0MjhhNDJiNjNiMWI2N2JhMg==
5
+ data.tar.gz: !binary |-
6
+ YmUxYzM1YjhmOWUyZjYxNTNlOGNjNjk3ZTRhYzE0ODM2NGRjMTFkZg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ OGE5NDAxMzZjNDVlYmUzYjA1MTZiOWFlZmVhYzg4Y2IzMzM1ZjdiMTU2ODE2
10
+ MmQyNjdlMGFhMjM0ZGMzYzEyNDQ1MzVhNzY3ZTJmMzcwYzFlYTRiOTQzZjY2
11
+ NWEzN2VhYTFlNWJmMzhhZmRjNGZhNTEyOWJiOWExODlhMTlmMDA=
12
+ data.tar.gz: !binary |-
13
+ MTcyZWVkNjc3MGNkN2I1OTUzOWZjMWI5NmFmYmUxMzQ0MWYzOGI2NGNmMzU4
14
+ M2QyOTkwMjYyZjZmYTA5ZjQwNGVmMTAyN2JhM2QzNWVhYTQ2NmU2NjA2Y2U2
15
+ YTBiZDgwYmI0Y2JhYmNjZTNlOWIxMGY5OWVkMmZjNGUyMWE4YmQ=
data/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # Vendor
2
+
3
+ ## Installation
4
+
5
+ Add this line to your application's Gemfile:
6
+
7
+ gem 'rm_vendor'
8
+
9
+ And then execute:
10
+
11
+ $ bundle
12
+
13
+ ## USAGE
14
+
15
+ #### Initialize.
16
+
17
+ Initialize your product with your In App Purchase ID. Vendor will check if the product exists and return it's attributes:
18
+
19
+ ```Ruby
20
+ @product = Vendor::Product.new(:id => "com.your.item.id") do |product|
21
+ p "Product exists: #{product.success}"
22
+ p "Product response: #{product.response}"
23
+ end
24
+ ```
25
+
26
+ You don't need to pass along a block though. If you're sure your product exists, then you can just do this:
27
+
28
+ ```Ruby
29
+ @product = Vendor::Product.new(:id => "com.your.item.id")
30
+ ```
31
+
32
+ #### Get product info.
33
+
34
+ After you've initialized your product, you get its information:
35
+
36
+ ```Ruby
37
+ @product.price
38
+ @product.title
39
+ @product.description
40
+ @product.bought?
41
+ ```
42
+
43
+ #### Purchase product.
44
+
45
+ To purchase a product, simply do:
46
+
47
+ ```Ruby
48
+ @product.purchase do |product|
49
+ p "Purchase successful: #{product.success}"
50
+ p "Purchase transaction: #{product.transaction}"
51
+ end
52
+ ```
53
+
54
+ #### Subscriptions.
55
+
56
+ Vendor also works with subscriptions. To initialize a subscription:
57
+
58
+ ```Ruby
59
+ @subscription = Vendor::Product.new(:id => "com.your.subscription.id", :secret => "abcdefg12345", :subscription => true) do |subscription|
60
+ p "Subscription exists: #{subscription.success}"
61
+ p "Subscription response: #{subscription.response}"
62
+ end
63
+ ```
64
+
65
+ To purchase a subscription:
66
+
67
+ ```Ruby
68
+ @subscription.purchase do |subscription|
69
+ p "Subscription successful: #{subscription.success}"
70
+ p "Subscription transaction: #{subscription.transaction}"
71
+ end
72
+ ```
73
+
74
+ To see wether product is registered as a subscription and if user is currently subscribed:
75
+
76
+ ```Ruby
77
+ @subscription.subscribed?
78
+ @subscription.subscription?
79
+ ```
80
+
81
+ ## Example App.
82
+
83
+ You can find an example app inside this gem (inside the app folder).
data/lib/Vendor.rb ADDED
@@ -0,0 +1,27 @@
1
+ #encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bubble-wrap/core'
5
+ require 'sugarcube-anonymous'
6
+ require 'sugarcube-legacy'
7
+ require 'sugarcube-numbers'
8
+ require 'sugarcube-nsuserdefaults'
9
+ require 'sugarcube-files'
10
+ require 'motion-cocoapods'
11
+
12
+ unless defined?(Motion::Project::Config)
13
+ raise "This file must be required within a RubyMotion project Rakefile."
14
+ end
15
+
16
+ require 'motion/project/template/ios'
17
+
18
+ lib_dir_path = File.dirname(File.expand_path(__FILE__))
19
+ Motion::Project::App.setup do |app|
20
+ app.files.unshift(Dir.glob(File.join(lib_dir_path, "project/**/*.rb")))
21
+
22
+ app.frameworks += ['StoreKit']
23
+
24
+ app.pods ||= Motion::Project::CocoaPods.new(app)
25
+ app.pods.pod 'CocoaSecurity', '~> 1.2.1'
26
+ app.pods.pod 'AFNetworking'
27
+ end
@@ -0,0 +1,73 @@
1
+ module Vendor
2
+ class Buy
3
+ attr_accessor :params, :block, :request_operation_queue
4
+
5
+ def initialize(params)
6
+ @params = params
7
+ SKPaymentQueue.defaultQueue.addTransactionObserver(self)
8
+ end
9
+
10
+ def close
11
+ SKPaymentQueue.defaultQueue.removeTransactionObserver(self)
12
+ end
13
+
14
+
15
+
16
+ # PUBLIC METHODS
17
+ def purchase(&block)
18
+ @block = block
19
+ SKPaymentQueue.defaultQueue.addPayment(SKPayment.paymentWithProductIdentifier(@params.id))
20
+ end
21
+
22
+ def restore(&block)
23
+ @block = block
24
+ SKPaymentQueue.defaultQueue.restoreCompletedTransactions
25
+ end
26
+
27
+
28
+
29
+ # DELEGATE METHODS
30
+ def finishTransaction(transaction, success:success)
31
+ SKPaymentQueue.defaultQueue.finishTransaction(transaction)
32
+ SKPaymentQueue.defaultQueue.removeTransactionObserver(self)
33
+
34
+ if success
35
+ Vendor::Receipt.new(transaction.transactionReceipt, @params) do |block|
36
+ result_object = BW::JSON.parse(block.object).to_object
37
+ valid_receipt = block.success && result_object.status.to_i == 0
38
+
39
+ @block.call({success: valid_receipt, transaction: transaction}.to_object) unless @block.blank?
40
+ end
41
+ else
42
+ @block.call({success: success, transaction: transaction}.to_object) unless @block.blank?
43
+ end
44
+ end
45
+
46
+ def paymentQueue(queue, updatedTransactions:transactions)
47
+ transactions.each do |transaction|
48
+ case transaction.transactionState
49
+ when SKPaymentTransactionStatePurchased
50
+ completeTransaction(transaction)
51
+ when SKPaymentTransactionStateFailed
52
+ failedTransaction(transaction)
53
+ when SKPaymentTransactionStateRestored
54
+ restoreTransaction(transaction)
55
+ else
56
+ end
57
+ end
58
+ end
59
+
60
+ def completeTransaction(transaction)
61
+ finishTransaction(transaction, success:true)
62
+ end
63
+
64
+ def restoreTransaction(transaction)
65
+ finishTransaction(transaction, success:true)
66
+ end
67
+
68
+ def failedTransaction(transaction)
69
+ finishTransaction(transaction, success:false)
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,84 @@
1
+ module Vendor
2
+ class Info
3
+ attr_accessor :params, :block
4
+
5
+ def initialize(params, &block)
6
+ @params = params
7
+ @block = block
8
+
9
+ # Start product request
10
+ productsRequest = SKProductsRequest.alloc.initWithProductIdentifiers(NSSet.setWithObject(@params.id))
11
+ productsRequest.delegate = self
12
+ productsRequest.start
13
+
14
+ # Update receipt if bought and subscription
15
+ Vendor::Receipt.new(NSUserDefaults["#{@params.id}.receipt_data"], @params) if bought? && subscription?
16
+ end
17
+
18
+
19
+
20
+ # INFO METHODS
21
+ def price
22
+ price = NSUserDefaults["#{@params.id}.price"] || @params.price
23
+ price.to_f.string_with_style(NSNumberFormatterCurrencyStyle)
24
+ end
25
+
26
+ def title
27
+ NSUserDefaults["#{@params.id}.localizedTitle"] || @params.title
28
+ end
29
+
30
+ def description
31
+ NSUserDefaults["#{@params.id}.localizedDescription"] || @params.desc
32
+ end
33
+
34
+ def bought?
35
+ NSUserDefaults["#{@params.id}.receipt"] != nil
36
+ end
37
+
38
+ def subscription?
39
+ @params.subscription
40
+ end
41
+
42
+ def subscribed?
43
+ return false if !subscription?
44
+ receipt_object = BW::JSON.parse(NSUserDefaults["#{@params.id}.receipt"]).to_object
45
+ return false if receipt_object.blank? || receipt_object.status!=0
46
+
47
+ decoder = CocoaSecurityDecoder.new
48
+ latest_receipt_data = decoder.base64(receipt_object.latest_receipt)
49
+ latest_receipt_plist = NSPropertyListSerialization.propertyListWithData(latest_receipt_data, options:NSPropertyListImmutable, format:nil, error:nil)
50
+
51
+ purchase_info_data = decoder.base64(latest_receipt_plist.objectForKey("purchase-info"))
52
+ purchase_info_plist = NSPropertyListSerialization.propertyListWithData(purchase_info_data, options:NSPropertyListImmutable, format:nil, error:nil)
53
+ purchase_info_plist = NSPropertyListSerialization.propertyListWithData(purchase_info_data, options:NSPropertyListImmutable, format:nil, error:nil)
54
+
55
+ expires_date = purchase_info_plist.objectForKey("expires-date")
56
+ # ap "expires_date: #{expires_date}"
57
+ expires_calc = expires_date.to_i/1000
58
+
59
+ return expires_calc > NSDate.date.timeIntervalSince1970
60
+ end
61
+
62
+
63
+
64
+ # DELEGATE METHODS
65
+ def productsRequest(request, didReceiveResponse:response)
66
+ exists = response.invalidProductIdentifiers.count==0
67
+
68
+ # Save needed product info
69
+ if exists
70
+ product = response.products.first
71
+ NSUserDefaults["#{@params.id}.price"] = product.price
72
+ NSUserDefaults["#{@params.id}.localizedTitle"] = product.localizedTitle
73
+ NSUserDefaults["#{@params.id}.localizedDescription"] = product.localizedDescription
74
+ end
75
+
76
+ @block.call({success: exists, response: response}.to_object)
77
+ end
78
+
79
+ def request(request, didFailWithError:error)
80
+ @block.call({success: false, error: error}.to_object)
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,78 @@
1
+ module Vendor
2
+ class Product
3
+ attr_reader :params, :block, :exists, :info, :buy
4
+
5
+ def initialize(options={}, &block)
6
+ # Set default options
7
+ default_options = {
8
+ :id => "no_id",
9
+ :secret => "no_secret",
10
+ :subscription => false,
11
+ :price => "0.99",
12
+ :title => "No Title",
13
+ :desc => "No Description."
14
+ }
15
+
16
+ # Set global params and block
17
+ options = default_options.merge(options)
18
+ @params = options.to_object
19
+ @block = block
20
+
21
+ # Raise argument error if id is not included
22
+ raise ArgumentError, "VENDOR WARNING: You forgot to write in your item id. You can't sell item without an id." if @params.id == "no_id"
23
+ raise ArgumentError, "VENDOR WARNING: You're missing your subscriptions shared secret. Subscriptions must have a shared secret." if @params.subscription && @params.secret == "no_secret"
24
+
25
+ # Update product and set exists variable
26
+ @info = Vendor::Info.new(@params) do |block|
27
+ @exists = block.success
28
+ @block.call(block) unless @block.nil?
29
+ end
30
+
31
+ # Initialize purchase
32
+ @buy = Vendor::Buy.new(@params)
33
+ end
34
+
35
+
36
+
37
+ # PURCHASE METHODS
38
+ def purchase(&block)
39
+ @buy.purchase { |result| block.call(result)}
40
+ end
41
+
42
+ def restore(&block)
43
+ @buy.restore { |result| block.call(result)}
44
+ end
45
+
46
+
47
+
48
+ # INFO METHODS
49
+ def price
50
+ @info.price
51
+ end
52
+
53
+ def title
54
+ @info.title
55
+ end
56
+
57
+ def description
58
+ @info.description
59
+ end
60
+
61
+ def exists?
62
+ @exists || false
63
+ end
64
+
65
+ def bought?
66
+ @info.bought?
67
+ end
68
+
69
+ def subscription?
70
+ @info.subscription?
71
+ end
72
+
73
+ def subscribed?
74
+ @info.subscribed?
75
+ end
76
+
77
+ end
78
+ end
@@ -0,0 +1,42 @@
1
+ module Vendor
2
+ class Receipt
3
+
4
+ attr_reader :receipt_data, :params, :request_operation_queue, :block
5
+
6
+ def initialize(receipt_data, params, &block)
7
+ @receipt_data = receipt_data
8
+ @params = params
9
+ @block = block
10
+ @request_operation_queue = NSOperationQueue.alloc.init
11
+
12
+ check_receipt(receipt_data)
13
+ end
14
+
15
+ def check_receipt(receipt_data)
16
+ encoder = CocoaSecurityEncoder.new
17
+ json_data = {"receipt-data" => encoder.base64(receipt_data)}
18
+ json_data[:password] = @params.secret if @params.secret != "no_secret"
19
+ server = App.development? ? "sandbox" : "buy"
20
+ base_url = NSURL.URLWithString("https://#{server}.itunes.apple.com")
21
+
22
+ # TODO - Could look nicer. Dunno if AFMotion could be used instead.
23
+ client = AFHTTPClient.alloc.initWithBaseURL(base_url)
24
+ client.setDefaultHeader("Accept", value:"application/json")
25
+ client.registerHTTPOperationClass(AFJSONRequestOperation.class)
26
+ client.setParameterEncoding(AFJSONParameterEncoding)
27
+ AFJSONRequestOperation.addAcceptableContentTypes(NSSet.setWithObject("text/plain"))
28
+
29
+ request = client.requestWithMethod("POST", path:"verifyReceipt", parameters:json_data)
30
+ request_operation = client.HTTPRequestOperationWithRequest(request, success: lambda { |operation, response_object|
31
+ NSUserDefaults["#{@params.id}.receipt_data"] = receipt_data
32
+ NSUserDefaults["#{@params.id}.receipt"] = response_object
33
+ @block.call({success: true, object: response_object}.to_object) unless @block.blank?
34
+ }, failure: lambda {|operation, error|
35
+ @block.call({success: false, error: error}.to_object) unless @block.blank?
36
+ })
37
+
38
+ @request_operation_queue.addOperation(request_operation)
39
+ end
40
+
41
+ end
42
+ end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rm_vendor
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Holger Sindbaek
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-01-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sugarcube
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bubble-wrap
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: motion-cocoapods
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: A RubyMotion StoreKit Wrapper that allows you to buy, restore and get
70
+ product info on your in app purchases and subscriptions
71
+ email:
72
+ - HolgerSindbaek@gmail.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - README.md
78
+ - lib/project/buy.rb
79
+ - lib/project/info.rb
80
+ - lib/project/product.rb
81
+ - lib/project/receipt.rb
82
+ - lib/Vendor.rb
83
+ homepage: https://github.com/holgersindbaek/Vendor
84
+ licenses:
85
+ - MIT
86
+ metadata: {}
87
+ post_install_message:
88
+ rdoc_options: []
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ! '>='
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ! '>='
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ requirements: []
102
+ rubyforge_project:
103
+ rubygems_version: 2.1.10
104
+ signing_key:
105
+ specification_version: 4
106
+ summary: RubyMotion StoreKit Wrapper
107
+ test_files: []