simplepay 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.
Files changed (41) hide show
  1. data.tar.gz.sig +2 -0
  2. data/History.txt +5 -0
  3. data/Manifest.txt +38 -0
  4. data/README.rdoc +78 -0
  5. data/Rakefile +24 -0
  6. data/lib/simplepay.rb +26 -0
  7. data/lib/simplepay/authentication.rb +69 -0
  8. data/lib/simplepay/constants.rb +26 -0
  9. data/lib/simplepay/errors.rb +16 -0
  10. data/lib/simplepay/helpers/form_helper.rb +29 -0
  11. data/lib/simplepay/helpers/rails_helper.rb +28 -0
  12. data/lib/simplepay/service.rb +122 -0
  13. data/lib/simplepay/services/standard.rb +36 -0
  14. data/lib/simplepay/services/subscription.rb +53 -0
  15. data/lib/simplepay/support.rb +15 -0
  16. data/lib/simplepay/support/amount.rb +55 -0
  17. data/lib/simplepay/support/billing_frequency.rb +14 -0
  18. data/lib/simplepay/support/boolean.rb +28 -0
  19. data/lib/simplepay/support/currency.rb +21 -0
  20. data/lib/simplepay/support/epoch.rb +39 -0
  21. data/lib/simplepay/support/field.rb +147 -0
  22. data/lib/simplepay/support/interval.rb +143 -0
  23. data/lib/simplepay/support/subscription_period.rb +25 -0
  24. data/script/console +10 -0
  25. data/script/destroy +14 -0
  26. data/script/generate +14 -0
  27. data/simplepay.gemspec +43 -0
  28. data/test/simplepay/services/test_subscription.rb +109 -0
  29. data/test/simplepay/support/test_amount.rb +46 -0
  30. data/test/simplepay/support/test_billing_frequency.rb +43 -0
  31. data/test/simplepay/support/test_boolean.rb +17 -0
  32. data/test/simplepay/support/test_epoch.rb +34 -0
  33. data/test/simplepay/support/test_field.rb +99 -0
  34. data/test/simplepay/support/test_interval.rb +92 -0
  35. data/test/simplepay/support/test_subscription_period.rb +49 -0
  36. data/test/simplepay/test_authentication.rb +33 -0
  37. data/test/simplepay/test_service.rb +118 -0
  38. data/test/test_helper.rb +77 -0
  39. data/test/test_simplepay.rb +11 -0
  40. metadata +155 -0
  41. metadata.gz.sig +0 -0
@@ -0,0 +1,92 @@
1
+ require File.dirname(__FILE__) + '/../../test_helper'
2
+ require 'simplepay/support/interval'
3
+
4
+ class TestInterval < Simplepay::Support::Interval
5
+ ALLOWED_QUANTITY_RANGE = (1..200)
6
+ ALLOWED_INTERVALS = ['testinterval', 'testinterval2']
7
+ DEFAULT_QUANTITY = 3
8
+ DEFAULT_INTERVAL = 'testinterval'
9
+ end
10
+
11
+ class Simplepay::Support::TestInterval < Test::Unit::TestCase
12
+
13
+ context 'Simplepay::Support::Interval' do
14
+
15
+ setup do
16
+ @interval = TestInterval.new
17
+ end
18
+
19
+ should 'set the default quantity' do
20
+ assert_equal 3, @interval.quantity
21
+ end
22
+
23
+ should 'set the default interval' do
24
+ assert_equal 'testinterval', @interval.interval
25
+ end
26
+
27
+ should 'properly convert to string' do
28
+ assert_equal '3 testinterval', @interval.to_s
29
+ end
30
+
31
+ should 'return nil with converted to a string with no interval set' do
32
+ @interval.interval = nil
33
+ assert_nil @interval.to_s
34
+ end
35
+
36
+ should 'raise an error for quantities outside of the allowed range' do
37
+ assert_raises(ArgumentError) {
38
+ @interval.quantity = @interval.class.allowed_quantity_range.last + 1
39
+ }
40
+ end
41
+
42
+ should 'accept valid quantities' do
43
+ assert_nothing_raised(Exception) {
44
+ @interval.quantity = @interval.class.allowed_quantity_range.first
45
+ @interval.quantity = @interval.class.allowed_quantity_range.first + 1
46
+ @interval.quantity = @interval.class.allowed_quantity_range.last - 1
47
+ @interval.quantity = @interval.class.allowed_quantity_range.last
48
+ }
49
+ assert_equal @interval.class.allowed_quantity_range.last, @interval.quantity
50
+ end
51
+
52
+ should 'raise an error for intervals which are not allowed' do
53
+ assert_raises(ArgumentError) {
54
+ @interval.interval = 'badtestinterval'
55
+ }
56
+ end
57
+
58
+ should 'accept valid intervals' do
59
+ assert_nothing_raised(Exception) {
60
+ @interval.interval = @interval.class.allowed_intervals.first
61
+ @interval.interval = @interval.class.allowed_intervals.last
62
+ }
63
+ assert_equal @interval.class.allowed_intervals.last, @interval.interval
64
+ end
65
+
66
+ should 'be definable by string' do
67
+ interval = TestInterval.new("5 testinterval2")
68
+ assert_equal 5, interval.quantity
69
+ assert_equal 'testinterval2', interval.interval
70
+ end
71
+
72
+ should 'raise an error for an improperly formatted string' do
73
+ assert_raise(ArgumentError) {
74
+ TestInterval.new("Bad String")
75
+ }
76
+ end
77
+
78
+ should 'be definable by parameter' do
79
+ interval = TestInterval.new(5, 'testinterval2')
80
+ assert_equal 5, interval.quantity
81
+ assert_equal 'testinterval2', interval.interval
82
+ end
83
+
84
+ should 'be definable by a hash' do
85
+ interval = TestInterval.new({:quantity => 5, :interval => 'testinterval2'})
86
+ assert_equal 5, interval.quantity
87
+ assert_equal 'testinterval2', interval.interval
88
+ end
89
+
90
+ end
91
+
92
+ end
@@ -0,0 +1,49 @@
1
+ require File.dirname(__FILE__) + '/../../test_helper'
2
+ require 'simplepay/support/subscription_period'
3
+
4
+ class Simplepay::Support::TestSubscriptionPeriod < Test::Unit::TestCase
5
+
6
+ context 'Simplepay::Support::SubscriptionPeriod' do
7
+
8
+ setup do
9
+ @interval = Simplepay::Support::SubscriptionPeriod.new
10
+ end
11
+
12
+ should 'not set default values' do
13
+ assert_nil @interval.interval
14
+ assert_nil @interval.quantity
15
+ end
16
+
17
+ should 'only allow defined intervals' do
18
+ intervals = ['day', 'week', 'month', 'year', 'forever']
19
+ added = @interval.class.allowed_intervals - intervals
20
+ missed = intervals - @interval.class.allowed_intervals
21
+ assert added.empty?, "#{@interval.class.name} defines unexpected intervals: #{added.inspect}"
22
+ assert missed.empty?, "#{@interval.class.name} failed to define expected intervals: #{missed.inspect}"
23
+ end
24
+
25
+ should 'limit quantities to three digits' do
26
+ assert_nothing_raised(ArgumentError) { @interval.quantity = 999 }
27
+ assert_raise(ArgumentError) { @interval.quantity = 1000 }
28
+ end
29
+
30
+ should 'disallow 0 quantity' do
31
+ assert_raise(ArgumentError) { @interval.quantity = 0 }
32
+ end
33
+
34
+ should 'disallow negative quantities' do
35
+ assert_raise(ArgumentError) { @interval.quantity = -1 }
36
+ end
37
+
38
+ should 'convert to String' do
39
+ assert_equal '5 year', @interval.class.new(5, 'year').to_s
40
+ end
41
+
42
+ should 'return nil when converted to string with FOREVER interval' do
43
+ @interval.interval = 'forever'
44
+ assert_nil @interval.to_s
45
+ end
46
+
47
+ end
48
+
49
+ end
@@ -0,0 +1,33 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+ require 'simplepay/authentication'
3
+
4
+ class Simplepay::TestAuthentication < Test::Unit::TestCase
5
+
6
+ context 'Simplepay::Authentication' do
7
+
8
+ setup do
9
+ @secret_key = "TESTINGKEY"
10
+ @signature = 'c7Nyzt61Bwe2pec9rO2nM5ZjmC0='
11
+ @data = {
12
+ :symbol => 'string',
13
+ 'string' => 1,
14
+ 2 => :symbol
15
+ }
16
+ @auth = Simplepay::Authentication
17
+ end
18
+
19
+ should 'compute an Amazon signature for hash data' do
20
+ assert_equal @signature, @auth.generate(@data, @secret_key)
21
+ end
22
+
23
+ should 'authenticate correctly signed data' do
24
+ assert @auth.authentic?(@data, @signature, @secret_key)
25
+ end
26
+
27
+ should 'not validate incorrectly signed data' do
28
+ assert !@auth.authentic?(@data, 'thisisnotavalidsignaturetoo=', @secret_key)
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,118 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+ require 'rexml/document'
3
+ require 'simplepay/service'
4
+
5
+ class TestService < Simplepay::Service
6
+ ENDPOINT_URL = 'http://test.host.url/api'
7
+ SANDBOX_URL = 'http://test.host.url/sandbox'
8
+
9
+ set_submit_tag 'testing'
10
+
11
+ field :field_1
12
+ field :field_2
13
+ required_field :required
14
+ required_field :required2, :value => 'preset'
15
+ end
16
+
17
+ class Simplepay::TestService < Test::Unit::TestCase
18
+
19
+ context 'Simplepay::Service' do
20
+
21
+ setup do
22
+ @service = ::TestService.new
23
+ end
24
+
25
+ context 'class' do
26
+
27
+ setup do
28
+ @class = @service.class
29
+ end
30
+
31
+ should 'contain the defined fields' do
32
+ fields = @class.fields
33
+ assert_not_nil fields
34
+ assert !fields.empty?
35
+ end
36
+
37
+ should 'contain required field(s)' do
38
+ fields = @class.fields.select { |f| f.required? }
39
+ assert fields.size > 0
40
+ end
41
+
42
+ should 'have different field instances than its service instance' do
43
+ class_fields, instance_fields = @class.fields, @service.fields
44
+ instance_fields.each do |instance_field|
45
+ assert !class_fields.include?(instance_field)
46
+ assert class_fields.any? {|f| instance_field.service_name == f.service_name }
47
+ end
48
+ end
49
+
50
+ end
51
+
52
+ should 'return a collection of fields' do
53
+ assert !@service.fields.empty?
54
+ assert @service.fields.all? { |f| f.kind_of?(Simplepay::Support::Field) }
55
+ end
56
+
57
+ should 'use the live endpoint url' do
58
+ assert_equal TestService::ENDPOINT_URL, @service.url(false)
59
+ end
60
+
61
+ should 'use the sandbox url' do
62
+ assert_equal TestService::SANDBOX_URL, @service.url(true)
63
+ end
64
+
65
+ should 'generate an HTML FORM' do
66
+ @service.required = 'set'
67
+ assert_match /\A<form/i, @service.form
68
+ assert_match /<\/form>\Z/i, @service.form
69
+ end
70
+
71
+ should 'post the form to the endpoint' do
72
+ assert_match /<form action="#{Regexp.escape('http://test.host.url/sandbox')}" method="post"/, @service.form({:required => 'set'})
73
+ end
74
+
75
+ should 'generate HIDDEN HTML INPUTs for each non-empty field' do
76
+ @service.required = 'Test Field'
77
+ @service.field_1 = 'Testing'
78
+
79
+ defined_fields = @service.fields.select { |f| !f.value.blank? }
80
+ assert defined_fields.size > 0, "This test cannot be run without defined fields"
81
+ document = REXML::Document.new(@service.form)
82
+ form_inputs = []
83
+ document.root.each_element("//input[@type='hidden']") do |e|
84
+ field = defined_fields.detect { |f| f.service_name == e.attributes['name'] }
85
+ assert field, "#{e} unrecognized"
86
+ form_inputs << e
87
+ end
88
+ assert_equal defined_fields.size, form_inputs.size
89
+ end
90
+
91
+ should 'allow fields to be set through hash' do
92
+ assert_nothing_raised(Simplepay::RequiredFieldMissing) do
93
+ TestService.new.form({:required => 'covered'})
94
+ end
95
+ end
96
+
97
+ should 'raise an error for undefined required fields' do
98
+ assert_raise(Simplepay::RequiredFieldMissing) { @service.form }
99
+ end
100
+
101
+ should 'default to a basic HTML SUBMIT button' do
102
+ document = REXML::Document.new(@service.form({:required => 'set'}))
103
+ assert_not_nil document.root.elements["input[@type='submit']"]
104
+ end
105
+
106
+ should 'allow class definition of the default submit text' do
107
+ document = REXML::Document.new(@service.form({:required => 'set'}))
108
+ assert_not_nil document.root.elements["input[@type='submit' and @value='testing']"]
109
+ end
110
+
111
+ should 'allow the HTML SUBMIT button to be overridden' do
112
+ document = REXML::Document.new(@service.form({:required => 'set'}, Simplepay::Helpers::FormHelper.tag(:input, {:type => 'submit', :value => 'Send It'})))
113
+ assert_not_nil document.root.elements["input[@type='submit' and @value='Send It']"]
114
+ end
115
+
116
+ end
117
+
118
+ end
@@ -0,0 +1,77 @@
1
+ require 'stringio'
2
+ require 'test/unit'
3
+
4
+ require 'rubygems'
5
+ require 'redgreen' rescue nil
6
+
7
+ require 'shoulda'
8
+ require 'mocha'
9
+
10
+ require File.dirname(__FILE__) + '/../lib/simplepay'
11
+
12
+
13
+ module Simplepay #:nodoc:
14
+ module Macros #:nodoc: all
15
+
16
+ def should_have_service_field(*names)
17
+ delegate_class, service_name, required = get_options!(names, :class, :as, :required)
18
+ klass = model_class
19
+
20
+ names.each do |name|
21
+ should "have a field named #{name.inspect}" do
22
+ message = "#{klass} does not have a Field named #{name.inspect}"
23
+ assert klass.fields.any? { |f| f.name == name }, message
24
+ end
25
+
26
+ context "#{name} field" do
27
+ should("delegate to #{delegate_class}") do
28
+ message = "#{klass} field #{name.inspect} failed to delegate to #{delegate_class}"
29
+ field = klass.fields.detect { |f| f.name == name }
30
+ assert_equal delegate_class, field.delegate, message
31
+ end if delegate_class
32
+
33
+ should("not delegate") do
34
+ field = klass.fields.detect { |f| f.name == name }
35
+ message = "#{klass} field #{name.inspect} unexpectedly delegated to #{field.delegate}"
36
+ assert !field.delegated?, message
37
+ end unless delegate_class
38
+
39
+ should("have a #{service_name} service name") do
40
+ message = "#{klass} field #{name.inspect} failed to have expected service name"
41
+ field = klass.fields.detect { |f| f.name == name }
42
+ assert_equal service_name, field.service_name
43
+ end if service_name
44
+
45
+ should("#{"not " unless required}be required") do
46
+ message = "#{klass} field #{name.inspect} was#{" not" if required} required"
47
+ field = klass.fields.detect { |f| f.name == name }
48
+ assert_equal required, field.required?, message
49
+ end
50
+ end
51
+ end
52
+ end unless method_defined?(:should_have_service_field)
53
+
54
+
55
+ private
56
+
57
+
58
+ # Returns the values for the entries in the args hash who's keys are listed in the wanted array.
59
+ # Will raise if there are keys in the args hash that aren't listed.
60
+ def get_options!(args, *wanted)
61
+ ret = []
62
+ opts = (args.last.is_a?(Hash) ? args.pop : {})
63
+ wanted.each {|w| ret << opts.delete(w)}
64
+ raise ArgumentError, "Unsupported options given: #{opts.keys.join(', ')}" unless opts.keys.empty?
65
+ return *ret
66
+ end unless defined?(:get_options!)
67
+
68
+ end
69
+ end
70
+
71
+ module Test #:nodoc: all
72
+ module Unit
73
+ class TestCase
74
+ extend Simplepay::Macros
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,11 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class TestSimplepay < Test::Unit::TestCase
4
+
5
+ def setup
6
+ end
7
+
8
+ def test_truth
9
+ assert true
10
+ end
11
+ end
metadata ADDED
@@ -0,0 +1,155 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simplepay
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nathaniel E. Bibler
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain:
11
+ - |
12
+ -----BEGIN CERTIFICATE-----
13
+ MIIDPDCCAiSgAwIBAgIBADANBgkqhkiG9w0BAQUFADBEMQwwCgYDVQQDDANnZW0x
14
+ HzAdBgoJkiaJk/IsZAEZFg9uYXRoYW5pZWxiaWJsZXIxEzARBgoJkiaJk/IsZAEZ
15
+ FgNjb20wHhcNMDgxMTI1MjA0MTUwWhcNMDkxMTI1MjA0MTUwWjBEMQwwCgYDVQQD
16
+ DANnZW0xHzAdBgoJkiaJk/IsZAEZFg9uYXRoYW5pZWxiaWJsZXIxEzARBgoJkiaJ
17
+ k/IsZAEZFgNjb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDWEZKa
18
+ DYt7JwQ24j/DRlEW4PdynK7KJFmpmwUdisxan7FuzeGy/ReWXkvNrO/7jBFvGy7b
19
+ cDPuW+ipoL4W7HUul73+Tz98vTDLng8xrv4wEo2IDOu+qcFd2dk+s/I6zgPwRAzR
20
+ OP9+CqbgSC/zTV0YBFqXZWvMGuDp71JkF0B+oZ6a2PQ4VJPCV4raZBnzaSfWvRHe
21
+ yr0qOSOtivysEKoPWuhn01PXY1dMvUnmFUs83EEp1tetIbvNnVB+y130ZS/NOMpM
22
+ zaeTSTME3gVTFL8qOHvG4ReezSIh93bowWXMkgB8S9s/f80DE5WIDsV4cN1jNsT8
23
+ SAUCr7ZXlKX9jcQBAgMBAAGjOTA3MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0G
24
+ A1UdDgQWBBTW0Y/shVEOzNrkpMSNBaZLEKEaSzANBgkqhkiG9w0BAQUFAAOCAQEA
25
+ TFBcrpTgF6AFiSLpqwbvKQtR1u02NyIP0xAFOcTUI67ITu9JJbedhXMNSxb+KuAb
26
+ 5cYajLE2/J8ci7zL6MVqecOyTAVOqUJbnOAhdGMBT2UWXLATwnRlIar8bXUvcbEh
27
+ 3ub+b3QHb+2QJyAFfrlTARXvsZflYU3F8kUBwnLrnZ7DO+RqyvAVSvxcknp8KF7e
28
+ tYk7pZM3zi1PsveIUao++fZKCfCnl/NtSHKGkuonlWbbjGgFs9ubKf8fBr83AKJK
29
+ UUqK1hFjxnviLP41sx8WT3jYfFS/HFTJO/+MjuoeIligHOrlw1kCS3UowjHHsGV+
30
+ DXxToz6dXyCgpN1XYMMB+A==
31
+ -----END CERTIFICATE-----
32
+
33
+ date: 2008-12-06 00:00:00 -05:00
34
+ default_executable:
35
+ dependencies:
36
+ - !ruby/object:Gem::Dependency
37
+ name: activesupport
38
+ type: :runtime
39
+ version_requirement:
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: 2.0.2
45
+ version:
46
+ - !ruby/object:Gem::Dependency
47
+ name: newgem
48
+ type: :development
49
+ version_requirement:
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 1.1.0
55
+ version:
56
+ - !ruby/object:Gem::Dependency
57
+ name: hoe
58
+ type: :development
59
+ version_requirement:
60
+ version_requirements: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: 1.8.0
65
+ version:
66
+ description: This gem provides a Rails interface to the Amazon Simple Pay payment service.
67
+ email:
68
+ - gem@nathanielbibler.com
69
+ executables: []
70
+
71
+ extensions: []
72
+
73
+ extra_rdoc_files:
74
+ - History.txt
75
+ - Manifest.txt
76
+ - README.rdoc
77
+ files:
78
+ - History.txt
79
+ - Manifest.txt
80
+ - README.rdoc
81
+ - Rakefile
82
+ - lib/simplepay.rb
83
+ - lib/simplepay/authentication.rb
84
+ - lib/simplepay/constants.rb
85
+ - lib/simplepay/errors.rb
86
+ - lib/simplepay/helpers/form_helper.rb
87
+ - lib/simplepay/helpers/rails_helper.rb
88
+ - lib/simplepay/service.rb
89
+ - lib/simplepay/services/standard.rb
90
+ - lib/simplepay/services/subscription.rb
91
+ - lib/simplepay/support.rb
92
+ - lib/simplepay/support/amount.rb
93
+ - lib/simplepay/support/billing_frequency.rb
94
+ - lib/simplepay/support/boolean.rb
95
+ - lib/simplepay/support/currency.rb
96
+ - lib/simplepay/support/epoch.rb
97
+ - lib/simplepay/support/field.rb
98
+ - lib/simplepay/support/interval.rb
99
+ - lib/simplepay/support/subscription_period.rb
100
+ - script/console
101
+ - script/destroy
102
+ - script/generate
103
+ - simplepay.gemspec
104
+ - test/simplepay/services/test_subscription.rb
105
+ - test/simplepay/support/test_amount.rb
106
+ - test/simplepay/support/test_billing_frequency.rb
107
+ - test/simplepay/support/test_boolean.rb
108
+ - test/simplepay/support/test_epoch.rb
109
+ - test/simplepay/support/test_field.rb
110
+ - test/simplepay/support/test_interval.rb
111
+ - test/simplepay/support/test_subscription_period.rb
112
+ - test/simplepay/test_authentication.rb
113
+ - test/simplepay/test_service.rb
114
+ - test/test_helper.rb
115
+ - test/test_simplepay.rb
116
+ has_rdoc: true
117
+ homepage: http://simplepay.rubyforge.org
118
+ post_install_message:
119
+ rdoc_options:
120
+ - --main
121
+ - README.rdoc
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: "0"
129
+ version:
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: "0"
135
+ version:
136
+ requirements: []
137
+
138
+ rubyforge_project: simplepay
139
+ rubygems_version: 1.3.1
140
+ signing_key:
141
+ specification_version: 2
142
+ summary: This gem provides a Rails interface to the Amazon Simple Pay payment service.
143
+ test_files:
144
+ - test/simplepay/services/test_subscription.rb
145
+ - test/simplepay/support/test_amount.rb
146
+ - test/simplepay/support/test_billing_frequency.rb
147
+ - test/simplepay/support/test_boolean.rb
148
+ - test/simplepay/support/test_epoch.rb
149
+ - test/simplepay/support/test_field.rb
150
+ - test/simplepay/support/test_interval.rb
151
+ - test/simplepay/support/test_subscription_period.rb
152
+ - test/simplepay/test_authentication.rb
153
+ - test/simplepay/test_service.rb
154
+ - test/test_helper.rb
155
+ - test/test_simplepay.rb