folked-venice 0.5.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 +7 -0
- data/Gemfile +6 -0
- data/LICENSE +19 -0
- data/README.md +99 -0
- data/Rakefile +6 -0
- data/bin/iap +64 -0
- data/daiki44.gemspec +27 -0
- data/lib/venice.rb +5 -0
- data/lib/venice/client.rb +116 -0
- data/lib/venice/in_app_receipt.rb +121 -0
- data/lib/venice/pending_renewal_info.rb +68 -0
- data/lib/venice/receipt.rb +186 -0
- data/lib/venice/version.rb +3 -0
- data/spec/client_spec.rb +169 -0
- data/spec/in_app_receipt_spec.rb +67 -0
- data/spec/pending_renewal_info_spec.rb +39 -0
- data/spec/receipt +1 -0
- data/spec/receipt_spec.rb +191 -0
- data/spec/spec_helper.rb +18 -0
- metadata +165 -0
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
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
|
+

|
2
|
+
|
3
|
+
[](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
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,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
|