ios-receipt 0.1.2
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 +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +34 -0
- data/Rakefile +1 -0
- data/ios-receipt.gemspec +23 -0
- data/lib/ios/receipt/client.rb +51 -0
- data/lib/ios/receipt/exceptions.rb +15 -0
- data/lib/ios/receipt/receipt.rb +57 -0
- data/lib/ios/receipt/result.rb +82 -0
- data/lib/ios/receipt/version.rb +5 -0
- data/lib/ios/receipt.rb +15 -0
- metadata +98 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4f9cfdea0aef2a3e629ec8e5c7565e4dd4d5a719
|
4
|
+
data.tar.gz: b7e0d4e5a77ea79f2066773ef76b6d73f35559c4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5ff3e79e6b1203c1051a88814b31789aad1c76de60005ab45323bd69140ec763ff2720e4a6e044247a8adcc4225a1770d6994849301b25d7a91f2f7fc50f2ebb
|
7
|
+
data.tar.gz: 6a41e5a316dd0646afa0e066af5c4a32697506c7bf159e907c3c3b798cb9f569632c72e924f45d7a3a4177583530ad1b7fa7b3df804f696658276a011e3cb67b
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Chris Sturgill
|
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,34 @@
|
|
1
|
+
# IosReceipt
|
2
|
+
|
3
|
+
ios-receipt is a RubyGem to manage validating and parsing iOS purchase receipts.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'ios-receipt'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install ios-receipt
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
result = Ios::Receipt.verify! encoded_receipt, mode, ENV['IOS_SHARED_SECRET']
|
22
|
+
mode can be one of :sandbox, :production, or :mixed (try both)
|
23
|
+
result.latest returns all receipts
|
24
|
+
Each receipt object has fields: :quantity, :product_id, :original_transaction_id, :transaction_id, :original_purchase_date,
|
25
|
+
:purchase_date, :expires_date, :cancellation_date, :app_item_id, :version_external_identifier,
|
26
|
+
:web_order_line_item_id, :is_trial_period
|
27
|
+
|
28
|
+
## Contributing
|
29
|
+
|
30
|
+
1. Fork it
|
31
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
32
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
33
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
34
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/ios-receipt.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ios/receipt/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "ios-receipt"
|
8
|
+
spec.version = Ios::Receipt::VERSION
|
9
|
+
spec.authors = ["DailyBurn"]
|
10
|
+
spec.email = ["dev@dailyburn.com"]
|
11
|
+
spec.description = %q{Verify iOS receipts server-side}
|
12
|
+
spec.summary = %q{Verify iOS receipts server-side}
|
13
|
+
spec.homepage = "https://github.com/DailyBurn/ios-receipt"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
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
|
+
spec.add_dependency 'rest_client'
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
class Ios::Receipt::Client
|
2
|
+
ENDPOINTS = {
|
3
|
+
production: 'https://buy.itunes.apple.com/verifyReceipt',
|
4
|
+
sandbox: 'https://sandbox.itunes.apple.com/verifyReceipt'
|
5
|
+
}
|
6
|
+
|
7
|
+
def initialize(method=nil, secret=nil, ssl_version='TLSv1')
|
8
|
+
method ||= :mixed
|
9
|
+
@method = method
|
10
|
+
@secret = secret
|
11
|
+
@ssl_version = ssl_version
|
12
|
+
|
13
|
+
raise Ios::Receipt::Exceptions::InvalidConfiguration unless valid_method?
|
14
|
+
end
|
15
|
+
|
16
|
+
def verify!(receipt)
|
17
|
+
data = { 'receipt-data' => receipt }
|
18
|
+
data['password'] = @secret unless @secret.nil?
|
19
|
+
data = data.to_json
|
20
|
+
|
21
|
+
response = nil
|
22
|
+
response = post_to_endpoint(:production, data) if try_production?
|
23
|
+
response = post_to_endpoint(:sandbox, data) if response.nil? && try_sandbox?
|
24
|
+
|
25
|
+
response
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
def try_production?
|
31
|
+
[:mixed, :production].include? @method
|
32
|
+
end
|
33
|
+
|
34
|
+
def try_sandbox?
|
35
|
+
[:mixed, :sandbox].include? @method
|
36
|
+
end
|
37
|
+
|
38
|
+
def valid_method?
|
39
|
+
try_production? || try_sandbox?
|
40
|
+
end
|
41
|
+
|
42
|
+
def post_to_endpoint(env, data)
|
43
|
+
begin
|
44
|
+
response = RestClient::Request.execute method: :post, url: ENDPOINTS[env], payload: data, ssl_version: @ssl_version
|
45
|
+
Ios::Receipt::Result.new JSON.parse(response), env
|
46
|
+
rescue Ios::Receipt::Exceptions::SandboxReceipt => e
|
47
|
+
raise Ios::Receipt::Exceptions::SandboxReceipt unless env == :production && try_sandbox?
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Ios
|
2
|
+
module Receipt
|
3
|
+
module Exceptions
|
4
|
+
class Exception < Exception; end
|
5
|
+
class InvalidConfiguration < Ios::Receipt::Exceptions::Exception; end
|
6
|
+
class Json < Ios::Receipt::Exceptions::Exception; end
|
7
|
+
class ReceiptFormat < Ios::Receipt::Exceptions::Exception; end
|
8
|
+
class ReceiptAuthentication < Ios::Receipt::Exceptions::Exception; end
|
9
|
+
class SharedSecret < Ios::Receipt::Exceptions::Exception; end
|
10
|
+
class ServerOffline < Ios::Receipt::Exceptions::Exception; end
|
11
|
+
class SandboxReceipt < Ios::Receipt::Exceptions::Exception; end
|
12
|
+
class ProductionReceipt < Ios::Receipt::Exceptions::Exception; end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
class Ios::Receipt::Receipt
|
2
|
+
attr_reader :quantity, :product_id, :original_transaction_id, :transaction_id, :original_purchase_date,
|
3
|
+
:purchase_date, :expires_date, :cancellation_date, :app_item_id, :version_external_identifier,
|
4
|
+
:web_order_line_item_id, :is_trial_period
|
5
|
+
|
6
|
+
def initialize(receipt_hash)
|
7
|
+
@quantity = receipt_hash['quantity']
|
8
|
+
@product_id = receipt_hash['product_id']
|
9
|
+
@original_transaction_id = receipt_hash['original_transaction_id']
|
10
|
+
@transaction_id = receipt_hash['transaction_id']
|
11
|
+
@original_purchase_date = parse_time receipt_hash['original_purchase_date']
|
12
|
+
@purchase_date = parse_time receipt_hash['purchase_date']
|
13
|
+
@expires_date = parse_time receipt_hash['expires_date']
|
14
|
+
@cancellation_date = parse_time receipt_hash['cancellation_date']
|
15
|
+
@app_item_id = receipt_hash['app_item_id']
|
16
|
+
@version_external_identifier = receipt_hash['version_external_identifier']
|
17
|
+
@web_order_line_item_id = receipt_hash['web_order_line_item_id']
|
18
|
+
@is_trial_period = receipt_hash['is_trial_period'] == 'true'
|
19
|
+
end
|
20
|
+
|
21
|
+
def once_off?
|
22
|
+
@expires_date.nil?
|
23
|
+
end
|
24
|
+
|
25
|
+
def recurring?
|
26
|
+
!once_off?
|
27
|
+
end
|
28
|
+
|
29
|
+
def active?
|
30
|
+
!cancelled? && !expired?
|
31
|
+
end
|
32
|
+
|
33
|
+
def expired?
|
34
|
+
return false unless recurring?
|
35
|
+
!!(@expires_date && @expires_date < Time.now)
|
36
|
+
end
|
37
|
+
|
38
|
+
def cancelled?
|
39
|
+
!@cancellation_date.nil?
|
40
|
+
end
|
41
|
+
|
42
|
+
def in_trial?
|
43
|
+
@is_trial_period
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def parse_time(str)
|
49
|
+
return nil if str.nil? || str == ''
|
50
|
+
|
51
|
+
if !str.match(/^[0-9]+$/).nil? # It's in number format
|
52
|
+
Time.at(str.to_i / 1000)
|
53
|
+
else # They've sent it in string format
|
54
|
+
Time.parse(str.sub('Etc/GMT', 'GMT')) rescue nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
class Ios::Receipt::Result
|
2
|
+
attr_reader :result, :environment, :bundle_id, :application_version, :original_application_version,
|
3
|
+
:in_app, :latest
|
4
|
+
|
5
|
+
def initialize(result, environment=nil)
|
6
|
+
@result = result
|
7
|
+
@status = result['status']
|
8
|
+
check_status!
|
9
|
+
|
10
|
+
@environment = (result['environment'] || environment).try(:downcase).try(:to_sym)
|
11
|
+
raise Ios::Receipt::Exceptions::InvalidConfiguration if @environment.nil?
|
12
|
+
|
13
|
+
receipt = result['receipt']
|
14
|
+
@bundle_id = receipt['bid'] || receipt['bundle_id']
|
15
|
+
@application_version = receipt['application_version']
|
16
|
+
@original_application_version = receipt['original_application_version']
|
17
|
+
|
18
|
+
if receipt['original_transaction_id'] # ios6 style receipt
|
19
|
+
latest = result['latest_receipt_info'] || result['latest_expired_receipt_info']
|
20
|
+
@latest = if latest
|
21
|
+
[ Ios::Receipt::Receipt.new(latest) ]
|
22
|
+
elsif receipt['original_transaction_id']
|
23
|
+
[ Ios::Receipt::Receipt.new(receipt) ]
|
24
|
+
else
|
25
|
+
[]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
@in_app = [receipt['in_app'] || []].flatten.compact.collect { |r| Ios::Receipt::Receipt.new r }
|
30
|
+
@latest = [result['latest_receipt_info'] || []].flatten.compact.collect { |r| Ios::Receipt::Receipt.new r } unless @latest
|
31
|
+
end
|
32
|
+
|
33
|
+
def recurring_receipts
|
34
|
+
@recurring ||= @latest.select { |r| r.recurring? }
|
35
|
+
end
|
36
|
+
|
37
|
+
def once_off_receipts
|
38
|
+
@once_offs ||= @latest.select { |r| r.once_off? }
|
39
|
+
end
|
40
|
+
|
41
|
+
def expired?
|
42
|
+
@status == 21006
|
43
|
+
end
|
44
|
+
|
45
|
+
def sandbox?
|
46
|
+
@environment == :sandbox
|
47
|
+
end
|
48
|
+
|
49
|
+
def production?
|
50
|
+
@environment == :production
|
51
|
+
end
|
52
|
+
|
53
|
+
def active_recurring_receipts
|
54
|
+
@active_recurring_receipts ||= recurring_receipts.select { |r| r.active? }
|
55
|
+
end
|
56
|
+
|
57
|
+
def next_recurring_receipt
|
58
|
+
@next_recurring_receipt ||= active_recurring_receipts.sort_by { |r| r.expires_date }.first
|
59
|
+
end
|
60
|
+
|
61
|
+
def in_trial_receipts
|
62
|
+
@in_trial_receipts ||= active_recurring_receipts.select { |r| r.in_trial? }
|
63
|
+
end
|
64
|
+
|
65
|
+
def transaction_ids
|
66
|
+
(@in_app.collect { |r| r.transaction_id } + @latest.collect { |r| r.transaction_id }).flatten.compact.uniq
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
|
71
|
+
def check_status!
|
72
|
+
case @status
|
73
|
+
when 21000 then raise Ios::Receipt::Exceptions::Json
|
74
|
+
when 21002 then raise Ios::Receipt::Exceptions::ReceiptFormat
|
75
|
+
when 21003 then raise Ios::Receipt::Exceptions::ReceiptAuthentication
|
76
|
+
when 21004 then raise Ios::Receipt::Exceptions::SharedSecret
|
77
|
+
when 21005 then raise Ios::Receipt::Exceptions::ServerOffline
|
78
|
+
when 21007 then raise Ios::Receipt::Exceptions::SandboxReceipt
|
79
|
+
when 21008 then raise Ios::Receipt::Exceptions::ProductionReceipt
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/ios/receipt.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rest_client'
|
2
|
+
require 'ios/receipt/version'
|
3
|
+
require 'ios/receipt/exceptions'
|
4
|
+
require 'ios/receipt/client'
|
5
|
+
require 'ios/receipt/result'
|
6
|
+
require 'ios/receipt/receipt'
|
7
|
+
|
8
|
+
module Ios
|
9
|
+
module Receipt
|
10
|
+
def self.verify!(receipt, method=nil, secret=nil)
|
11
|
+
client = Ios::Receipt::Client.new method, secret
|
12
|
+
client.verify! receipt
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ios-receipt
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- DailyBurn
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-02-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rest_client
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
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: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
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
|
+
description: Verify iOS receipts server-side
|
56
|
+
email:
|
57
|
+
- dev@dailyburn.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- .gitignore
|
63
|
+
- Gemfile
|
64
|
+
- LICENSE.txt
|
65
|
+
- README.md
|
66
|
+
- Rakefile
|
67
|
+
- ios-receipt.gemspec
|
68
|
+
- lib/ios/receipt.rb
|
69
|
+
- lib/ios/receipt/client.rb
|
70
|
+
- lib/ios/receipt/exceptions.rb
|
71
|
+
- lib/ios/receipt/receipt.rb
|
72
|
+
- lib/ios/receipt/result.rb
|
73
|
+
- lib/ios/receipt/version.rb
|
74
|
+
homepage: https://github.com/DailyBurn/ios-receipt
|
75
|
+
licenses:
|
76
|
+
- MIT
|
77
|
+
metadata: {}
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - '>='
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
requirements: []
|
93
|
+
rubyforge_project:
|
94
|
+
rubygems_version: 2.0.14
|
95
|
+
signing_key:
|
96
|
+
specification_version: 4
|
97
|
+
summary: Verify iOS receipts server-side
|
98
|
+
test_files: []
|