iap_verifier 0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b3c46ebc360b35276b2ef9a7d66f55e38a5a999d
4
+ data.tar.gz: 8888b7705e0a972d34571b50b676c3fd8e92000c
5
+ SHA512:
6
+ metadata.gz: d9cb9d90072e7f2319d368b4fd1a0e17e20133338caa61ad97e52102be1d6e8838e3082faeb98805e06754e549041014fe6d6608f5e5868e5d606f6e278b7c1f
7
+ data.tar.gz: 3ce5852c0950034c91bb9e69250a4fd3b36ffeea0f4fafded4bff20dca00683b7b6594747693ec1ff62b704a5fc9a60e7c3d9351d66c33fc9c89f2c312ea7b7c
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - head
4
+ - 2.1.2
5
+ - rbx-2.1.1
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # CHANGELOG
2
+
3
+ ## master
4
+
5
+ ## v0.0.1
6
+
7
+ Initial release.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in iap_verifier.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Jun Lin
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # IAPVerifier
2
+
3
+ In-App Purchase receipt verification.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'iap_verifier'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install iap_verifier
18
+
19
+ ## Usage
20
+
21
+ ```ruby
22
+ receipt = IAPVerifier.verify_receipt(receipt: 'base64_encoded_receipt_string')
23
+ receipt.original_application_version # 1234
24
+ receipt.original_purchase_date_pst # 2014-08-31 11:24:13 America/Los_Angeles
25
+ ```
26
+
27
+ ## Contributing
28
+
29
+ 1. Fork it ( https://github.com/linjunpop/iap_verifier/fork )
30
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
31
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
32
+ 4. Push to the branch (`git push origin my-new-feature`)
33
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require "rake/testtask"
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << "test" # here is the test_helper
7
+ t.test_files = FileList['test/*_test.rb']
8
+ end
9
+
10
+ task default: :test
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'iap_verifier/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "iap_verifier"
8
+ spec.version = IAPVerifier::VERSION
9
+ spec.authors = ["Jun Lin"]
10
+ spec.email = ["linjunpop@gmail.com"]
11
+ spec.summary = %q{In-App Purchase receipt verification.}
12
+ spec.description = %q{In-App Purchase receipt verification.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "pry"
24
+ spec.add_development_dependency "mocha"
25
+ spec.add_development_dependency "minitest-reporters"
26
+ spec.add_development_dependency "webmock"
27
+ spec.add_development_dependency "vcr"
28
+ end
@@ -0,0 +1,55 @@
1
+ module IAPVerifier
2
+ module Error
3
+ class Standard < StandardError; end
4
+
5
+ class EmptyReceipt < Standard
6
+ def message
7
+ 'Receipt cannot be empty.'
8
+ end
9
+ end
10
+
11
+ class NetworkDown < Standard
12
+ def initialize(response)
13
+ @response = response
14
+ end
15
+
16
+ def message
17
+ "Server returns unexpected code: #{@response.code}."
18
+ end
19
+ end
20
+
21
+ class MalformedReceiptData < Standard
22
+ def initialize(data)
23
+ @data = data
24
+ end
25
+
26
+ def message
27
+ "Malformed Receipt data: #{@data}"
28
+ end
29
+ end
30
+
31
+ class InvalidResponseData < Standard
32
+ MESSAGES = {
33
+ 21000 => "App store could not read",
34
+ 21002 => "Data was malformed",
35
+ 21003 => "Receipt not authenticated",
36
+ 21004 => "Shared secret does not match",
37
+ 21005 => "Receipt server unavailable",
38
+ 21006 => "Receipt valid but sub expired",
39
+ 21007 => "Sandbox receipt sent to Production environment",
40
+ 21008 => "Production receipt sent to Sandbox environment"
41
+ }
42
+
43
+ def initialize(code)
44
+ @code = code
45
+ end
46
+
47
+ def message
48
+ {
49
+ error_code: @code,
50
+ message: MESSAGES.fetch(@code, 'Unknown error code')
51
+ }
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,19 @@
1
+ module IAPVerifier
2
+ class Receipt
3
+ def initialize(response_data)
4
+ @receipt_data = response_data['receipt']
5
+
6
+ unless @receipt_data.is_a?(Hash)
7
+ raise Error::MalformedReceiptData
8
+ end
9
+
10
+ @receipt_data.each do |key, value|
11
+ define_singleton_method key.to_s, -> { value }
12
+ end
13
+ end
14
+
15
+ def to_h
16
+ @receipt_data
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,54 @@
1
+ require 'json'
2
+ require 'net/https'
3
+ require 'uri'
4
+
5
+ module IAPVerifier
6
+ class Request
7
+ PRODUCTION_URL = "https://buy.itunes.apple.com/verifyReceipt"
8
+ SANDBOX_URL = "https://sandbox.itunes.apple.com/verifyReceipt"
9
+
10
+ def initialize(receipt:)
11
+ @request_data = RequestData.new(receipt)
12
+ unless receipt.length > 0
13
+ raise Error::EmptyReceipt.new
14
+ end
15
+ end
16
+
17
+ def response
18
+ verify_with_retry(@request_data)
19
+ end
20
+
21
+ private
22
+
23
+ def verify_with_retry(request_data)
24
+ response_data = verify(request_data, PRODUCTION_URL)
25
+
26
+ if response_data.sandbox?
27
+ response_data = verify(request_data, SANDBOX_URL)
28
+ end
29
+
30
+ response_data
31
+ end
32
+
33
+ def verify(request_data, url)
34
+ uri = URI(url)
35
+ http = Net::HTTP.new(uri.host, uri.port)
36
+
37
+ http.use_ssl = true
38
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
39
+
40
+ request = Net::HTTP::Post.new(uri.request_uri)
41
+ request['Content-Type'] = "application/json"
42
+ request['Accept'] = "application/json"
43
+ request.body = request_data.to_json
44
+
45
+ response = http.request(request)
46
+
47
+ if response.instance_of?(Net::HTTPOK)
48
+ ResponseData.new(JSON.parse(response.body))
49
+ else
50
+ raise Error::NetworkDown.new(response)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,17 @@
1
+ module IAPVerifier
2
+ class RequestData
3
+ def initialize(receipt)
4
+ @receipt = receipt
5
+ end
6
+
7
+ def to_h
8
+ {
9
+ 'receipt-data' => @receipt
10
+ }
11
+ end
12
+
13
+ def to_json
14
+ to_h.to_json
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,32 @@
1
+ module IAPVerifier
2
+ class ResponseData
3
+ SUCCESS_RESPONSE_CODE = 0
4
+ SANDBOX_RECEIPT_CODE = 21007
5
+
6
+ def initialize(response_data)
7
+ @response_data = response_data
8
+ end
9
+
10
+ def valid?
11
+ status_code == SUCCESS_RESPONSE_CODE
12
+ end
13
+
14
+ def sandbox?
15
+ status_code == SANDBOX_RECEIPT_CODE
16
+ end
17
+
18
+ def receipt
19
+ if valid?
20
+ Receipt.new(@response_data)
21
+ else
22
+ raise Error::InvalidResponseData.new(status_code)
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def status_code
29
+ @response_data['status']
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,3 @@
1
+ module IAPVerifier
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,22 @@
1
+ require "iap_verifier/version"
2
+ require 'iap_verifier/error'
3
+ require 'iap_verifier/request'
4
+ require 'iap_verifier/request_data'
5
+ require 'iap_verifier/response_data'
6
+ require 'iap_verifier/receipt'
7
+
8
+ module IAPVerifier
9
+ class << self
10
+ def verify_receipt!(receipt:)
11
+ IAPVerifier::Request.new(receipt: receipt).response.receipt
12
+ end
13
+
14
+ def verify_receipt(receipt:)
15
+ begin
16
+ verify_receipt!(receipt: receipt)
17
+ rescue IAPVerifier::Error::Standard
18
+ return nil
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1 @@
1
+ ewoJInNpZ25hdHVyZSIgPSAiQXBNVUJDODZBbHpOaWtWNVl0clpBTWlKUWJLOEVkZVhrNjNrV0JBWHpsQzhkWEd1anE0N1puSVlLb0ZFMW9OL0ZTOGNYbEZmcDlZWHQ5aU1CZEwyNTBsUlJtaU5HYnloaXRyeVlWQVFvcmkzMlc5YVIwVDhML2FZVkJkZlcrT3kvUXlQWkVtb05LeGhudDJXTlNVRG9VaFo4Wis0cFA3MHBlNWtVUWxiZElWaEFBQURWekNDQTFNd2dnSTdvQU1DQVFJQ0NHVVVrVTNaV0FTMU1BMEdDU3FHU0liM0RRRUJCUVVBTUg4eEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUtEQXBCY0hCc1pTQkpibU11TVNZd0pBWURWUVFMREIxQmNIQnNaU0JEWlhKMGFXWnBZMkYwYVc5dUlFRjFkR2h2Y21sMGVURXpNREVHQTFVRUF3d3FRWEJ3YkdVZ2FWUjFibVZ6SUZOMGIzSmxJRU5sY25ScFptbGpZWFJwYjI0Z1FYVjBhRzl5YVhSNU1CNFhEVEE1TURZeE5USXlNRFUxTmxvWERURTBNRFl4TkRJeU1EVTFObG93WkRFak1DRUdBMVVFQXd3YVVIVnlZMmhoYzJWU1pXTmxhWEIwUTJWeWRHbG1hV05oZEdVeEd6QVpCZ05WQkFzTUVrRndjR3hsSUdsVWRXNWxjeUJUZEc5eVpURVRNQkVHQTFVRUNnd0tRWEJ3YkdVZ1NXNWpMakVMTUFrR0ExVUVCaE1DVlZNd2daOHdEUVlKS29aSWh2Y05BUUVCQlFBRGdZMEFNSUdKQW9HQkFNclJqRjJjdDRJclNkaVRDaGFJMGc4cHd2L2NtSHM4cC9Sd1YvcnQvOTFYS1ZoTmw0WElCaW1LalFRTmZnSHNEczZ5anUrK0RyS0pFN3VLc3BoTWRkS1lmRkU1ckdYc0FkQkVqQndSSXhleFRldngzSExFRkdBdDFtb0t4NTA5ZGh4dGlJZERnSnYyWWFWczQ5QjB1SnZOZHk2U01xTk5MSHNETHpEUzlvWkhBZ01CQUFHamNqQndNQXdHQTFVZEV3RUIvd1FDTUFBd0h3WURWUjBqQkJnd0ZvQVVOaDNvNHAyQzBnRVl0VEpyRHRkREM1RllRem93RGdZRFZSMFBBUUgvQkFRREFnZUFNQjBHQTFVZERnUVdCQlNwZzRQeUdVakZQaEpYQ0JUTXphTittVjhrOVRBUUJnb3Foa2lHOTJOa0JnVUJCQUlGQURBTkJna3Foa2lHOXcwQkFRVUZBQU9DQVFFQUVhU2JQanRtTjRDL0lCM1FFcEszMlJ4YWNDRFhkVlhBZVZSZVM1RmFaeGMrdDg4cFFQOTNCaUF4dmRXLzNlVFNNR1k1RmJlQVlMM2V0cVA1Z204d3JGb2pYMGlreVZSU3RRKy9BUTBLRWp0cUIwN2tMczlRVWU4Y3pSOFVHZmRNMUV1bVYvVWd2RGQ0TndOWXhMUU1nNFdUUWZna1FRVnk4R1had1ZIZ2JFL1VDNlk3MDUzcEdYQms1MU5QTTN3b3hoZDNnU1JMdlhqK2xvSHNTdGNURXFlOXBCRHBtRzUrc2s0dHcrR0szR01lRU41LytlMVFUOW5wL0tsMW5qK2FCdzdDMHhzeTBiRm5hQWQxY1NTNnhkb3J5L0NVdk02Z3RLc21uT09kcVRlc2JwMGJzOHNuNldxczBDOWRnY3hSSHVPTVoydG04bnBMVW03YXJnT1N6UT09IjsKCSJwdXJjaGFzZS1pbmZvIiA9ICJld29KSW05eWFXZHBibUZzTFhCMWNtTm9ZWE5sTFdSaGRHVXRjSE4wSWlBOUlDSXlNREV5TFRBMExUTXdJREE0T2pBMU9qVTFJRUZ0WlhKcFkyRXZURzl6WDBGdVoyVnNaWE1pT3dvSkltOXlhV2RwYm1Gc0xYUnlZVzV6WVdOMGFXOXVMV2xrSWlBOUlDSXhNREF3TURBd01EUTJNVGM0T0RFM0lqc0tDU0ppZG5KeklpQTlJQ0l5TURFeU1EUXlOeUk3Q2draWRISmhibk5oWTNScGIyNHRhV1FpSUQwZ0lqRXdNREF3TURBd05EWXhOemc0TVRjaU93b0pJbkYxWVc1MGFYUjVJaUE5SUNJeElqc0tDU0p2Y21sbmFXNWhiQzF3ZFhKamFHRnpaUzFrWVhSbExXMXpJaUE5SUNJeE16TTFOems0TXpVMU9EWTRJanNLQ1NKd2NtOWtkV04wTFdsa0lpQTlJQ0pqYjIwdWJXbHVaRzF2WW1Gd2NDNWtiM2R1Ykc5aFpDSTdDZ2tpYVhSbGJTMXBaQ0lnUFNBaU5USXhNVEk1T0RFeUlqc0tDU0ppYVdRaUlEMGdJbU52YlM1dGFXNWtiVzlpWVhCd0xrMXBibVJOYjJJaU93b0pJbkIxY21Ob1lYTmxMV1JoZEdVdGJYTWlJRDBnSWpFek16VTNPVGd6TlRVNE5qZ2lPd29KSW5CMWNtTm9ZWE5sTFdSaGRHVWlJRDBnSWpJd01USXRNRFF0TXpBZ01UVTZNRFU2TlRVZ1JYUmpMMGROVkNJN0Nna2ljSFZ5WTJoaGMyVXRaR0YwWlMxd2MzUWlJRDBnSWpJd01USXRNRFF0TXpBZ01EZzZNRFU2TlRVZ1FXMWxjbWxqWVM5TWIzTmZRVzVuWld4bGN5STdDZ2tpYjNKcFoybHVZV3d0Y0hWeVkyaGhjMlV0WkdGMFpTSWdQU0FpTWpBeE1pMHdOQzB6TUNBeE5Ub3dOVG8xTlNCRmRHTXZSMDFVSWpzS2ZRPT0iOwoJImVudmlyb25tZW50IiA9ICJTYW5kYm94IjsKCSJwb2QiID0gIjEwMCI7Cgkic2lnbmluZy1zdGF0dXMiID0gIjAiOwp9
@@ -0,0 +1,150 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://buy.itunes.apple.com/verifyReceipt
6
+ body:
7
+ encoding: UTF-8
8
+ string: '{"receipt-data":"ewoJInNpZ25hdHVyZSIgPSAiQXBNVUJDODZBbHpOaWtWNVl0clpBTWlKUWJLOEVkZVhrNjNrV0JBWHpsQzhkWEd1anE0N1puSVlLb0ZFMW9OL0ZTOGNYbEZmcDlZWHQ5aU1CZEwyNTBsUlJtaU5HYnloaXRyeVlWQVFvcmkzMlc5YVIwVDhML2FZVkJkZlcrT3kvUXlQWkVtb05LeGhudDJXTlNVRG9VaFo4Wis0cFA3MHBlNWtVUWxiZElWaEFBQURWekNDQTFNd2dnSTdvQU1DQVFJQ0NHVVVrVTNaV0FTMU1BMEdDU3FHU0liM0RRRUJCUVVBTUg4eEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUtEQXBCY0hCc1pTQkpibU11TVNZd0pBWURWUVFMREIxQmNIQnNaU0JEWlhKMGFXWnBZMkYwYVc5dUlFRjFkR2h2Y21sMGVURXpNREVHQTFVRUF3d3FRWEJ3YkdVZ2FWUjFibVZ6SUZOMGIzSmxJRU5sY25ScFptbGpZWFJwYjI0Z1FYVjBhRzl5YVhSNU1CNFhEVEE1TURZeE5USXlNRFUxTmxvWERURTBNRFl4TkRJeU1EVTFObG93WkRFak1DRUdBMVVFQXd3YVVIVnlZMmhoYzJWU1pXTmxhWEIwUTJWeWRHbG1hV05oZEdVeEd6QVpCZ05WQkFzTUVrRndjR3hsSUdsVWRXNWxjeUJUZEc5eVpURVRNQkVHQTFVRUNnd0tRWEJ3YkdVZ1NXNWpMakVMTUFrR0ExVUVCaE1DVlZNd2daOHdEUVlKS29aSWh2Y05BUUVCQlFBRGdZMEFNSUdKQW9HQkFNclJqRjJjdDRJclNkaVRDaGFJMGc4cHd2L2NtSHM4cC9Sd1YvcnQvOTFYS1ZoTmw0WElCaW1LalFRTmZnSHNEczZ5anUrK0RyS0pFN3VLc3BoTWRkS1lmRkU1ckdYc0FkQkVqQndSSXhleFRldngzSExFRkdBdDFtb0t4NTA5ZGh4dGlJZERnSnYyWWFWczQ5QjB1SnZOZHk2U01xTk5MSHNETHpEUzlvWkhBZ01CQUFHamNqQndNQXdHQTFVZEV3RUIvd1FDTUFBd0h3WURWUjBqQkJnd0ZvQVVOaDNvNHAyQzBnRVl0VEpyRHRkREM1RllRem93RGdZRFZSMFBBUUgvQkFRREFnZUFNQjBHQTFVZERnUVdCQlNwZzRQeUdVakZQaEpYQ0JUTXphTittVjhrOVRBUUJnb3Foa2lHOTJOa0JnVUJCQUlGQURBTkJna3Foa2lHOXcwQkFRVUZBQU9DQVFFQUVhU2JQanRtTjRDL0lCM1FFcEszMlJ4YWNDRFhkVlhBZVZSZVM1RmFaeGMrdDg4cFFQOTNCaUF4dmRXLzNlVFNNR1k1RmJlQVlMM2V0cVA1Z204d3JGb2pYMGlreVZSU3RRKy9BUTBLRWp0cUIwN2tMczlRVWU4Y3pSOFVHZmRNMUV1bVYvVWd2RGQ0TndOWXhMUU1nNFdUUWZna1FRVnk4R1had1ZIZ2JFL1VDNlk3MDUzcEdYQms1MU5QTTN3b3hoZDNnU1JMdlhqK2xvSHNTdGNURXFlOXBCRHBtRzUrc2s0dHcrR0szR01lRU41LytlMVFUOW5wL0tsMW5qK2FCdzdDMHhzeTBiRm5hQWQxY1NTNnhkb3J5L0NVdk02Z3RLc21uT09kcVRlc2JwMGJzOHNuNldxczBDOWRnY3hSSHVPTVoydG04bnBMVW03YXJnT1N6UT09IjsKCSJwdXJjaGFzZS1pbmZvIiA9ICJld29KSW05eWFXZHBibUZzTFhCMWNtTm9ZWE5sTFdSaGRHVXRjSE4wSWlBOUlDSXlNREV5TFRBMExUTXdJREE0T2pBMU9qVTFJRUZ0WlhKcFkyRXZURzl6WDBGdVoyVnNaWE1pT3dvSkltOXlhV2RwYm1Gc0xYUnlZVzV6WVdOMGFXOXVMV2xrSWlBOUlDSXhNREF3TURBd01EUTJNVGM0T0RFM0lqc0tDU0ppZG5KeklpQTlJQ0l5TURFeU1EUXlOeUk3Q2draWRISmhibk5oWTNScGIyNHRhV1FpSUQwZ0lqRXdNREF3TURBd05EWXhOemc0TVRjaU93b0pJbkYxWVc1MGFYUjVJaUE5SUNJeElqc0tDU0p2Y21sbmFXNWhiQzF3ZFhKamFHRnpaUzFrWVhSbExXMXpJaUE5SUNJeE16TTFOems0TXpVMU9EWTRJanNLQ1NKd2NtOWtkV04wTFdsa0lpQTlJQ0pqYjIwdWJXbHVaRzF2WW1Gd2NDNWtiM2R1Ykc5aFpDSTdDZ2tpYVhSbGJTMXBaQ0lnUFNBaU5USXhNVEk1T0RFeUlqc0tDU0ppYVdRaUlEMGdJbU52YlM1dGFXNWtiVzlpWVhCd0xrMXBibVJOYjJJaU93b0pJbkIxY21Ob1lYTmxMV1JoZEdVdGJYTWlJRDBnSWpFek16VTNPVGd6TlRVNE5qZ2lPd29KSW5CMWNtTm9ZWE5sTFdSaGRHVWlJRDBnSWpJd01USXRNRFF0TXpBZ01UVTZNRFU2TlRVZ1JYUmpMMGROVkNJN0Nna2ljSFZ5WTJoaGMyVXRaR0YwWlMxd2MzUWlJRDBnSWpJd01USXRNRFF0TXpBZ01EZzZNRFU2TlRVZ1FXMWxjbWxqWVM5TWIzTmZRVzVuWld4bGN5STdDZ2tpYjNKcFoybHVZV3d0Y0hWeVkyaGhjMlV0WkdGMFpTSWdQU0FpTWpBeE1pMHdOQzB6TUNBeE5Ub3dOVG8xTlNCRmRHTXZSMDFVSWpzS2ZRPT0iOwoJImVudmlyb25tZW50IiA9ICJTYW5kYm94IjsKCSJwb2QiID0gIjEwMCI7Cgkic2lnbmluZy1zdGF0dXMiID0gIjAiOwp9"}'
9
+ headers:
10
+ Accept-Encoding:
11
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
12
+ Accept:
13
+ - application/json
14
+ User-Agent:
15
+ - Ruby
16
+ Content-Type:
17
+ - application/json
18
+ response:
19
+ status:
20
+ code: 200
21
+ message: Apple WebObjects
22
+ headers:
23
+ X-Apple-Jingle-Correlation-Key:
24
+ - 2YZEYGN63OI7E
25
+ Pod:
26
+ - '53'
27
+ X-Apple-Translated-Wo-Url:
28
+ - "/WebObjects/MZFinance.woa/wa/verifyReceipt"
29
+ X-Apple-Orig-Url:
30
+ - http://buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/verifyReceipt
31
+ X-Apple-Application-Site:
32
+ - ST13
33
+ Edge-Control:
34
+ - cache-maxage=0
35
+ - no-store
36
+ Date:
37
+ - Mon, 01 Sep 2014 23:29:26 GMT
38
+ Set-Cookie:
39
+ - NSC_nagjobodf-bopo-qppm*0=ffffffff12a5a8e645525d5f4f58455e445a4a423660;path=/;secure;httponly
40
+ - Pod=53; version="1"; expires=Wed, 01-Oct-2014 23:29:26 GMT; path=/; domain=.apple.com
41
+ - itspod=53; version="1"; expires=Wed, 01-Oct-2014 23:29:26 GMT; path=/; domain=.apple.com
42
+ - mzf_dr=0; version="1"; expires=Thu, 01-Jan-1970 00:00:00 GMT; path=/WebObjects;
43
+ domain=.apple.com
44
+ - mzf_in=532400; version="1"; path=/WebObjects; domain=.apple.com; secure
45
+ - ns-mzf-inst=182-247-80-217-23-8155-532400-53-st13; version=1; Max-Age=1800;
46
+ path=/; domain=.apple.com; httponly
47
+ Apple-Timing-App:
48
+ - 533 ms
49
+ Cache-Control:
50
+ - max-age=0
51
+ - must-revalidate
52
+ - no-cache
53
+ - no-store
54
+ - no-transform
55
+ - private
56
+ Expires:
57
+ - Mon, 01 Sep 2014 23:29:26 GMT
58
+ X-Apple-Lokamai-No-Cache:
59
+ - 'true'
60
+ X-Apple-Application-Instance:
61
+ - '532400'
62
+ X-Frame-Options:
63
+ - SAMEORIGIN
64
+ Itspod:
65
+ - '53'
66
+ X-Webobjects-Loadaverage:
67
+ - '24'
68
+ Connection:
69
+ - keep-alive
70
+ Content-Length:
71
+ - '36'
72
+ body:
73
+ encoding: UTF-8
74
+ string: '{"status":21007}'
75
+ http_version:
76
+ recorded_at: Mon, 01 Sep 2014 23:29:26 GMT
77
+ - request:
78
+ method: post
79
+ uri: https://sandbox.itunes.apple.com/verifyReceipt
80
+ body:
81
+ encoding: UTF-8
82
+ string: '{"receipt-data":"ewoJInNpZ25hdHVyZSIgPSAiQXBNVUJDODZBbHpOaWtWNVl0clpBTWlKUWJLOEVkZVhrNjNrV0JBWHpsQzhkWEd1anE0N1puSVlLb0ZFMW9OL0ZTOGNYbEZmcDlZWHQ5aU1CZEwyNTBsUlJtaU5HYnloaXRyeVlWQVFvcmkzMlc5YVIwVDhML2FZVkJkZlcrT3kvUXlQWkVtb05LeGhudDJXTlNVRG9VaFo4Wis0cFA3MHBlNWtVUWxiZElWaEFBQURWekNDQTFNd2dnSTdvQU1DQVFJQ0NHVVVrVTNaV0FTMU1BMEdDU3FHU0liM0RRRUJCUVVBTUg4eEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUtEQXBCY0hCc1pTQkpibU11TVNZd0pBWURWUVFMREIxQmNIQnNaU0JEWlhKMGFXWnBZMkYwYVc5dUlFRjFkR2h2Y21sMGVURXpNREVHQTFVRUF3d3FRWEJ3YkdVZ2FWUjFibVZ6SUZOMGIzSmxJRU5sY25ScFptbGpZWFJwYjI0Z1FYVjBhRzl5YVhSNU1CNFhEVEE1TURZeE5USXlNRFUxTmxvWERURTBNRFl4TkRJeU1EVTFObG93WkRFak1DRUdBMVVFQXd3YVVIVnlZMmhoYzJWU1pXTmxhWEIwUTJWeWRHbG1hV05oZEdVeEd6QVpCZ05WQkFzTUVrRndjR3hsSUdsVWRXNWxjeUJUZEc5eVpURVRNQkVHQTFVRUNnd0tRWEJ3YkdVZ1NXNWpMakVMTUFrR0ExVUVCaE1DVlZNd2daOHdEUVlKS29aSWh2Y05BUUVCQlFBRGdZMEFNSUdKQW9HQkFNclJqRjJjdDRJclNkaVRDaGFJMGc4cHd2L2NtSHM4cC9Sd1YvcnQvOTFYS1ZoTmw0WElCaW1LalFRTmZnSHNEczZ5anUrK0RyS0pFN3VLc3BoTWRkS1lmRkU1ckdYc0FkQkVqQndSSXhleFRldngzSExFRkdBdDFtb0t4NTA5ZGh4dGlJZERnSnYyWWFWczQ5QjB1SnZOZHk2U01xTk5MSHNETHpEUzlvWkhBZ01CQUFHamNqQndNQXdHQTFVZEV3RUIvd1FDTUFBd0h3WURWUjBqQkJnd0ZvQVVOaDNvNHAyQzBnRVl0VEpyRHRkREM1RllRem93RGdZRFZSMFBBUUgvQkFRREFnZUFNQjBHQTFVZERnUVdCQlNwZzRQeUdVakZQaEpYQ0JUTXphTittVjhrOVRBUUJnb3Foa2lHOTJOa0JnVUJCQUlGQURBTkJna3Foa2lHOXcwQkFRVUZBQU9DQVFFQUVhU2JQanRtTjRDL0lCM1FFcEszMlJ4YWNDRFhkVlhBZVZSZVM1RmFaeGMrdDg4cFFQOTNCaUF4dmRXLzNlVFNNR1k1RmJlQVlMM2V0cVA1Z204d3JGb2pYMGlreVZSU3RRKy9BUTBLRWp0cUIwN2tMczlRVWU4Y3pSOFVHZmRNMUV1bVYvVWd2RGQ0TndOWXhMUU1nNFdUUWZna1FRVnk4R1had1ZIZ2JFL1VDNlk3MDUzcEdYQms1MU5QTTN3b3hoZDNnU1JMdlhqK2xvSHNTdGNURXFlOXBCRHBtRzUrc2s0dHcrR0szR01lRU41LytlMVFUOW5wL0tsMW5qK2FCdzdDMHhzeTBiRm5hQWQxY1NTNnhkb3J5L0NVdk02Z3RLc21uT09kcVRlc2JwMGJzOHNuNldxczBDOWRnY3hSSHVPTVoydG04bnBMVW03YXJnT1N6UT09IjsKCSJwdXJjaGFzZS1pbmZvIiA9ICJld29KSW05eWFXZHBibUZzTFhCMWNtTm9ZWE5sTFdSaGRHVXRjSE4wSWlBOUlDSXlNREV5TFRBMExUTXdJREE0T2pBMU9qVTFJRUZ0WlhKcFkyRXZURzl6WDBGdVoyVnNaWE1pT3dvSkltOXlhV2RwYm1Gc0xYUnlZVzV6WVdOMGFXOXVMV2xrSWlBOUlDSXhNREF3TURBd01EUTJNVGM0T0RFM0lqc0tDU0ppZG5KeklpQTlJQ0l5TURFeU1EUXlOeUk3Q2draWRISmhibk5oWTNScGIyNHRhV1FpSUQwZ0lqRXdNREF3TURBd05EWXhOemc0TVRjaU93b0pJbkYxWVc1MGFYUjVJaUE5SUNJeElqc0tDU0p2Y21sbmFXNWhiQzF3ZFhKamFHRnpaUzFrWVhSbExXMXpJaUE5SUNJeE16TTFOems0TXpVMU9EWTRJanNLQ1NKd2NtOWtkV04wTFdsa0lpQTlJQ0pqYjIwdWJXbHVaRzF2WW1Gd2NDNWtiM2R1Ykc5aFpDSTdDZ2tpYVhSbGJTMXBaQ0lnUFNBaU5USXhNVEk1T0RFeUlqc0tDU0ppYVdRaUlEMGdJbU52YlM1dGFXNWtiVzlpWVhCd0xrMXBibVJOYjJJaU93b0pJbkIxY21Ob1lYTmxMV1JoZEdVdGJYTWlJRDBnSWpFek16VTNPVGd6TlRVNE5qZ2lPd29KSW5CMWNtTm9ZWE5sTFdSaGRHVWlJRDBnSWpJd01USXRNRFF0TXpBZ01UVTZNRFU2TlRVZ1JYUmpMMGROVkNJN0Nna2ljSFZ5WTJoaGMyVXRaR0YwWlMxd2MzUWlJRDBnSWpJd01USXRNRFF0TXpBZ01EZzZNRFU2TlRVZ1FXMWxjbWxqWVM5TWIzTmZRVzVuWld4bGN5STdDZ2tpYjNKcFoybHVZV3d0Y0hWeVkyaGhjMlV0WkdGMFpTSWdQU0FpTWpBeE1pMHdOQzB6TUNBeE5Ub3dOVG8xTlNCRmRHTXZSMDFVSWpzS2ZRPT0iOwoJImVudmlyb25tZW50IiA9ICJTYW5kYm94IjsKCSJwb2QiID0gIjEwMCI7Cgkic2lnbmluZy1zdGF0dXMiID0gIjAiOwp9"}'
83
+ headers:
84
+ Accept-Encoding:
85
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
86
+ Accept:
87
+ - application/json
88
+ User-Agent:
89
+ - Ruby
90
+ Content-Type:
91
+ - application/json
92
+ response:
93
+ status:
94
+ code: 200
95
+ message: Apple WebObjects
96
+ headers:
97
+ X-Apple-Jingle-Correlation-Key:
98
+ - I7RNKZTLOR2JI
99
+ Pod:
100
+ - '100'
101
+ X-Apple-Translated-Wo-Url:
102
+ - "/WebObjects/MZFinance.woa/wa/verifyReceipt"
103
+ X-Apple-Orig-Url:
104
+ - http://sandbox.itunes.apple.com/WebObjects/MZFinance.woa/wa/verifyReceipt
105
+ X-Apple-Application-Site:
106
+ - SB
107
+ Edge-Control:
108
+ - cache-maxage=0
109
+ - no-store
110
+ Set-Cookie:
111
+ - Pod=100; version="1"; expires=Wed, 01-Oct-2014 23:29:27 GMT; path=/; domain=.apple.com
112
+ - itspod=100; version="1"; expires=Wed, 01-Oct-2014 23:29:27 GMT; path=/; domain=.apple.com
113
+ - mzf_dr=0; version="1"; expires=Thu, 01-Jan-1970 00:00:00 GMT; path=/WebObjects;
114
+ domain=.apple.com
115
+ - mzf_in=990140; version="1"; path=/WebObjects; domain=.apple.com; secure
116
+ Apple-Timing-App:
117
+ - 11 ms
118
+ Cache-Control:
119
+ - max-age=0
120
+ - must-revalidate
121
+ - no-cache
122
+ - no-store
123
+ - no-transform
124
+ - private
125
+ Expires:
126
+ - Mon, 01 Sep 2014 23:29:27 GMT
127
+ X-Apple-Lokamai-No-Cache:
128
+ - 'true'
129
+ X-Apple-Application-Instance:
130
+ - '990140'
131
+ X-Frame-Options:
132
+ - SAMEORIGIN
133
+ Itspod:
134
+ - '100'
135
+ X-Webobjects-Loadaverage:
136
+ - '0'
137
+ Connection:
138
+ - keep-alive
139
+ Content-Length:
140
+ - '259'
141
+ Date:
142
+ - Mon, 01 Sep 2014 23:29:27 GMT
143
+ body:
144
+ encoding: UTF-8
145
+ string: |-
146
+ {
147
+ "receipt":{"original_purchase_date_pst":"2012-04-30 08:05:55 America/Los_Angeles", "original_transaction_id":"1000000046178817", "original_purchase_date_ms":"1335798355868", "transaction_id":"1000000046178817", "quantity":"1", "product_id":"com.mindmobapp.download", "bvrs":"20120427", "purchase_date_ms":"1335798355868", "purchase_date":"2012-04-30 15:05:55 Etc/GMT", "original_purchase_date":"2012-04-30 15:05:55 Etc/GMT", "purchase_date_pst":"2012-04-30 08:05:55 America/Los_Angeles", "bid":"com.mindmobapp.MindMob", "item_id":"521129812"}, "status":0}
148
+ http_version:
149
+ recorded_at: Mon, 01 Sep 2014 23:29:27 GMT
150
+ recorded_with: VCR 2.9.2
@@ -0,0 +1,13 @@
1
+ require 'test_helper'
2
+ require 'iap_verifier/receipt'
3
+
4
+ class ReceiptTest < MiniTest::Test
5
+ def test_method_missing
6
+ receipt = IAPVerifier::Receipt.new({ 'receipt' => { 'foo' => 'bar' } })
7
+
8
+ assert_equal 'bar', receipt.foo
9
+ assert_raises(NoMethodError) do
10
+ receipt.bar
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ require 'test_helper'
2
+ require 'iap_verifier/request_data'
3
+
4
+ class RequestDataTest < MiniTest::Test
5
+ def test_base64_receipt
6
+ receipt = File.read(File.expand_path("../fixtures/base64_receipt", __FILE__)).chop
7
+
8
+ expected = {
9
+ 'receipt-data' => receipt
10
+ }
11
+ assert_equal expected, IAPVerifier::RequestData.new(receipt).to_h
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ require 'test_helper'
2
+ require 'iap_verifier/request'
3
+
4
+ class RequestTest < MiniTest::Test
5
+ def test_empty_receipt
6
+ assert_raises(IAPVerifier::Error::EmptyReceipt) do
7
+ IAPVerifier::Request.new(receipt: '')
8
+ end
9
+ end
10
+
11
+ def test_response
12
+ VCR.use_cassette('valid_receipt') do
13
+ receipt = File.read(File.expand_path("../fixtures/base64_receipt", __FILE__)).chop
14
+ response_data = IAPVerifier::Request.new(receipt: receipt).response
15
+
16
+ assert_equal true, response_data.valid?
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,45 @@
1
+ require 'test_helper'
2
+ require 'iap_verifier/response_data'
3
+ require 'iap_verifier/receipt'
4
+ require 'iap_verifier/error'
5
+
6
+ class ResponseDataTest < MiniTest::Test
7
+ def test_success_response_json
8
+ success_response_json = { 'status' => 0, 'receipt' => { } }
9
+ response_data = IAPVerifier::ResponseData.new(success_response_json)
10
+
11
+ assert_equal true, response_data.valid?
12
+ end
13
+
14
+ def test_error_response_json
15
+ error_response_json = { 'status' => 21002 }
16
+ response_data = IAPVerifier::ResponseData.new(error_response_json)
17
+
18
+ assert_equal false, response_data.valid?
19
+ end
20
+
21
+ def test_sandbox_response_json
22
+ # sandbox receipt sent to production server
23
+ sandbox_response_json = { 'status' => 21007 }
24
+ response_data = IAPVerifier::ResponseData.new(sandbox_response_json)
25
+
26
+ assert_equal false, response_data.valid?
27
+ assert_equal true, response_data.sandbox?
28
+ end
29
+
30
+ def test_receipt
31
+ response_data = IAPVerifier::ResponseData.new({ 'receipt' => { } })
32
+ response_data.stubs(:valid?).returns(true)
33
+
34
+ assert_equal true, response_data.receipt.instance_of?(IAPVerifier::Receipt)
35
+ end
36
+
37
+ def test_invalid_response_data
38
+ assert_raises(IAPVerifier::Error::InvalidResponseData) do
39
+ response_data = IAPVerifier::ResponseData.new({ 'receipt' => { } })
40
+ response_data.stubs(:valid?).returns(false)
41
+
42
+ response_data.receipt
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,12 @@
1
+ require 'minitest/autorun'
2
+ require "minitest/reporters"
3
+ require 'mocha/mini_test'
4
+ require 'webmock/minitest'
5
+ require 'vcr'
6
+
7
+ Minitest::Reporters.use!
8
+
9
+ VCR.configure do |c|
10
+ c.cassette_library_dir = File.expand_path( '../fixtures/vcr_cassettes', __FILE__)
11
+ c.hook_into :webmock # or :fakeweb
12
+ end
metadata ADDED
@@ -0,0 +1,171 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: iap_verifier
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jun Lin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-09-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
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
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
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: mocha
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest-reporters
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: webmock
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: vcr
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: In-App Purchase receipt verification.
112
+ email:
113
+ - linjunpop@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - ".travis.yml"
120
+ - CHANGELOG.md
121
+ - Gemfile
122
+ - LICENSE.txt
123
+ - README.md
124
+ - Rakefile
125
+ - iap_verifier.gemspec
126
+ - lib/iap_verifier.rb
127
+ - lib/iap_verifier/error.rb
128
+ - lib/iap_verifier/receipt.rb
129
+ - lib/iap_verifier/request.rb
130
+ - lib/iap_verifier/request_data.rb
131
+ - lib/iap_verifier/response_data.rb
132
+ - lib/iap_verifier/version.rb
133
+ - test/fixtures/base64_receipt
134
+ - test/fixtures/vcr_cassettes/valid_receipt.yml
135
+ - test/receipt_test.rb
136
+ - test/request_data_test.rb
137
+ - test/request_test.rb
138
+ - test/response_data_test.rb
139
+ - test/test_helper.rb
140
+ homepage: ''
141
+ licenses:
142
+ - MIT
143
+ metadata: {}
144
+ post_install_message:
145
+ rdoc_options: []
146
+ require_paths:
147
+ - lib
148
+ required_ruby_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ required_rubygems_version: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ requirements: []
159
+ rubyforge_project:
160
+ rubygems_version: 2.2.2
161
+ signing_key:
162
+ specification_version: 4
163
+ summary: In-App Purchase receipt verification.
164
+ test_files:
165
+ - test/fixtures/base64_receipt
166
+ - test/fixtures/vcr_cassettes/valid_receipt.yml
167
+ - test/receipt_test.rb
168
+ - test/request_data_test.rb
169
+ - test/request_test.rb
170
+ - test/response_data_test.rb
171
+ - test/test_helper.rb