bigcharger 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/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 John Grimes
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,149 @@
1
+ # BigCharger - eWAY Token Payments Client
2
+
3
+ The idea of this project is to build a lightweight Ruby library for
4
+ interfacing with the [eWAY Token Payments
5
+ API](http://www.eway.com.au/Developer/eway-api/token-payments.aspx).
6
+
7
+ ## Installation
8
+
9
+ ```ruby
10
+ gem install bigcharger
11
+ ```
12
+
13
+ ## Setup
14
+
15
+ ```ruby
16
+ client = BigCharger.new(
17
+ '87654321',
18
+ 'test@eway.com.au',
19
+ 'test123'
20
+ )
21
+ ```
22
+
23
+ ## Create a new customer
24
+
25
+ ```ruby
26
+ client.create_customer({
27
+ 'CustomerRef' => 'Test 123',
28
+ 'Title' => 'Mr.',
29
+ 'FirstName' => 'Jo',
30
+ 'LastName' => 'Smith',
31
+ 'Company' => 'company',
32
+ 'JobDesc' => 'Analyst',
33
+ 'Email' => 'test@eway.com.au',
34
+ 'Address' => '15 Dundas Court',
35
+ 'Suburb' => 'phillip',
36
+ 'State' => 'act',
37
+ 'PostCode' => '2606',
38
+ 'Country' => 'au',
39
+ 'Phone' => '02111111111',
40
+ 'Mobile' => '04111111111',
41
+ 'Fax' => '111122222',
42
+ 'URL' => 'http://eway.com.au',
43
+ 'Comments' => 'Comments',
44
+ 'CCNameOnCard' => 'Jo Smith',
45
+ 'CCNumber' => '444433XXXXXX1111',
46
+ 'CCExpiryMonth' => '08',
47
+ 'CCExpiryYear' => '15'
48
+ })
49
+ # => "9876543211000" (managedCustomerID of new customer)
50
+ ```
51
+
52
+ ## Process a payment
53
+
54
+ ```ruby
55
+ client.process_payment(
56
+ 9876543211000,
57
+ 1000,
58
+ 'INV-80251',
59
+ 'Pants alteration'
60
+ )
61
+ # => {
62
+ # 'ewayTrxnError' => '00,Transaction Approved(Test Gateway)',
63
+ # 'ewayTrxnStatus' => 'True',
64
+ # 'ewayTrxnNumber' => '10498',
65
+ # 'ewayReturnAmount' => '1000',
66
+ # 'ewayAuthCode' => '123456'
67
+ # }
68
+
69
+ # Alternatively, improve security by requiring a CVN/CVV
70
+ client.process_payment_with_cvn(
71
+ 9876543211000,
72
+ 1000,
73
+ '123',
74
+ 'INV-80251',
75
+ 'Pants alteration'
76
+ )
77
+ ```
78
+
79
+ ## Find a customer
80
+
81
+ ```ruby
82
+ # Find a customer using the managed customer ID
83
+ client.query_customer(9876543211000)
84
+ # => {
85
+ # 'ManagedCustomerID' => '9876543211000',
86
+ # 'CustomerRef' => 'Test 123',
87
+ # 'CustomerTitle' => 'Mr.',
88
+ # 'CustomerFirstName' => 'Jo',
89
+ # 'CustomerLastName' => 'Smith',
90
+ # 'CustomerCompany' => 'company',
91
+ # 'CustomerJobDesc' => nil,
92
+ # 'CustomerEmail' => 'test@eway.com.au',
93
+ # 'CustomerAddress' => '15 Dundas Court',
94
+ # 'CustomerSuburb' => 'phillip',
95
+ # 'CustomerState' => 'act',
96
+ # 'CustomerPostCode' => '2606',
97
+ # 'CustomerCountry' => 'au',
98
+ # 'CustomerPhone1' => '02111111111',
99
+ # 'CustomerPhone2' => '04111111111',
100
+ # 'CustomerFax' => '111122222',
101
+ # 'CustomerURL' => 'http://eway.com.au',
102
+ # 'CustomerComments' => 'Comments',
103
+ # 'CCName' => 'Jo Smith',
104
+ # 'CCNumber' => '444433XXXXXX1111',
105
+ # 'CCExpiryMonth' => '08',
106
+ # 'CCExpiryYear' => '15'
107
+ # }
108
+
109
+ # Find a customer using the reference you supplied when you created the
110
+ # customer
111
+ client.query_customer_by_reference('Test 123')
112
+ ```
113
+
114
+ ## Get list of payments for a customer
115
+
116
+ ```ruby
117
+ client.query_payment(9876543211000)
118
+ # => [
119
+ # {
120
+ # 'TotalAmount' => '1000',
121
+ # 'Result' => '1',
122
+ # 'ResponseText' => 'Approved',
123
+ # 'TransactionDate' => '2012-02-20T00:00:00+11:00',
124
+ # 'ewayTrxnNumber' => '1'
125
+ # },
126
+ # {
127
+ # 'TotalAmount' => '1008',
128
+ # 'Result' => '1',
129
+ # 'ResponseText' => 'Approved',
130
+ # 'TransactionDate' => '2012-02-20T00:00:00+11:00',
131
+ # 'ewayTrxnNumber' => '2'
132
+ # }
133
+ # ]
134
+ ```
135
+
136
+ ## Update a customer
137
+
138
+ ```ruby
139
+ client.update_customer(
140
+ 9876543211000,
141
+ {
142
+ 'CCNameOnCard' => 'Jo Smith',
143
+ 'CCNumber' => '444433XXXXXX2222',
144
+ 'CCExpiryMonth' => '06',
145
+ 'CCExpiryYear' => '22'
146
+ }
147
+ )
148
+ # => true
149
+ ```
@@ -0,0 +1,21 @@
1
+ require File.dirname(__FILE__) + '/bigcharger/config'
2
+ require File.dirname(__FILE__) + '/bigcharger/utils'
3
+ require File.dirname(__FILE__) + '/bigcharger/ops'
4
+
5
+ class BigCharger
6
+ attr_accessor :logger
7
+
8
+ def initialize(customer_id, username, password, test_mode = false, logger = Logger.new('/dev/null'))
9
+ @credentials = {
10
+ :customer_id => customer_id,
11
+ :username => username,
12
+ :password => password
13
+ }
14
+ @client = Curl::Easy.new
15
+ @endpoint = test_mode ? TEST_ENDPOINT : ENDPOINT
16
+ @logger = logger
17
+ set_request_defaults
18
+ end
19
+
20
+ class Error < Exception; end
21
+ end
@@ -0,0 +1,32 @@
1
+ class BigCharger
2
+ SOAP_NAMESPACE = 'http://schemas.xmlsoap.org/soap/envelope/'
3
+ SERVICE_NAMESPACE = 'https://www.eway.com.au/gateway/managedpayment'
4
+ ENDPOINT = 'https://www.eway.com.au/gateway/ManagedPaymentService/managedCreditCardPayment.asmx'
5
+ TEST_ENDPOINT = 'https://www.eway.com.au/gateway/ManagedPaymentService/test/managedCreditCardPayment.asmx'
6
+
7
+ # List of fields (with significant ordering as per WSDL) for customer
8
+ # requests
9
+ CUSTOMER_REQUEST_FIELDS = [
10
+ 'Title',
11
+ 'FirstName',
12
+ 'LastName',
13
+ 'Address',
14
+ 'Suburb',
15
+ 'State',
16
+ 'Company',
17
+ 'PostCode',
18
+ 'Country',
19
+ 'Email',
20
+ 'Fax',
21
+ 'Phone',
22
+ 'Mobile',
23
+ 'CustomerRef',
24
+ 'JobDesc',
25
+ 'Comments',
26
+ 'URL',
27
+ 'CCNumber',
28
+ 'CCNameOnCard',
29
+ 'CCExpiryMonth',
30
+ 'CCExpiryYear'
31
+ ]
32
+ end
@@ -0,0 +1,148 @@
1
+ require 'nokogiri'
2
+ require 'curb'
3
+
4
+ class BigCharger
5
+ # Creates a new managed customer on the eWAY server.
6
+ # @param customer_fields Title, FirstName, LastName, Address, Suburb,
7
+ # State, Company, PostCode, Country, Email, Fax, Phone, Mobile,
8
+ # CustomerRef, JobDesc, Comments, URL, CCNumber, CCNameOnCard,
9
+ # CCExpiryMonth, CCExpiryYear
10
+ # @return [String] Managed customer ID of new customer.
11
+ def create_customer(customer_fields = {})
12
+ log_operation(:create_customer, customer_fields)
13
+ raise Error, 'customer_fields is not a Hash' unless customer_fields.class == Hash
14
+ envelope = wrap_in_envelope do |xml|
15
+ xml['man'].CreateCustomer {
16
+ CUSTOMER_REQUEST_FIELDS.each do |field|
17
+ xml['man'].send(field, customer_fields[field]) if customer_fields[field]
18
+ end
19
+ }
20
+ end
21
+ response = post(envelope, 'CreateCustomer')
22
+ result = response.xpath('//man:CreateCustomerResult', { 'man' => SERVICE_NAMESPACE }).first
23
+ return result ? result.text : false
24
+ end
25
+
26
+ # Processes a payment of a given amount for the specified customer.
27
+ # @param [#to_s] managed_customer_id
28
+ # @param [Integer] amount The amount of the payment, in cents. (e.g.
29
+ # 1000 = $10)
30
+ # @param invoice_ref An optional field that you can use to identify
31
+ # the corresponding invoice within your system.
32
+ # @param invoice_desc An optional field to describe the subject of the
33
+ # payment.
34
+ # @return [Hash] ewayTrxnError, ewayTrxnStatus, ewayTrxnNumber,
35
+ # ewayReturnAmount, ewayAuthCode
36
+ def process_payment(managed_customer_id, amount, invoice_ref = nil, invoice_desc = nil)
37
+ log_operation(:process_payment, managed_customer_id, amount, invoice_ref, invoice_desc)
38
+ envelope = wrap_in_envelope do |xml|
39
+ xml['man'].ProcessPayment {
40
+ xml['man'].managedCustomerID managed_customer_id
41
+ xml['man'].amount amount
42
+ xml['man'].invoiceReference invoice_ref if invoice_ref
43
+ xml['man'].invoiceDescription invoice_desc if invoice_desc
44
+ }
45
+ end
46
+ response = post(envelope, 'ProcessPayment')
47
+ result = response.xpath('//man:ProcessPaymentResponse/man:ewayResponse', { 'man' => SERVICE_NAMESPACE }).first
48
+ return result ? node_to_hash(result) : false
49
+ end
50
+
51
+ # Identical to {#process_payment} except that it accepts a CVN/CVV for
52
+ # greater security.
53
+ def process_payment_with_cvn(managed_customer_id, amount, cvn = nil, invoice_ref = nil, invoice_desc = nil)
54
+ log_operation(:process_payment_with_cvn, managed_customer_id, amount, cvn, invoice_ref, invoice_desc)
55
+ envelope = wrap_in_envelope do |xml|
56
+ xml['man'].ProcessPaymentWithCVN {
57
+ xml['man'].managedCustomerID managed_customer_id
58
+ xml['man'].amount amount
59
+ xml['man'].invoiceReference invoice_ref if invoice_ref
60
+ xml['man'].invoiceDescription invoice_desc if invoice_desc
61
+ xml['man'].cvn cvn if cvn
62
+ }
63
+ end
64
+ response = post(envelope, 'ProcessPaymentWithCVN')
65
+ result = response.xpath('//man:ProcessPaymentWithCVNResponse/man:ewayResponse', { 'man' => SERVICE_NAMESPACE }).first
66
+ return result ? node_to_hash(result) : false
67
+ end
68
+
69
+ # Returns the eWAY record for a specific managed customer ID.
70
+ # @param [#to_s] managed_customer_id
71
+ # @return [Hash] ManagedCustomerID, CustomerRef, CustomerTitle,
72
+ # CustomerFirstName, CustomerLastName, CustomerCompany,
73
+ # CustomerJobDesc, CustomerEmail, CustomerAddress, CustomerSuburb,
74
+ # CustomerState, CustomerPostCode, CustomerCountry, CustomerPhone1,
75
+ # CustomerPhone2, CustomerFax, CustomerURL, CustomerComments
76
+ def query_customer(managed_customer_id)
77
+ log_operation(:query_customer, managed_customer_id)
78
+ envelope = wrap_in_envelope do |xml|
79
+ xml['man'].QueryCustomer {
80
+ xml['man'].managedCustomerID managed_customer_id
81
+ }
82
+ end
83
+ response = post(envelope, 'QueryCustomer')
84
+ result = response.xpath('//man:QueryCustomerResult', { 'man' => SERVICE_NAMESPACE }).first
85
+ return result ? node_to_hash(result) : false
86
+ end
87
+
88
+ # Returns the eWAY customer record with the specified customer
89
+ # reference.
90
+ # @param [#to_s] customer_ref
91
+ # @return [Hash] ManagedCustomerID, CustomerRef, CustomerTitle,
92
+ # CustomerFirstName, CustomerLastName, CustomerCompany,
93
+ # CustomerJobDesc, CustomerEmail, CustomerAddress, CustomerSuburb,
94
+ # CustomerState, CustomerPostCode, CustomerCountry, CustomerPhone1,
95
+ # CustomerPhone2, CustomerFax, CustomerURL, CustomerComments
96
+ def query_customer_by_reference(customer_ref)
97
+ log_operation(:query_customer_by_reference, customer_ref)
98
+ envelope = wrap_in_envelope do |xml|
99
+ xml['man'].QueryCustomerByReference {
100
+ xml['man'].CustomerReference customer_ref
101
+ }
102
+ end
103
+ response = post(envelope, 'QueryCustomerByReference')
104
+ result = response.xpath('//man:QueryCustomerByReferenceResult', { 'man' => SERVICE_NAMESPACE }).first
105
+ return result ? node_to_hash(result) : false
106
+ end
107
+
108
+
109
+ # Returns all the payment records associated with a specified managed
110
+ # customer ID.
111
+ # @param [#to_s] customer_ref
112
+ # @return [Array of Hashes] TotalAmount, Result, ResponseText,
113
+ # TransactionDate, ewayTrxnNumber
114
+ def query_payment(managed_customer_id)
115
+ log_operation(:query_payment, managed_customer_id)
116
+ envelope = wrap_in_envelope do |xml|
117
+ xml['man'].QueryPayment {
118
+ xml['man'].managedCustomerID managed_customer_id
119
+ }
120
+ end
121
+ response = post(envelope, 'QueryPayment')
122
+ result = response.xpath('//man:QueryPaymentResult', { 'man' => SERVICE_NAMESPACE }).first
123
+ return result ? node_collection_to_array(result) : false
124
+ end
125
+
126
+ # Update details of an eWAY customer record.
127
+ # @param [#to_s] managed_customer_id
128
+ # @param customer_fields Title, FirstName, LastName, Address, Suburb,
129
+ # State, Company, PostCode, Country, Email, Fax, Phone, Mobile,
130
+ # CustomerRef, JobDesc, Comments, URL, CCNumber, CCNameOnCard,
131
+ # CCExpiryMonth, CCExpiryYear
132
+ # @return [Boolean] Success or otherwise
133
+ def update_customer(managed_customer_id, customer_fields = {})
134
+ log_operation(:update_customer, managed_customer_id, customer_fields)
135
+ raise Error, 'customer_fields is not a Hash' unless customer_fields.class == Hash
136
+ envelope = wrap_in_envelope do |xml|
137
+ xml['man'].UpdateCustomer {
138
+ xml['man'].managedCustomerID managed_customer_id
139
+ CUSTOMER_REQUEST_FIELDS.each do |field|
140
+ xml['man'].send(field, customer_fields[field]) if customer_fields[field]
141
+ end
142
+ }
143
+ end
144
+ response = post(envelope, 'UpdateCustomer')
145
+ result = response.xpath('//man:UpdateCustomerResult', { 'man' => SERVICE_NAMESPACE }).first
146
+ return result ? result.text == 'true' : false
147
+ end
148
+ end
@@ -0,0 +1,115 @@
1
+ require 'logger'
2
+
3
+ class BigCharger
4
+ private
5
+
6
+ def set_request_defaults
7
+ @client.verbose = false
8
+ @client.url = @endpoint
9
+ @client.headers['Content-Type'] = 'text/xml'
10
+ end
11
+
12
+ def wrap_in_envelope(&block)
13
+ envelope = Nokogiri::XML::Builder.new do |xml|
14
+ xml.Envelope('xmlns:soap' => SOAP_NAMESPACE,
15
+ 'xmlns:man' => SERVICE_NAMESPACE) {
16
+ xml.parent.namespace = xml.parent.namespace_definitions.find { |ns| ns.prefix == 'soap' }
17
+ xml['soap'].Header {
18
+ xml['man'].eWAYHeader {
19
+ xml['man'].eWAYCustomerID @credentials[:customer_id]
20
+ xml['man'].Username @credentials[:username]
21
+ xml['man'].Password @credentials[:password]
22
+ }
23
+ }
24
+ xml['soap'].Body {
25
+ yield xml
26
+ }
27
+ }
28
+ end
29
+ end
30
+
31
+ def post(envelope, action_name)
32
+ @client.headers['SOAPAction'] = "#{SERVICE_NAMESPACE}/#{action_name}"
33
+ record_request(@client, envelope.to_xml)
34
+ @client.http_post @last_request[:body]
35
+ log_last_request
36
+ record_response(@client)
37
+ log_last_response
38
+ check_last_response_for_faults
39
+ check_last_response_for_errors
40
+ return @last_response[:body_document]
41
+ end
42
+
43
+ def record_request(request, body)
44
+ @last_request = {}
45
+ @last_request[:headers] = request.headers.clone
46
+ @last_request[:body] = body
47
+ end
48
+
49
+ def record_response(response)
50
+ @last_response = {}
51
+ @last_response[:header_string] = response.header_str.clone
52
+ @last_response[:body] = response.body_str.clone
53
+ @last_response[:body_document] = Nokogiri::XML(response.body_str)
54
+ end
55
+
56
+ def log_operation(name, *args)
57
+ log_string = "BigCharger##{name} called with: "
58
+ log_string << args.map { |x| x.inspect }.join(', ')
59
+ @logger.debug log_string
60
+ end
61
+
62
+ def log_last_request
63
+ header_output = @last_request[:headers].map { |k,v| "#{k}: #{v}" }.join("\n")
64
+ log_string = "BigCharger - Request sent to eWAY server\n"
65
+ log_string << "#{header_output}\n#{@last_request[:body]}"
66
+ @logger.debug log_string
67
+ end
68
+
69
+ def log_last_response
70
+ body_output = @last_response[:body_document].serialize(:encoding => 'UTF-8') do |config|
71
+ config.format.as_xml
72
+ end
73
+ log_string = "BigCharger - Response received from eWAY server\n"
74
+ log_string << "#{@last_response[:header_string]}\n#{body_output}"
75
+ @logger.debug log_string
76
+ end
77
+
78
+ def check_last_response_for_faults
79
+ faults = @last_response[:body_document].xpath('//soap:Fault', { 'soap' => SOAP_NAMESPACE })
80
+ unless faults.empty?
81
+ fault = faults.first
82
+ fault_code = fault.xpath('faultcode').first.text
83
+ fault_message = fault.xpath('faultstring').first.text
84
+ raise Error, "eWAY server responded with \"#{fault_message}\" (#{fault_code})"
85
+ end
86
+ end
87
+
88
+ def check_last_response_for_errors
89
+ status_info = @last_response[:header_string].match(/HTTP\/[\d\.]+ (\d{3}) ([\w\s]+)[\r\n]/)
90
+ status_code, status_reason = status_info[1].strip, status_info[2].strip
91
+ unless ['200', '100'].include? status_code
92
+ raise Error, "eWAY server responded with \"#{status_reason}\" (#{status_code})"
93
+ end
94
+ end
95
+
96
+ def node_to_hash(node)
97
+ hash = {}
98
+ node.children.each do |node|
99
+ if node.type == Nokogiri::XML::Node::ELEMENT_NODE
100
+ hash[node.name] = node.text.empty? ? nil : node.text
101
+ end
102
+ end
103
+ return hash
104
+ end
105
+
106
+ def node_collection_to_array(node)
107
+ array = []
108
+ node.children.each do |node|
109
+ if node.type == Nokogiri::XML::Node::ELEMENT_NODE
110
+ array << node_to_hash(node)
111
+ end
112
+ end
113
+ return array
114
+ end
115
+ end