storekit 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1ee9b3cd6f71b6dd6d5505efc6cc45d0bfde0126
4
+ data.tar.gz: 39f312184c3d8e3f3a8559d61e97c405ddc9abc1
5
+ SHA512:
6
+ metadata.gz: 7e917170d988f9120b46647d489ac718263dc0c345e3ed1929390868f8668fa5b6366457150f4d2f8abd8766f2ed84a89157b2b49ccda6a3cecab9bd8fb12fe7
7
+ data.tar.gz: 9a9b9cd81e8089d56b8f5ce8afafdfa151edcba212e29ae34f9a4cd9ed733f2200fd3987a0df329339a55b31a579afa5cfa14b75038c9691eb3491cbc4335d4c
@@ -0,0 +1,36 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ ## Specific to RubyMotion:
14
+ .dat*
15
+ .repl_history
16
+ build/
17
+
18
+ ## Documentation cache and generated files:
19
+ /.yardoc/
20
+ /_yardoc/
21
+ /doc/
22
+ /rdoc/
23
+
24
+ ## Environment normalization:
25
+ /.bundle/
26
+ /vendor/bundle
27
+ /lib/bundler/man/
28
+
29
+ # for a library or gem, you might want to ignore these files since the code is
30
+ # intended to run in multiple environments; otherwise, check them in:
31
+ Gemfile.lock
32
+ # .ruby-version
33
+ # .ruby-gemset
34
+
35
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
36
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 R&D Allm Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,47 @@
1
+ # storekit_ruby
2
+
3
+ [![Code Climate](https://codeclimate.com/github/sujrd/storekit_ruby/badges/gpa.svg)](https://codeclimate.com/github/sujrd/storekit_ruby)
4
+ [![Gem Version](http://img.shields.io/gem/v/storekit.svg)](https://rubygems.org/gems/storekit)
5
+
6
+ This is a no frills gem for verifying Apple App Store In App Purchase receipts.
7
+
8
+ ## Installation
9
+
10
+ $ gem install storekit
11
+
12
+ ## Usage
13
+
14
+ ```ruby
15
+ require 'storekit'
16
+
17
+ data = "<Base64-Encoded Receipt Data>"
18
+
19
+ # This client tries to validate against the production endpoint first. If it
20
+ # fails because of error 21007, it automatically validates against the sandbox
21
+ # endpoint.
22
+ client = StoreKit::FallbackClient.new
23
+ client.shared_secret = "really secret string"
24
+
25
+ begin
26
+ receipt = client.verify! data
27
+
28
+ # The raw data
29
+ p receipt.receipt_data
30
+
31
+ # Just the IAP receipts
32
+ p receipt.iap_receipts
33
+
34
+ # Hash, mapping from original TX IDs to array of transactions sorted in
35
+ # ascending order by purchase date.
36
+ p receipt.receipt_chains
37
+
38
+ # Do whatever you need to do to store the purchase state...
39
+ rescue StoreKit::ValidationError => e
40
+ # ¯\_(ツ)_/¯
41
+ raise 'no soup for you!'
42
+ end
43
+ ```
44
+
45
+ ## License
46
+
47
+ This gem is available under the MIT license. See the LICENSE file for more info.
@@ -0,0 +1,9 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs << 'test'
5
+ t.test_files = FileList['test/**/*_test.rb']
6
+ end
7
+
8
+ desc 'Run tests'
9
+ task default: :test
@@ -0,0 +1,12 @@
1
+ require 'net/http'
2
+ require 'openssl'
3
+ require 'json'
4
+
5
+ module StoreKit
6
+ end
7
+
8
+ require 'storekit/version'
9
+ require 'storekit/client'
10
+ require 'storekit/fallback_client'
11
+ require 'storekit/error'
12
+ require 'storekit/receipt'
@@ -0,0 +1,39 @@
1
+ module StoreKit
2
+ SANDBOX_HOST = 'sandbox.itunes.apple.com'
3
+ PRODUCTION_HOST = 'buy.itunes.apple.com'
4
+
5
+ class Client
6
+ attr_accessor :shared_secret
7
+
8
+ def self.sandbox
9
+ new(SANDBOX_HOST)
10
+ end
11
+
12
+ def self.production
13
+ new(PRODUCTION_HOST)
14
+ end
15
+
16
+ def initialize(host)
17
+ @http = Net::HTTP.new host, 443
18
+ @http.use_ssl = true
19
+ @http.verify_mode = OpenSSL::SSL::VERIFY_PEER
20
+ end
21
+
22
+ def verify!(base64_payload)
23
+ payload = {'receipt-data' => base64_payload}
24
+ payload['password'] = shared_secret if shared_secret
25
+
26
+ req = Net::HTTP::Post.new '/verifyReceipt'
27
+ req.body = JSON.generate payload
28
+ resp = @http.request(req)
29
+
30
+ decoded = JSON.parse resp.body
31
+
32
+ if decoded['status'] == 0
33
+ Receipt.new decoded
34
+ else
35
+ raise ValidationError.new decoded['status']
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,29 @@
1
+ module StoreKit
2
+ class Error < ::StandardError
3
+ end
4
+
5
+ class ValidationError < Error
6
+ attr_reader :status_code
7
+
8
+ def initialize(status_code)
9
+ @status_code = status_code
10
+ message =
11
+ case status_code
12
+ when 21000 then 'The App Store could not read the JSON object you provided.'
13
+ when 21002 then 'The data in the receipt-data property was malformed or missing.'
14
+ when 21003 then 'The receipt could not be authenticated.'
15
+ when 21004 then 'The shared secret you provided does not match the shared secret on file for your account.'
16
+ when 21005 then 'The receipt server is not currently available.'
17
+ when 21006 then 'This receipt is valid but the subscription has expired.'
18
+ when 21007 then 'This receipt is from the test environment, but it was sent to the production environment for verification.'
19
+ when 21008 then 'This receipt is from the production environment, but it was sent to the test environment for verification.'
20
+ else 'Unknown error'
21
+ end
22
+ super(message)
23
+ end
24
+
25
+ def server_error?
26
+ [21004, 21007, 21008].include?(status_code) || message == 'Unknown error'
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,16 @@
1
+ module StoreKit
2
+ class FallbackClient
3
+ attr_accessor :shared_secret
4
+
5
+ def verify!(base64_payload)
6
+ client = Client.production
7
+ client.shared_secret = shared_secret
8
+ client.verify! base64_payload
9
+ rescue ValidationError => err
10
+ raise err unless err.status_code == 21007
11
+ client = Client.sandbox
12
+ client.shared_secret = shared_secret
13
+ client.verify! base64_payload
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,19 @@
1
+ module StoreKit
2
+ class Receipt
3
+ attr_reader :receipt_data
4
+
5
+ def initialize(receipt_data)
6
+ @receipt_data = receipt_data
7
+ end
8
+
9
+ def iap_receipts
10
+ @receipt_data['receipt']['in_app'] || []
11
+ end
12
+
13
+ def receipt_chains
14
+ iap_receipts
15
+ .group_by { |receipt| receipt['original_transaction_id'] }
16
+ .each_value { |chain| chain.sort_by! { |receipt| receipt['purchase_date_ms'].to_i } }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+ module StoreKit
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,21 @@
1
+ $LOAD_PATH.unshift 'lib'
2
+ require 'storekit/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'storekit'
6
+ s.version = StoreKit::VERSION
7
+ s.date = Time.now.strftime('%Y-%m-%d')
8
+ s.summary = 'Apple App Store IAP receipt validation'
9
+ s.description = 'Validate In App Purchase receipts against the Apple App Store'
10
+ s.homepage = 'http://github.com/sujrd/storekit_ruby'
11
+ s.authors = ['Douglas Teoh']
12
+ s.email = 'douglas@dteoh.com'
13
+ s.has_rdoc = false
14
+ s.files = `git ls-files -z`.split("\x0")
15
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
16
+ s.require_paths = %w(lib)
17
+ s.license = 'MIT'
18
+
19
+ s.add_development_dependency 'rake'
20
+ s.add_development_dependency 'minitest'
21
+ end
@@ -0,0 +1,145 @@
1
+ require 'test_helper'
2
+
3
+ module StoreKit
4
+ class ReceiptTest < Test
5
+
6
+ def test_get_iap_receipts
7
+ data = single_purchase_receipt_data
8
+ receipt = Receipt.new data
9
+ assert_respond_to receipt, :iap_receipts
10
+ assert_equal data['receipt']['in_app'], receipt.iap_receipts
11
+ end
12
+
13
+ def test_get_receipt_chains
14
+ receipt = Receipt.new recurring_purchases_receipt_data
15
+ assert_respond_to receipt, :receipt_chains
16
+
17
+ chains = receipt.receipt_chains
18
+ # The keys are the original transaction ID
19
+ assert chains.has_key? '1000000165289217'
20
+ assert chains.has_key? '1000000165289219'
21
+
22
+ assert_equal 2, chains['1000000165289217'].size
23
+ assert_equal 1, chains['1000000165289219'].size
24
+ end
25
+
26
+ def test_individual_receipt_chain_transactions_sorted_by_purchase_date
27
+ receipt = Receipt.new recurring_purchases_receipt_data
28
+ chains = receipt.receipt_chains
29
+
30
+ expected_tx_id_order = ['1000000165289944', '1000000165290338']
31
+ assert_equal expected_tx_id_order, chains['1000000165289217'].map{ |tx| tx['transaction_id'] }
32
+ end
33
+
34
+ private
35
+
36
+ def single_purchase_receipt_data
37
+ {
38
+ "status" => 0,
39
+ "environment" => "Sandbox",
40
+ "receipt" => {
41
+ "receipt_type" => "ProductionSandbox",
42
+ "adam_id" => 0,
43
+ "app_item_id" => 0,
44
+ "bundle_id" => "com.example.FakeApp",
45
+ "application_version" => "5",
46
+ "download_id" => 0,
47
+ "version_external_identifier" => 0,
48
+ "request_date" => "2015-07-29 00:30:46 Etc/GMT",
49
+ "request_date_ms" => "1438129846850",
50
+ "request_date_pst" => "2015-07-28 17:30:46 America/Los_Angeles",
51
+ "original_purchase_date" => "2013-08-01 07:00:00 Etc/GMT",
52
+ "original_purchase_date_ms" => "1375340400000",
53
+ "original_purchase_date_pst" => "2013-08-01 00:00:00 America/Los_Angeles",
54
+ "original_application_version" => "1.0",
55
+ "in_app" => [{
56
+ "quantity" => "1",
57
+ "product_id" => "com.example.FakeApp.Product1",
58
+ "transaction_id" => "1000000165471599",
59
+ "original_transaction_id" => "1000000165471599",
60
+ "purchase_date" => "2015-07-29 01:31:33 Etc/GMT",
61
+ "purchase_date_ms" => "1438133493000",
62
+ "purchase_date_pst" => "2015-07-28 18:31:33 America/Los_Angeles",
63
+ "original_purchase_date" => "2015-07-29 01:31:33 Etc/GMT",
64
+ "original_purchase_date_ms" => "1438133493000",
65
+ "original_purchase_date_pst" => "2015-07-28 18:31:33 America/Los_Angeles",
66
+ "is_trial_period" => "false"
67
+ }]
68
+ }
69
+ }
70
+ end
71
+
72
+ def recurring_purchases_receipt_data
73
+ {
74
+ "status" => 0,
75
+ "environment" => "Sandbox",
76
+ "receipt" => {
77
+ "receipt_type" => "ProductionSandbox",
78
+ "adam_id" => 0,
79
+ "app_item_id" => 0,
80
+ "bundle_id" => "com.manabook.FakeApp",
81
+ "application_version" => "5",
82
+ "download_id" => 0,
83
+ "version_external_identifier" => 0,
84
+ "request_date" => "2015-07-29 00:30:46 Etc/GMT",
85
+ "request_date_ms" => "1438129846850",
86
+ "request_date_pst" => "2015-07-28 17:30:46 America/Los_Angeles",
87
+ "original_purchase_date" => "2013-08-01 07:00:00 Etc/GMT",
88
+ "original_purchase_date_ms" => "1375340400000",
89
+ "original_purchase_date_pst" => "2013-08-01 00:00:00 America/Los_Angeles",
90
+ "original_application_version" => "1.0",
91
+ "in_app" => [{
92
+ "quantity" => "1",
93
+ "product_id" => "com.example.FakeApp.Product2",
94
+ "transaction_id" => "1000000165290338",
95
+ "original_transaction_id" => "1000000165289217",
96
+ "purchase_date" => "2015-07-28 01:44:38 Etc/GMT",
97
+ "purchase_date_ms" => "1438047878000",
98
+ "purchase_date_pst" => "2015-07-27 18:44:38 America/Los_Angeles",
99
+ "original_purchase_date" => "2015-07-28 01:42:39 Etc/GMT",
100
+ "original_purchase_date_ms" => "1438047759000",
101
+ "original_purchase_date_pst" => "2015-07-27 18:42:39 America/Los_Angeles",
102
+ "expires_date" => "2015-07-28 01:49:38 Etc/GMT",
103
+ "expires_date_ms" => "1438048178000",
104
+ "expires_date_pst" => "2015-07-27 18:49:38 America/Los_Angeles",
105
+ "web_order_line_item_id" => "1000000030215909",
106
+ "is_trial_period" => "false"
107
+ }, {
108
+ "quantity" => "1",
109
+ "product_id" => "com.example.FakeApp.Product2",
110
+ "transaction_id" => "1000000165289944",
111
+ "original_transaction_id" => "1000000165289217",
112
+ "purchase_date" => "2015-07-28 01:39:38 Etc/GMT",
113
+ "purchase_date_ms" => "1438047578000",
114
+ "purchase_date_pst" => "2015-07-27 18:39:38 America/Los_Angeles",
115
+ "original_purchase_date" => "2015-07-28 01:37:43 Etc/GMT",
116
+ "original_purchase_date_ms" => "1438047463000",
117
+ "original_purchase_date_pst" => "2015-07-27 18:37:43 America/Los_Angeles",
118
+ "expires_date" => "2015-07-28 01:44:38 Etc/GMT",
119
+ "expires_date_ms" => "1438047878000",
120
+ "expires_date_pst" => "2015-07-27 18:44:38 America/Los_Angeles",
121
+ "web_order_line_item_id" => "1000000030215898",
122
+ "is_trial_period" => "false"
123
+ }, {
124
+ "quantity" => "1",
125
+ "product_id" => "com.example.FakeApp.Product3",
126
+ "transaction_id" => "1000000165289217",
127
+ "original_transaction_id" => "1000000165289219",
128
+ "purchase_date" => "2015-07-28 01:34:38 Etc/GMT",
129
+ "purchase_date_ms" => "1438047278000",
130
+ "purchase_date_pst" => "2015-07-27 18:34:38 America/Los_Angeles",
131
+ "original_purchase_date" => "2015-07-28 01:34:39 Etc/GMT",
132
+ "original_purchase_date_ms" => "1438047279000",
133
+ "original_purchase_date_pst" => "2015-07-27 18:34:39 America/Los_Angeles",
134
+ "expires_date" => "2015-07-28 01:39:38 Etc/GMT",
135
+ "expires_date_ms" => "1438047578000",
136
+ "expires_date_pst" => "2015-07-27 18:39:38 America/Los_Angeles",
137
+ "web_order_line_item_id" => "1000000030215897",
138
+ "is_trial_period" => "false"
139
+ }]
140
+ }
141
+ }
142
+ end
143
+
144
+ end
145
+ end
@@ -0,0 +1,7 @@
1
+ require 'minitest/autorun'
2
+ require 'storekit'
3
+
4
+ module StoreKit
5
+ class Test < Minitest::Test
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: storekit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Douglas Teoh
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-04-13 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: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Validate In App Purchase receipts against the Apple App Store
42
+ email: douglas@dteoh.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - ".gitignore"
48
+ - Gemfile
49
+ - LICENSE
50
+ - README.md
51
+ - Rakefile
52
+ - lib/storekit.rb
53
+ - lib/storekit/client.rb
54
+ - lib/storekit/error.rb
55
+ - lib/storekit/fallback_client.rb
56
+ - lib/storekit/receipt.rb
57
+ - lib/storekit/version.rb
58
+ - storekit.gemspec
59
+ - test/storekit/receipt_test.rb
60
+ - test/test_helper.rb
61
+ homepage: http://github.com/sujrd/storekit_ruby
62
+ licenses:
63
+ - MIT
64
+ metadata: {}
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubyforge_project:
81
+ rubygems_version: 2.5.1
82
+ signing_key:
83
+ specification_version: 4
84
+ summary: Apple App Store IAP receipt validation
85
+ test_files:
86
+ - test/storekit/receipt_test.rb
87
+ - test/test_helper.rb