servicemerchant 0.1.0
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.
- 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
|