folked-venice 0.5.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,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d0106d4861052d62e1deab824561dda6e8c3b1228525e209c8071b425bfbd111
4
+ data.tar.gz: 8a21855a20ae2842beed502519fdc08e901de61be2cb9c4d112651246145d426
5
+ SHA512:
6
+ metadata.gz: 5b751b8828216313749ae85e68df5c88cf966d17d957fdb0f152379351a76cc42abc2f24c3136d93cf915deaf36efc813b5850a2b8fac08e63ccfc761c199d8a
7
+ data.tar.gz: f36858c2610318004fbea5c6f7acca802b4859ef7c77c44398ecec304a9cac25b4ceaf9be4bf8e5f01b5eef7a5bab1bbba20f09646e255613fceecdd5ddb65aa
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in hello-rspec.gemspec
6
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2013–2015 Mattt Thompson (http://mattt.me/)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,99 @@
1
+ ![Venice](https://raw.github.com/nomad/nomad.github.io/assets/venice-banner.png)
2
+
3
+ [![Travis](https://img.shields.io/travis/nomad/venice.svg)](https://travis-ci.org/nomad/venice)
4
+
5
+ Venice is a simple gem for verifying Apple In-App Purchase receipts, and retrieving the information associated with receipt data.
6
+
7
+ There are two reasons why you should verify in-app purchase receipts on the server: First, it allows you to keep your own records of past purchases, which is useful for up-to-the-minute metrics and historical analysis. Second, server-side verification over SSL is the most reliable way to determine the authenticity of purchasing records.
8
+
9
+ See Apple's [Receipt Validation Programming Guide](https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Introduction.html) for additional information.
10
+
11
+ > Venice is named for [Venice, Italy](http://en.wikipedia.org/wiki/Venice,_Italy)—or more specifically, Shakespeare's [_The Merchant of Venice_](http://en.wikipedia.org/wiki/The_Merchant_of_Venice).
12
+ > It's part of a series of world-class command-line utilities for iOS development, which includes [Cupertino](https://github.com/mattt/cupertino) (Apple Dev Center management), [Shenzhen](https://github.com/mattt/shenzhen) (Building & Distribution), [Houston](https://github.com/mattt/houston) (Push Notifications), [Dubai](https://github.com/mattt/dubai) (Passbook pass generation), and [Nashville](https://github.com/nomad/nashville) (iTunes Store API).
13
+
14
+ ## Installation
15
+
16
+ $ gem install venice
17
+
18
+ ## Usage
19
+
20
+ ### Basic
21
+
22
+ ```ruby
23
+ require 'venice'
24
+
25
+ data = '(Base64-Encoded Receipt Data)'
26
+ if receipt = Venice::Receipt.verify(data)
27
+ p receipt.to_h
28
+
29
+ # You can refer an original JSON response via a Receipt instance.
30
+ case receipt.original_json_response['status'].to_i
31
+ when 0 then foo
32
+ when 21006 then bar
33
+ end
34
+ end
35
+ ```
36
+
37
+ ### For Auto-Renewable
38
+
39
+ ```ruby
40
+ require 'venice'
41
+
42
+ data = '(Base64-Encoded Receipt Data)'
43
+
44
+ # You must pass shared secret when verification on Auto-Renewable
45
+ opts = { shared_secret: 'your key' }
46
+
47
+ if receipt = Venice::Receipt.verify(data, opts)
48
+ # Renewed receipts are added into `latest_receipt_info` array.
49
+ p receipt.latest_receipt_info.map(&:expires_at)
50
+ # => [2016-05-19 20:35:59 +0000, 2016-06-18 20:35:59 +0000, 2016-07-18 20:35:59 +0000]
51
+ end
52
+ ```
53
+
54
+ ## Command Line Interface
55
+
56
+ Venice also comes with the `iap` binary, which provides a convenient way to verify receipts from the command line.
57
+
58
+
59
+ $ iap verify /path/to/receipt
60
+
61
+ +--------------------------------+------------------------------------+
62
+ | Receipt |
63
+ +--------------------------------+------------------------------------+
64
+ | adam_id | 664753504 |
65
+ | application_version | 123 |
66
+ | bundle_id | com.example.product |
67
+ | download_id | 30000000000005 |
68
+ | expires_at | |
69
+ | latest_receipt | |
70
+ | original_application_version | 123 |
71
+ | original_purchase_date | Fri, 07 Mar 2014 20:59:24 GMT |
72
+ | receipt_type | Production |
73
+ | receipt_created_at | Mon, 23 Jun 2014 17:59:38 GMT |
74
+ | requested_at | Mon, 23 Jun 2014 17:59:38 GMT |
75
+ +--------------------------------+------------------------------------+
76
+ | in_app | 1 |
77
+ | - app_item_id | |
78
+ | - cancellation_at | |
79
+ | - expires_at | |
80
+ | - original_purchase_date | |
81
+ | - original_transaction_id | 1000000000000001 |
82
+ | - product_id | com.example.product |
83
+ | - purchase_date | |
84
+ | - quantity | 1 |
85
+ | - transaction_id | 1000000000000001 |
86
+ | - web_order_line_item_id | 1000000000000001 |
87
+ | - version_external_identifier | |
88
+ | - is_trial_period | true |
89
+ | - is_in_intro_offer_period | true |
90
+ +--------------------------------+------------------------------------+
91
+
92
+
93
+ ## Creator
94
+
95
+ Mattt Thompson ([@mattt](https://twitter.com/mattt))
96
+
97
+ ## License
98
+
99
+ Venice is available under the MIT license. See the LICENSE file for more info.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
data/bin/iap ADDED
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'venice'
4
+
5
+ require 'commander/import'
6
+ require 'terminal-table'
7
+
8
+ HighLine.track_eof = false # Fix for built-in Ruby
9
+ Signal.trap('INT') {} # Suppress backtrace when exiting command
10
+
11
+ program :version, Venice::VERSION
12
+ program :description, 'A command-line interface for verifying Apple In-App Purchase receipts'
13
+
14
+ program :help, 'Author', 'Mattt Thompson <m@mattt.me>'
15
+ program :help, 'Website', 'https://github.com/mattt'
16
+ program :help_formatter, :compact
17
+
18
+ default_command :help
19
+
20
+ command :verify do |c|
21
+ c.syntax = 'iap verify RECEIPT'
22
+ c.summary = 'Verifies an In-App Purchase Receipt'
23
+ c.description = ''
24
+ c.option '-S', '--[no-]sandbox', 'Use sandbox verification webservice'
25
+ c.option '-p', '--secret SECRET', 'Use a shared secret for auto-renewable subscription receipts'
26
+
27
+ c.example 'description', 'iap verify /path/to/receipt [--secret shared_secret]'
28
+
29
+ c.action do |args, options|
30
+ say_error('Missing receipt argument') and abort unless file = args.first
31
+ say_error 'Receipt file does not exist' unless File.exist?(file)
32
+
33
+ client = options.sandbox ? Venice::Client.development : Venice::Client.production
34
+ client.shared_secret = options.secret if options.secret
35
+
36
+ begin
37
+ receipt = client.verify!(File.read(file))
38
+
39
+ table = Terminal::Table.new title: 'Receipt' do |t|
40
+ hash = receipt.to_h
41
+ hash.keys.sort.each do |key|
42
+ next if key == :in_app
43
+ t << [key, hash[key]]
44
+ end
45
+
46
+ if hash[:in_app]
47
+ index = 0
48
+ hash[:in_app].each do |iap|
49
+ index += 1
50
+ t << :separator
51
+ t << [:in_app, index]
52
+ iap.keys.sort.each do |key|
53
+ t << [" - #{key}", iap[key]]
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ puts table
60
+ rescue StandardError => error
61
+ say_error(error.message) and abort
62
+ end
63
+ end
64
+ end
data/daiki44.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ $LOAD_PATH.push File.expand_path('lib', __dir__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'folked-venice'
5
+ s.authors = ['daiki.sekiguchi']
6
+ s.email = 'sekiseki0213@gmail.com'
7
+ s.license = 'MIT'
8
+ s.homepage = 'https://daiki-sekiguchi.com'
9
+ s.version = '0.5.0.1'
10
+ s.platform = Gem::Platform::RUBY
11
+ s.summary = 'iTunes In-App Purchase Receipt Verification'
12
+ s.description = 'Ruby Gem for In-App Purchase Receipt Verification'
13
+
14
+ s.add_dependency 'commander', '~> 4.1'
15
+ s.add_dependency 'json'
16
+ s.add_dependency 'terminal-table', '~> 1.4'
17
+
18
+ s.add_development_dependency 'rake'
19
+ s.add_development_dependency 'rspec', '~> 3.6'
20
+ s.add_development_dependency 'rspec-its', '~> 1.2'
21
+ s.add_development_dependency 'simplecov'
22
+
23
+ s.files = Dir['./**/*'].reject { |file| file =~ /\.\/(bin|log|pkg|script|spec|test|vendor)/ }
24
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
25
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
26
+ s.require_paths = ['lib']
27
+ end
data/lib/venice.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'venice/version'
2
+ require 'venice/client'
3
+ require 'venice/in_app_receipt'
4
+ require 'venice/receipt'
5
+ require 'venice/pending_renewal_info'
@@ -0,0 +1,116 @@
1
+ require 'json'
2
+ require 'net/https'
3
+ require 'uri'
4
+
5
+ module Venice
6
+ ITUNES_PRODUCTION_RECEIPT_VERIFICATION_ENDPOINT = 'https://buy.itunes.apple.com/verifyReceipt'
7
+ ITUNES_DEVELOPMENT_RECEIPT_VERIFICATION_ENDPOINT = 'https://sandbox.itunes.apple.com/verifyReceipt'
8
+
9
+ class Client
10
+ attr_accessor :verification_url
11
+ attr_writer :shared_secret
12
+ attr_writer :exclude_old_transactions
13
+
14
+ class << self
15
+ def development
16
+ client = new
17
+ client.verification_url = ITUNES_DEVELOPMENT_RECEIPT_VERIFICATION_ENDPOINT
18
+ client
19
+ end
20
+
21
+ def production
22
+ client = new
23
+ client.verification_url = ITUNES_PRODUCTION_RECEIPT_VERIFICATION_ENDPOINT
24
+ client
25
+ end
26
+ end
27
+
28
+ def initialize
29
+ @verification_url = ENV['IAP_VERIFICATION_ENDPOINT']
30
+ end
31
+
32
+ def verify!(data, options = {})
33
+ @shared_secret = options[:shared_secret] if options[:shared_secret]
34
+ @exclude_old_transactions = options[:exclude_old_transactions] if options[:exclude_old_transactions]
35
+
36
+ json = json_response_from_verifying_data(data, options)
37
+ receipt_attributes = json['receipt'].dup if json['receipt']
38
+ receipt_attributes['original_json_response'] = json if receipt_attributes
39
+
40
+ case json['status'].to_i
41
+ when 0, 21006
42
+ receipt = Receipt.new(receipt_attributes)
43
+
44
+ # From Apple docs:
45
+ # > Only returned for iOS 6 style transaction receipts for auto-renewable subscriptions.
46
+ # > The JSON representation of the receipt for the most recent renewal
47
+ if latest_receipt_info_attributes = json['latest_receipt_info']
48
+ latest_receipt_info_attributes = [latest_receipt_info_attributes] if latest_receipt_info_attributes.is_a?(Hash)
49
+
50
+ # AppStore returns 'latest_receipt_info' even if we use over iOS 6. Besides, its format is an Array.
51
+ if latest_receipt_info_attributes.is_a?(Array)
52
+ receipt.latest_receipt_info = []
53
+ latest_receipt_info_attributes.each do |latest_receipt_info_attribute|
54
+ # latest_receipt_info format is identical with in_app
55
+ receipt.latest_receipt_info << InAppReceipt.new(latest_receipt_info_attribute)
56
+ end
57
+ else
58
+ receipt.latest_receipt_info = latest_receipt_info_attributes
59
+ end
60
+ end
61
+
62
+ return receipt
63
+ else
64
+ raise Receipt::VerificationError, json
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def json_response_from_verifying_data(data, options = {})
71
+ parameters = {
72
+ 'receipt-data' => data
73
+ }
74
+
75
+ parameters['password'] = @shared_secret if @shared_secret
76
+ parameters['exclude-old-transactions'] = @exclude_old_transactions if @exclude_old_transactions
77
+
78
+ uri = URI(@verification_url)
79
+ http = Net::HTTP.new(uri.host, uri.port)
80
+ http.use_ssl = true
81
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
82
+
83
+ http.open_timeout = options[:open_timeout] if options[:open_timeout]
84
+ http.read_timeout = options[:read_timeout] if options[:read_timeout]
85
+
86
+ request = Net::HTTP::Post.new(uri.request_uri)
87
+ request['Accept'] = 'application/json'
88
+ request['Content-Type'] = 'application/json'
89
+ request.body = parameters.to_json
90
+
91
+ begin
92
+ response = http.request(request)
93
+ rescue Timeout::Error
94
+ raise TimeoutError
95
+ end
96
+
97
+ begin
98
+ JSON.parse(response.body)
99
+ rescue JSON::ParserError
100
+ raise InvalidResponseError
101
+ end
102
+ end
103
+ end
104
+
105
+ class Client::TimeoutError < Timeout::Error
106
+ def message
107
+ 'The App Store timed out.'
108
+ end
109
+ end
110
+
111
+ class Client::InvalidResponseError < StandardError
112
+ def message
113
+ 'The App Store returned invalid response'
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,121 @@
1
+ require 'time'
2
+
3
+ module Venice
4
+ class InAppReceipt
5
+ # For detailed explanations on these keys/values, see
6
+ # https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html#//apple_ref/doc/uid/TP40010573-CH106-SW12
7
+
8
+ # Original JSON data returned from Apple for an InAppReceipt object.
9
+ attr_reader :original_json_data
10
+
11
+ # The number of items purchased. This value corresponds to the quantity property of
12
+ # the SKPayment object stored in the transaction’s payment property.
13
+ attr_reader :quantity
14
+
15
+ # The product identifier of the item that was purchased. This value corresponds to
16
+ # the productIdentifier property of the SKPayment object stored in the transaction’s
17
+ # payment property.
18
+ attr_reader :product_id
19
+
20
+ # The transaction identifier of the item that was purchased. This value corresponds
21
+ # to the transaction’s transactionIdentifier property.
22
+ attr_reader :transaction_id
23
+
24
+ # The primary key for identifying subscription purchases. This value is a unique ID that identifies purchase events across devices, including subscription renewal purchase events.
25
+ # When restoring purchase, transaction_id could change
26
+ attr_reader :web_order_line_item_id
27
+
28
+ # The date and time this transaction occurred. This value corresponds to the
29
+ # transaction’s transactionDate property.
30
+ attr_reader :purchased_at
31
+
32
+ # A string that the App Store uses to uniquely identify the application that created
33
+ # the payment transaction. If your server supports multiple applications, you can use
34
+ # this value to differentiate between them. Applications that are executing in the
35
+ # sandbox do not yet have an app-item-id assigned to them, so this key is missing from
36
+ # receipts created by the sandbox.
37
+ attr_reader :app_item_id
38
+
39
+ # An arbitrary number that uniquely identifies a revision of your application. This key
40
+ # is missing in receipts created by the sandbox.
41
+ attr_reader :version_external_identifier
42
+
43
+ # For a transaction that restores a previous transaction, this is the original receipt
44
+ attr_accessor :original
45
+
46
+ # For auto-renewable subscriptions, returns the date the subscription will expire
47
+ attr_reader :expires_at
48
+
49
+ # For a transaction that was canceled by Apple customer support, the time and date of the cancellation.
50
+ # For an auto-renewable subscription plan that was upgraded, the time and date of the upgrade transaction.
51
+ attr_reader :cancellation_at
52
+
53
+ # Only present for auto-renewable subscription receipts. Value is true if the customer’s subscription is
54
+ # currently in the free trial period, false if not, nil if key is not present on receipt.
55
+ attr_reader :is_trial_period
56
+ # Only present for auto-renewable subscription receipts. Value is true if the customer’s subscription is
57
+ # currently in an introductory price period, false if not, nil if key is not present on receipt.
58
+ attr_reader :is_in_intro_offer_period
59
+
60
+ def initialize(attributes = {})
61
+ @original_json_data = attributes
62
+ @quantity = Integer(attributes['quantity']) if attributes['quantity']
63
+ @product_id = attributes['product_id']
64
+ @transaction_id = attributes['transaction_id']
65
+ @web_order_line_item_id = attributes['web_order_line_item_id']
66
+ @purchased_at = DateTime.parse(attributes['purchase_date']) if attributes['purchase_date']
67
+ @app_item_id = attributes['app_item_id']
68
+ @version_external_identifier = attributes['version_external_identifier']
69
+ @is_trial_period = attributes['is_trial_period'].to_s == 'true' if attributes['is_trial_period']
70
+ @is_in_intro_offer_period = attributes['is_in_intro_offer_period'] == 'true' if attributes['is_in_intro_offer_period']
71
+
72
+ # expires_date is in ms since the Epoch, Time.at expects seconds
73
+ if attributes['expires_date_ms']
74
+ @expires_at = Time.at(attributes['expires_date_ms'].to_i / 1000)
75
+ elsif attributes['expires_date'] && is_number?(attributes['expires_date'])
76
+ @expires_at = Time.at(attributes['expires_date'].to_i / 1000)
77
+ end
78
+
79
+ # cancellation_date is in ms since the Epoch, Time.at expects seconds
80
+ @cancellation_at = Time.at(attributes['cancellation_date_ms'].to_i / 1000) if attributes['cancellation_date_ms']
81
+
82
+ if attributes['original_transaction_id'] || attributes['original_purchase_date']
83
+ original_attributes = {
84
+ 'transaction_id' => attributes['original_transaction_id'],
85
+ 'purchase_date' => attributes['original_purchase_date']
86
+ }
87
+
88
+ self.original = InAppReceipt.new(original_attributes)
89
+ end
90
+ end
91
+
92
+ def to_hash
93
+ {
94
+ quantity: @quantity,
95
+ product_id: @product_id,
96
+ transaction_id: @transaction_id,
97
+ web_order_line_item_id: @web_order_line_item_id,
98
+ purchase_date: (@purchased_at.httpdate rescue nil),
99
+ original_transaction_id: (@original.transaction_id rescue nil),
100
+ original_purchase_date: (@original.purchased_at.httpdate rescue nil),
101
+ app_item_id: @app_item_id,
102
+ version_external_identifier: @version_external_identifier,
103
+ is_trial_period: @is_trial_period,
104
+ is_in_intro_offer_period: @is_in_intro_offer_period,
105
+ expires_at: (@expires_at.httpdate rescue nil),
106
+ cancellation_at: (@cancellation_at.httpdate rescue nil)
107
+ }
108
+ end
109
+ alias_method :to_h, :to_hash
110
+
111
+ def to_json
112
+ to_hash.to_json
113
+ end
114
+
115
+ private
116
+
117
+ def is_number?(string)
118
+ !!(string && string.to_s =~ /^[0-9]+$/)
119
+ end
120
+ end
121
+ end