activemerchant-paymentech-orbital 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/.document +5 -0
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +18 -0
- data/Rakefile +55 -0
- data/VERSION +1 -0
- data/activemerchant-paymentech-orbital.gemspec +79 -0
- data/lib/active_merchant/billing/paymentech_orbital.rb +113 -0
- data/lib/active_merchant/billing/paymentech_orbital/request.rb +67 -0
- data/lib/active_merchant/billing/paymentech_orbital/request/end_of_day.rb +22 -0
- data/lib/active_merchant/billing/paymentech_orbital/request/new_order.rb +121 -0
- data/lib/active_merchant/billing/paymentech_orbital/request/profile_management.rb +82 -0
- data/lib/active_merchant/billing/paymentech_orbital/request/void.rb +39 -0
- data/lib/active_merchant/billing/paymentech_orbital/response.rb +100 -0
- data/lib/activemerchant-paymentech-orbital.rb +9 -0
- data/test/activemerchant-paymentech-orbital_test.rb +7 -0
- data/test/factories.rb +170 -0
- data/test/mocks/active_merchant/billing/gateway.rb +9 -0
- data/test/options.rb +85 -0
- data/test/payment_orbital/gateway_test.rb +131 -0
- data/test/payment_orbital/request/end_of_day_test.rb +30 -0
- data/test/payment_orbital/request/new_order_test.rb +40 -0
- data/test/payment_orbital/request/profile_management_test.rb +47 -0
- data/test/payment_orbital/request/void_test.rb +28 -0
- data/test/payment_orbital/request_test.rb +28 -0
- data/test/test_helper.rb +22 -0
- metadata +100 -0
@@ -0,0 +1,82 @@
|
|
1
|
+
module ActiveMerchant
|
2
|
+
module Billing
|
3
|
+
module PaymentechOrbital
|
4
|
+
module Request
|
5
|
+
class ProfileManagement < PaymentechOrbital::Request::Base
|
6
|
+
attr_accessor :action, :credit_card
|
7
|
+
|
8
|
+
cattr_accessor :action_map
|
9
|
+
self.action_map = {
|
10
|
+
"create" => "C",
|
11
|
+
"retrieve" => "R",
|
12
|
+
"update" => "U",
|
13
|
+
"delete" => "D"
|
14
|
+
}
|
15
|
+
|
16
|
+
def initialize(action, credit_card=nil, options={})
|
17
|
+
@action = action.to_s
|
18
|
+
@credit_card = credit_card
|
19
|
+
super(options)
|
20
|
+
end
|
21
|
+
|
22
|
+
def request_type; "Profile"; end
|
23
|
+
|
24
|
+
private
|
25
|
+
def customer_profile_action
|
26
|
+
self.class.action_map[action.downcase.to_s]
|
27
|
+
end
|
28
|
+
|
29
|
+
def writing?
|
30
|
+
["create", "update"].include?(action)
|
31
|
+
end
|
32
|
+
|
33
|
+
def request_body(xml)
|
34
|
+
add_meta_info(xml)
|
35
|
+
add_profile_info(xml)
|
36
|
+
|
37
|
+
xml.tag! "CustomerProfileAction", customer_profile_action
|
38
|
+
|
39
|
+
add_customer_profile_management_options(xml)
|
40
|
+
add_account_info(xml) if writing?
|
41
|
+
add_credit_card_info(xml) if writing? && credit_card
|
42
|
+
end
|
43
|
+
|
44
|
+
def add_meta_info(xml)
|
45
|
+
xml.tag! "CustomerBin", bin
|
46
|
+
xml.tag! "CustomerMerchantID", merchant_id
|
47
|
+
end
|
48
|
+
|
49
|
+
def add_profile_info(xml)
|
50
|
+
xml.tag! "CustomerName", address[:name]
|
51
|
+
xml.tag! "CustomerRefNum", customer_ref_num if customer_ref_num
|
52
|
+
xml.tag! "CustomerAddress1", address[:address1]
|
53
|
+
xml.tag! "CustomerAddress2", address[:address]
|
54
|
+
xml.tag! "CustomerCity", address[:city]
|
55
|
+
xml.tag! "CustomerState", address[:state]
|
56
|
+
xml.tag! "CustomerZIP", address[:zip]
|
57
|
+
xml.tag! "CustomerEmail", address[:email]
|
58
|
+
xml.tag! "CustomerPhone", address[:phone]
|
59
|
+
xml.tag! "CustomerCountryCode", address[:country]
|
60
|
+
end
|
61
|
+
|
62
|
+
def add_customer_profile_management_options(xml)
|
63
|
+
unless customer_ref_num
|
64
|
+
xml.tag! "CustomerProfileOrderOverrideInd", "NO"
|
65
|
+
xml.tag! "CustomerProfileFromOrderInd", "A"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def add_account_info(xml)
|
70
|
+
xml.tag! "CustomerAccountType", "CC"
|
71
|
+
xml.tag! "Status", options[:status] || "A"
|
72
|
+
end
|
73
|
+
|
74
|
+
def add_credit_card_info(xml)
|
75
|
+
xml.tag! "CCAccountNum", credit_card.number
|
76
|
+
xml.tag! "CCExpireDate", "#{credit_card.month}#{credit_card.year}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module ActiveMerchant
|
2
|
+
module Billing
|
3
|
+
module PaymentechOrbital
|
4
|
+
module Request
|
5
|
+
class Void < PaymentechOrbital::Request::Base
|
6
|
+
attr_reader :tx_ref_num, :tx_ref_idx, :money
|
7
|
+
|
8
|
+
def initialize(tx_ref_num, tx_ref_idx, money=nil, options={})
|
9
|
+
@tx_ref_num = tx_ref_num
|
10
|
+
@tx_ref_idx = tx_ref_idx
|
11
|
+
@money = money
|
12
|
+
super(options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def request_type; "Reversal"; end
|
16
|
+
|
17
|
+
private
|
18
|
+
def request_body(xml)
|
19
|
+
add_transaction_info(xml)
|
20
|
+
xml.tag! "AdjustedAmt", money if money
|
21
|
+
add_meta_info(xml)
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_transaction_info(xml)
|
25
|
+
xml.tag! "TxRefNum", tx_ref_num
|
26
|
+
xml.tag! "TxRefIdx", tx_ref_idx
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_meta_info(xml)
|
30
|
+
xml.tag! "OrderID", order_id
|
31
|
+
xml.tag! "BIN", bin
|
32
|
+
xml.tag! "MerchantID", merchant_id
|
33
|
+
xml.tag! "TerminalID", terminal_id
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module ActiveMerchant
|
2
|
+
module Billing
|
3
|
+
module PaymentechOrbital
|
4
|
+
class Response
|
5
|
+
cattr_accessor :elements
|
6
|
+
attr_reader :doc, :request_type
|
7
|
+
|
8
|
+
self.elements = [
|
9
|
+
:industry_type, :message_type, :merchant_id,
|
10
|
+
:terminal_id, :card_brand, :account_num,
|
11
|
+
:order_id, :tx_ref_num, :tx_ref_idx, :proc_status,
|
12
|
+
:approval_status, :resp_code, :avs_resp_code,
|
13
|
+
:cvv2_resp_code, :auth_code, :status_msg, :resp_msg,
|
14
|
+
:customer_ref_num, :customer_name, :profile_proc_status,
|
15
|
+
:customer_profile_message, :resp_time, :batch_seq_num,
|
16
|
+
:CCAccountNum, :customer_address_1, :customer_address_2,
|
17
|
+
:customer_city, :CustomerZIP, :customer_state,
|
18
|
+
:CCExpireDate
|
19
|
+
]
|
20
|
+
|
21
|
+
def initialize(doc, request_type, options={})
|
22
|
+
@doc = REXML::Document.new(doc)
|
23
|
+
@request_type = request_type
|
24
|
+
@options = options
|
25
|
+
end
|
26
|
+
|
27
|
+
def success?
|
28
|
+
case request_type
|
29
|
+
when "NewOrder"
|
30
|
+
proc_success? && approved?
|
31
|
+
when "Profile"
|
32
|
+
profile_proc_success?
|
33
|
+
when "Reversal", "EndOfDay"
|
34
|
+
proc_success?
|
35
|
+
else
|
36
|
+
false
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def proc_success?
|
41
|
+
proc_status == "0"
|
42
|
+
end
|
43
|
+
|
44
|
+
def profile_proc_success?
|
45
|
+
profile_proc_status == "0"
|
46
|
+
end
|
47
|
+
|
48
|
+
def approved?
|
49
|
+
approval_status == "1"
|
50
|
+
end
|
51
|
+
|
52
|
+
def authorization
|
53
|
+
approval_status
|
54
|
+
end
|
55
|
+
|
56
|
+
def test?
|
57
|
+
@options[:test] || false
|
58
|
+
end
|
59
|
+
|
60
|
+
def avs_result
|
61
|
+
@avs_result ||= AVSResult.new({:code => avs_resp_code})
|
62
|
+
end
|
63
|
+
|
64
|
+
def cvv_result
|
65
|
+
@cvv_result ||= CVVResult.new({:code => cvv2_result_code})
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_xml
|
69
|
+
unless @_xml
|
70
|
+
@_xml = ""
|
71
|
+
doc.write(@_xml, 2)
|
72
|
+
end
|
73
|
+
@_xml
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
def tagify(s)
|
78
|
+
s.to_s.gsub(/\/(.?)/) {
|
79
|
+
"::#{$1.upcase}"
|
80
|
+
}.gsub(/(?:^|_)(.)/) {
|
81
|
+
$1.upcase
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
def value_at(sym)
|
86
|
+
node = REXML::XPath.first(@doc, "//#{tagify(sym)}")
|
87
|
+
node ? node.text : nil
|
88
|
+
end
|
89
|
+
|
90
|
+
def method_missing(sym, *args, &blk)
|
91
|
+
if self.class.elements.include?(sym)
|
92
|
+
value_at(sym)
|
93
|
+
else
|
94
|
+
super(sym, *args, &blk)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'active_merchant'
|
2
|
+
|
3
|
+
require 'active_merchant/billing/paymentech_orbital'
|
4
|
+
require 'active_merchant/billing/paymentech_orbital/request'
|
5
|
+
require 'active_merchant/billing/paymentech_orbital/request/end_of_day'
|
6
|
+
require 'active_merchant/billing/paymentech_orbital/request/new_order'
|
7
|
+
require 'active_merchant/billing/paymentech_orbital/request/profile_management'
|
8
|
+
require 'active_merchant/billing/paymentech_orbital/request/void'
|
9
|
+
require 'active_merchant/billing/paymentech_orbital/response'
|
data/test/factories.rb
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
Factory.define :credit_card, {
|
2
|
+
:class => ActiveMerchant::Billing::CreditCard,
|
3
|
+
:default_strategy => :build
|
4
|
+
} do |f|
|
5
|
+
f.number "4242424242424242"
|
6
|
+
f.month 9
|
7
|
+
f.year Time.now.year + 1
|
8
|
+
f.first_name 'Joe'
|
9
|
+
f.last_name 'Smith'
|
10
|
+
f.verification_value '123'
|
11
|
+
f.add_attribute :type, 'visa'
|
12
|
+
end
|
13
|
+
|
14
|
+
# Option factories
|
15
|
+
Options.define(:billing_address, :defaults => {
|
16
|
+
:zip => "12345",
|
17
|
+
:address1 => "123 Happy Place",
|
18
|
+
:address2 => "Apt 1",
|
19
|
+
:city => "SuperVille",
|
20
|
+
:state => "NY",
|
21
|
+
:name => "Joe Smith",
|
22
|
+
:country => "US"
|
23
|
+
})
|
24
|
+
Options.define(:gateway_auth, :defaults => {
|
25
|
+
:login => "user",
|
26
|
+
:password => "mytestpass",
|
27
|
+
:merchant_id => "1",
|
28
|
+
:bin => "1",
|
29
|
+
:terminal_id => "1"
|
30
|
+
})
|
31
|
+
Options.define(:soft_descriptors, :defaults => {
|
32
|
+
:merchant_name => 'merchant',
|
33
|
+
:merchant_url => 'mmmerchant.com'
|
34
|
+
})
|
35
|
+
|
36
|
+
# Request options:
|
37
|
+
Options.define(:request_options, :parent => :gateway_auth)
|
38
|
+
Options.define(:end_of_day_options, :parent => :request_options)
|
39
|
+
Options.define(:new_order_options, :parent => [
|
40
|
+
:request_options, :billing_address, :soft_descriptors
|
41
|
+
], :defaults => {
|
42
|
+
:currency_code => "840",
|
43
|
+
:currency_exponent => "2",
|
44
|
+
:order_id => "1",
|
45
|
+
:recurring_frequency => "#{Date.today.strftime("%d")} * ?"
|
46
|
+
})
|
47
|
+
Options.define(:profile_management_options, :parent => [
|
48
|
+
:request_options, :billing_address
|
49
|
+
], :defaults => {
|
50
|
+
:customer_ref_num => "123456"
|
51
|
+
})
|
52
|
+
Options.define(:void_options, :parent => :request_options, :defaults => {
|
53
|
+
:tx_ref_num => "1",
|
54
|
+
:tx_ref_idx => "1",
|
55
|
+
:order_id => "1"
|
56
|
+
})
|
57
|
+
|
58
|
+
# Pseudo factories
|
59
|
+
def gateway(options={})
|
60
|
+
ActiveMerchant::Billing::PaymentechOrbital::Gateway.new(Options(:gateway_auth, options))
|
61
|
+
end
|
62
|
+
|
63
|
+
def base_request(options={})
|
64
|
+
ActiveMerchant::Billing::PaymentechOrbital::Request::Base.new(
|
65
|
+
Options(:request_options, options)
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
def end_of_day_request(options={})
|
70
|
+
ActiveMerchant::Billing::PaymentechOrbital::Request::EndOfDay.new(
|
71
|
+
Options(:end_of_day_options, options)
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
def new_order_request(options={})
|
76
|
+
ActiveMerchant::Billing::PaymentechOrbital::Request::NewOrder.new(
|
77
|
+
"AC", 100, Factory(:credit_card), Options(:new_order_options, options)
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
def profile_management_request(action=:create, credit_card=Factory(:credit_card), options={})
|
82
|
+
ActiveMerchant::Billing::PaymentechOrbital::Request::ProfileManagement.new(
|
83
|
+
action, credit_card, Options(:profile_management_options, options)
|
84
|
+
)
|
85
|
+
end
|
86
|
+
|
87
|
+
def void_request(tx_ref_num="12345", tx_ref_idx="1", money="100", options={})
|
88
|
+
ActiveMerchant::Billing::PaymentechOrbital::Request::Void.new(
|
89
|
+
tx_ref_num, tx_ref_idx, money, Options(:void_options, options)
|
90
|
+
)
|
91
|
+
end
|
92
|
+
|
93
|
+
def new_order_response(doc=new_order_response_success, request_type="NewOrder", options={})
|
94
|
+
ActiveMerchant::Billing::PaymentechOrbital::Response.new(doc, request_type, options)
|
95
|
+
end
|
96
|
+
|
97
|
+
def new_order_response_success
|
98
|
+
<<-RESPONSE
|
99
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
100
|
+
<Response>
|
101
|
+
<NewOrderResp>
|
102
|
+
<IndustryType/>
|
103
|
+
<MessageType>AC</MessageType>
|
104
|
+
<MerchantID>000000</MerchantID>
|
105
|
+
<TerminalID>000</TerminalID>
|
106
|
+
<CardBrand>MC</CardBrand>
|
107
|
+
<AccountNum>5454545454545454</AccountNum>
|
108
|
+
<OrderID>1</OrderID>
|
109
|
+
<TxRefNum>4A785F5106CCDC41A936BFF628BF73036FEC5401</TxRefNum>
|
110
|
+
<TxRefIdx>1</TxRefIdx>
|
111
|
+
<ProcStatus>0</ProcStatus>
|
112
|
+
<ApprovalStatus>1</ApprovalStatus>
|
113
|
+
<RespCode>00</RespCode>
|
114
|
+
<AVSRespCode>B </AVSRespCode>
|
115
|
+
<CVV2RespCode>M</CVV2RespCode>
|
116
|
+
<AuthCode>tst554</AuthCode>
|
117
|
+
<RecurringAdviceCd/>
|
118
|
+
<CAVVRespCode/>
|
119
|
+
<StatusMsg>Approved</StatusMsg>
|
120
|
+
<RespMsg/>
|
121
|
+
<HostRespCode>100</HostRespCode>
|
122
|
+
<HostAVSRespCode>I3</HostAVSRespCode>
|
123
|
+
<HostCVV2RespCode>M</HostCVV2RespCode>
|
124
|
+
<CustomerRefNum>2145108</CustomerRefNum>
|
125
|
+
<CustomerName>JOE SMITH</CustomerName>
|
126
|
+
<ProfileProcStatus>0</ProfileProcStatus>
|
127
|
+
<CustomerProfileMessage>Profile Created</CustomerProfileMessage>
|
128
|
+
<RespTime>121825</RespTime>
|
129
|
+
</NewOrderResp>
|
130
|
+
</Response>
|
131
|
+
RESPONSE
|
132
|
+
end
|
133
|
+
|
134
|
+
def new_order_response_failure
|
135
|
+
<<-RESPONSE
|
136
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
137
|
+
<Response>
|
138
|
+
<QuickResp>
|
139
|
+
<ProcStatus>841</ProcStatus>
|
140
|
+
<StatusMsg>Error validating card/account number range</StatusMsg>
|
141
|
+
<CustomerBin></CustomerBin>
|
142
|
+
<CustomerMerchantID></CustomerMerchantID>
|
143
|
+
<CustomerName></CustomerName>
|
144
|
+
<CustomerRefNum></CustomerRefNum>
|
145
|
+
<CustomerProfileAction></CustomerProfileAction>
|
146
|
+
<ProfileProcStatus>9576</ProfileProcStatus>
|
147
|
+
<CustomerProfileMessage>Profile: Unable to Perform Profile Transaction. The Associated Transaction Failed. </CustomerProfileMessage>
|
148
|
+
<CustomerAddress1></CustomerAddress1>
|
149
|
+
<CustomerAddress2></CustomerAddress2>
|
150
|
+
<CustomerCity></CustomerCity>
|
151
|
+
<CustomerState></CustomerState>
|
152
|
+
<CustomerZIP></CustomerZIP>
|
153
|
+
<CustomerEmail></CustomerEmail>
|
154
|
+
<CustomerPhone></CustomerPhone>
|
155
|
+
<CustomerProfileOrderOverrideInd></CustomerProfileOrderOverrideInd>
|
156
|
+
<OrderDefaultDescription></OrderDefaultDescription>
|
157
|
+
<OrderDefaultAmount></OrderDefaultAmount>
|
158
|
+
<CustomerAccountType></CustomerAccountType>
|
159
|
+
<CCAccountNum></CCAccountNum>
|
160
|
+
<CCExpireDate></CCExpireDate>
|
161
|
+
<ECPAccountDDA></ECPAccountDDA>
|
162
|
+
<ECPAccountType></ECPAccountType>
|
163
|
+
<ECPAccountRT></ECPAccountRT>
|
164
|
+
<ECPBankPmtDlv></ECPBankPmtDlv>
|
165
|
+
<SwitchSoloStartDate></SwitchSoloStartDate>
|
166
|
+
<SwitchSoloIssueNum></SwitchSoloIssueNum>
|
167
|
+
</QuickResp>
|
168
|
+
</Response>
|
169
|
+
RESPONSE
|
170
|
+
end
|
data/test/options.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# Simple stupid factory girl inspired way to easily setup
|
2
|
+
# option hashes across the tests.
|
3
|
+
#
|
4
|
+
# Setup like:
|
5
|
+
#
|
6
|
+
# Options.define(:address, :defaults => {
|
7
|
+
# :street => "1234 somewhere lane",
|
8
|
+
# :city => "right here town",
|
9
|
+
# :zip => "12345"
|
10
|
+
# })
|
11
|
+
#
|
12
|
+
# Use like:
|
13
|
+
#
|
14
|
+
# Options(:address)
|
15
|
+
#
|
16
|
+
# Over ride defaults like:
|
17
|
+
#
|
18
|
+
# Options(:address, :street => "4321 somewhere else")
|
19
|
+
#
|
20
|
+
# Provide nil to remove an option:
|
21
|
+
#
|
22
|
+
# Options(:address, :street => nil) => {:city => "right here town", :zip => "12345"}
|
23
|
+
#
|
24
|
+
# Option hashes can have parent hashes:
|
25
|
+
#
|
26
|
+
# Options.define(:new_address, {
|
27
|
+
# :parent => :address,
|
28
|
+
# :defaults => {
|
29
|
+
# :street => "4321 somewhere else",
|
30
|
+
# :apartment => "3"
|
31
|
+
# }
|
32
|
+
# })
|
33
|
+
# Options(:new_address) => {
|
34
|
+
# :street => "4321 somewhere else", :apartment => "3", :city => "right here town", :zip => "12345"
|
35
|
+
# }
|
36
|
+
#
|
37
|
+
# Multiple parents can be combined:
|
38
|
+
#
|
39
|
+
# Options.define(:name, :defaults => {
|
40
|
+
# :first_name => "joe",
|
41
|
+
# :last_name => "smith"
|
42
|
+
# })
|
43
|
+
#
|
44
|
+
# Options.define(:full_address, :parent => [ :name, :address ], :defaults => {
|
45
|
+
# :phone_number => "5555555555"
|
46
|
+
# })
|
47
|
+
#
|
48
|
+
# Options(:full_address) => {
|
49
|
+
# :street=>"1234 somewhere lane", :zip=>"12345", :city=>"right here town",
|
50
|
+
# :first_name=>"joe", :phone_number=>"5555555555", :last_name=>"smith"
|
51
|
+
# }
|
52
|
+
#
|
53
|
+
class Options
|
54
|
+
class << self
|
55
|
+
def define(name, options={})
|
56
|
+
self[name] = (self[options[:parent]] || {}).merge(options[:defaults] || {})
|
57
|
+
end
|
58
|
+
|
59
|
+
def [](k)
|
60
|
+
return {} if k.nil?
|
61
|
+
|
62
|
+
if k.is_a?(Array)
|
63
|
+
Hash[*k.map { |default|
|
64
|
+
self[default].map { |k,v|
|
65
|
+
[k,v]
|
66
|
+
}
|
67
|
+
}.flatten]
|
68
|
+
else
|
69
|
+
factories[k.to_sym]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def []=(k,v)
|
74
|
+
factories[k.to_sym] = v
|
75
|
+
end
|
76
|
+
|
77
|
+
def factories
|
78
|
+
@@_factories ||= {}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def Options(name, options={})
|
84
|
+
Options[name].merge(options).delete_if { |k,v| v.nil? }
|
85
|
+
end
|