ruby_psigate 0.7

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 (47) hide show
  1. data/CHANGELOG +2 -0
  2. data/Gemfile +12 -0
  3. data/LICENSE +0 -0
  4. data/Manifest +45 -0
  5. data/README.markdown +99 -0
  6. data/Rakefile +28 -0
  7. data/lib/certs/cacert.pem +2633 -0
  8. data/lib/ruby_psigate/account.rb +152 -0
  9. data/lib/ruby_psigate/account_manager_api.rb +137 -0
  10. data/lib/ruby_psigate/account_methods.rb +5 -0
  11. data/lib/ruby_psigate/address.rb +54 -0
  12. data/lib/ruby_psigate/connection.rb +96 -0
  13. data/lib/ruby_psigate/credit_card.rb +104 -0
  14. data/lib/ruby_psigate/credit_card_methods.rb +12 -0
  15. data/lib/ruby_psigate/error.rb +33 -0
  16. data/lib/ruby_psigate/gateway.rb +126 -0
  17. data/lib/ruby_psigate/hash_variables.rb +58 -0
  18. data/lib/ruby_psigate/item.rb +65 -0
  19. data/lib/ruby_psigate/item_option.rb +10 -0
  20. data/lib/ruby_psigate/number_validation_methods.rb +12 -0
  21. data/lib/ruby_psigate/order.rb +120 -0
  22. data/lib/ruby_psigate/recurring_charge.rb +53 -0
  23. data/lib/ruby_psigate/recurring_item.rb +26 -0
  24. data/lib/ruby_psigate/response.rb +109 -0
  25. data/lib/ruby_psigate/serializer.rb +59 -0
  26. data/lib/ruby_psigate/transaction_methods.rb +21 -0
  27. data/lib/ruby_psigate/utils.rb +18 -0
  28. data/lib/ruby_psigate.rb +57 -0
  29. data/ruby_psigate.gemspec +31 -0
  30. data/test/remote/remote_account_test.rb +33 -0
  31. data/test/remote/remote_gateway_test.rb +32 -0
  32. data/test/test_helper.rb +144 -0
  33. data/test/unit/account_manager_api_test.rb +96 -0
  34. data/test/unit/account_test.rb +388 -0
  35. data/test/unit/address_test.rb +99 -0
  36. data/test/unit/connection_test.rb +153 -0
  37. data/test/unit/credit_card_test.rb +152 -0
  38. data/test/unit/gateway_test.rb +112 -0
  39. data/test/unit/item_option_test.rb +19 -0
  40. data/test/unit/item_test.rb +106 -0
  41. data/test/unit/order_test.rb +491 -0
  42. data/test/unit/recurring_charge_test.rb +89 -0
  43. data/test/unit/recurring_item_test.rb +62 -0
  44. data/test/unit/response_test.rb +110 -0
  45. data/test/unit/serializer_test.rb +89 -0
  46. data/test/unit/xml_api_test.rb +25 -0
  47. metadata +154 -0
@@ -0,0 +1,126 @@
1
+ module RubyPsigate
2
+
3
+ class InvalidOrder < RubyPsigateError; end
4
+ class InvalidAccount < RubyPsigateError; end
5
+ class InvalidGatewayMode < RubyPsigateError; end
6
+ class InvalidCredentials < RubyPsigateError; end
7
+ class InvalidCreditCard < RubyPsigateError; end
8
+ class IncompleteParameters < RubyPsigateError; end
9
+
10
+ class Gateway
11
+
12
+ # Class defaults to
13
+ @env = :test
14
+
15
+ def self.env
16
+ @env
17
+ end
18
+
19
+ def self.live!
20
+ @env = :live
21
+ end
22
+
23
+ def self.test!
24
+ @env = :test
25
+ end
26
+
27
+ include ::RubyPsigate::TransactionMethods
28
+ # include RubyPsigate::AccountManagerMethods
29
+
30
+ attr_reader :mode, :order, :options, :account
31
+
32
+ def initialize(options={})
33
+ @options = options
34
+ set_mode(@options)
35
+ end
36
+
37
+ def mode=(type)
38
+ valid_modes = %w( transaction account_manager )
39
+ raise InvalidGatewayMode unless valid_modes.include?(type.to_s)
40
+ @mode = type.to_sym
41
+ end
42
+
43
+ def order=(order_object)
44
+ raise InvalidOrder unless order_object.is_a?(RubyPsigate::Order)
45
+ @order = order_object
46
+ end
47
+
48
+ def account=(account_object)
49
+ raise InvalidAccount unless account_object.is_a?(RubyPsigate::Account)
50
+ @account = account_object
51
+ end
52
+
53
+ TRANSACTION_LOOKUP = {
54
+ :sale => "0",
55
+ :preauth => "1",
56
+ :postauth => "2",
57
+ :credit => "3",
58
+ :force_postauth => "4",
59
+ :void => "9"
60
+ }
61
+
62
+ def commit!
63
+ # 1) Check the mode
64
+ # 2) Ensure credentials are set
65
+ # 3) Check which action user is wanting to take
66
+ # 4) Ensure adequate minimum parameters for the given action
67
+ # 5) Parameterize and ensure it is properly formatted
68
+ # 6) Send!
69
+
70
+ raise InvalidGatewayMode unless mode_set?
71
+ raise InvalidCredentials unless sufficient_login_credentials?
72
+
73
+ if mode == :transaction
74
+ raise InvalidOrder unless order.valid?
75
+
76
+ @params = {}
77
+ @params[:Order] = {}
78
+ @params[:Order] = { :StoreID => options[:store_id], :Passphrase => options[:passphrase] }
79
+ @params[:Order].merge!(order.to_hash(order.action))
80
+
81
+ @endpoint = "https://dev.psigate.com:7989/Messenger/XMLMessenger"
82
+ end
83
+
84
+ if mode == :account_manager
85
+ # raise InvalidAccount unless account.valid?
86
+
87
+ # Parameterize based on account manager
88
+ @params = {}
89
+ @params[:Request] = {}
90
+ @params[:Request] = { :CID => options[:cid], :UserID => options[:user_id], :Password => options[:password] }
91
+ @params[:Request].merge!(account.to_hash(account.action))
92
+
93
+ @endpoint = "https://dev.psigate.com:8645/Messenger/AMMessenger"
94
+ end
95
+
96
+ # Serializes the parameters to xml
97
+ @params = Serializer.new(@params, :header => true).to_xml
98
+
99
+ @connection = Connection.new(@endpoint)
100
+ @response = @connection.post(@params)
101
+ Response.new(@response)
102
+ end
103
+
104
+ private
105
+
106
+ def set_mode(options)
107
+ self.mode = :account_manager if options[:cid] && options[:user_id] && options[:password]
108
+ self.mode = :transaction if options[:store_id] && options[:passphrase]
109
+ end
110
+
111
+ def mode_set?
112
+ (mode == :account_manager || mode == :transaction) ? true : false
113
+ end
114
+
115
+ def sufficient_login_credentials?
116
+ result = false
117
+ if mode == :account_manager
118
+ result = (options[:cid] && options[:user_id] && options[:password] ? true : false)
119
+ elsif mode == :transaction
120
+ result = (options[:store_id] && options[:passphrase] ? true : false)
121
+ end
122
+ result
123
+ end
124
+
125
+ end
126
+ end
@@ -0,0 +1,58 @@
1
+ module RubyPsigate
2
+ # In your class, you can define the instance variables you want to wrap in a hash output
3
+ #
4
+ # Usage:
5
+ #
6
+ # hashable :billing, [:name, :address, :city, :state, :country, :zipcode]
7
+ #
8
+ # What this will do is create a hash like this when you call the class' instance #to_hash(:billing)
9
+ # { :name => "Bob", :address => "1234 West St", :city => "North York", :state => "NY", :country => "USA", zipcode => "12345"}
10
+ #
11
+ # If you don't specify a "name", you can simply call #to_hash:
12
+ #
13
+ # hashable [:name, :address, :city, :state, :country, :zipcode]
14
+ # instance#to_hash will yield the same output as above
15
+ #
16
+ module HashVariables
17
+
18
+ def self.included(klass)
19
+ klass.extend ClassMethods
20
+ klass.send(:include, InstanceMethods)
21
+ end
22
+
23
+ module ClassMethods
24
+ def hashable(*args)
25
+ @hashable_attributes ||= {}
26
+ if args[0].is_a?(Symbol) && args[1].is_a?(Array)
27
+ @hashable_attributes[args[0]] = args[1]
28
+ elsif args[0].is_a?(Array)
29
+ @hashable_attributes[:default] = args[0]
30
+ else
31
+ raise ArgumentError
32
+ end
33
+ end
34
+
35
+ def hashable_attributes
36
+ @hashable_attributes
37
+ end
38
+ end
39
+
40
+ module InstanceMethods
41
+ def to_hash(name=nil)
42
+ return_hash = {}
43
+ if name.nil?
44
+ attributes = self.class.hashable_attributes[:default]
45
+ else
46
+ attributes = self.class.hashable_attributes[name.to_sym]
47
+ end
48
+ attributes.each do |a|
49
+ key = "#{a}"
50
+ value = self.send(a.downcase)
51
+ return_hash[key.to_sym] = value
52
+ end
53
+ return_hash
54
+ end
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,65 @@
1
+ module RubyPsigate
2
+
3
+ class InvalidItemOption < RubyPsigateError; end
4
+
5
+ class Item
6
+ include NumberValidationMethods
7
+ include HashVariables
8
+
9
+ hashable %w( ItemID ItemDescription ItemQty ItemPrice )
10
+
11
+ attr_reader :qty, :price
12
+ attr_accessor :unique_id, :desc
13
+
14
+ # For hashable
15
+ alias_method :itemid, :unique_id
16
+ alias_method :itemdescription, :desc
17
+ alias_method :itemqty, :qty
18
+ alias_method :itemprice, :price
19
+
20
+ def to_hash
21
+ @return_hash = super
22
+ unless options.count == 0
23
+ @return_hash[:Option] = {}
24
+ options.each do |opt|
25
+ opt = opt.first
26
+ key = opt[0].to_sym
27
+ value = opt[1]
28
+ @return_hash[:Option][key] = value
29
+ end
30
+ end
31
+ @return_hash
32
+ end
33
+ # End hashable
34
+
35
+ def initialize(parameters = {})
36
+ @options = []
37
+ end
38
+
39
+ def qty=(x)
40
+ x = x.to_i
41
+ @qty = validate(x)
42
+ end
43
+
44
+ def price=(x)
45
+ x = x.to_f
46
+ @price = Money.new(x*100)
47
+ end
48
+
49
+ def << (item_option)
50
+ raise InvalidItemOption unless item_option.is_a?(RubyPsigate::ItemOption)
51
+ @options << item_option
52
+ return self
53
+ end
54
+
55
+ def options
56
+ holder = []
57
+ @options.each do |opt|
58
+ holder << opt.instance_variable_get(:@option)
59
+ end
60
+ holder
61
+ end
62
+
63
+ end
64
+
65
+ end
@@ -0,0 +1,10 @@
1
+ module RubyPsigate
2
+ class ItemOption
3
+
4
+ def initialize(option_hash={})
5
+ @option = option_hash
6
+ self.freeze
7
+ end
8
+
9
+ end
10
+ end
@@ -0,0 +1,12 @@
1
+ module RubyPsigate
2
+
3
+ class NumberLessThanZero < RubyPsigateError; end
4
+
5
+ module NumberValidationMethods
6
+ def validate(number)
7
+ raise NumberLessThanZero if number < 0
8
+ return number
9
+ end
10
+ end
11
+
12
+ end
@@ -0,0 +1,120 @@
1
+ module RubyPsigate
2
+
3
+ class InvalidAction < RubyPsigateError; end
4
+ class InvalidItem < RubyPsigateError; end
5
+ class InvalidAmount < RubyPsigateError; end
6
+ class InvalidAddress < RubyPsigateError; end
7
+
8
+ class Order
9
+
10
+ include CreditCardMethods
11
+ include HashVariables
12
+
13
+ hashable :sale, %w( Subtotal Email CardAction Comments Tax1 Tax2 Tax3 Tax4 Tax5 )
14
+ hashable :preauth, %w( Subtotal Email CardAction Comments Tax1 Tax2 Tax3 Tax4 Tax5 )
15
+ hashable :postauth, %w( Subtotal Email CardAction OrderID )
16
+ hashable :credit, %w( Subtotal Email CardAction OrderID )
17
+ hashable :force_postauth, %w( Subtotal Email CardAction CardAuthNumber)
18
+ hashable :void, %w( CardAction OrderID TransRefNumber )
19
+
20
+ attr_reader :action, :items, :amount, :billing, :shipping, :tax1, :tax2, :tax3, :tax4, :tax5
21
+ attr_accessor :comment, :email, :order_id, :card_auth_number, :trans_ref_number
22
+
23
+ alias_method :orderid, :order_id
24
+ alias_method :cardauthnumber, :card_auth_number
25
+ alias_method :transrefnumber, :trans_ref_number
26
+
27
+ def initialize
28
+ @items = []
29
+ end
30
+
31
+ def to_hash(type = nil)
32
+ result = super
33
+ result = result.merge(billing.to_hash(:billing)) unless billing.nil? # Add billing address if present
34
+ result = result.merge(shipping.to_hash(:shipping)) unless shipping.nil? # Add shipping address if present
35
+
36
+ # Add items if present
37
+ if items.count > 0
38
+ result[:Item] = []
39
+ items.each do |item|
40
+ result[:Item] << item.to_hash
41
+ end
42
+ end
43
+
44
+ result.merge!(cc.to_hash) if cc
45
+
46
+ result = result.delete_if { |key, value| value.nil? } # Delete empty hash values
47
+ result
48
+ end
49
+
50
+ def << (new_item)
51
+ raise InvalidItem unless new_item.is_a?(RubyPsigate::Item)
52
+ @items << new_item
53
+ return self
54
+ end
55
+
56
+ def action=(x)
57
+ x = x.downcase.to_sym
58
+ raise InvalidAction unless [:sale, :preauth, :postauth, :credit, :force_postauth, :void].include?(x)
59
+ @action = x
60
+ end
61
+
62
+ CARD_ACTION = {
63
+ :sale => 0,
64
+ :preauth => 1,
65
+ :postauth => 2,
66
+ :credit => 3,
67
+ :force_postauth => 4,
68
+ :void => 9
69
+ }
70
+
71
+ # Returns the Psigate format equivalent for the action method
72
+ def cardaction
73
+ CARD_ACTION[action]
74
+ end
75
+
76
+ alias_method :subtotal, :amount
77
+
78
+ def amount=(x)
79
+ raise InvalidAmount if x.to_f < 0
80
+ @amount = Money.new(x.to_f*100)
81
+ end
82
+
83
+ def billing=(billing_address)
84
+ raise InvalidAddress unless billing_address.is_a?(RubyPsigate::Address)
85
+ @billing = billing_address
86
+ end
87
+
88
+ def shipping=(shipping_address)
89
+ raise InvalidAddress unless shipping_address.is_a?(RubyPsigate::Address)
90
+ @shipping = shipping_address
91
+ end
92
+
93
+ alias_method :comments, :comment
94
+
95
+ %w( tax1 tax2 tax3 tax4 tax5 ).each do |tax|
96
+ self.class_eval <<-EOF
97
+ def #{tax}=(tax_amount)
98
+ raise InvalidAmount if tax_amount.to_f < 0
99
+ @#{tax} = Money.new(tax_amount*100)
100
+ end
101
+ EOF
102
+ end
103
+
104
+ # Used internally by the Gateway class to determine if minimal parameters are set to be processed
105
+ def valid?
106
+ errors = 0
107
+
108
+ if amount.zero?
109
+ errors += 1 unless action == :postauth || action == :void
110
+ end
111
+ errors += 1 if action.nil?
112
+ errors += 1 if email.nil?
113
+ errors += 1 if ( action == :postauth && order_id.nil? ) || ( action == :credit && order_id.nil? ) || ( action == :void && order_id.nil? )
114
+ errors += 1 if ( action == :force_postauth && card_auth_number.nil? )
115
+ errors += 1 if ( action == :void && trans_ref_number.nil? )
116
+ errors > 0 ? false : true
117
+ end
118
+
119
+ end
120
+ end
@@ -0,0 +1,53 @@
1
+ module RubyPsigate
2
+
3
+ class InvalidRecurringItem < RubyPsigateError; end
4
+
5
+ # This class encapsulates the recurring charge elements for use in the Account Class
6
+ class RecurringCharge
7
+
8
+ include HashVariables
9
+ hashable %w( RBCID RBName Interval RBTrigger ProcessType Status StartTime EndTime )
10
+
11
+ attr_accessor :rbcid, :rbname, :interval, :rbtrigger, :status, :starttime, :endtime, :processtype
12
+
13
+ def initialize(attributes = {})
14
+ @rbcid = attributes[:rbcid]
15
+ @rbname = attributes[:rbname]
16
+ @interval = attributes[:interval]
17
+ @rbtrigger = attributes[:rbtrigger]
18
+ @status = attributes[:status]
19
+ @starttime = attributes[:starttime]
20
+ @endtime = attributes[:endtime]
21
+ @processtype = attributes[:processtype]
22
+ @items = []
23
+ end
24
+
25
+ def to_hash
26
+ @return_hash = super
27
+ unless items.count == 0
28
+ @return_hash[:ItemInfo] = []
29
+ items.each do |item|
30
+ @return_hash[:ItemInfo] << item.to_hash
31
+ end
32
+ end
33
+
34
+ @return_hash = @return_hash.delete_if { |key, value| value.nil? } # Delete empty hash values
35
+ @return_hash
36
+ end
37
+
38
+ def << (item)
39
+ raise InvalidRecurringItem unless item.is_a?(RecurringItem)
40
+ @items << item
41
+ return self
42
+ end
43
+
44
+ def items
45
+ holder = []
46
+ @items.each do |item|
47
+ holder << item.to_hash
48
+ end
49
+ holder
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,26 @@
1
+ module RubyPsigate
2
+ class RecurringItem
3
+
4
+ include HashVariables
5
+ hashable %w( ProductID Description Quantity Price Tax1 Tax2 )
6
+
7
+ attr_accessor :product_id, :description, :quantity, :price, :tax1, :tax2, :cost
8
+ alias_method :productid, :product_id
9
+
10
+ def initialize(attributes = {})
11
+ @product_id = attributes[:product_id]
12
+ @description = attributes[:description]
13
+ @quantity = attributes[:quantity]
14
+ @price = attributes[:price]
15
+ @tax1 = attributes[:tax1]
16
+ @tax2 = attributes[:tax2]
17
+ end
18
+
19
+ def to_hash
20
+ result = super
21
+ result = result.delete_if { |key, value| value.nil? } # Delete empty hash values
22
+ result
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,109 @@
1
+ module RubyPsigate
2
+
3
+ # The Response class deals with the XML responses from Psigate's servers
4
+ #
5
+ class Response
6
+
7
+ ACCOUNT_SUCCESS_CODES = %w(
8
+ RPA-0000
9
+ RPA-0001
10
+ RPA-0010
11
+ RPA-0015
12
+ RPA-0020
13
+ RPA-0022
14
+ RPA-0025
15
+ RPA-0040
16
+ RPA-0042
17
+ RPA-0046
18
+ RPA-0048
19
+ RPA-0058
20
+ PSI-0000
21
+ RRC-0000
22
+ RRC-0005
23
+ RRC-0050
24
+ RRC-0060
25
+ RRC-0065
26
+ RRC-0072
27
+ RRC-0075
28
+ RRC-0082
29
+ RRC-0090
30
+ RRC-0092
31
+ RRC-0095
32
+ RRC-0098
33
+ RRC-0190
34
+ CTL-0000
35
+ CTL-0005
36
+ CTL-0050
37
+ CTL-0060
38
+ CTL-0065
39
+ CTL-0072
40
+ CTL-0075
41
+ CTL-0082
42
+ CTL-0090
43
+ CTL-0092
44
+ CTL-0098
45
+ CTL-0190
46
+ CTL-0192
47
+ RIV-0050
48
+ RIV-0060
49
+ RIV-0072
50
+ RIV-0090
51
+ RIV-0190
52
+ RIV-0197
53
+ RIV-0198
54
+ EMR-0000
55
+ EMR-0005
56
+ EMR-0050
57
+ EMR-0060
58
+ EMR-0072
59
+ EMR-0082
60
+ EMR-0090
61
+ EMR-0190
62
+ )
63
+
64
+ # The Response object is initialized with the response output from the response body of the HTTP request to Psigate's servers
65
+ # The expected format of the raw_response is a properly formed XML document
66
+ # Please refer to Psigate's technical documents (XML Interface PDF and Account Manager API PDF)
67
+ #
68
+ # Once the raw_resposne is received, we use nokogiri to parse and format the output to a usable hash
69
+ def initialize(raw_response)
70
+ @response = Crack::XML.parse(raw_response)
71
+ end
72
+
73
+ def hash
74
+ @response
75
+ end
76
+
77
+ def parsed
78
+ return @parsed if @parsed
79
+ @parsed = @response["Result"] || @response["Response"]
80
+ end
81
+
82
+ def success?
83
+ parsed["Approved"] == "APPROVED" || ACCOUNT_SUCCESS_CODES.include?(self.returncode)
84
+ end
85
+
86
+ def method_missing(name, *args, &block)
87
+ @result = nil
88
+ name = name.downcase.to_sym
89
+ @result = find_value_in_hash(name, parsed)
90
+ @result
91
+ end
92
+
93
+ def find_value_in_hash(input_key, hash)
94
+ result = nil
95
+ hash.each_pair do |key, value|
96
+ if value.is_a? Hash
97
+ result = find_value_in_hash(input_key, value)
98
+ else
99
+ key = key.downcase.to_sym
100
+ result = value if input_key == key
101
+ end
102
+
103
+ break unless result.nil?
104
+ end
105
+ result
106
+ end
107
+
108
+ end
109
+ end
@@ -0,0 +1,59 @@
1
+ module RubyPsigate
2
+
3
+ class InvalidHashError < ArgumentError; end;
4
+
5
+ class Serializer
6
+ # Initializes a new object with an inputted hash
7
+ #
8
+ # my_hash = { :something => "special" }
9
+ # Serializer.new(my_hash)
10
+ def initialize(input_hash, options = { :header => false })
11
+ raise InvalidHashError, "Input parameters does not contain a hash!" unless input_hash.is_a?(Hash)
12
+ @input_hash = input_hash
13
+ @header = options[:header]
14
+ end
15
+
16
+ # Converts from a hash to a hierarchical XML
17
+ #
18
+ # An example:
19
+ #
20
+ # Converts
21
+ # => { :Something => "Hello World" }
22
+ # to this:
23
+ # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Something>Hello World</Something>"
24
+ #
25
+ # Can also handle nested hashes
26
+ def to_xml
27
+ @builder = Builder::XmlMarkup.new
28
+ @builder.instruct! if @header
29
+ build_level(@input_hash)
30
+ @builder.target!
31
+ end
32
+
33
+ def build_level(hash_level)
34
+ raise InvalidHashError, "Need hash to build XML" unless hash_level.is_a?(Hash)
35
+ for key, value in hash_level
36
+
37
+ # If the element at this level is :Item, then we know that it will be an array
38
+ unless key == :Item
39
+ @builder.tag!(key) { |level| value.is_a?(Hash) ? build_level(value) : level.text!(value.to_s) }
40
+ else
41
+ build_level_from_array_element(value)
42
+ end
43
+
44
+ end
45
+ end
46
+
47
+ def build_level_from_array_element(input_array)
48
+ input_array.each do |element|
49
+ @builder.tag!(:Item) {
50
+ element.each_pair do |key, value|
51
+ # @builder.tag!(key) { |level| level.text!(value) }
52
+ @builder.tag!(key) { |level| value.is_a?(Hash) ? build_level(value) : level.text!(value) }
53
+ end
54
+ }
55
+ end
56
+ end
57
+ end
58
+
59
+ end
@@ -0,0 +1,21 @@
1
+ module RubyPsigate
2
+
3
+ module TransactionMethods
4
+
5
+ # URL for test environment
6
+ TRANSACTION_TEST_URL = 'https://dev.psigate.com:7989/Messenger/XMLMessenger'
7
+
8
+ # URL for live environment is not specified
9
+ # It must be obtained from Psigate's tech support and be specified
10
+
11
+ def amount
12
+ @amount
13
+ end
14
+
15
+ def amount=(x)
16
+ raise InvalidAmount if x.to_f < 0
17
+ @amount = x
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ module RubyPsigate
2
+ module Utils
3
+
4
+ def requires!(hash, *params)
5
+ params.each do |param|
6
+ if param.is_a?(Array)
7
+ raise ArgumentError.new("Missing required parameter: #{param.first}") unless hash.has_key?(param.first)
8
+
9
+ valid_options = param[1..-1]
10
+ raise ArgumentError.new("Parameter: #{param.first} must be one of #{valid_options.to_sentence(:words_connector => 'or')}") unless valid_options.include?(hash[param.first])
11
+ else
12
+ raise ArgumentError.new("Missing required parameter: #{param}") unless hash.has_key?(param)
13
+ end
14
+ end
15
+ end
16
+
17
+ end
18
+ end