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