servicemerchant 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE.txt +20 -0
- data/README.txt +231 -0
- data/Rakefile +122 -0
- data/demo.rb +69 -0
- data/recurring_billing/lib/am_extensions.rb +1 -0
- data/recurring_billing/lib/am_extensions/paypal_extension.rb +170 -0
- data/recurring_billing/lib/dependencies.rb +14 -0
- data/recurring_billing/lib/gateways.rb +5 -0
- data/recurring_billing/lib/gateways/authorize_net.rb +103 -0
- data/recurring_billing/lib/gateways/paypal.rb +124 -0
- data/recurring_billing/lib/recurring_billing.rb +130 -0
- data/recurring_billing/lib/recurring_billing.rdoc +87 -0
- data/recurring_billing/lib/utils.rb +81 -0
- data/recurring_billing/test/fixtures.yml +33 -0
- data/recurring_billing/test/remote/authorize_net_test.rb +36 -0
- data/recurring_billing/test/remote/paypal_test.rb +46 -0
- data/recurring_billing/test/remote/recurring_billing_test.rb +41 -0
- data/recurring_billing/test/test_helper.rb +153 -0
- data/recurring_billing/test/unit/authorize_net_gateway_class_test.rb +42 -0
- data/recurring_billing/test/unit/paypal_gateway_class_test.rb +23 -0
- data/recurring_billing/test/unit/recurring_billing_gateway_class_test.rb +35 -0
- data/recurring_billing/test/unit/utils_test.rb +17 -0
- data/subscription_management/Rakefile +29 -0
- data/subscription_management/lib/models/subscription.rb +9 -0
- data/subscription_management/lib/models/subscription_profile.rb +4 -0
- data/subscription_management/lib/subscription_management.rb +326 -0
- data/subscription_management/samples/backpack.yml +101 -0
- data/subscription_management/samples/basecamp.yml +71 -0
- data/subscription_management/samples/brainkeeper.yml +90 -0
- data/subscription_management/samples/campfire.yml +74 -0
- data/subscription_management/samples/clickandpledge.yml +24 -0
- data/subscription_management/samples/demo.rb +19 -0
- data/subscription_management/samples/elm.yml +174 -0
- data/subscription_management/samples/freshbooks.yml +78 -0
- data/subscription_management/samples/highrise.yml +100 -0
- data/subscription_management/samples/presets.yml +10 -0
- data/subscription_management/samples/tariff.outline.yml +0 -0
- data/subscription_management/samples/taxes.yml +21 -0
- data/subscription_management/subscription_management.rb +7 -0
- data/subscription_management/tasks/schema.rb +50 -0
- data/subscription_management/test/connection.rb +10 -0
- data/subscription_management/test/remote/subscription_management_test.rb +112 -0
- data/subscription_management/test/test_helper.rb +84 -0
- data/subscription_management/test/unit/subscription_management_test.rb +40 -0
- data/tracker/README +12 -0
- data/tracker/Rakefile +40 -0
- data/tracker/db/migrations/empty-directory +0 -0
- data/tracker/demo.rb +12 -0
- data/tracker/lib/models/recurring_payment_profile.rb +134 -0
- data/tracker/lib/models/transaction.rb +19 -0
- data/tracker/lib/recurring_billing_extension.rb +103 -0
- data/tracker/lib/recurring_billing_extension.rdoc +34 -0
- data/tracker/tasks/schema.rb +66 -0
- data/tracker/test/connection.rb +10 -0
- data/tracker/test/recurring_payment_profile.rb +35 -0
- data/tracker/test/remote/authorize_net_test.rb +68 -0
- data/tracker/test/remote/paypal_test.rb +115 -0
- data/tracker/test/test_helper.rb +87 -0
- data/tracker/test/unit/recurring_payment_profile_test.rb +62 -0
- data/tracker/tracker.rb +10 -0
- data/vendor/money-1.7.1/MIT-LICENSE +20 -0
- data/vendor/money-1.7.1/README +75 -0
- data/vendor/money-1.7.1/lib/bank/no_exchange_bank.rb +9 -0
- data/vendor/money-1.7.1/lib/bank/variable_exchange_bank.rb +30 -0
- data/vendor/money-1.7.1/lib/money.rb +29 -0
- data/vendor/money-1.7.1/lib/money/core_extensions.rb +26 -0
- data/vendor/money-1.7.1/lib/money/money.rb +209 -0
- data/vendor/money-1.7.1/lib/support/cattr_accessor.rb +57 -0
- metadata +153 -0
@@ -0,0 +1,10 @@
|
|
1
|
+
# As we're not using Rails in tests, we have to establish DB conneciton ourselves
|
2
|
+
require 'rubygems'
|
3
|
+
gem 'activerecord'
|
4
|
+
require 'activerecord'
|
5
|
+
gem 'sqlite3-ruby'
|
6
|
+
|
7
|
+
ActiveRecord::Base.establish_connection(
|
8
|
+
:adapter => "sqlite3",
|
9
|
+
:database => (File.dirname(__FILE__) + "/../db/test.db")
|
10
|
+
)
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class RandomRecurringProfileTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
cred = fixtures(:paypal)
|
7
|
+
assert @gw = RecurringBilling::PaypalGateway.new(cred)
|
8
|
+
@card = credit_card(number='4532130086825928')
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_crud_recurring_payment
|
12
|
+
payment_options = {
|
13
|
+
:subscription_name => 'Test Subscription 1337',
|
14
|
+
:order => {:invoice_number => '407933'}
|
15
|
+
}
|
16
|
+
recurring_options = {
|
17
|
+
:start_date => Date.today + 1,
|
18
|
+
:end_date => Date.today + 290,
|
19
|
+
:interval => '1m'
|
20
|
+
}
|
21
|
+
|
22
|
+
billing_id = @gw.create_with_persist(Money.us_dollar(15), @card, payment_options=payment_options, recurring_options=recurring_options)
|
23
|
+
print "\n\nCreate:\n"
|
24
|
+
print @gw.last_response.inspect
|
25
|
+
payment_options[:order] = {:invoice_number => '407934'}
|
26
|
+
@gw.update_with_persist(billing_id, Money.us_dollar(16), @card, payment_options=payment_options)
|
27
|
+
print "\n\nUpdate:\n"
|
28
|
+
print @gw.last_response.inspect
|
29
|
+
@gw.delete_with_persist(billing_id)
|
30
|
+
print "\n\nDelete:\n"
|
31
|
+
print @gw.last_response.inspect
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
require "tracker"
|
3
|
+
|
4
|
+
class AuthorizeNetGatewayRemoteTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
credentials = fixtures(:authorize_net)
|
8
|
+
assert @gw = RecurringBilling::AuthorizeNetGateway.new(credentials)
|
9
|
+
@card = credit_card()
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_crud_recurring_payment
|
13
|
+
payment_options = {
|
14
|
+
:subscription_name => 'Random subscription',
|
15
|
+
:order => {:invoice_number => 'ODMX31337'}
|
16
|
+
}
|
17
|
+
recurring_options = {
|
18
|
+
:start_date => Date.today,
|
19
|
+
:occurrences => 10,
|
20
|
+
:interval => '10d'
|
21
|
+
}
|
22
|
+
|
23
|
+
sum = rand(50000)+120
|
24
|
+
billing_id = @gw.create(Money.us_dollar(sum), @card, payment_options, recurring_options)
|
25
|
+
assert @gw.last_response.success?
|
26
|
+
|
27
|
+
payment_options[:order] = {:invoice_number => 'ODMX17532'}
|
28
|
+
|
29
|
+
another_sum = rand(50000)+120
|
30
|
+
@gw.update(billing_id, Money.us_dollar(another_sum), @card, payment_options, {})
|
31
|
+
assert @gw.last_response.success?
|
32
|
+
|
33
|
+
assert_raise NotImplementedError do; @gw.inquiry(billing_id); end
|
34
|
+
|
35
|
+
@gw.delete(billing_id)
|
36
|
+
assert @gw.last_response.success?
|
37
|
+
profile = RecurringPaymentProfile.find_by_gateway_reference(billing_id)
|
38
|
+
assert_equal 'deleted', profile.status
|
39
|
+
|
40
|
+
assert_raise StandardError do;@gw.update(billing_id, Money.us_dollar(another_sum), @card, payment_options, {});end;
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_update_or_recreate
|
44
|
+
payment_options = {
|
45
|
+
:subscription_name => 'Random subscription',
|
46
|
+
:order => {:invoice_number => 'ODMX31337'}
|
47
|
+
}
|
48
|
+
recurring_options = {
|
49
|
+
:start_date => Date.today,
|
50
|
+
:occurrences => 10,
|
51
|
+
:interval => '10d'
|
52
|
+
}
|
53
|
+
|
54
|
+
sum = rand(50000)+120
|
55
|
+
billing_id = @gw.create(Money.us_dollar(sum), @card, payment_options, recurring_options)
|
56
|
+
assert @gw.last_response.success?
|
57
|
+
|
58
|
+
payment_options[:order] = {:invoice_number => 'ODMX17532'}
|
59
|
+
|
60
|
+
another_sum = rand(50000)+120
|
61
|
+
@gw.update_or_recreate(billing_id, {:amount => Money.us_dollar(another_sum)})
|
62
|
+
assert @gw.last_response.success?
|
63
|
+
|
64
|
+
@gw.update_or_recreate(billing_id, {:card => @card, :start_date => Date.new(2009, 7, 8)})
|
65
|
+
assert @gw.last_response.success?
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
require "mocha"
|
4
|
+
require "tracker"
|
5
|
+
|
6
|
+
class PaypalRemoteTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def setup
|
9
|
+
cred = fixtures(:paypal)
|
10
|
+
assert @gw = RecurringBilling::PaypalGateway.new(cred)
|
11
|
+
@card = credit_card()
|
12
|
+
@card2 = credit_card('4929838635250031', {:first_name => 'James', :last_name => 'Lueser'})
|
13
|
+
@card_bogus = credit_card("ISMELLLIKEBOGUS")
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_inquiry_updates_tracker
|
17
|
+
payment_options = {
|
18
|
+
:subscription_name => 'Test Subscription 1337',
|
19
|
+
:order => {:invoice_number => '407933'}
|
20
|
+
}
|
21
|
+
recurring_options = {
|
22
|
+
:start_date => Date.today + 1,
|
23
|
+
:end_date => Date.today + 290,
|
24
|
+
:interval => '1m'
|
25
|
+
}
|
26
|
+
|
27
|
+
billing_id = @gw.create(Money.us_dollar(15), @card, payment_options=payment_options, recurring_options=recurring_options)
|
28
|
+
assert @gw.last_response.success?
|
29
|
+
|
30
|
+
profile = RecurringPaymentProfile.find_by_gateway_reference(billing_id)
|
31
|
+
assert_equal 'active', profile.status
|
32
|
+
assert_not_equal -1, profile.outstanding_balance
|
33
|
+
assert_not_equal -1, profile.complete_payments_count
|
34
|
+
assert_not_equal -1, profile.failed_payments_count
|
35
|
+
assert_not_equal -1, profile.remaining_payments_count
|
36
|
+
|
37
|
+
# Mock result of original inquire methods
|
38
|
+
result_mock = {'profile_status' => 'verified',
|
39
|
+
'outstanding_balance' => ::Money.new(-100),
|
40
|
+
'number_cycles_completed'=> -1,
|
41
|
+
'failed_payment_count' => -1,
|
42
|
+
'number_cycles_remaining'=> -1}
|
43
|
+
|
44
|
+
@gw.methods.include? :inquiry_without_persist
|
45
|
+
@gw.expects(:inquiry_without_persist).returns(result_mock)
|
46
|
+
result = @gw.inquiry(billing_id)
|
47
|
+
assert @gw.last_response.success?
|
48
|
+
|
49
|
+
assert_equal result_mock, result
|
50
|
+
|
51
|
+
# Get profile and check whether data is updated
|
52
|
+
profile = RecurringPaymentProfile.find_by_gateway_reference(billing_id)
|
53
|
+
assert_equal 'verified', profile.status
|
54
|
+
assert_equal -100, profile.outstanding_balance
|
55
|
+
assert_equal -1, profile.complete_payments_count
|
56
|
+
assert_equal -1, profile.failed_payments_count
|
57
|
+
assert_equal -1, profile.remaining_payments_count
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_create_update_failure
|
61
|
+
payment_options = {
|
62
|
+
:subscription_name => 'Unsuccessful payment',
|
63
|
+
:order => {:invoice_number => '032895'}
|
64
|
+
}
|
65
|
+
recurring_options = {
|
66
|
+
:start_date => Date.today + 1,
|
67
|
+
:occurrences => 402,
|
68
|
+
:interval => '1w'
|
69
|
+
}
|
70
|
+
|
71
|
+
new_recurring_options = {
|
72
|
+
:pay_on_day_x => 3
|
73
|
+
}
|
74
|
+
|
75
|
+
token_sum = rand(50000)+120
|
76
|
+
@gw.create(Money.us_dollar(token_sum), @card_bogus, payment_options=payment_options, recurring_options=recurring_options)
|
77
|
+
assert !@gw.last_response.success?
|
78
|
+
assert RecurringPaymentProfile.find_by_net_amount(Money.us_dollar(token_sum).cents).nil?
|
79
|
+
|
80
|
+
new_token_sum = rand(50000)+120
|
81
|
+
billing_id = @gw.create(Money.us_dollar(new_token_sum), @card, payment_options=payment_options, recurring_options=recurring_options)
|
82
|
+
assert @gw.last_response.success?
|
83
|
+
assert_equal profile = RecurringPaymentProfile.find_by_net_amount(Money.us_dollar(new_token_sum).cents), RecurringPaymentProfile.find_by_gateway_reference(billing_id)
|
84
|
+
|
85
|
+
another_token_sum = rand(50000)+120
|
86
|
+
@gw.update(billing_id, Money.us_dollar(another_token_sum), @card, {}, new_recurring_options)
|
87
|
+
assert_not_equal profile.updated_at, (profile2 = RecurringPaymentProfile.find_by_gateway_reference(billing_id)).updated_at #meaning the profile hadn't been updated
|
88
|
+
assert_equal profile2.amount, Money.us_dollar(another_token_sum).cents
|
89
|
+
|
90
|
+
|
91
|
+
@gw.update(billing_id, Money.us_dollar(new_token_sum), @card_bogus, {}, {})
|
92
|
+
assert_equal profile2.updated_at, RecurringPaymentProfile.find_by_gateway_reference(billing_id).updated_at #meaning the profile hadn't been updated
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_update_or_create
|
96
|
+
payment_options = {
|
97
|
+
:subscription_name => 'Random subscription',
|
98
|
+
:order => {:invoice_number => 'ODMX31337'}
|
99
|
+
}
|
100
|
+
recurring_options = {
|
101
|
+
:start_date => Date.today,
|
102
|
+
:occurrences => 10,
|
103
|
+
:interval => '10 w'
|
104
|
+
}
|
105
|
+
|
106
|
+
sum = rand(50000)+120
|
107
|
+
billing_id = @gw.create(Money.us_dollar(sum), @card, payment_options, recurring_options)
|
108
|
+
assert @gw.last_response.success?
|
109
|
+
|
110
|
+
another_sum = rand(50000)+120
|
111
|
+
@gw.update_or_recreate(billing_id, {:card => @card2, :occurrences => 20})
|
112
|
+
assert @gw.last_response.success?
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'test/unit'
|
5
|
+
require 'activesupport'
|
6
|
+
|
7
|
+
silence_warnings do
|
8
|
+
require 'active_merchant'
|
9
|
+
end
|
10
|
+
|
11
|
+
require File.dirname(__FILE__) + "/connection"
|
12
|
+
|
13
|
+
$: << File.dirname(__FILE__) + "/../" # Tracker root
|
14
|
+
|
15
|
+
require 'tracker'
|
16
|
+
|
17
|
+
# Turn off invalid certificate crashes
|
18
|
+
require 'openssl'
|
19
|
+
silence_warnings do
|
20
|
+
OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE
|
21
|
+
end
|
22
|
+
|
23
|
+
ActiveMerchant::Billing::Base.mode = :test
|
24
|
+
|
25
|
+
module Test
|
26
|
+
module Unit
|
27
|
+
class TestCase
|
28
|
+
|
29
|
+
|
30
|
+
DEFAULT_CREDENTIALS = File.dirname(__FILE__) + '/fixtures.yml'
|
31
|
+
|
32
|
+
private
|
33
|
+
def credit_card(number = '4242424242424242', options = {})
|
34
|
+
defaults = {
|
35
|
+
:number => number,
|
36
|
+
:month => 9,
|
37
|
+
:year => Time.now.year + 1,
|
38
|
+
:first_name => 'John',
|
39
|
+
:last_name => 'Doe',
|
40
|
+
:verification_value => '123',
|
41
|
+
:type => 'visa'
|
42
|
+
}.update(options)
|
43
|
+
|
44
|
+
ActiveMerchant::Billing::CreditCard.new(defaults)
|
45
|
+
end
|
46
|
+
|
47
|
+
def address(options = {})
|
48
|
+
{
|
49
|
+
:name => 'John Doe',
|
50
|
+
:address1 => '1234 My Street',
|
51
|
+
:address2 => 'Apt 1',
|
52
|
+
:company => 'Widgets Inc',
|
53
|
+
:city => 'Ottawa',
|
54
|
+
:state => 'ON',
|
55
|
+
:zip => 'K1C2N6',
|
56
|
+
:country => 'CA',
|
57
|
+
:phone => '(555)555-5555'
|
58
|
+
}.update(options)
|
59
|
+
end
|
60
|
+
|
61
|
+
def all_fixtures
|
62
|
+
@@fixtures ||= load_fixtures
|
63
|
+
end
|
64
|
+
|
65
|
+
def fixtures(key)
|
66
|
+
data = all_fixtures[key] || raise(StandardError, "No fixture data was found for '#{key}'")
|
67
|
+
|
68
|
+
data.dup
|
69
|
+
end
|
70
|
+
|
71
|
+
def load_fixtures
|
72
|
+
file = DEFAULT_CREDENTIALS
|
73
|
+
yaml_data = YAML.load(File.read(file))
|
74
|
+
symbolize_keys(yaml_data)
|
75
|
+
|
76
|
+
yaml_data
|
77
|
+
end
|
78
|
+
|
79
|
+
def symbolize_keys(hash)
|
80
|
+
return unless hash.is_a?(Hash)
|
81
|
+
|
82
|
+
hash.symbolize_keys!
|
83
|
+
hash.each{|k,v| symbolize_keys(v)}
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
require 'money'
|
4
|
+
|
5
|
+
class RecurringPaymentProfileTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def setup
|
8
|
+
@profile = ::RecurringPaymentProfile.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_net_money_assigns
|
12
|
+
@profile.net_money=Money.new(333, currency='CAD')
|
13
|
+
assert_equal 333, @profile.net_amount
|
14
|
+
assert_equal 'CAD', @profile.currency
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_taxes_money_assigns
|
18
|
+
@profile.taxes_money=Money.new(444, currency='RUR')
|
19
|
+
assert_equal 444, @profile.taxes_amount
|
20
|
+
assert_equal 'RUR', @profile.currency
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_net_money_fails
|
24
|
+
@profile.taxes_money=Money.new(333, currency='CAD')
|
25
|
+
assert_raise ArgumentError do; @profile.net_money=Money.new(444, currency='RUR'); end
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_taxes_money_fails
|
29
|
+
@profile.net_money=Money.new(333, currency='CAD')
|
30
|
+
assert_raise ArgumentError do; @profile.taxes_money=Money.new(444, currency='RUR'); end
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_mask_card_number
|
34
|
+
assert_equal 'abba', @profile.mask_card_number('abba')
|
35
|
+
assert_equal 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXched', @profile.mask_card_number('whatisthethingthatcannotbetouched')
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_parse_and_set_card
|
39
|
+
card = credit_card(number='1111111111', options={:type => 'master', :year => 2003, :month => 3, :first_name => 'Name', :last_name => 'Surname'})
|
40
|
+
@profile.parse_and_set_card(card)
|
41
|
+
assert_equal 'master', @profile.card_type
|
42
|
+
assert_equal 'Name Surname, MASTER, XXXXXX1111, exp. 2003-03', @profile.card_owner_memo
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_parse_and_set_card_with_hint
|
46
|
+
card = credit_card(number='1111111111', options={:type => 'master'})
|
47
|
+
@profile.parse_and_set_card(card, 'this is my hint')
|
48
|
+
assert_equal 'master', @profile.card_type
|
49
|
+
assert_equal 'this is my hint', @profile.card_owner_memo
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_card_exp_date
|
53
|
+
card = credit_card(number='4242424242424242', options={:year => 2001, :month => 1})
|
54
|
+
assert_equal '2001-01', @profile.card_exp_date(card)
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_no_obsolete_fields
|
58
|
+
assert_raise NoMethodError do; @profile.payment_offset = 0; end
|
59
|
+
assert_raise NoMethodError do; @profile.payments_start_on = Date.today; end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
data/tracker/tracker.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#TODO: Use auto-loading from activesupport
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'activerecord'
|
5
|
+
|
6
|
+
$: << File.dirname(__FILE__) + "/../vendor/money-1.7.1/lib"
|
7
|
+
|
8
|
+
require File.dirname(__FILE__) + '/lib/models/recurring_payment_profile'
|
9
|
+
require File.dirname(__FILE__) + '/lib/models/transaction'
|
10
|
+
require File.dirname(__FILE__) + '/lib/recurring_billing_extension'
|
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2005 Tobias Lutke
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,75 @@
|
|
1
|
+
== Money class
|
2
|
+
|
3
|
+
This money class is based on the example from the ActiveRecord doc:
|
4
|
+
http://api.rubyonrails.org/classes/ActiveRecord/Aggregations/ClassMethods.html
|
5
|
+
|
6
|
+
Its in production use at http://www.snowdevil.ca and I haven't found any major issues
|
7
|
+
so far.
|
8
|
+
The main reason to open source it is because It might be useful to other people and
|
9
|
+
I hope i'll get some feedback on how to improve the class.
|
10
|
+
|
11
|
+
I bundled the exporter with the money class since some tests depend on it and I figured
|
12
|
+
that most applications which need to deal with Money also need to deal with proper
|
13
|
+
exporting.
|
14
|
+
|
15
|
+
== Download
|
16
|
+
|
17
|
+
Preferred method of installation is gem:
|
18
|
+
|
19
|
+
gem install --source http://dist.leetsoft.com money
|
20
|
+
|
21
|
+
Alternatively you can get the library packed
|
22
|
+
|
23
|
+
http://dist.leetsoft.com/pkg/
|
24
|
+
|
25
|
+
== Usage
|
26
|
+
|
27
|
+
Use the compose_of helper to let active record deal with embedding the money
|
28
|
+
object in your models. The following example requires a cents and a currency field.
|
29
|
+
|
30
|
+
class ProductUnit < ActiveRecord::Base
|
31
|
+
belongs_to :product
|
32
|
+
composed_of :price, :class_name => "Money", :mapping => [%w(cents cents) %(currency currency)]
|
33
|
+
|
34
|
+
private
|
35
|
+
validate :cents_not_zero
|
36
|
+
|
37
|
+
def cents_not_zero
|
38
|
+
errors.add("cents", "cannot be zero or less") unless cents > 0
|
39
|
+
end
|
40
|
+
|
41
|
+
validates_presence_of :sku, :currency
|
42
|
+
validates_uniqueness_of :sku
|
43
|
+
end
|
44
|
+
|
45
|
+
== Class configuration
|
46
|
+
|
47
|
+
Two const class variables are available to tailor Money to your needs.
|
48
|
+
If you don't need currency exchange at all, just ignore those.
|
49
|
+
|
50
|
+
=== Default Currency
|
51
|
+
|
52
|
+
By default Money defaults to USD as its currency. This can be overwritten using
|
53
|
+
|
54
|
+
Money.default_currency = "CAD"
|
55
|
+
|
56
|
+
If you use rails, the environment.rb is a very good place to put this.
|
57
|
+
|
58
|
+
=== Currency Exchange
|
59
|
+
|
60
|
+
The second parameter is a bit more complex. It lets you provide your own implementation of the
|
61
|
+
currency exchange service. By default Money throws an exception when trying to call .exchange_to.
|
62
|
+
|
63
|
+
A second minimalist implementation is provided which lets you supply custom exchange rates:
|
64
|
+
|
65
|
+
Money.bank = VariableExchangeBank.new
|
66
|
+
Money.bank.add_rate("USD", "CAD", 1.24515)
|
67
|
+
Money.bank.add_rate("CAD", "USD", 0.803115)
|
68
|
+
Money.us_dollar(100).exchange_to("CAD") => Money.ca_dollar(124)
|
69
|
+
Money.ca_dollar(100).exchange_to("USD") => Money.us_dollar(80)
|
70
|
+
|
71
|
+
There is nothing stopping you from creating bank objects which scrape www.xe.com for the current rates or just return rand(2)
|
72
|
+
|
73
|
+
== Code
|
74
|
+
|
75
|
+
If you have any improvements please email them to tobi [at] leetsoft.com
|