vaulted_billing 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,4 @@
1
+ # VaultedBilling
2
+
3
+ A generic interface to integrate with multiple vault-style Gateways (Authorize.net CIM, NMI Customer Vault, etc.)
4
+
@@ -0,0 +1,30 @@
1
+ module VaultedBilling
2
+ autoload :Version, 'vaulted_billing/version'
3
+ autoload :Gateway, 'vaulted_billing/gateway'
4
+ autoload :Gateways, 'vaulted_billing/gateways'
5
+ autoload :Customer, 'vaulted_billing/customer'
6
+ autoload :CreditCard, 'vaulted_billing/credit_card'
7
+ autoload :Transaction, 'vaulted_billing/transaction'
8
+ autoload :HttpsInterface, 'vaulted_billing/https_interface'
9
+
10
+ mattr_accessor :logger
11
+
12
+ Dir[File.expand_path(File.join(File.dirname(__FILE__), 'vaulted_billing', 'core_ext', '**', '*.rb'))].each do |extension|
13
+ require extension
14
+ end
15
+
16
+ ##
17
+ # Return the matching gateway for the name provided.
18
+ #
19
+ # * <tt>bogus</tt>:: BogusGateway - always successful, does nothing.
20
+ # * <tt>nmi_customer_vault</tt>:: NMICustomerVaultGateway
21
+ #
22
+ def self.gateway(name)
23
+ Gateways.const_get(name.to_s.camelize)
24
+ end
25
+
26
+ def self.logger?
27
+ @@logger.present?
28
+ end
29
+
30
+ end
@@ -0,0 +1,24 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMCVVMxFDASBgNV
3
+ BAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5uZXQvQ1BTIGluY29ycC4gYnkg
4
+ cmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRl
5
+ ZDE6MDgGA1UEAxMxRW50cnVzdC5uZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhv
6
+ cml0eTAeFw05OTA1MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIG
7
+ A1UEChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBi
8
+ eSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1p
9
+ dGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0
10
+ aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQ
11
+ aO2f55M28Qpku0f1BBc/I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5
12
+ gXpa0zf3wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OCAdcw
13
+ ggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHboIHYpIHVMIHSMQsw
14
+ CQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5l
15
+ dC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF
16
+ bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENl
17
+ cnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu
18
+ dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0MFqBDzIwMTkw
19
+ NTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8BdiE1U9s/8KAGv7UISX8+1i0Bow
20
+ HQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAaMAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EA
21
+ BAwwChsEVjQuMAMCBJAwDQYJKoZIhvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyN
22
+ Ewr75Ji174z4xRAN95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9
23
+ n9cd2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI=
24
+ -----END CERTIFICATE-----
@@ -0,0 +1,14 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG
3
+ A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
4
+ cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
5
+ MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
6
+ BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
7
+ YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
8
+ ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
9
+ BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
10
+ I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
11
+ CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do
12
+ lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc
13
+ AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k
14
+ -----END CERTIFICATE-----
@@ -0,0 +1,22 @@
1
+ module VaultedBilling
2
+ module CoreExt
3
+ module Hash
4
+ def to_querystring
5
+ to_a.reject { |pair| pair.last.nil? }.
6
+ sort_by { |item| item.first.to_s }.
7
+ collect { |key, value| "#{key}=#{value}" }.join('&')
8
+ end
9
+
10
+ module ClassMethods
11
+ def from_querystring(string)
12
+ ::Hash[string.split(/&/).
13
+ collect { |i| i.split(/=/) }.
14
+ collect { |e| e.size == 1 ? (e << '') : e }]
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ ::Hash.send :include, VaultedBilling::CoreExt::Hash
22
+ ::Hash.extend(VaultedBilling::CoreExt::Hash::ClassMethods)
@@ -0,0 +1,24 @@
1
+ module VaultedBilling
2
+ class CreditCard
3
+ attr_accessor :id
4
+ attr_accessor :currency
5
+ attr_accessor :card_number
6
+ attr_accessor :cvv_number
7
+ attr_accessor :expires_on
8
+ attr_accessor :first_name
9
+ attr_accessor :last_name
10
+ attr_accessor :street_address
11
+ attr_accessor :locality
12
+ attr_accessor :region
13
+ attr_accessor :postal_code
14
+ attr_accessor :country
15
+ attr_accessor :phone
16
+
17
+ def initialize(attributes = {})
18
+ attributes = HashWithIndifferentAccess.new(attributes)
19
+ attributes.each_pair do |key, value|
20
+ send("#{key}=", value) if respond_to?("#{key}=")
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,12 @@
1
+ module VaultedBilling
2
+ class Customer
3
+ attr_accessor :id
4
+ attr_accessor :email
5
+
6
+ def initialize(attributes = {})
7
+ attributes = HashWithIndifferentAccess.new(attributes)
8
+ @id = attributes[:id]
9
+ @email = attributes[:email]
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,61 @@
1
+ module VaultedBilling
2
+ module Gateway
3
+ module Response
4
+ attr_accessor :response_message
5
+ attr_writer :success
6
+ def success?; @success; end
7
+ end
8
+
9
+ def add_customer(customer)
10
+ raise NotImplementedError
11
+ end
12
+
13
+ def update_customer(customer)
14
+ raise NotImplementedError
15
+ end
16
+
17
+ def remove_customer(customer)
18
+ raise NotImplementedError
19
+ end
20
+
21
+ def add_customer_credit_card(customer, credit_card)
22
+ raise NotImplementedError
23
+ end
24
+
25
+ def update_customer_credit_card(customer, credit_card)
26
+ raise NotImplementedError
27
+ end
28
+
29
+ def remove_customer_credit_card(customer, credit_card)
30
+ raise NotImplementedError
31
+ end
32
+
33
+ def authorize(customer, credit_card, amount)
34
+ raise NotImplementedError
35
+ end
36
+
37
+ def capture(transaction_id, amount)
38
+ raise NotImplementedError
39
+ end
40
+
41
+ def refund(transaction_id, amount)
42
+ raise NotImplementedError
43
+ end
44
+
45
+ def void(transaction_id)
46
+ raise NotImplementedError
47
+ end
48
+
49
+
50
+ protected
51
+
52
+
53
+ def respond_with(object, options = {})
54
+ object.tap do |o|
55
+ o.extend(VaultedBilling::Gateway::Response)
56
+ o.success = options.has_key?(:success) ? options[:success] : true
57
+ yield(o) if block_given?
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,9 @@
1
+ module VaultedBilling
2
+ module Gateways
3
+ Dir[File.expand_path(File.join(File.dirname(__FILE__), 'gateways', '**', '*.rb'))].each do |file|
4
+ filename = File.basename(file, '.rb')
5
+ gateway_class = filename.camelize
6
+ autoload gateway_class, file
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,217 @@
1
+ module VaultedBilling
2
+ module Gateways
3
+
4
+ ##
5
+ # An interface to Authorize.net's CIM.
6
+ #
7
+ class AuthorizeNetCim
8
+ include VaultedBilling::Gateway
9
+ include VaultedBilling::HttpsInterface
10
+
11
+ def initialize(options = {})
12
+ self.test_uri = 'https://apitest.authorize.net/xml/v1/request.api'
13
+ self.live_uri = 'https://api.authorize.net/xml/v1/request.api'
14
+ self.ssl_pem = File.read(File.expand_path(File.join(File.dirname(__FILE__), '..', 'certificate_authorities', 'entrust.pem')))
15
+
16
+ options = HashWithIndifferentAccess.new(options)
17
+ @login = options[:username]
18
+ @password = options[:password]
19
+ self.use_test_uri = options[:test]
20
+ end
21
+
22
+ def add_customer(customer)
23
+ data = build_request('createCustomerProfileRequest') do |xml|
24
+ xml.tag!('profile') do
25
+ xml.merchantCustomerId customer.id if customer.id
26
+ xml.email customer.email if customer.email
27
+ end
28
+ end
29
+ result = post_data(data)
30
+ respond_with(customer, :success => result.success?) { |c| c.id = (result.body['createCustomerProfileResponse'] || {})['customerProfileId'] }
31
+ end
32
+
33
+ def update_customer(customer)
34
+ result = post_data(build_request('updateCustomerProfileRequest') { |xml|
35
+ xml.tag!('profile') {
36
+ xml.merchantCustomerId customer.id
37
+ xml.email customer.email
38
+ }
39
+ })
40
+ respond_with(customer, :success => result.success?)
41
+ end
42
+
43
+ def remove_customer(customer)
44
+ result = post_data(build_request('deleteCustomerProfileRequest') { |xml|
45
+ xml.customerProfileId customer.id
46
+ })
47
+ respond_with(customer, :success => result.success?)
48
+ end
49
+
50
+ def add_customer_credit_card(customer, credit_card)
51
+ result = post_data(build_request('createCustomerPaymentProfileRequest') { |xml|
52
+ xml.customerProfileId customer.id
53
+ xml.paymentProfile do
54
+ billing_info!(xml, customer, credit_card)
55
+ credit_card_info!(xml, customer, credit_card)
56
+ end
57
+ })
58
+ respond_with(credit_card, :success => result.success?) { |c| c.id = (result.body['createCustomerPaymentProfileResponse'] || {})['customerPaymentProfileId'] }
59
+ end
60
+
61
+ def update_customer_credit_card(customer, credit_card)
62
+ result = post_data(build_request('updateCustomerPaymentProfileRequest') { |xml|
63
+ xml.customerProfileId customer.id
64
+ xml.paymentProfile do
65
+ billing_info!(xml, customer, credit_card)
66
+ credit_card_info!(xml, customer, credit_card)
67
+ xml.customerPaymentProfileId credit_card.id
68
+ end
69
+ })
70
+ respond_with(credit_card, :success => result.success?)
71
+ end
72
+
73
+ def remove_customer_credit_card(customer, credit_card)
74
+ result = post_data(build_request('deleteCustomerPaymentProfileRequest') { |xml|
75
+ xml.customerProfileId customer.id
76
+ xml.customerPaymentProfileId credit_card.id
77
+ })
78
+ respond_with(credit_card, :success => result.success?)
79
+ end
80
+
81
+ def authorize(customer, credit_card, amount)
82
+ result = post_data(build_request('createCustomerProfileTransactionRequest') { |xml|
83
+ xml.transaction do
84
+ xml.profileTransAuthOnly do
85
+ xml.amount amount
86
+ xml.customerProfileId customer.id
87
+ xml.customerPaymentProfileId credit_card.id
88
+ end
89
+ end
90
+ })
91
+ respond_with(new_transaction_from_response(result.body), :success => result.success?)
92
+ end
93
+
94
+ def capture(transaction_id, amount)
95
+ result = post_data(build_request('createCustomerProfileTransactionRequest') { |xml|
96
+ xml.transaction do
97
+ xml.profileTransPriorAuthCapture do
98
+ xml.amount amount
99
+ xml.transId transaction_id
100
+ end
101
+ end
102
+ })
103
+ respond_with(new_transaction_from_response(result.body), :success => result.success?)
104
+ end
105
+
106
+ def refund(transaction_id, amount)
107
+ result = post_data(build_request('createCustomerProfileTransactionRequest') { |xml|
108
+ xml.transaction do
109
+ xml.profileTransRefund do
110
+ xml.amount amount
111
+ xml.transId transaction_id
112
+ end
113
+ end
114
+ })
115
+ respond_with(new_transaction_from_response(result.body), :success => result.success?)
116
+ end
117
+
118
+ def void(transaction_id)
119
+ result = post_data(build_request('createCustomerProfileTransactionRequest') { |xml|
120
+ xml.transaction do
121
+ xml.profileTransVoid do
122
+ xml.transId transaction_id
123
+ end
124
+ end
125
+ })
126
+ respond_with(new_transaction_from_response(result.body), :success => result.success?)
127
+ end
128
+
129
+
130
+ protected
131
+
132
+
133
+ def post_data(data, headers = {})
134
+ super(data, {'Content-Type' => 'text/xml'}.merge(headers))
135
+ end
136
+
137
+ def before_post(data)
138
+ VaultedBilling.logger.debug { "Posting: %s to %s" % [data.inspect, uri.inspect] } if VaultedBilling.logger?
139
+ end
140
+
141
+ def after_post(response)
142
+ VaultedBilling.logger.debug { "Response code %s (HTTP %d), %s" % [response.message, response.code, response.body.inspect] } if VaultedBilling.logger?
143
+ response.body = Hash.from_xml(response.body)
144
+ response.success = response.body[response.body.keys.first]['messages']['resultCode'] == 'Ok'
145
+ end
146
+
147
+ def build_request(request, xml = Builder::XmlMarkup.new(:indent => 2))
148
+ xml.instruct!
149
+ xml.tag!(request, :xmlns => 'AnetApi/xml/v1/schema/AnetApiSchema.xsd') do
150
+ xml.tag!('merchantAuthentication') do
151
+ xml.name @login
152
+ xml.transactionKey @password
153
+ end
154
+ yield(xml)
155
+ end
156
+ xml.target!
157
+ end
158
+
159
+
160
+ private
161
+
162
+
163
+ def billing_info!(xml, customer, credit_card)
164
+ xml.billTo do
165
+ xml.firstName credit_card.first_name if credit_card.first_name.present?
166
+ xml.lastName credit_card.last_name if credit_card.last_name.present?
167
+ xml.address credit_card.street_address if credit_card.street_address.present?
168
+ xml.city credit_card.locality if credit_card.locality.present?
169
+ xml.state credit_card.region if credit_card.region.present?
170
+ xml.zip credit_card.postal_code if credit_card.postal_code.present?
171
+ xml.country credit_card.country if credit_card.country.present?
172
+ xml.phoneNumber credit_card.phone if credit_card.phone.present?
173
+ end
174
+ end
175
+
176
+ def credit_card_info!(xml, customer, credit_card)
177
+ xml.payment do
178
+ xml.creditCard do
179
+ xml.cardNumber credit_card.card_number if credit_card.card_number.present?
180
+ xml.expirationDate credit_card.expires_on.strftime("%Y-%m") if credit_card.expires_on.present?
181
+ xml.cardCode credit_card.cvv_number if credit_card.cvv_number.present?
182
+ end
183
+ end
184
+ end
185
+
186
+ def new_transaction_from_response(response)
187
+ root = response.keys.first
188
+ if root == 'ErrorResponse'
189
+ Transaction.new
190
+ else
191
+ direct_response = parse_direct_response(response[root]['directResponse'])
192
+ Transaction.new({
193
+ :id => direct_response['transaction_id'],
194
+ :avs_response => direct_response['avs_response'],
195
+ :cvv_response => direct_response['cvv_response'],
196
+ :authcode => direct_response['approval_code'],
197
+ :message => response[root]['messages']['text'],
198
+ :code => response[root]['messages']['code']
199
+ })
200
+ end
201
+ end
202
+
203
+ def parse_direct_response(string)
204
+ fields = string.split(',')
205
+ {
206
+ 'message' => fields[3],
207
+ 'approval_code' => fields[4],
208
+ 'avs_response' => fields[5],
209
+ 'transaction_id' => fields[6],
210
+ 'cvv_response' => fields[39]
211
+ }
212
+ end
213
+
214
+ end
215
+
216
+ end
217
+ end
@@ -0,0 +1,61 @@
1
+ require 'digest/md5'
2
+
3
+ module VaultedBilling
4
+ module Gateways
5
+ class Bogus
6
+ include VaultedBilling::Gateway
7
+
8
+ def add_customer(customer)
9
+ respond_with(customer) { |c| c.id = new_identifier }
10
+ end
11
+
12
+ def update_customer(customer)
13
+ respond_with customer
14
+ end
15
+
16
+ def remove_customer(customer)
17
+ respond_with customer
18
+ end
19
+
20
+ def add_customer_credit_card(customer, credit_card)
21
+ respond_with(credit_card) { |c| c.id = new_identifier }
22
+ end
23
+
24
+ def update_customer_credit_card(customer, credit_card)
25
+ respond_with credit_card
26
+ end
27
+
28
+ def remove_customer_credit_card(customer, credit_card)
29
+ respond_with credit_card
30
+ end
31
+
32
+ def authorize(customer, credit_card, amount)
33
+ transaction_response
34
+ end
35
+
36
+ def void(transaction_id)
37
+ transaction_response
38
+ end
39
+
40
+ def capture(transaction_id, amount)
41
+ transaction_response
42
+ end
43
+
44
+ def refund(transaction_id, amount)
45
+ transaction_response
46
+ end
47
+
48
+
49
+ private
50
+
51
+
52
+ def new_identifier
53
+ Digest::MD5.hexdigest("--#{Time.now.to_f}--#{rand(1_000_000)}--#{rand(1_000_000)}--")
54
+ end
55
+
56
+ def transaction_response
57
+ respond_with VaultedBilling::Transaction.new(:id => new_identifier)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,171 @@
1
+ module VaultedBilling
2
+ module Gateways
3
+
4
+ ##
5
+ # An interface to the NMI Customer Vault.
6
+ #
7
+ # Currently, the Customer Vault is setup to be one-to-one with
8
+ # a customer to a credit card. Unlike Authorize.net's CIM, a
9
+ # single customer cannot carry multiple credit cards. Therefore
10
+ # most of the individual customer manipulation methods are
11
+ # simply stubbed to always be successful. The meat of the library
12
+ # is in the credit card methods and transactions.
13
+ #
14
+ class NmiCustomerVault
15
+ include VaultedBilling::Gateway
16
+ include VaultedBilling::HttpsInterface
17
+
18
+ def initialize(options = {})
19
+ self.live_uri = self.test_uri = "https://secure.nmi.com/api/transact.php"
20
+ self.ssl_pem = File.read(File.expand_path(File.join(File.dirname(__FILE__), '..', 'certificate_authorities', 'verisign.pem')))
21
+
22
+ options = HashWithIndifferentAccess.new(options)
23
+ @username = options[:username]
24
+ @password = options[:password]
25
+ self.use_test_uri = options[:test]
26
+ end
27
+
28
+ ##
29
+ # A stub, since the vault requires both customer information
30
+ # and credit card information. Actual additions are handled
31
+ # via the add_customer_credit_card method.
32
+ #
33
+ def add_customer(customer)
34
+ respond_with customer
35
+ end
36
+
37
+ ##
38
+ # A stub, since the vault requires both customer information
39
+ # and credit card information. Actual modifications are
40
+ # handled via the update_customer_credit_card method.
41
+ #
42
+ def update_customer(customer)
43
+ respond_with customer
44
+ end
45
+
46
+ ##
47
+ # A stub, since the vault requires both customer information
48
+ # and credit card information. Actual removals are
49
+ # handled via the remove_customer_credit_card method.
50
+ #
51
+ def remove_customer(customer)
52
+ respond_with customer
53
+ end
54
+
55
+ def add_customer_credit_card(customer, credit_card)
56
+ response = post_data(storage_data('add_customer', customer, credit_card))
57
+ respond_with(credit_card, :success => response.success?) do |c|
58
+ c.id = response.body['customer_vault_id']
59
+ end
60
+ end
61
+
62
+ def update_customer_credit_card(customer, credit_card)
63
+ response = post_data(storage_data('update_customer', customer, credit_card))
64
+ respond_with(credit_card, :success => response.success?)
65
+ end
66
+
67
+ def remove_customer_credit_card(customer, credit_card)
68
+ response = post_data(core_data.merge({
69
+ :customer_vault => 'delete_customer',
70
+ :customer_vault_id => credit_card.id
71
+ }).to_querystring)
72
+ respond_with(credit_card, :success => response.success?)
73
+ end
74
+
75
+ def authorize(customer, credit_card, amount)
76
+ response = post_data(transaction_data('auth', {
77
+ :customer_vault_id => credit_card.id,
78
+ :amount => amount
79
+ }))
80
+ respond_with(new_transaction_from_response(response.body),
81
+ :success => response.success?)
82
+ end
83
+
84
+ def capture(transaction_id, amount)
85
+ response = post_data(transaction_data('capture', {
86
+ :transactionid => transaction_id,
87
+ :amount => amount
88
+ }))
89
+ respond_with(new_transaction_from_response(response.body),
90
+ :success => response.success?)
91
+ end
92
+
93
+ def refund(transaction_id, amount)
94
+ response = post_data(transaction_data('refund', {
95
+ :transactionid => transaction_id,
96
+ :amount => amount
97
+ }))
98
+ respond_with(new_transaction_from_response(response.body),
99
+ :success => response.success?)
100
+ end
101
+
102
+ def void(transaction_id)
103
+ response = post_data(transaction_data('void', {
104
+ :transactionid => transaction_id
105
+ }))
106
+ respond_with(new_transaction_from_response(response.body),
107
+ :success => response.success?)
108
+ end
109
+
110
+
111
+ protected
112
+
113
+
114
+ def before_post(data)
115
+ VaultedBilling.logger.debug { "Posting %s to %s" % [data.inspect, uri.to_s] } if VaultedBilling.logger?
116
+ end
117
+
118
+ def after_post(response)
119
+ VaultedBilling.logger.debug { "Response code %s (HTTP %d), %s" % [response.message, response.code, response.body.inspect] } if VaultedBilling.logger?
120
+ response.body = Hash.from_querystring(response.body)
121
+ response.success = response.body['response'] == '1'
122
+ end
123
+
124
+
125
+ private
126
+
127
+
128
+ def core_data
129
+ {
130
+ :username => @username,
131
+ :password => @password
132
+ }
133
+ end
134
+
135
+ def transaction_data(method, overrides = {})
136
+ core_data.merge(overrides).to_querystring
137
+ end
138
+
139
+ def storage_data(method, customer, credit_card)
140
+ core_data.merge({
141
+ :customer_vault => method.to_s,
142
+ :customer_vault_id => credit_card.id,
143
+ :currency => credit_card.currency,
144
+ :method => 'creditcard',
145
+ :ccnumber => credit_card.card_number,
146
+ :ccexp => credit_card.expires_on.try(:strftime, "%y%m"),
147
+ :first_name => credit_card.first_name,
148
+ :last_name => credit_card.last_name,
149
+ :address1 => credit_card.street_address,
150
+ :city => credit_card.locality,
151
+ :state => credit_card.region,
152
+ :zip => credit_card.postal_code,
153
+ :country => credit_card.country,
154
+ :phone => credit_card.phone,
155
+ :email => customer.email
156
+ }).to_querystring
157
+ end
158
+
159
+ def new_transaction_from_response(response)
160
+ Transaction.new({
161
+ :id => response['transactionid'],
162
+ :avs_response => response['avsresponse'] == 'Y',
163
+ :cvv_response => response['cvvresponse'] == 'Y',
164
+ :authcode => response['authcode'],
165
+ :message => response['responsetext'],
166
+ :code => response['response_code']
167
+ })
168
+ end
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,80 @@
1
+ require 'net/https'
2
+ require 'uri'
3
+
4
+ module VaultedBilling
5
+ module HttpsInterface
6
+
7
+ class PostResponse
8
+ attr_accessor :code
9
+ attr_accessor :message
10
+ attr_accessor :body
11
+ attr_accessor :success
12
+ attr_accessor :raw_response
13
+
14
+ def initialize(http_response)
15
+ self.raw_response = http_response
16
+ self.code = http_response.code
17
+ self.message = http_response.message
18
+ self.body = http_response.body
19
+ self.success = ((http_response.code =~ /^2\d{2}/) == 0)
20
+ end
21
+
22
+ alias :success? :success
23
+ alias :status_code :code
24
+ end
25
+
26
+ attr_writer :use_test_uri
27
+ attr_writer :ssl_pem
28
+
29
+ def live_uri=(input)
30
+ @live_uri = input ? URI.parse(input) : nil
31
+ end
32
+
33
+ def test_uri=(input)
34
+ @test_uri = input ? URI.parse(input) : nil
35
+ end
36
+
37
+ ##
38
+ # Returns the protocol and host and any path details that is used
39
+ # when making communications.
40
+ #
41
+ def uri
42
+ @use_test_uri ? @test_uri : @live_uri
43
+ end
44
+
45
+ ##
46
+ # Posts the given data to the uri and returns the response.
47
+ #
48
+ def post_data(data, request_headers = {})
49
+ request = Net::HTTP::Post.new(uri.path)
50
+ request.initialize_http_header({
51
+ 'User-Agent' => "vaulted_billing #{VaultedBilling::Version}"
52
+ }.reverse_merge(request_headers))
53
+ request.body = data
54
+ response = Net::HTTP.new(uri.host, uri.port).tap do |https|
55
+ https.use_ssl = true
56
+ if @ssl_pem
57
+ https.cert = OpenSSL::X509::Certificate.new(@ssl_pem)
58
+ https.verify_mode = OpenSSL::SSL::VERIFY_PEER
59
+ else
60
+ https.verify_mode = OpenSSL::SSL::VERIFY_NONE
61
+ end
62
+ end
63
+
64
+ before_post(data)
65
+ response = response.request(request)
66
+ PostResponse.new(response).tap do |post_response|
67
+ after_post(post_response)
68
+ end
69
+ end
70
+ protected :post_data
71
+
72
+ def before_post(data)
73
+ end
74
+ protected :before_post
75
+
76
+ def after_post(response)
77
+ end
78
+ protected :after_post
79
+ end
80
+ end
@@ -0,0 +1,16 @@
1
+ module VaultedBilling
2
+ class Transaction
3
+ attr_accessor :id
4
+ attr_accessor :authcode
5
+ attr_accessor :avs_response
6
+ attr_accessor :cvv_response
7
+ attr_accessor :code
8
+ attr_accessor :message
9
+
10
+ def initialize(attributes = {})
11
+ attributes.each_pair do |key, value|
12
+ send("#{key}=", value) if respond_to?("#{key}=")
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module VaultedBilling
2
+ Version = '0.0.0'
3
+ end
metadata ADDED
@@ -0,0 +1,177 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vaulted_billing
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 0
9
+ version: 0.0.0
10
+ platform: ruby
11
+ authors:
12
+ - Nathaniel Bibler
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-08-23 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: activesupport
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 2
29
+ - 3
30
+ version: "2.3"
31
+ type: :runtime
32
+ version_requirements: *id001
33
+ - !ruby/object:Gem::Dependency
34
+ name: builder
35
+ prerelease: false
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ segments:
41
+ - 2
42
+ - 1
43
+ - 2
44
+ version: 2.1.2
45
+ type: :runtime
46
+ version_requirements: *id002
47
+ - !ruby/object:Gem::Dependency
48
+ name: rspec
49
+ prerelease: false
50
+ requirement: &id003 !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ segments:
55
+ - 2
56
+ - 0
57
+ - 0
58
+ - beta
59
+ - 19
60
+ version: 2.0.0.beta.19
61
+ type: :development
62
+ version_requirements: *id003
63
+ - !ruby/object:Gem::Dependency
64
+ name: vcr
65
+ prerelease: false
66
+ requirement: &id004 !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ segments:
71
+ - 1
72
+ - 0
73
+ - 3
74
+ version: 1.0.3
75
+ type: :development
76
+ version_requirements: *id004
77
+ - !ruby/object:Gem::Dependency
78
+ name: webmock
79
+ prerelease: false
80
+ requirement: &id005 !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ segments:
85
+ - 1
86
+ - 3
87
+ - 4
88
+ version: 1.3.4
89
+ type: :development
90
+ version_requirements: *id005
91
+ - !ruby/object:Gem::Dependency
92
+ name: factory_girl
93
+ prerelease: false
94
+ requirement: &id006 !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ segments:
99
+ - 1
100
+ - 3
101
+ - 2
102
+ version: 1.3.2
103
+ type: :development
104
+ version_requirements: *id006
105
+ - !ruby/object:Gem::Dependency
106
+ name: faker
107
+ prerelease: false
108
+ requirement: &id007 !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ segments:
113
+ - 0
114
+ - 3
115
+ - 1
116
+ version: 0.3.1
117
+ type: :development
118
+ version_requirements: *id007
119
+ description: Several card processors and gateways support offloading the storage of credit card information onto their service. This offloads PCI compliance to the gateway rather than keeping it with each retailer. This library abstracts the interface to many of them, making it trivial to work with any or all of them.
120
+ email:
121
+ - nate@envylabs.com
122
+ executables: []
123
+
124
+ extensions: []
125
+
126
+ extra_rdoc_files: []
127
+
128
+ files:
129
+ - lib/vaulted_billing/certificate_authorities/entrust.pem
130
+ - lib/vaulted_billing/certificate_authorities/verisign.pem
131
+ - lib/vaulted_billing/core_ext/hash.rb
132
+ - lib/vaulted_billing/credit_card.rb
133
+ - lib/vaulted_billing/customer.rb
134
+ - lib/vaulted_billing/gateway.rb
135
+ - lib/vaulted_billing/gateways/authorize_net_cim.rb
136
+ - lib/vaulted_billing/gateways/bogus.rb
137
+ - lib/vaulted_billing/gateways/nmi_customer_vault.rb
138
+ - lib/vaulted_billing/gateways.rb
139
+ - lib/vaulted_billing/https_interface.rb
140
+ - lib/vaulted_billing/transaction.rb
141
+ - lib/vaulted_billing/version.rb
142
+ - lib/vaulted_billing.rb
143
+ - README.md
144
+ has_rdoc: true
145
+ homepage: http://github.com/envylabs/vaulted_billing
146
+ licenses: []
147
+
148
+ post_install_message:
149
+ rdoc_options: []
150
+
151
+ require_paths:
152
+ - lib
153
+ required_ruby_version: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ segments:
158
+ - 0
159
+ version: "0"
160
+ required_rubygems_version: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - ">="
163
+ - !ruby/object:Gem::Version
164
+ segments:
165
+ - 1
166
+ - 3
167
+ - 6
168
+ version: 1.3.6
169
+ requirements: []
170
+
171
+ rubyforge_project:
172
+ rubygems_version: 1.3.6
173
+ signing_key:
174
+ specification_version: 3
175
+ summary: A library for working with credit card storage gateways
176
+ test_files: []
177
+