authorize_net 0.0.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.
- checksums.yaml +7 -0
- data/lib/authorize_net/address.rb +22 -0
- data/lib/authorize_net/api.rb +325 -0
- data/lib/authorize_net/credit_card.rb +16 -0
- data/lib/authorize_net/customer_profile.rb +26 -0
- data/lib/authorize_net/data_object.rb +123 -0
- data/lib/authorize_net/error_handler.rb +137 -0
- data/lib/authorize_net/exception.rb +13 -0
- data/lib/authorize_net/payment_profile.rb +39 -0
- data/lib/authorize_net/request.rb +93 -0
- data/lib/authorize_net/response.rb +50 -0
- data/lib/authorize_net/transaction.rb +45 -0
- data/lib/authorize_net/util.rb +55 -0
- data/lib/authorize_net.rb +30 -0
- metadata +78 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: af027737f355d0be9ecdf632b089872b67c055ba
|
4
|
+
data.tar.gz: af7373a60654eb83262507dd8b5b3475bf0018f1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 67c4b05bbea66632978c76b3d7e35ce210746fba8cdd7bd1cf1479a5499d863d3df2da4cc55396d049524a92d52076ac5a091a231424fa085bc67a0eef1d5f50
|
7
|
+
data.tar.gz: 3045de76b817e9e898478829c768c7165be7db6ffb0b96f13065fe13be8e89f4a1800aaf2df6704d729d32b5d03fc35f212b3efc4f85c6c9cb252852ef24f067
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'authorize_net/data_object'
|
2
|
+
|
3
|
+
class AuthorizeNet::Address < AuthorizeNet::DataObject
|
4
|
+
|
5
|
+
ATTRIBUTES = {
|
6
|
+
:first_name => {:key => "firstName"},
|
7
|
+
:last_name => {:key => "lastName"},
|
8
|
+
:company => nil,
|
9
|
+
:address => nil,
|
10
|
+
:city => nil,
|
11
|
+
:state => nil,
|
12
|
+
:zip => nil,
|
13
|
+
:country => nil,
|
14
|
+
:phone => nil,
|
15
|
+
:fax => nil,
|
16
|
+
}
|
17
|
+
|
18
|
+
self::ATTRIBUTES.keys.each do |attr|
|
19
|
+
attr_accessor attr
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,325 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
# ===============================================================
|
4
|
+
# This class uses the AuthroizeRequest object to interact with
|
5
|
+
# the Authorize.Net API
|
6
|
+
#
|
7
|
+
# Add any new Authroize.Net API endpoints here
|
8
|
+
# ===============================================================
|
9
|
+
class AuthorizeNet::Api
|
10
|
+
|
11
|
+
def initialize(api_login_id, api_transaction_key, is_test_api)
|
12
|
+
@api_login_id = api_login_id
|
13
|
+
@api_transaction_key = api_transaction_key
|
14
|
+
@is_test_api = is_test_api
|
15
|
+
@logger = nil
|
16
|
+
@log_full_request = false
|
17
|
+
end
|
18
|
+
|
19
|
+
def setLogger(logger)
|
20
|
+
@logger = logger
|
21
|
+
end
|
22
|
+
|
23
|
+
def setLogFullRequest(log_full_request)
|
24
|
+
@log_full_request = log_full_request
|
25
|
+
end
|
26
|
+
|
27
|
+
# ===============================================================
|
28
|
+
# Charges the given credit card
|
29
|
+
# @param Number amount
|
30
|
+
# @param AuthorizeNet::CreditCard credit_card
|
31
|
+
# @param AuthorizeNet::Address billing_address
|
32
|
+
# @return {transaction_id}
|
33
|
+
# ===============================================================
|
34
|
+
def chargeCard(amount, credit_card, billing_address=nil)
|
35
|
+
xml_obj = getXmlAuth
|
36
|
+
xml_obj["transactionRequest"] = {
|
37
|
+
"transactionType" => "authCaptureTransaction",
|
38
|
+
"amount" => amount,
|
39
|
+
"payment" => {
|
40
|
+
"creditCard" => credit_card.to_h,
|
41
|
+
},
|
42
|
+
}
|
43
|
+
if !billing_address.nil?
|
44
|
+
xml_obj["transactionRequest"]["billTo"] = billing_address.to_h
|
45
|
+
end
|
46
|
+
|
47
|
+
response = sendRequest("createTransactionRequest", xml_obj)
|
48
|
+
if !response.nil?
|
49
|
+
return AuthorizeNet::Transaction.parse(response)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# ===============================================================
|
54
|
+
# Creates the CustomerProfile and charges the first listed
|
55
|
+
# PaymentProfile on AuthorizeNet
|
56
|
+
# @param Number amount
|
57
|
+
# @param AuthorizeNet::CustomerProfile customer_profile
|
58
|
+
# @return {customer_profile_id, payment_profile_id}
|
59
|
+
# ===============================================================
|
60
|
+
def chargeAndCreateProfile(amount, customer_profile)
|
61
|
+
if customer_profile.payment_profiles.empty?
|
62
|
+
raise "[AuthorizeNet] CustomerProfile in Api.chargeAndCreateProfile requires a PaymentProfile"
|
63
|
+
end
|
64
|
+
|
65
|
+
payment_profile = customer_profile.payment_profiles.first
|
66
|
+
xml_obj = getXmlAuth
|
67
|
+
xml_obj["transactionRequest"] = {
|
68
|
+
"transactionType" => "authCaptureTransaction",
|
69
|
+
"amount" => amount,
|
70
|
+
"payment" => {
|
71
|
+
"creditCard" => payment_profile.credit_card.to_h,
|
72
|
+
},
|
73
|
+
"profile" => {
|
74
|
+
"createProfile" => true,
|
75
|
+
},
|
76
|
+
"customer" => {
|
77
|
+
"id" => customer_profile.merchant_id,
|
78
|
+
"email" => customer_profile.email,
|
79
|
+
"description" => customer_profile.description,
|
80
|
+
"billTo" => payment_profile.billing_address.to_h,
|
81
|
+
},
|
82
|
+
}
|
83
|
+
|
84
|
+
response = sendRequest("createTransactionRequest", xml_obj)
|
85
|
+
|
86
|
+
if !response.nil?
|
87
|
+
return {
|
88
|
+
:transaction => AuthorizeNet::Transaction.parse(response),
|
89
|
+
:customer_profile_id => AuthorizeNet::Util.getXmlValue(
|
90
|
+
response, "customerProfileId"),
|
91
|
+
:payment_profile_id => AuthorizeNet::Util.getXmlValue(
|
92
|
+
response, "customerPaymentProfileIdList numericString"),
|
93
|
+
}
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# ===============================================================
|
98
|
+
# Charges the given profile and payment profile on Authorize.net
|
99
|
+
# @param Number amount
|
100
|
+
# @param String/Number customer_profile_id
|
101
|
+
# @param String/Number payment_profile_id
|
102
|
+
# @return transaction_id
|
103
|
+
# ===============================================================
|
104
|
+
def chargeProfile(amount, profile_id, payment_profile_id)
|
105
|
+
xml_obj = getXmlAuth
|
106
|
+
xml_obj["transactionRequest"] = {
|
107
|
+
"transactionType" => "authCaptureTransaction",
|
108
|
+
"amount" => amount,
|
109
|
+
"profile" => {
|
110
|
+
"customerProfileId" => profile_id,
|
111
|
+
"paymentProfile" => {
|
112
|
+
"paymentProfileId" => payment_profile_id,
|
113
|
+
},
|
114
|
+
},
|
115
|
+
}
|
116
|
+
|
117
|
+
response = sendRequest("createTransactionRequest", xml_obj)
|
118
|
+
if !response.nil?
|
119
|
+
return AuthorizeNet::Transaction.parse(response)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# ===============================================================
|
124
|
+
# Creates the given customer profile on Authorize.net
|
125
|
+
# @param AuthorizeNet::CustomerProfile customer_profile
|
126
|
+
# @param Number amount
|
127
|
+
# @param AuthorizeNet::ValidationMode validation_mode (optional)
|
128
|
+
# @return transaction_id
|
129
|
+
# ===============================================================
|
130
|
+
def createCustomerProfile(customer_profile, validation_mode=nil)
|
131
|
+
xml_obj = getXmlAuth
|
132
|
+
xml_obj["profile"] = customer_profile.to_h
|
133
|
+
|
134
|
+
addValidationMode!(xml_obj, validation_mode)
|
135
|
+
response = sendRequest("createCustomerProfileRequest", xml_obj)
|
136
|
+
|
137
|
+
if !response.nil?
|
138
|
+
return {
|
139
|
+
:customer_profile_id => AuthorizeNet::Util.getXmlValue(
|
140
|
+
response, "customerProfileId"),
|
141
|
+
:payment_profile_id => AuthorizeNet::Util.getXmlValue(
|
142
|
+
response, "customerPaymentProfileIdList numericString"),
|
143
|
+
}
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# ===============================================================
|
148
|
+
# Create Customer Payment Profile
|
149
|
+
# @param String/Number customer_profile_id
|
150
|
+
# @param AuthorizeNet::PaymentProfile payment_profile
|
151
|
+
# @param AuthorizeNet::ValidationMode validation_mode (optional)
|
152
|
+
# @return {customer_profile_id, payment_profile_id}
|
153
|
+
# ===============================================================
|
154
|
+
def createPaymentProfile(customer_profile_id, payment_profile, validation_mode=nil)
|
155
|
+
xml_obj = getXmlAuth
|
156
|
+
xml_obj["customerProfileId"] = customer_profile_id
|
157
|
+
xml_obj["paymentProfile"] = payment_profile.to_h
|
158
|
+
|
159
|
+
addValidationMode!(xml_obj, validation_mode)
|
160
|
+
response = sendRequest("createCustomerPaymentProfileRequest", xml_obj)
|
161
|
+
|
162
|
+
if !response.nil?
|
163
|
+
return {
|
164
|
+
:customer_profile_id => AuthorizeNet::Util.getXmlValue(
|
165
|
+
response, "customerProfileId"),
|
166
|
+
:payment_profile_id => AuthorizeNet::Util.getXmlValue(
|
167
|
+
response, "customerPaymentProfileId"),
|
168
|
+
}
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# ===============================================================
|
173
|
+
# Delete Customer Payment Profile
|
174
|
+
# @param String/Number customer_profile_id
|
175
|
+
# @param String/Number payment_profile_id
|
176
|
+
# @return boolean is delete successful?
|
177
|
+
# ===============================================================
|
178
|
+
def deletePaymentProfile(customer_profile_id, payment_profile_id)
|
179
|
+
xml_obj = getXmlAuth
|
180
|
+
xml_obj["customerProfileId"] = customer_profile_id
|
181
|
+
xml_obj["customerPaymentProfileId"] = payment_profile_id
|
182
|
+
|
183
|
+
response = sendRequest("deleteCustomerPaymentProfileRequest", xml_obj)
|
184
|
+
return !response.nil?
|
185
|
+
end
|
186
|
+
|
187
|
+
# ===============================================================
|
188
|
+
# Validate Customer Payment Profile
|
189
|
+
# @param String/Number customer_profile_id
|
190
|
+
# @param String/Number payment_profile_id
|
191
|
+
# @param AuthorizeNet::ValidationMode::(String) validation_mode
|
192
|
+
# @return boolean is update successful?
|
193
|
+
# ===============================================================
|
194
|
+
def validatePaymentProfile(customer_profile_id, payment_profile_id, validation_mode)
|
195
|
+
xml_obj = getXmlAuth
|
196
|
+
xml_obj["customerProfileId"] = customer_profile_id
|
197
|
+
xml_obj["customerPaymentProfileId"] = payment_profile_id
|
198
|
+
xml_obj["validationMode"] = validation_mode
|
199
|
+
|
200
|
+
response = sendRequest("validateCustomerPaymentProfileRequest", xml_obj)
|
201
|
+
return !response.nil?
|
202
|
+
end
|
203
|
+
|
204
|
+
# ===============================================================
|
205
|
+
# Get customer profile information
|
206
|
+
# @param String/Number customer_profile_id
|
207
|
+
# @param String/Number customer_profile_id
|
208
|
+
# @return AuthorizeNet::CustomerProfile
|
209
|
+
# ===============================================================
|
210
|
+
def getCustomerProfile(customer_profile_id)
|
211
|
+
xml_obj = getXmlAuth
|
212
|
+
xml_obj["customerProfileId"] = customer_profile_id
|
213
|
+
|
214
|
+
response = sendRequest("getCustomerProfileRequest", xml_obj)
|
215
|
+
if response
|
216
|
+
return AuthorizeNet::CustomerProfile.parse(response)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
# ===============================================================
|
221
|
+
# Gets transaction information
|
222
|
+
# @param String/Number customer_profile_id
|
223
|
+
# @param String/Number transaction_id
|
224
|
+
# @return AuthorizeNet::Transaction
|
225
|
+
# ===============================================================
|
226
|
+
def getTransactionInfo(transaction_id)
|
227
|
+
xml_obj = getXmlAuth
|
228
|
+
xml_obj["transId"] = transaction_id
|
229
|
+
|
230
|
+
response = sendRequest("getTransactionDetailsRequest", xml_obj)
|
231
|
+
if response
|
232
|
+
return AuthorizeNet::Transaction.parse(response)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
|
237
|
+
|
238
|
+
private
|
239
|
+
|
240
|
+
def getXmlAuth
|
241
|
+
return {
|
242
|
+
"merchantAuthentication" => {
|
243
|
+
"name" => @api_login_id,
|
244
|
+
"transactionKey" => @api_transaction_key,
|
245
|
+
}
|
246
|
+
}
|
247
|
+
end
|
248
|
+
|
249
|
+
def addValidationMode!(xml_obj, validation_mode)
|
250
|
+
if validation_mode
|
251
|
+
xml_obj["validationMode"] = validation_mode
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# =============================================
|
256
|
+
# Looks for potential errors in the response
|
257
|
+
# and raises an error if it finds any
|
258
|
+
# Passes through OK responses
|
259
|
+
# @throws AuthorizeNet::Exception
|
260
|
+
# =============================================
|
261
|
+
def handleResponse(raw_response)
|
262
|
+
logHttpResponse(raw_response)
|
263
|
+
|
264
|
+
response = AuthorizeNet::Response.parseXml(raw_response.read_body)
|
265
|
+
if response.result == AuthorizeNet::RESULT_OK && response.errors.nil?
|
266
|
+
return response.parsed_xml
|
267
|
+
else
|
268
|
+
logErrorResponse(response)
|
269
|
+
AuthorizeNet::ErrorHandler.handle(response)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
# =============================================
|
274
|
+
# Send HTTP request to Authorize Net
|
275
|
+
# @param Net::HTTPResponse
|
276
|
+
# @return response
|
277
|
+
# =============================================
|
278
|
+
def sendRequest(type, xml_obj)
|
279
|
+
uri = @is_test_api ? AuthorizeNet::TEST_URI : AuthorizeNet::URI
|
280
|
+
request = AuthorizeNet::Request.new(type, xml_obj, uri)
|
281
|
+
|
282
|
+
if @logger.respond_to? :info
|
283
|
+
@logger.info(request.toLog(@log_full_request))
|
284
|
+
end
|
285
|
+
|
286
|
+
return handleResponse(request.postRequest)
|
287
|
+
end
|
288
|
+
|
289
|
+
# =============================================
|
290
|
+
# Log HTTP response from Authorize Net
|
291
|
+
# @param Net::HTTPResponse
|
292
|
+
# @return String log
|
293
|
+
# =============================================
|
294
|
+
def logHttpResponse(response)
|
295
|
+
if @logger.respond_to? :logHttpResponse
|
296
|
+
@logger.logHttpResponse(response)
|
297
|
+
elsif @logger.respond_to? :info
|
298
|
+
@logger.info("[AuthorizeNet] HTTP Response code=#{response.code} message=#{response.message}")
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
# =============================================
|
303
|
+
# Returns a log string with http response data
|
304
|
+
# @param Net::HTTPResponse
|
305
|
+
# @throws RuntimeError
|
306
|
+
# =============================================
|
307
|
+
def logErrorResponse(response)
|
308
|
+
if @logger.respond_to? :info
|
309
|
+
@logger.info("[AuthorizeNet] Responded with resultCode=\"#{response.result}\"")
|
310
|
+
end
|
311
|
+
|
312
|
+
if !response.messages.nil? and @logger.respond_to? :info
|
313
|
+
response.messages.each do |msg|
|
314
|
+
@logger.info("[AuthorizeNet] Message code=\"#{msg[:code]}\" text=\"#{msg[:text]}\"")
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
if !response.errors.nil? and @logger.respond_to? :error
|
319
|
+
response.errors.each do |error|
|
320
|
+
@logger.error("[AuthorizeNet] Error code=\"#{error[:code]}\" text=\"#{error[:text]}\"")
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'authorize_net/data_object'
|
2
|
+
|
3
|
+
class AuthorizeNet::CreditCard < AuthorizeNet::DataObject
|
4
|
+
|
5
|
+
ATTRIBUTES = {
|
6
|
+
:card_num => {:key => "cardNumber"},
|
7
|
+
:expiration => {:key => "expirationDate"},
|
8
|
+
:security_code => {:key => "cardCode"},
|
9
|
+
:card_type => {:key => "cardType"},
|
10
|
+
}
|
11
|
+
|
12
|
+
self::ATTRIBUTES.keys.each do |attr|
|
13
|
+
attr_accessor attr
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'authorize_net/data_object'
|
2
|
+
require 'authorize_net/payment_profile'
|
3
|
+
|
4
|
+
class AuthorizeNet::CustomerProfile < AuthorizeNet::DataObject
|
5
|
+
|
6
|
+
ATTRIBUTES = {
|
7
|
+
:id => {:key => "customerProfileId"},
|
8
|
+
:merchant_id => {:key => "merchantCustomerId"},
|
9
|
+
:email => nil,
|
10
|
+
:description => nil,
|
11
|
+
:payment_profiles => {
|
12
|
+
:key => "paymentProfiles",
|
13
|
+
:type => AuthorizeNet::DataObject::TYPE_OBJECT_ARRAY,
|
14
|
+
:class => AuthorizeNet::PaymentProfile,
|
15
|
+
},
|
16
|
+
}
|
17
|
+
|
18
|
+
self::ATTRIBUTES.keys.each do |attr|
|
19
|
+
attr_accessor attr
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@payment_profiles = []
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# *** NOTE ***
|
2
|
+
# Data objects must have a static ATTRIBUTES hash followed by these lines
|
3
|
+
#
|
4
|
+
# self::ATTRIBUTES.keys.each do |attr|
|
5
|
+
# attr_accessor attr
|
6
|
+
# end
|
7
|
+
#
|
8
|
+
class AuthorizeNet::DataObject
|
9
|
+
|
10
|
+
TYPE_ARRAY = :type_array
|
11
|
+
TYPE_OBJECT = :type_object
|
12
|
+
TYPE_OBJECT_ARRAY = :type_object_array
|
13
|
+
|
14
|
+
# =======================================================
|
15
|
+
# Parses XML from the values in the ATTRIBUTES hash in
|
16
|
+
# to the attributes of this object.
|
17
|
+
# =======================================================
|
18
|
+
def parse(xml)
|
19
|
+
if xml.nil? || !xml.respond_to?(:at_css)
|
20
|
+
return
|
21
|
+
end
|
22
|
+
|
23
|
+
self.class::ATTRIBUTES.keys.each do |attr|
|
24
|
+
spec = self.class::ATTRIBUTES[attr].to_h
|
25
|
+
xml_key = spec[:key] || attr.to_s
|
26
|
+
type = spec[:type]
|
27
|
+
type_class = spec[:class]
|
28
|
+
|
29
|
+
if (type == TYPE_OBJECT or type == TYPE_OBJECT_ARRAY) and type_class.nil?
|
30
|
+
raise "DataObject=#{self.class} Attribute=#{attr} of type #{type} must specify a class"
|
31
|
+
end
|
32
|
+
|
33
|
+
if type == TYPE_OBJECT
|
34
|
+
obj_xml = xml.at_css(xml_key)
|
35
|
+
send("#{attr}=", type_class.parse(obj_xml))
|
36
|
+
|
37
|
+
elsif type == TYPE_OBJECT_ARRAY
|
38
|
+
array_xml = xml.css(xml_key)
|
39
|
+
send("#{attr}=", array_xml.map{ |x| type_class.parse(x) })
|
40
|
+
|
41
|
+
elsif type == TYPE_ARRAY
|
42
|
+
array_xml = xml.css(xml_key)
|
43
|
+
send("#{attr}=", array_xml.map{ |x| x.inner_text })
|
44
|
+
|
45
|
+
else
|
46
|
+
send("#{attr}=", AuthorizeNet::Util.getXmlValue(xml, xml_key))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# =======================================================
|
52
|
+
# Turns this object into a hash using the keys specified
|
53
|
+
# as the values in ATTRIBUTES
|
54
|
+
#
|
55
|
+
# If the value in ATTRIBUTES is nil, use the String
|
56
|
+
# version of the attribute itself
|
57
|
+
# =======================================================
|
58
|
+
def to_h(include_blanks=false)
|
59
|
+
hash = {}
|
60
|
+
self.class::ATTRIBUTES.keys.each do |attr|
|
61
|
+
spec = self.class::ATTRIBUTES[attr].to_h
|
62
|
+
key = spec[:key] || attr.to_s
|
63
|
+
type = spec[:type]
|
64
|
+
value = send(attr)
|
65
|
+
|
66
|
+
if value.nil?
|
67
|
+
if include_blanks
|
68
|
+
hash[key] = nil
|
69
|
+
end
|
70
|
+
elsif type == TYPE_OBJECT
|
71
|
+
hash[key] = value.to_h
|
72
|
+
elsif type == TYPE_OBJECT_ARRAY
|
73
|
+
hash[key] = value.map{ |e| e.to_h }
|
74
|
+
else
|
75
|
+
hash[key] = value
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
return hash
|
80
|
+
end
|
81
|
+
|
82
|
+
# =======================================================
|
83
|
+
# Turns this object into a hash using the keys specified
|
84
|
+
# as the keys in ATTRIBUTES
|
85
|
+
# =======================================================
|
86
|
+
def serialize
|
87
|
+
hash = {}
|
88
|
+
self.class::ATTRIBUTES.keys.each do |attr|
|
89
|
+
spec = self.class::ATTRIBUTES[attr].to_h
|
90
|
+
type = spec[:type]
|
91
|
+
value = send(attr)
|
92
|
+
|
93
|
+
if value.nil?
|
94
|
+
hash[attr] = nil
|
95
|
+
elsif type == TYPE_OBJECT
|
96
|
+
hash[attr] = value.serialize
|
97
|
+
elsif type == TYPE_OBJECT_ARRAY
|
98
|
+
hash[attr] = value.map{ |e| e.serialize }
|
99
|
+
else
|
100
|
+
hash[attr] = value
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
return hash
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
class << self
|
109
|
+
# =============================================
|
110
|
+
# Parses xml into a new instance of this class
|
111
|
+
# =============================================
|
112
|
+
def parse(xml)
|
113
|
+
if xml.nil? || !xml.respond_to?(:at_css)
|
114
|
+
return
|
115
|
+
end
|
116
|
+
|
117
|
+
object = new
|
118
|
+
object.parse(xml)
|
119
|
+
return object
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
|
2
|
+
class AuthorizeNet::ErrorHandler
|
3
|
+
class << self
|
4
|
+
ERROR_FIELD_REGEXES = [
|
5
|
+
/'AnetApi\/xml\/v1\/schema\/AnetApiSchema\.xsd:([a-zA-Z]*)'/,
|
6
|
+
/The element '([a-zA-Z]*)' in namespace 'AnetApi\/xml\/v1\/schema\/AnetApiSchema.xsd'/
|
7
|
+
]
|
8
|
+
|
9
|
+
MESSAGE_CODES = {
|
10
|
+
"E00003" => :invalid_field,
|
11
|
+
"E00015" => :invalid_field_length,
|
12
|
+
"E00027" => :missing_required_field,
|
13
|
+
"E00039" => :duplicate_record_exists,
|
14
|
+
"E00041" => :customer_profile_info_required,
|
15
|
+
}
|
16
|
+
|
17
|
+
ERROR_CODES = {
|
18
|
+
"210" => :transaction_declined,
|
19
|
+
"6" => :invalid_card_number,
|
20
|
+
"7" => :invalid_expiration_date,
|
21
|
+
"8" => :expired_credit_card,
|
22
|
+
}
|
23
|
+
|
24
|
+
ERROR_FIELDS = {
|
25
|
+
:invalid_card_number => :card_number,
|
26
|
+
:invalid_expiration_date => :card_expiration,
|
27
|
+
:expired_credit_card => :card_expiration,
|
28
|
+
|
29
|
+
"cardNumber" => :card_number,
|
30
|
+
"expirationDate" => :card_expiration,
|
31
|
+
"cardCode" => :card_security_code,
|
32
|
+
}
|
33
|
+
|
34
|
+
# =============================================
|
35
|
+
# Creates an exception, populates it as well
|
36
|
+
# as possible, and then raises it
|
37
|
+
# @param AuthorizeNet::Response
|
38
|
+
# @throws AuthorizeNet::Exception
|
39
|
+
# =============================================
|
40
|
+
def handle(response)
|
41
|
+
exception = AuthorizeNet::Exception.new
|
42
|
+
|
43
|
+
if !response.errors.nil?
|
44
|
+
first_error = response.errors.first
|
45
|
+
exception.message = first_error[:text]
|
46
|
+
|
47
|
+
# Add errors to exception
|
48
|
+
response.errors.each do |error|
|
49
|
+
exception.errors << buildError(error)
|
50
|
+
end
|
51
|
+
|
52
|
+
raise exception
|
53
|
+
|
54
|
+
# If there are no errors, then the "messages" are probably errors... *sigh*
|
55
|
+
elsif !response.messages.nil? and response.result == AuthorizeNet::RESULT_ERROR
|
56
|
+
first_msg = response.messages.first
|
57
|
+
exception.message = first_msg[:text]
|
58
|
+
|
59
|
+
# Add messages (that are sometimes actually errors) to exception
|
60
|
+
response.messages.each do |msg|
|
61
|
+
exception.errors << buildError(msg)
|
62
|
+
end
|
63
|
+
|
64
|
+
raise exception
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
# =============================================
|
70
|
+
# Attempts to determine the error type and field
|
71
|
+
# for an error hash
|
72
|
+
# @param Hash error
|
73
|
+
# @return Hash error
|
74
|
+
# =============================================
|
75
|
+
def buildError(error)
|
76
|
+
code = error[:code]
|
77
|
+
text = error[:text]
|
78
|
+
type = getTypeFromCode(code)
|
79
|
+
field = nil
|
80
|
+
|
81
|
+
if !type.nil? and ERROR_FIELDS.has_key? type
|
82
|
+
field = ERROR_FIELDS[type]
|
83
|
+
else
|
84
|
+
field = getFieldFromText(text)
|
85
|
+
end
|
86
|
+
|
87
|
+
return {
|
88
|
+
:code => code,
|
89
|
+
:text => text,
|
90
|
+
:type => type,
|
91
|
+
:field => field,
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
# =============================================
|
96
|
+
# Attempts to determine the error type given
|
97
|
+
# an error code
|
98
|
+
# @param String code
|
99
|
+
# @return Symbol|nil type
|
100
|
+
# =============================================
|
101
|
+
def getTypeFromCode(code)
|
102
|
+
if ERROR_CODES.has_key? code
|
103
|
+
return ERROR_CODES[code]
|
104
|
+
elsif MESSAGE_CODES.has_key? code
|
105
|
+
return MESSAGE_CODES[code]
|
106
|
+
end
|
107
|
+
return nil
|
108
|
+
end
|
109
|
+
|
110
|
+
# =============================================
|
111
|
+
# Attempts to determine the error field given
|
112
|
+
# an error message
|
113
|
+
# @param String text
|
114
|
+
# @return Symbol|nil field
|
115
|
+
# =============================================
|
116
|
+
def getFieldFromText(text)
|
117
|
+
if text.nil?
|
118
|
+
return nil
|
119
|
+
end
|
120
|
+
|
121
|
+
ERROR_FIELD_REGEXES.each do |regex|
|
122
|
+
field_match = text.match(regex)
|
123
|
+
if !field_match.nil?
|
124
|
+
field = field_match[1]
|
125
|
+
|
126
|
+
if ERROR_FIELDS.keys.include? field
|
127
|
+
return ERROR_FIELDS[field]
|
128
|
+
end
|
129
|
+
return field
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
return nil
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class AuthorizeNet::Exception < Exception
|
2
|
+
|
3
|
+
GENERIC_ERROR_MESSAGE = "[AuthorizeNet] The Authorize.Net API returned an error"
|
4
|
+
|
5
|
+
attr_accessor :message
|
6
|
+
attr_accessor :errors
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@message = GENERIC_ERROR_MESSAGE
|
10
|
+
@errors = []
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'authorize_net/data_object'
|
2
|
+
require 'authorize_net/credit_card'
|
3
|
+
require 'authorize_net/address'
|
4
|
+
|
5
|
+
class AuthorizeNet::PaymentProfile < AuthorizeNet::DataObject
|
6
|
+
|
7
|
+
ATTRIBUTES = {
|
8
|
+
:id => {:key => "customerPaymentProfileId"},
|
9
|
+
:credit_card => {
|
10
|
+
:key => "creditCard",
|
11
|
+
:type => AuthorizeNet::DataObject::TYPE_OBJECT,
|
12
|
+
:class => AuthorizeNet::CreditCard,
|
13
|
+
},
|
14
|
+
:billing_address => {
|
15
|
+
:key => "billTo",
|
16
|
+
:type => AuthorizeNet::DataObject::TYPE_OBJECT,
|
17
|
+
:class => AuthorizeNet::Address,
|
18
|
+
},
|
19
|
+
}
|
20
|
+
|
21
|
+
self::ATTRIBUTES.keys.each do |attr|
|
22
|
+
attr_accessor attr
|
23
|
+
end
|
24
|
+
|
25
|
+
# Override
|
26
|
+
def to_h
|
27
|
+
hash = super
|
28
|
+
|
29
|
+
hash.delete('creditCard')
|
30
|
+
if !@credit_card.nil?
|
31
|
+
hash['payment'] = {
|
32
|
+
'creditCard' => @credit_card.to_h
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
return hash
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
# ===============================================================
|
5
|
+
# This class represents a request to the Authorize.Net API
|
6
|
+
#
|
7
|
+
# Add any logic that applies to ALL requests here
|
8
|
+
# ===============================================================
|
9
|
+
class AuthorizeNet::Request
|
10
|
+
|
11
|
+
attr_accessor :response
|
12
|
+
|
13
|
+
def initialize(type, data, uri)
|
14
|
+
@xml_data = data
|
15
|
+
@request_type = type
|
16
|
+
@uri = URI(uri)
|
17
|
+
@response = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
# =============================================
|
21
|
+
# Uses the given data to make a POST request
|
22
|
+
# =============================================
|
23
|
+
def postRequest
|
24
|
+
assertRequestData
|
25
|
+
assertRequestType
|
26
|
+
req = Net::HTTP::Post.new(@uri.request_uri)
|
27
|
+
req.add_field('Content-Type', 'text/xml')
|
28
|
+
req.body = buildXmlRequest
|
29
|
+
@response = sendRequest(req)
|
30
|
+
return @response
|
31
|
+
end
|
32
|
+
|
33
|
+
# =============================================
|
34
|
+
# Uses the given data to make a GET request
|
35
|
+
# =============================================
|
36
|
+
def getRequest
|
37
|
+
assertRequestType
|
38
|
+
req = Net::HTTP::Get.new(@uri.request_uri)
|
39
|
+
req.add_field('Content-Type', 'text/xml')
|
40
|
+
req.body = buildXmlRequest
|
41
|
+
end
|
42
|
+
|
43
|
+
# =============================================
|
44
|
+
# Make a log string for this request
|
45
|
+
# =============================================
|
46
|
+
def toLog(log_body)
|
47
|
+
log = "[AuthorizeNet] HTTP Request type=#{@request_type} uri=#{@uri}"
|
48
|
+
|
49
|
+
if log_body
|
50
|
+
log += " body=\"#{buildXmlRequest}\""
|
51
|
+
end
|
52
|
+
|
53
|
+
return log
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def assertRequestData
|
60
|
+
if @xml_data.nil?
|
61
|
+
raise "AuthorizeRequest has no xml data"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def assertRequestType
|
66
|
+
if @request_type.nil?
|
67
|
+
raise "AuthorizeRequest has no request type"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# =============================================
|
72
|
+
# Builds the full XML request using request
|
73
|
+
# type and the xml data object
|
74
|
+
# =============================================
|
75
|
+
def buildXmlRequest
|
76
|
+
xml_string = AuthorizeNet::XML_HEADER
|
77
|
+
xml_string += "<#{@request_type} xmlns=\"#{AuthorizeNet::XML_SCHEMA}\">"
|
78
|
+
xml_string += AuthorizeNet::Util.buildXmlFromObject(@xml_data)
|
79
|
+
xml_string += "</#{@request_type}>"
|
80
|
+
return xml_string
|
81
|
+
end
|
82
|
+
|
83
|
+
# =============================================
|
84
|
+
# Sends the input request to Authorize.Net
|
85
|
+
# =============================================
|
86
|
+
def sendRequest(req)
|
87
|
+
http = Net::HTTP.start(@uri.host, @uri.port, :use_ssl => @uri.scheme == 'https')
|
88
|
+
@response = http.request(req)
|
89
|
+
return @response
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
class AuthorizeNet::Response
|
4
|
+
|
5
|
+
attr_accessor :result
|
6
|
+
attr_accessor :errors
|
7
|
+
attr_accessor :messages
|
8
|
+
attr_accessor :raw_xml
|
9
|
+
attr_accessor :parsed_xml
|
10
|
+
|
11
|
+
class << self
|
12
|
+
|
13
|
+
# =============================================
|
14
|
+
# Returns a populated response object
|
15
|
+
# @param String xml
|
16
|
+
# @return AuthorizeNet::Response
|
17
|
+
# =============================================
|
18
|
+
def parseXml(xml)
|
19
|
+
response = new
|
20
|
+
response.raw_xml = xml
|
21
|
+
response.parsed_xml = Nokogiri::XML.parse(xml)
|
22
|
+
response.result = AuthorizeNet::Util.getXmlValue(response.parsed_xml, "resultCode")
|
23
|
+
|
24
|
+
errors = response.parsed_xml.at_css("errors")
|
25
|
+
if !errors.nil?
|
26
|
+
response.errors = []
|
27
|
+
errors.css("error").each do |xml_error|
|
28
|
+
response.errors << {
|
29
|
+
:code => AuthorizeNet::Util.getXmlValue(xml_error, "errorCode"),
|
30
|
+
:text => AuthorizeNet::Util.getXmlValue(xml_error, "errorText"),
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
messages = response.parsed_xml.at_css("messages")
|
36
|
+
if !messages.nil?
|
37
|
+
response.messages = []
|
38
|
+
messages.css("message").each do |xml_msg|
|
39
|
+
response.messages << {
|
40
|
+
:code => AuthorizeNet::Util.getXmlValue(xml_msg, "code"),
|
41
|
+
:text => AuthorizeNet::Util.getXmlValue(xml_msg, "text"),
|
42
|
+
}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
return response
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'authorize_net/data_object'
|
2
|
+
require 'authorize_net/address'
|
3
|
+
require 'authorize_net/customer_profile'
|
4
|
+
require 'authorize_net/credit_card'
|
5
|
+
require 'authorize_net/util'
|
6
|
+
|
7
|
+
class AuthorizeNet::Transaction < AuthorizeNet::DataObject
|
8
|
+
|
9
|
+
ATTRIBUTES = {
|
10
|
+
:id => {:key => "transId"},
|
11
|
+
:timestamp_local => {:key => "submitTimeLocal"},
|
12
|
+
:timestamp_utc => {:key => "submitTimeUTC"},
|
13
|
+
:type => {:key => "transactionType"},
|
14
|
+
:status => {:key => "transactionStatus"},
|
15
|
+
:account_num => {:key => "accountNumber"},
|
16
|
+
:account_type => {:key => "accountType"},
|
17
|
+
:auth_code => {:key => "authCode"},
|
18
|
+
:credit_card => {
|
19
|
+
:key => "creditCard",
|
20
|
+
:type => AuthorizeNet::DataObject::TYPE_OBJECT,
|
21
|
+
:class => AuthorizeNet::CreditCard,
|
22
|
+
},
|
23
|
+
:customer_profile => {
|
24
|
+
:key => "customer",
|
25
|
+
:type => AuthorizeNet::DataObject::TYPE_OBJECT,
|
26
|
+
:class => AuthorizeNet::CustomerProfile,
|
27
|
+
},
|
28
|
+
:billing_address => {
|
29
|
+
:key => "billTo",
|
30
|
+
:type => AuthorizeNet::DataObject::TYPE_OBJECT,
|
31
|
+
:class => AuthorizeNet::Address,
|
32
|
+
},
|
33
|
+
}
|
34
|
+
|
35
|
+
self::ATTRIBUTES.keys.each do |attr|
|
36
|
+
attr_accessor attr
|
37
|
+
end
|
38
|
+
|
39
|
+
def parse(xml)
|
40
|
+
super
|
41
|
+
|
42
|
+
@customer_profile.merchant_id = AuthorizeNet::Util.getXmlValue(xml, 'customer id')
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
class AuthorizeNet::Util
|
2
|
+
|
3
|
+
class << self
|
4
|
+
# ==============================================
|
5
|
+
# A wrapper for safely getting the inner value
|
6
|
+
# of an XML attribute only if it exists
|
7
|
+
#
|
8
|
+
# If multiple instances exist, return the first one
|
9
|
+
# ==============================================
|
10
|
+
def getXmlValue(xml, attr_string)
|
11
|
+
if !xml.respond_to? :at_css || attr_string.nil?
|
12
|
+
return nil
|
13
|
+
end
|
14
|
+
|
15
|
+
attr = xml.at_css(attr_string)
|
16
|
+
if !attr.nil?
|
17
|
+
return attr.inner_text
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# ==============================================
|
22
|
+
# Builds XML from Ruby Hashes/Arrays/Primitives
|
23
|
+
# ==============================================
|
24
|
+
def buildXmlFromObject(obj, parent_tag=nil)
|
25
|
+
xml = ""
|
26
|
+
has_parent = !parent_tag.nil?
|
27
|
+
|
28
|
+
# Arrays are formatted with the parent tag
|
29
|
+
# wrapping each of the array elements for some
|
30
|
+
# reason
|
31
|
+
if obj.is_a? Array
|
32
|
+
obj.each do |e|
|
33
|
+
xml += has_parent ? "<#{parent_tag}>" : ""
|
34
|
+
xml += buildXmlFromObject(e)
|
35
|
+
xml += has_parent ? "</#{parent_tag}>" : ""
|
36
|
+
end
|
37
|
+
|
38
|
+
elsif obj.is_a? Hash
|
39
|
+
xml += has_parent ? "<#{parent_tag}>" : ""
|
40
|
+
obj.keys.each do |key|
|
41
|
+
xml += buildXmlFromObject(obj[key], key.to_s)
|
42
|
+
end
|
43
|
+
xml += has_parent ? "</#{parent_tag}>" : ""
|
44
|
+
|
45
|
+
elsif !obj.nil?
|
46
|
+
xml += has_parent ? "<#{parent_tag}>" : ""
|
47
|
+
xml += obj.to_s
|
48
|
+
xml += has_parent ? "</#{parent_tag}>" : ""
|
49
|
+
end
|
50
|
+
|
51
|
+
return xml
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module AuthorizeNet
|
2
|
+
URI = "https://api.authorize.net/xml/v1/request.api"
|
3
|
+
TEST_URI = "https://apitest.authorize.net/xml/v1/request.api"
|
4
|
+
XML_SCHEMA = "AnetApi/xml/v1/schema/AnetApiSchema.xsd"
|
5
|
+
XML_HEADER = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
|
6
|
+
RESULT_OK = "Ok"
|
7
|
+
RESULT_ERROR = "Error"
|
8
|
+
|
9
|
+
# ===============================================================
|
10
|
+
# Constants for types of authorize net credit card validation
|
11
|
+
#
|
12
|
+
# Live Mode - Executes a test charge on the credit card for $0.01
|
13
|
+
# that is immediately voided
|
14
|
+
# Test Mode - Does basic mathematical checks on card validity
|
15
|
+
# None - No validation, could be useful for integration tests?
|
16
|
+
# ===============================================================
|
17
|
+
module ValidationMode
|
18
|
+
LIVE = "liveMode"
|
19
|
+
TEST = "testMode"
|
20
|
+
NONE = "None"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# require all authorize-net files
|
25
|
+
Dir['lib/authorize_net/**/*.rb'].each do |filename|
|
26
|
+
match = filename.match(/lib\/(authorize_net\/.*).rb/)
|
27
|
+
if !match.nil?
|
28
|
+
require match[1]
|
29
|
+
end
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: authorize_net
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Avenir Interactive LLC
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-03-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: nokogiri
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.6.7.2
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.6.7.2
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - '='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.6.7.2
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.6.7.2
|
33
|
+
description: A RubyGem that interfaces with the Authorize.net payment gateway
|
34
|
+
email:
|
35
|
+
- info@avenirhq.com
|
36
|
+
executables: []
|
37
|
+
extensions: []
|
38
|
+
extra_rdoc_files: []
|
39
|
+
files:
|
40
|
+
- lib/authorize_net.rb
|
41
|
+
- lib/authorize_net/address.rb
|
42
|
+
- lib/authorize_net/api.rb
|
43
|
+
- lib/authorize_net/credit_card.rb
|
44
|
+
- lib/authorize_net/customer_profile.rb
|
45
|
+
- lib/authorize_net/data_object.rb
|
46
|
+
- lib/authorize_net/error_handler.rb
|
47
|
+
- lib/authorize_net/exception.rb
|
48
|
+
- lib/authorize_net/payment_profile.rb
|
49
|
+
- lib/authorize_net/request.rb
|
50
|
+
- lib/authorize_net/response.rb
|
51
|
+
- lib/authorize_net/transaction.rb
|
52
|
+
- lib/authorize_net/util.rb
|
53
|
+
homepage: https://github.com/AvenirHQ/authorize_net
|
54
|
+
licenses:
|
55
|
+
- MIT
|
56
|
+
metadata: {}
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options: []
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
requirements: []
|
72
|
+
rubyforge_project:
|
73
|
+
rubygems_version: 2.5.2
|
74
|
+
signing_key:
|
75
|
+
specification_version: 4
|
76
|
+
summary: API interface for Authorize.net payment gateway
|
77
|
+
test_files: []
|
78
|
+
has_rdoc:
|