recurly 0.4.16 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of recurly might be problematic. Click here for more details.
- data/README.markdown +118 -0
- data/bin/recurly +78 -0
- data/lib/rails/generators/recurly/config_generator.rb +16 -0
- data/lib/rails/recurly.rb +13 -0
- data/lib/recurly.rb +64 -139
- data/lib/recurly/account.rb +52 -111
- data/lib/recurly/add_on.rb +20 -0
- data/lib/recurly/adjustment.rb +51 -0
- data/lib/recurly/api.rb +73 -0
- data/lib/recurly/api/errors.rb +205 -0
- data/lib/recurly/api/net_http.rb +77 -0
- data/lib/recurly/billing_info.rb +45 -42
- data/lib/recurly/coupon.rb +63 -8
- data/lib/recurly/helper.rb +39 -0
- data/lib/recurly/invoice.rb +38 -16
- data/lib/recurly/js.rb +113 -0
- data/lib/recurly/money.rb +105 -0
- data/lib/recurly/plan.rb +26 -15
- data/lib/recurly/redemption.rb +34 -0
- data/lib/recurly/resource.rb +925 -0
- data/lib/recurly/resource/pager.rb +210 -0
- data/lib/recurly/subscription.rb +90 -67
- data/lib/recurly/subscription/add_ons.rb +73 -0
- data/lib/recurly/transaction.rb +65 -53
- data/lib/recurly/transaction/errors.rb +98 -0
- data/lib/recurly/version.rb +16 -2
- data/lib/recurly/xml.rb +85 -0
- data/lib/recurly/xml/nokogiri.rb +49 -0
- data/lib/recurly/xml/rexml.rb +50 -0
- metadata +76 -165
- data/LICENSE +0 -21
- data/README.md +0 -104
- data/init.rb +0 -1
- data/lib/patches/rails2/active_resource/base.rb +0 -35
- data/lib/patches/rails2/active_resource/connection.rb +0 -10
- data/lib/patches/rails3/active_model/serializers/xml.rb +0 -28
- data/lib/patches/rails3/active_resource/connection.rb +0 -10
- data/lib/recurly/account_base.rb +0 -35
- data/lib/recurly/base.rb +0 -195
- data/lib/recurly/charge.rb +0 -39
- data/lib/recurly/config_parser.rb +0 -31
- data/lib/recurly/credit.rb +0 -28
- data/lib/recurly/exceptions.rb +0 -32
- data/lib/recurly/formats/xml_with_errors.rb +0 -132
- data/lib/recurly/formats/xml_with_pagination.rb +0 -47
- data/lib/recurly/rails2/compatibility.rb +0 -8
- data/lib/recurly/rails3/railtie.rb +0 -21
- data/lib/recurly/rails3/recurly.rake +0 -28
- data/lib/recurly/transparent.rb +0 -148
- data/lib/recurly/verification.rb +0 -83
- data/spec/config/recurly.yml +0 -6
- data/spec/config/test1.yml +0 -4
- data/spec/config/test2.yml +0 -7
- data/spec/integration/account_spec.rb +0 -286
- data/spec/integration/add_on_spec.rb +0 -84
- data/spec/integration/billing_info_spec.rb +0 -148
- data/spec/integration/charge_spec.rb +0 -176
- data/spec/integration/coupon_spec.rb +0 -49
- data/spec/integration/credit_spec.rb +0 -106
- data/spec/integration/invoice_spec.rb +0 -86
- data/spec/integration/plan_spec.rb +0 -87
- data/spec/integration/subscription_spec.rb +0 -221
- data/spec/integration/transaction_spec.rb +0 -154
- data/spec/integration/transparent_spec.rb +0 -99
- data/spec/spec_helper.rb +0 -34
- data/spec/support/factory.rb +0 -211
- data/spec/support/vcr.rb +0 -11
- data/spec/unit/account_spec.rb +0 -19
- data/spec/unit/billing_info_spec.rb +0 -39
- data/spec/unit/charge_spec.rb +0 -20
- data/spec/unit/config_spec.rb +0 -42
- data/spec/unit/coupon_spec.rb +0 -13
- data/spec/unit/credit_spec.rb +0 -20
- data/spec/unit/plan_spec.rb +0 -18
- data/spec/unit/subscription_spec.rb +0 -25
- data/spec/unit/transaction_spec.rb +0 -32
- data/spec/unit/transparent_spec.rb +0 -152
- data/spec/unit/verification_spec.rb +0 -82
data/lib/recurly/verification.rb
DELETED
@@ -1,83 +0,0 @@
|
|
1
|
-
module Recurly
|
2
|
-
module Verification
|
3
|
-
|
4
|
-
def stringify_keys data
|
5
|
-
Hash[data.map { |k, v| [k.to_s, v] }]
|
6
|
-
end
|
7
|
-
|
8
|
-
class PreEscapedString < String
|
9
|
-
end
|
10
|
-
|
11
|
-
def digest_data(data)
|
12
|
-
if data.is_a? Array
|
13
|
-
return nil if data.empty?
|
14
|
-
PreEscapedString.new( '[%s]' % data.map{|v|digest_data(v)}.compact.join(',') )
|
15
|
-
elsif data.is_a? Hash
|
16
|
-
data = stringify_keys data
|
17
|
-
digest_data data.sort.map {|k,v|
|
18
|
-
prefix = (k =~ /\A\d+\Z/) ? '' : (k.to_s+':')
|
19
|
-
(v=digest_data(v)).nil? ? nil : PreEscapedString.new('%s%s' % [prefix,v])
|
20
|
-
}
|
21
|
-
elsif data.is_a?(String) && !data.instance_of?(PreEscapedString)
|
22
|
-
PreEscapedString.new( data.gsub(/([\[\]\,\:\\])/) { |c| '\\' + c } )
|
23
|
-
else
|
24
|
-
data
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def generate_signature(claim, args, timestamp=nil)
|
29
|
-
raise Recurly::ConfigurationError.new("Recurly gem not configured. 'private_key' missing.") if Recurly.private_key.blank?
|
30
|
-
|
31
|
-
timestamp ||= Time.now.to_i
|
32
|
-
timestamp = timestamp.to_s
|
33
|
-
input_data = [timestamp,claim,args]
|
34
|
-
input_string = digest_data(input_data)
|
35
|
-
|
36
|
-
digest_key = ::Digest::SHA1.digest(Recurly.private_key)
|
37
|
-
sha1_hash = ::OpenSSL::Digest::Digest.new("sha1")
|
38
|
-
signature = ::OpenSSL::HMAC.hexdigest(sha1_hash, digest_key, input_string.to_s)
|
39
|
-
signature + '-' + timestamp
|
40
|
-
end
|
41
|
-
|
42
|
-
# Raises a Recurly::ForgedQueryString exception if the signature cannot be validated
|
43
|
-
def verify_params!(claim, args)
|
44
|
-
args = stringify_keys args
|
45
|
-
signature = args.delete('signature') or raise Recurly::ForgedQueryString.new('Signature is missing')
|
46
|
-
hmac, timestamp = signature.split('-')
|
47
|
-
age = Time.now.to_i - timestamp.to_i
|
48
|
-
raise Recurly::ForgedQueryString.new('Timestamp is too old or new') if age > 3600 || age < -3600
|
49
|
-
|
50
|
-
if signature != generate_signature(claim, args, timestamp)
|
51
|
-
raise Recurly::ForgedQueryString.new('Signature cannot be verified')
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def sign_billing_info_update(account_code)
|
56
|
-
generate_signature('billinginfoupdate', {
|
57
|
-
:account_code => account_code
|
58
|
-
})
|
59
|
-
end
|
60
|
-
|
61
|
-
def sign_transaction(amount_in_cents, currency, account_code=nil)
|
62
|
-
generate_signature('transactioncreate', {
|
63
|
-
:account_code => account_code,
|
64
|
-
:currency => currency,
|
65
|
-
:amount_in_cents => amount_in_cents
|
66
|
-
})
|
67
|
-
end
|
68
|
-
|
69
|
-
def verify_subscription!(params)
|
70
|
-
verify_params! 'subscriptioncreated', params
|
71
|
-
end
|
72
|
-
|
73
|
-
def verify_transaction!(params)
|
74
|
-
verify_params! 'transactioncreated', params
|
75
|
-
end
|
76
|
-
|
77
|
-
def verify_billing_info_update!(params)
|
78
|
-
verify_params! 'billinginfoupdated', params
|
79
|
-
end
|
80
|
-
|
81
|
-
extend self
|
82
|
-
end
|
83
|
-
end
|
data/spec/config/recurly.yml
DELETED
data/spec/config/test1.yml
DELETED
data/spec/config/test2.yml
DELETED
@@ -1,286 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
module Recurly
|
4
|
-
describe Account do
|
5
|
-
timestamp = File.mtime(__FILE__).to_i
|
6
|
-
|
7
|
-
describe "#new" do
|
8
|
-
let(:attributes) { Factory.account_attributes("account-new-#{timestamp}") }
|
9
|
-
before(:each) do
|
10
|
-
@account = Account.new(attributes)
|
11
|
-
end
|
12
|
-
|
13
|
-
it "should be valid" do
|
14
|
-
@account.should be_valid
|
15
|
-
end
|
16
|
-
|
17
|
-
it "should not be persisted" do
|
18
|
-
@account.should_not be_persisted
|
19
|
-
end
|
20
|
-
|
21
|
-
it "should set the attributes correctly" do
|
22
|
-
attributes.each do |key, val|
|
23
|
-
@account.attributes[key].should == val
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
describe "#create" do
|
29
|
-
context "with full data" do
|
30
|
-
use_vcr_cassette "account/create/#{timestamp}"
|
31
|
-
|
32
|
-
let(:attributes) { Factory.account_attributes("account-create-#{timestamp}") }
|
33
|
-
before(:each) do
|
34
|
-
@account = Account.create(attributes)
|
35
|
-
end
|
36
|
-
|
37
|
-
it "should be persisted" do
|
38
|
-
@account.should be_persisted
|
39
|
-
end
|
40
|
-
|
41
|
-
it "should be valid" do
|
42
|
-
@account.should be_valid
|
43
|
-
end
|
44
|
-
|
45
|
-
it "should set a created_at date from the server" do
|
46
|
-
@account.created_at.should_not be_nil
|
47
|
-
end
|
48
|
-
|
49
|
-
it "should set a hosted_login_token from the server" do
|
50
|
-
@account.hosted_login_token.should_not be_nil
|
51
|
-
end
|
52
|
-
|
53
|
-
it "should set the balance to 0" do
|
54
|
-
@account.balance_in_cents.should == 0
|
55
|
-
end
|
56
|
-
|
57
|
-
it "should set the account status to active" do
|
58
|
-
@account.state.should == "active"
|
59
|
-
@account.closed?.should be_false
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
context "with just account-code" do
|
64
|
-
use_vcr_cassette "account/create-min/#{timestamp}"
|
65
|
-
|
66
|
-
before(:each) do
|
67
|
-
@account = Account.create(:account_code => "d00d-#{timestamp}")
|
68
|
-
end
|
69
|
-
|
70
|
-
it "should be valid" do
|
71
|
-
@account.should be_valid
|
72
|
-
end
|
73
|
-
|
74
|
-
it "should set a created_at date from the server" do
|
75
|
-
@account.created_at.should_not be_nil
|
76
|
-
end
|
77
|
-
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
describe "#find" do
|
82
|
-
use_vcr_cassette "account/find/#{timestamp}"
|
83
|
-
let(:orig){ Factory.create_account("account-get-#{timestamp}") }
|
84
|
-
|
85
|
-
before(:each) do
|
86
|
-
@account = Account.find(orig.account_code)
|
87
|
-
end
|
88
|
-
|
89
|
-
it "should return the account object" do
|
90
|
-
@account.should_not be_nil
|
91
|
-
end
|
92
|
-
|
93
|
-
it "should be persisted" do
|
94
|
-
@account.should be_persisted
|
95
|
-
end
|
96
|
-
|
97
|
-
describe "returned account" do
|
98
|
-
it "should have a created_at date" do
|
99
|
-
@account.created_at.should_not be_nil
|
100
|
-
end
|
101
|
-
|
102
|
-
it "should match the original account code" do
|
103
|
-
@account.account_code.should == orig.account_code
|
104
|
-
end
|
105
|
-
|
106
|
-
it "should match the original account email" do
|
107
|
-
@account.email.should == orig.email
|
108
|
-
end
|
109
|
-
|
110
|
-
it "should match the original first name" do
|
111
|
-
@account.first_name.should == orig.first_name
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
context "looking for a non-existant account" do
|
116
|
-
it "should raise an ActiveResource::ResourceNotFound exception" do
|
117
|
-
expect {
|
118
|
-
Account.find('account-that-doesnt-exist')
|
119
|
-
}.to raise_error ActiveResource::ResourceNotFound
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
|
125
|
-
# spec list queries for finding acocunts
|
126
|
-
describe "#list" do
|
127
|
-
use_vcr_cassette "account/list/#{timestamp}"
|
128
|
-
|
129
|
-
before(:each) do
|
130
|
-
|
131
|
-
# create new ones
|
132
|
-
@accounts = []
|
133
|
-
8.times do |i|
|
134
|
-
@accounts << Factory.create_account("account-list-num-#{i}-#{timestamp}")
|
135
|
-
end
|
136
|
-
|
137
|
-
# mark the 4 of them as paid
|
138
|
-
@subscriptions = []
|
139
|
-
@accounts[0, 4].each do |account|
|
140
|
-
@subscriptions << Factory.create_subscription(account, :paid)
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
it "should return a list of accounts with matching criteria" do
|
145
|
-
Account.list(:all).total_entries.should >= 8
|
146
|
-
Account.list(:active).total_entries.should >= 4
|
147
|
-
Account.list(:free).total_entries.should >= 4
|
148
|
-
end
|
149
|
-
|
150
|
-
end
|
151
|
-
|
152
|
-
|
153
|
-
describe "#update" do
|
154
|
-
use_vcr_cassette "account/update/#{timestamp}"
|
155
|
-
|
156
|
-
let(:orig){ Factory.create_account("account-update-#{timestamp}") }
|
157
|
-
|
158
|
-
before(:each) do
|
159
|
-
# update account data
|
160
|
-
@account = Account.find(orig.account_code)
|
161
|
-
@account.last_name = "Update Test"
|
162
|
-
@account.company_name = "Recurly Ruby Gem -- Update"
|
163
|
-
@account.save!
|
164
|
-
|
165
|
-
# refetch account
|
166
|
-
@account = Account.find(orig.account_code)
|
167
|
-
end
|
168
|
-
|
169
|
-
it "should not have updated the email address" do
|
170
|
-
@account.email.should == orig.email
|
171
|
-
end
|
172
|
-
|
173
|
-
it "should have updated the last_name" do
|
174
|
-
@account.last_name.should == "Update Test"
|
175
|
-
end
|
176
|
-
|
177
|
-
it "should have updated the company_name" do
|
178
|
-
@account.company_name.should == "Recurly Ruby Gem -- Update"
|
179
|
-
end
|
180
|
-
end
|
181
|
-
|
182
|
-
# EDITING ACCOUNT_CODE NOT YET SUPPORTED ON RECURLY API
|
183
|
-
# describe "#update with account_code" do
|
184
|
-
# use_vcr_cassette "account/update2/#{timestamp}"
|
185
|
-
#
|
186
|
-
# let(:orig){ Factory.create_account("account-update2-#{timestamp}") }
|
187
|
-
#
|
188
|
-
# let(:new_account_code){ "#{orig.account_code}-edited" }
|
189
|
-
#
|
190
|
-
# before(:each) do
|
191
|
-
# # update account data
|
192
|
-
# @account = Account.find(orig.account_code)
|
193
|
-
# @account.account_code = new_account_code
|
194
|
-
# @account.save!
|
195
|
-
#
|
196
|
-
# @account = Account.find(new_account_code)
|
197
|
-
# end
|
198
|
-
#
|
199
|
-
# it "should update the account code" do
|
200
|
-
# @account.account_code.should == new_account_code
|
201
|
-
# end
|
202
|
-
# end
|
203
|
-
|
204
|
-
describe "#close_account" do
|
205
|
-
use_vcr_cassette "account/close/#{timestamp}"
|
206
|
-
let(:account){ Factory.create_account("account-close-#{timestamp}") }
|
207
|
-
|
208
|
-
before(:each) do
|
209
|
-
account.close_account
|
210
|
-
|
211
|
-
# load a fresh account
|
212
|
-
@account = Account.find(account.account_code)
|
213
|
-
end
|
214
|
-
|
215
|
-
it "should mark the account as closed" do
|
216
|
-
@account.state.should == "closed"
|
217
|
-
@account.closed?.should be_true
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
describe "validations" do
|
222
|
-
context "with blank data" do
|
223
|
-
use_vcr_cassette "account/create-blank/#{timestamp}"
|
224
|
-
|
225
|
-
before(:each) do
|
226
|
-
@account = Account.create({:account_code => ""})
|
227
|
-
end
|
228
|
-
|
229
|
-
it "should not be valid" do
|
230
|
-
@account.should_not be_valid
|
231
|
-
end
|
232
|
-
|
233
|
-
it "should require setting an account code" do
|
234
|
-
@account.errors[:account_code].should include("can't be blank")
|
235
|
-
@account.errors[:account_code].should include("is invalid")
|
236
|
-
end
|
237
|
-
end
|
238
|
-
|
239
|
-
context "with duplicate data" do
|
240
|
-
use_vcr_cassette "account/create-duplicate/#{timestamp}"
|
241
|
-
|
242
|
-
before(:each) do
|
243
|
-
Account.create({:account_code => "account-exists"})
|
244
|
-
@account = Account.create({:account_code => "account-exists"})
|
245
|
-
end
|
246
|
-
|
247
|
-
it "should not be valid" do
|
248
|
-
@account.should_not be_valid
|
249
|
-
end
|
250
|
-
|
251
|
-
it "should require setting an account code" do
|
252
|
-
@account.errors[:account_code].should include("has already been taken")
|
253
|
-
end
|
254
|
-
end
|
255
|
-
end
|
256
|
-
|
257
|
-
describe "defaulting account's accept_language to Recurly.current_accept_language" do
|
258
|
-
|
259
|
-
context "user is from france" do
|
260
|
-
# wrap these example runs, setting current_accept_language properly
|
261
|
-
around(:each) do |example|
|
262
|
-
old_setting = Recurly.current_accept_language
|
263
|
-
Recurly.current_accept_language = "fr"
|
264
|
-
example.run
|
265
|
-
Recurly.current_accept_language = old_setting
|
266
|
-
end
|
267
|
-
|
268
|
-
context "user creates an account" do
|
269
|
-
use_vcr_cassette "account/accept-language-account/#{timestamp}"
|
270
|
-
let(:attributes) { Factory.account_attributes("account1-french-#{timestamp}") }
|
271
|
-
|
272
|
-
before(:each) do
|
273
|
-
@account = Account.create(attributes)
|
274
|
-
end
|
275
|
-
|
276
|
-
it "should automaticaly set the accounts accept_language" do
|
277
|
-
@account.attributes[:accept_language].should == "fr"
|
278
|
-
end
|
279
|
-
end
|
280
|
-
|
281
|
-
end
|
282
|
-
|
283
|
-
end
|
284
|
-
|
285
|
-
end
|
286
|
-
end
|
@@ -1,84 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
module Recurly
|
4
|
-
describe "Subscription Addons" do
|
5
|
-
# version accounts based on this current files modification dates
|
6
|
-
timestamp = File.mtime(__FILE__).to_i
|
7
|
-
|
8
|
-
describe "create a subscription with addons" do
|
9
|
-
use_vcr_cassette "subscription/addons/create/#{timestamp}"
|
10
|
-
|
11
|
-
let(:account){ Factory.create_account("subscription-addons-create-#{timestamp}") }
|
12
|
-
|
13
|
-
before(:each) do
|
14
|
-
Factory.create_subscription(account, :paid, :add_ons => [
|
15
|
-
{
|
16
|
-
:add_on_code => "special",
|
17
|
-
:quantity => 1,
|
18
|
-
:unit_amount => 10,
|
19
|
-
}
|
20
|
-
])
|
21
|
-
end
|
22
|
-
|
23
|
-
it "should return the subscription addons" do
|
24
|
-
subscription = Subscription.find(account.account_code)
|
25
|
-
subscription.add_ons.count == 1
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
describe "add an addon to a subscription" do
|
30
|
-
use_vcr_cassette "subscription/addons/add/#{timestamp}"
|
31
|
-
|
32
|
-
let(:account){ Factory.create_account("subscription-addons-add-#{timestamp}") }
|
33
|
-
|
34
|
-
before(:each) do
|
35
|
-
Factory.create_subscription(account, :paid, :add_ons => [
|
36
|
-
{
|
37
|
-
:add_on_code => "special",
|
38
|
-
:quantity => 1,
|
39
|
-
:unit_amount => 10,
|
40
|
-
}
|
41
|
-
])
|
42
|
-
end
|
43
|
-
|
44
|
-
it "should allow adding a new addon" do
|
45
|
-
subscription = Subscription.find(account.account_code)
|
46
|
-
subscription.add_ons << {
|
47
|
-
:add_on_code => "special2",
|
48
|
-
:quantity => 2,
|
49
|
-
:unit_amount => 500
|
50
|
-
}
|
51
|
-
subscription.change('now', :add_ons => subscription.add_ons)
|
52
|
-
|
53
|
-
subscription = Subscription.find(account.account_code)
|
54
|
-
subscription.add_ons.count == 2
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
describe "remove an addon from a subscription" do
|
59
|
-
use_vcr_cassette "subscription/addons/remove/#{timestamp}"
|
60
|
-
|
61
|
-
let(:account){ Factory.create_account("subscription-addons-remove-#{timestamp}") }
|
62
|
-
|
63
|
-
before(:each) do
|
64
|
-
Factory.create_subscription(account, :paid, :add_ons => [
|
65
|
-
{
|
66
|
-
:add_on_code => "special",
|
67
|
-
:quantity => 1,
|
68
|
-
:unit_amount => 1000,
|
69
|
-
}
|
70
|
-
])
|
71
|
-
end
|
72
|
-
|
73
|
-
it "should allow removing an addon" do
|
74
|
-
subscription = Subscription.find(account.account_code)
|
75
|
-
subscription.change('now', :add_ons => [])
|
76
|
-
|
77
|
-
subscription = Subscription.find(account.account_code)
|
78
|
-
subscription.add_ons.count == 0
|
79
|
-
end
|
80
|
-
|
81
|
-
end
|
82
|
-
|
83
|
-
end
|
84
|
-
end
|