ruby_psigate 0.7

Sign up to get free protection for your applications and to get access to all the features.
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