rm_vendor 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/README.md +83 -0
- data/lib/Vendor.rb +27 -0
- data/lib/project/buy.rb +73 -0
- data/lib/project/info.rb +84 -0
- data/lib/project/product.rb +78 -0
- data/lib/project/receipt.rb +42 -0
- metadata +107 -0
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
|
data/lib/project/buy.rb
ADDED
@@ -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
|
data/lib/project/info.rb
ADDED
@@ -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: []
|