venice 0.4.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +13 -13
- data/coverage/index.html +385 -403
- data/lib/venice/client.rb +8 -7
- data/lib/venice/in_app_receipt.rb +11 -14
- data/lib/venice/pending_renewal_info.rb +3 -5
- data/lib/venice/receipt.rb +35 -34
- data/lib/venice/version.rb +1 -1
- data/spec/client_spec.rb +53 -37
- data/spec/in_app_receipt_spec.rb +26 -32
- data/spec/pending_renewal_info_spec.rb +21 -24
- data/spec/receipt_spec.rb +53 -56
- data/spec/spec_helper.rb +0 -2
- data/venice.gemspec +17 -17
- metadata +26 -26
data/lib/venice/client.rb
CHANGED
@@ -3,8 +3,8 @@ require 'net/https'
|
|
3
3
|
require 'uri'
|
4
4
|
|
5
5
|
module Venice
|
6
|
-
ITUNES_PRODUCTION_RECEIPT_VERIFICATION_ENDPOINT =
|
7
|
-
ITUNES_DEVELOPMENT_RECEIPT_VERIFICATION_ENDPOINT =
|
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
8
|
|
9
9
|
class Client
|
10
10
|
attr_accessor :verification_url
|
@@ -12,13 +12,13 @@ module Venice
|
|
12
12
|
|
13
13
|
class << self
|
14
14
|
def development
|
15
|
-
client =
|
15
|
+
client = new
|
16
16
|
client.verification_url = ITUNES_DEVELOPMENT_RECEIPT_VERIFICATION_ENDPOINT
|
17
17
|
client
|
18
18
|
end
|
19
19
|
|
20
20
|
def production
|
21
|
-
client =
|
21
|
+
client = new
|
22
22
|
client.verification_url = ITUNES_PRODUCTION_RECEIPT_VERIFICATION_ENDPOINT
|
23
23
|
client
|
24
24
|
end
|
@@ -33,7 +33,8 @@ module Venice
|
|
33
33
|
@shared_secret = options[:shared_secret] if options[:shared_secret]
|
34
34
|
|
35
35
|
json = json_response_from_verifying_data(data)
|
36
|
-
status
|
36
|
+
status = json['status'].to_i
|
37
|
+
receipt_attributes = json['receipt'].dup
|
37
38
|
receipt_attributes['original_json_response'] = json if receipt_attributes
|
38
39
|
|
39
40
|
case status
|
@@ -73,8 +74,8 @@ module Venice
|
|
73
74
|
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
74
75
|
|
75
76
|
request = Net::HTTP::Post.new(uri.request_uri)
|
76
|
-
request['Accept'] =
|
77
|
-
request['Content-Type'] =
|
77
|
+
request['Accept'] = 'application/json'
|
78
|
+
request['Content-Type'] = 'application/json'
|
78
79
|
request.body = parameters.to_json
|
79
80
|
|
80
81
|
response = http.request(request)
|
@@ -42,7 +42,6 @@ module Venice
|
|
42
42
|
# For a transaction that was canceled by Apple customer support, the time and date of the cancellation.
|
43
43
|
attr_reader :cancellation_at
|
44
44
|
|
45
|
-
|
46
45
|
def initialize(attributes = {})
|
47
46
|
@quantity = Integer(attributes['quantity']) if attributes['quantity']
|
48
47
|
@product_id = attributes['product_id']
|
@@ -65,28 +64,26 @@ module Venice
|
|
65
64
|
|
66
65
|
self.original = InAppReceipt.new(original_attributes)
|
67
66
|
end
|
68
|
-
|
69
67
|
end
|
70
68
|
|
71
69
|
def to_hash
|
72
70
|
{
|
73
|
-
:
|
74
|
-
:
|
75
|
-
:
|
76
|
-
:
|
77
|
-
:
|
78
|
-
:
|
79
|
-
:
|
80
|
-
:
|
81
|
-
:
|
82
|
-
:
|
71
|
+
quantity: @quantity,
|
72
|
+
product_id: @product_id,
|
73
|
+
transaction_id: @transaction_id,
|
74
|
+
purchase_date: (@purchased_at.httpdate rescue nil),
|
75
|
+
original_transaction_id: (@original.transaction_id rescue nil),
|
76
|
+
original_purchase_date: (@original.purchased_at.httpdate rescue nil),
|
77
|
+
app_item_id: @app_item_id,
|
78
|
+
version_external_identifier: @version_external_identifier,
|
79
|
+
expires_at: (@expires_at.httpdate rescue nil),
|
80
|
+
cancellation_at: (@cancellation_at.httpdate rescue nil)
|
83
81
|
}
|
84
82
|
end
|
85
83
|
alias_method :to_h, :to_hash
|
86
84
|
|
87
85
|
def to_json
|
88
|
-
|
86
|
+
to_hash.to_json
|
89
87
|
end
|
90
|
-
|
91
88
|
end
|
92
89
|
end
|
@@ -1,8 +1,7 @@
|
|
1
1
|
module Venice
|
2
2
|
class PendingRenewalInfo
|
3
|
-
|
4
3
|
# For an expired subscription, the reason for the subscription expiration.
|
5
|
-
# This key is only present for a receipt containing an expired auto-renewable subscription.
|
4
|
+
# This key is only present for a receipt containing an expired auto-renewable subscription.
|
6
5
|
attr_reader :expiration_intent
|
7
6
|
|
8
7
|
# The current renewal status for the auto-renewable subscription.
|
@@ -29,14 +28,13 @@ module Venice
|
|
29
28
|
# Use this value along with the cancellation date to identify possible issues in your app that may lead customers to contact Apple customer support.
|
30
29
|
attr_reader :cancellation_reason
|
31
30
|
|
32
|
-
|
33
31
|
def initialize(attributes)
|
34
32
|
@expiration_intent = Integer(attributes['expiration_intent']) if attributes['expiration_intent']
|
35
33
|
@auto_renew_status = Integer(attributes['auto_renew_status']) if attributes['auto_renew_status']
|
36
34
|
@auto_renew_product_id = attributes['auto_renew_product_id']
|
37
35
|
|
38
36
|
if attributes['is_in_billing_retry_period']
|
39
|
-
@is_in_billing_retry_period = (attributes[is_in_billing_retry_period] ==
|
37
|
+
@is_in_billing_retry_period = (attributes[is_in_billing_retry_period] == '1')
|
40
38
|
end
|
41
39
|
|
42
40
|
@product_id = attributes['product_id']
|
@@ -60,7 +58,7 @@ module Venice
|
|
60
58
|
alias_method :to_h, :to_hash
|
61
59
|
|
62
60
|
def to_json
|
63
|
-
|
61
|
+
to_hash.to_json
|
64
62
|
end
|
65
63
|
end
|
66
64
|
end
|
data/lib/venice/receipt.rb
CHANGED
@@ -68,29 +68,28 @@ module Venice
|
|
68
68
|
@pending_renewal_info << PendingRenewalInfo.new(pending_renewal_attributes)
|
69
69
|
end
|
70
70
|
end
|
71
|
-
|
72
71
|
end
|
73
72
|
|
74
73
|
def to_hash
|
75
74
|
{
|
76
|
-
:
|
77
|
-
:
|
78
|
-
:
|
79
|
-
:
|
80
|
-
:
|
81
|
-
:
|
82
|
-
:
|
83
|
-
:
|
84
|
-
:
|
85
|
-
:
|
86
|
-
:
|
87
|
-
:
|
75
|
+
bundle_id: @bundle_id,
|
76
|
+
application_version: @application_version,
|
77
|
+
original_application_version: @original_application_version,
|
78
|
+
original_purchase_date: (@original_purchase_date.httpdate rescue nil),
|
79
|
+
expires_at: (@expires_at.httpdate rescue nil),
|
80
|
+
receipt_type: @receipt_type,
|
81
|
+
adam_id: @adam_id,
|
82
|
+
download_id: @download_id,
|
83
|
+
requested_at: (@requested_at.httpdate rescue nil),
|
84
|
+
in_app: @in_app.map(&:to_h),
|
85
|
+
pending_renewal_info: @pending_renewal_info.map(&:to_h),
|
86
|
+
latest_receipt_info: @latest_receipt_info
|
88
87
|
}
|
89
88
|
end
|
90
89
|
alias_method :to_h, :to_hash
|
91
90
|
|
92
91
|
def to_json
|
93
|
-
|
92
|
+
to_hash.to_json
|
94
93
|
end
|
95
94
|
|
96
95
|
class << self
|
@@ -132,26 +131,28 @@ module Venice
|
|
132
131
|
|
133
132
|
def message
|
134
133
|
case @code
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
134
|
+
when 21000
|
135
|
+
'The App Store could not read the JSON object you provided.'
|
136
|
+
when 21002
|
137
|
+
'The data in the receipt-data property was malformed.'
|
138
|
+
when 21003
|
139
|
+
'The receipt could not be authenticated.'
|
140
|
+
when 21004
|
141
|
+
'The shared secret you provided does not match the shared secret on file for your account.'
|
142
|
+
when 21005
|
143
|
+
'The receipt server is not currently available.'
|
144
|
+
when 21006
|
145
|
+
'This receipt is valid but the subscription has expired. When this status code is returned to your server, the receipt data is also decoded and returned as part of the response.'
|
146
|
+
when 21007
|
147
|
+
'This receipt is a sandbox receipt, but it was sent to the production service for verification.'
|
148
|
+
when 21008
|
149
|
+
'This receipt is a production receipt, but it was sent to the sandbox service for verification.'
|
150
|
+
when 21010
|
151
|
+
'This receipt could not be authorized. Treat this the same as if a purchase was never made.'
|
152
|
+
when 21100..21199
|
153
|
+
'Internal data access error.'
|
154
|
+
else
|
155
|
+
"Unknown Error: #{@code}"
|
155
156
|
end
|
156
157
|
end
|
157
158
|
end
|
data/lib/venice/version.rb
CHANGED
data/spec/client_spec.rb
CHANGED
@@ -1,61 +1,78 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Venice::Client do
|
4
|
-
let(:receipt_data) {
|
4
|
+
let(:receipt_data) { 'asdfzxcvjklqwer' }
|
5
5
|
let(:client) { subject }
|
6
6
|
|
7
|
-
describe
|
8
|
-
context
|
7
|
+
describe '#verify!' do
|
8
|
+
context 'with a receipt response' do
|
9
|
+
before do
|
10
|
+
client.stub(:json_response_from_verifying_data).and_return(response)
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:response) do
|
14
|
+
{
|
15
|
+
'status' => 0,
|
16
|
+
'receipt' => {}
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'does not generate a self-referencing Hash' do
|
21
|
+
receipt = client.verify! 'asdf'
|
22
|
+
expect(receipt.original_json_response['receipt']).not_to have_key('original_json_response')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'no shared_secret' do
|
9
27
|
before do
|
10
28
|
client.shared_secret = nil
|
11
29
|
Venice::Receipt.stub :new
|
12
30
|
end
|
13
31
|
|
14
|
-
it
|
32
|
+
it 'should only include the receipt_data' do
|
15
33
|
Net::HTTP.any_instance.should_receive(:request) do |post|
|
16
|
-
post.body.should eq({'receipt-data' => receipt_data}.to_json)
|
34
|
+
post.body.should eq({ 'receipt-data' => receipt_data }.to_json)
|
17
35
|
post
|
18
36
|
end
|
19
37
|
client.verify! receipt_data
|
20
38
|
end
|
21
39
|
end
|
22
40
|
|
23
|
-
context
|
24
|
-
let(:secret) {
|
41
|
+
context 'with a shared secret' do
|
42
|
+
let(:secret) { 'shhhhhh' }
|
25
43
|
|
26
44
|
before do
|
27
45
|
Venice::Receipt.stub :new
|
28
46
|
end
|
29
47
|
|
30
|
-
context
|
48
|
+
context 'set secret manually' do
|
31
49
|
before do
|
32
50
|
client.shared_secret = secret
|
33
51
|
end
|
34
52
|
|
35
|
-
it
|
53
|
+
it 'should include the secret in the post' do
|
36
54
|
Net::HTTP.any_instance.should_receive(:request) do |post|
|
37
|
-
post.body.should eq({'receipt-data' => receipt_data, 'password' => secret}.to_json)
|
55
|
+
post.body.should eq({ 'receipt-data' => receipt_data, 'password' => secret }.to_json)
|
38
56
|
post
|
39
57
|
end
|
40
58
|
client.verify! receipt_data
|
41
59
|
end
|
42
60
|
end
|
43
61
|
|
44
|
-
context
|
45
|
-
let(:options) { {shared_secret: secret}
|
62
|
+
context 'set secret when verification' do
|
63
|
+
let(:options) { { shared_secret: secret } }
|
46
64
|
|
47
|
-
it
|
65
|
+
it 'should include the secret in the post' do
|
48
66
|
Net::HTTP.any_instance.should_receive(:request) do |post|
|
49
|
-
post.body.should eq({'receipt-data' => receipt_data, 'password' => secret}.to_json)
|
67
|
+
post.body.should eq({ 'receipt-data' => receipt_data, 'password' => secret }.to_json)
|
50
68
|
post
|
51
69
|
end
|
52
70
|
client.verify! receipt_data, options
|
53
71
|
end
|
54
72
|
end
|
55
|
-
|
56
73
|
end
|
57
74
|
|
58
|
-
context
|
75
|
+
context 'with a latest receipt info attribute' do
|
59
76
|
before do
|
60
77
|
client.stub(:json_response_from_verifying_data).and_return(response)
|
61
78
|
end
|
@@ -64,37 +81,36 @@ describe Venice::Client do
|
|
64
81
|
{
|
65
82
|
'status' => 0,
|
66
83
|
'receipt' => {},
|
67
|
-
'latest_receipt' =>
|
84
|
+
'latest_receipt' => '<encoded string>',
|
68
85
|
'latest_receipt_info' => [
|
69
86
|
{
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
87
|
+
'original_purchase_date_pst' => '2012-12-30 09:39:24 America/Los_Angeles',
|
88
|
+
'unique_identifier' => '0000b01147b8',
|
89
|
+
'original_transaction_id' => '1000000061051565',
|
90
|
+
'expires_date' => '1365114731000',
|
91
|
+
'transaction_id' => '1000000070104252',
|
92
|
+
'quantity' => '1',
|
93
|
+
'product_id' => 'com.ficklebits.nsscreencast.monthly_sub',
|
94
|
+
'original_purchase_date_ms' => '1356889164000',
|
95
|
+
'bid' => 'com.ficklebits.nsscreencast',
|
96
|
+
'web_order_line_item_id' => '1000000026812043',
|
97
|
+
'bvrs' => '0.1',
|
98
|
+
'expires_date_formatted' => '2013-04-04 22:32:11 Etc/GMT',
|
99
|
+
'purchase_date' => '2013-04-04 22:27:11 Etc/GMT',
|
100
|
+
'purchase_date_ms' => '1365114431000',
|
101
|
+
'expires_date_formatted_pst' => '2013-04-04 15:32:11 America/Los_Angeles',
|
102
|
+
'purchase_date_pst' => '2013-04-04 15:27:11 America/Los_Angeles',
|
103
|
+
'original_purchase_date' => '2012-12-30 17:39:24 Etc/GMT',
|
104
|
+
'item_id' => '590265423'
|
88
105
|
}
|
89
106
|
]
|
90
107
|
}
|
91
108
|
end
|
92
109
|
|
93
|
-
it
|
110
|
+
it 'should create a latest receipt' do
|
94
111
|
receipt = client.verify! 'asdf'
|
95
112
|
receipt.latest_receipt_info.should_not be_nil
|
96
113
|
end
|
97
114
|
end
|
98
|
-
|
99
115
|
end
|
100
116
|
end
|
data/spec/in_app_receipt_spec.rb
CHANGED
@@ -1,26 +1,24 @@
|
|
1
1
|
require 'spec_helper.rb'
|
2
2
|
|
3
3
|
describe Venice::InAppReceipt do
|
4
|
-
|
5
|
-
describe ".new" do
|
6
|
-
|
4
|
+
describe '.new' do
|
7
5
|
let :attributes do
|
8
6
|
{
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
7
|
+
'quantity' => 1,
|
8
|
+
'product_id' => 'com.foo.product1',
|
9
|
+
'transaction_id' => '1000000070107235',
|
10
|
+
'purchase_date' => '2014-05-28 14:47:53 Etc/GMT',
|
11
|
+
'purchase_date_ms' => '1401288473000',
|
12
|
+
'purchase_date_pst' => '2014-05-28 07:47:53 America/Los_Angeles',
|
13
|
+
'original_transaction_id' => '140xxx867509',
|
14
|
+
'original_purchase_date' => '2014-05-28 14:47:53 Etc/GMT',
|
15
|
+
'original_purchase_date_ms' => '1401288473000',
|
16
|
+
'original_purchase_date_pst' => '2014-05-28 07:47:53 America/Los_Angeles',
|
17
|
+
'is_trial_period' => false,
|
18
|
+
'version_external_identifier' => '123',
|
19
|
+
'app_item_id' => 'com.foo.app1',
|
20
|
+
'expires_date' => '2014-06-28 07:47:53 America/Los_Angeles',
|
21
|
+
'expires_date_ms' => '1403941673000'
|
24
22
|
}
|
25
23
|
end
|
26
24
|
|
@@ -29,30 +27,26 @@ describe Venice::InAppReceipt do
|
|
29
27
|
end
|
30
28
|
|
31
29
|
its(:quantity) { 1 }
|
32
|
-
its(:product_id) {
|
33
|
-
its(:transaction_id) {
|
30
|
+
its(:product_id) { 'com.foo.product1' }
|
31
|
+
its(:transaction_id) { '1000000070107235' }
|
34
32
|
its(:purchased_at) { should be_instance_of DateTime }
|
35
33
|
its(:app_item_id) { 'com.foo.app1' }
|
36
|
-
its(:version_external_identifier) {
|
34
|
+
its(:version_external_identifier) { '123' }
|
37
35
|
its(:original) { should be_instance_of Venice::InAppReceipt }
|
38
36
|
its(:expires_at) { should be_instance_of Time }
|
39
37
|
|
40
38
|
it "should parse the 'original' attributes" do
|
41
39
|
subject.original.should be_instance_of Venice::InAppReceipt
|
42
|
-
subject.original.transaction_id.should ==
|
40
|
+
subject.original.transaction_id.should == '140xxx867509'
|
43
41
|
subject.original.purchased_at.should be_instance_of DateTime
|
44
42
|
end
|
45
43
|
|
46
|
-
it
|
47
|
-
in_app_receipt.to_h.should include(:
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
)
|
44
|
+
it 'should output a hash with attributes' do
|
45
|
+
in_app_receipt.to_h.should include(quantity: 1,
|
46
|
+
product_id: 'com.foo.product1',
|
47
|
+
transaction_id: '1000000070107235',
|
48
|
+
purchase_date: 'Wed, 28 May 2014 14:47:53 GMT',
|
49
|
+
original_purchase_date: 'Wed, 28 May 2014 14:47:53 GMT')
|
53
50
|
end
|
54
|
-
|
55
|
-
|
56
51
|
end
|
57
|
-
|
58
52
|
end
|