killbill-cybersource 4.0.2 → 4.0.3
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 +4 -4
- data/Gemfile.lock +2 -2
- data/NEWS +13 -0
- data/README.md +29 -26
- data/VERSION +1 -1
- data/cybersource.yml +1 -0
- data/db/ddl.sql +1 -1
- data/db/migrate/20162519092522_enlarge_message.rb +11 -0
- data/db/schema.rb +1 -1
- data/lib/cybersource/api.rb +79 -10
- data/lib/cybersource/cyber_source_on_demand.rb +47 -8
- data/lib/cybersource/ext/active_merchant/active_merchant.rb +111 -5
- data/lib/cybersource/models/response.rb +18 -0
- data/pom.xml +1 -1
- data/spec/cybersource/base_plugin_spec.rb +114 -15
- data/spec/cybersource/remote/integration_spec.rb +289 -51
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bf0b374a0672d53db036f9f39c45d663c5478b71
|
|
4
|
+
data.tar.gz: f7244811da2b9e5c2dae307c84e919b3886a1692
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 23bd9c1612dfa4bc4c2af62fb090865a64ab1e8ef2acbd8bb7d6ad9ead71da4b22814c0fe08cabf1b50a362d9e25528909fe475a696924477e124fc8c30d14a3
|
|
7
|
+
data.tar.gz: 3989c693a1c021c9ec0186638bbaeb78154c007884be21b5a237281948cb5f49016b5c15ab93ac9f6f988d3f28cb60af84eb99cd764393f6d3edcbf2b92e609d
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
killbill-cybersource (4.0.
|
|
4
|
+
killbill-cybersource (4.0.3)
|
|
5
5
|
actionpack (~> 4.1.0)
|
|
6
6
|
actionview (~> 4.1.0)
|
|
7
7
|
activemerchant (~> 1.48.0)
|
|
@@ -65,7 +65,7 @@ GEM
|
|
|
65
65
|
diff-lcs (1.1.3)
|
|
66
66
|
equalizer (0.0.11)
|
|
67
67
|
erubis (2.7.0)
|
|
68
|
-
ethon (0.
|
|
68
|
+
ethon (0.9.0)
|
|
69
69
|
ffi (>= 1.3.0)
|
|
70
70
|
ffi (1.9.10-java)
|
|
71
71
|
i18n (0.7.0)
|
data/NEWS
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
4.0.3
|
|
2
|
+
Add support for business rules on Apple Pay
|
|
3
|
+
You can now specify commerce_indicator as a plugin property to override the commerceIndicator value
|
|
4
|
+
You can now specify force_validation=true as a plugin property to trigger $1 CC validation in case
|
|
5
|
+
the processor used doesn't support $0 auth for that card
|
|
6
|
+
Disable costly duplicate checks by default when triggering a payment if the reporting API is configured
|
|
7
|
+
- Set check_for_duplicates: true in your configuration to enable them
|
|
8
|
+
- No change in the GET path (UNDEFINED transactions will be fixed if possible)
|
|
9
|
+
Fix XML parsing of responses (authorization reversal errors weren't parsed correctly)
|
|
10
|
+
Improve categorization of error codes to return the right transaction status
|
|
11
|
+
Set clientLibrary to 'Kill Bill' and clientLibraryVersion to the plugin version
|
|
12
|
+
Change cybersource_responses.message to text
|
|
13
|
+
|
|
1
14
|
4.0.2
|
|
2
15
|
Add support for auth reversal after voiding a capture
|
|
3
16
|
Add support for ignore_avs and ignore_cvv properties
|
data/README.md
CHANGED
|
@@ -130,29 +130,32 @@ curl -v \
|
|
|
130
130
|
Plugin properties
|
|
131
131
|
-----------------
|
|
132
132
|
|
|
133
|
-
| Key | Description
|
|
134
|
-
| ---------------------------: |
|
|
135
|
-
| skip_gw | If true, skip the call to CyberSource
|
|
136
|
-
| payment_processor_account_id | Config entry name of the merchant account to use
|
|
137
|
-
| external_key_as_order_id | If true, set the payment external key as the CyberSource order id
|
|
138
|
-
| ignore_avs | If true, ignore the results of AVS checking
|
|
139
|
-
| ignore_cvv | If true, ignore the results of CVN checking
|
|
140
|
-
| cc_first_name | Credit card holder first name
|
|
141
|
-
| cc_last_name | Credit card holder last name
|
|
142
|
-
| cc_type | Credit card brand
|
|
143
|
-
| cc_expiration_month | Credit card expiration month
|
|
144
|
-
| cc_expiration_year | Credit card expiration year
|
|
145
|
-
| cc_verification_value | CVC/CVV/CVN
|
|
146
|
-
| email | Purchaser email
|
|
147
|
-
| address1 | Billing address first line
|
|
148
|
-
| address2 | Billing address second line
|
|
149
|
-
| city | Billing address city
|
|
150
|
-
| zip | Billing address zip code
|
|
151
|
-
| state | Billing address state
|
|
152
|
-
| country | Billing address country
|
|
153
|
-
|
|
|
154
|
-
|
|
|
155
|
-
|
|
|
156
|
-
|
|
|
157
|
-
|
|
|
158
|
-
|
|
|
133
|
+
| Key | Description |
|
|
134
|
+
| ---------------------------: | ------------------------------------------------------------------------|
|
|
135
|
+
| skip_gw | If true, skip the call to CyberSource |
|
|
136
|
+
| payment_processor_account_id | Config entry name of the merchant account to use |
|
|
137
|
+
| external_key_as_order_id | If true, set the payment external key as the CyberSource order id |
|
|
138
|
+
| ignore_avs | If true, ignore the results of AVS checking |
|
|
139
|
+
| ignore_cvv | If true, ignore the results of CVN checking |
|
|
140
|
+
| cc_first_name | Credit card holder first name |
|
|
141
|
+
| cc_last_name | Credit card holder last name |
|
|
142
|
+
| cc_type | Credit card brand |
|
|
143
|
+
| cc_expiration_month | Credit card expiration month |
|
|
144
|
+
| cc_expiration_year | Credit card expiration year |
|
|
145
|
+
| cc_verification_value | CVC/CVV/CVN |
|
|
146
|
+
| email | Purchaser email |
|
|
147
|
+
| address1 | Billing address first line |
|
|
148
|
+
| address2 | Billing address second line |
|
|
149
|
+
| city | Billing address city |
|
|
150
|
+
| zip | Billing address zip code |
|
|
151
|
+
| state | Billing address state |
|
|
152
|
+
| country | Billing address country |
|
|
153
|
+
| commerce_indicator | Override the commerce indicator field |
|
|
154
|
+
| eci | Network tokenization attribute |
|
|
155
|
+
| payment_cryptogram | Network tokenization attribute |
|
|
156
|
+
| transaction_id | Network tokenization attribute |
|
|
157
|
+
| payment_instrument_name | ApplePay tokenization attribute |
|
|
158
|
+
| payment_network | ApplePay tokenization attribute |
|
|
159
|
+
| transaction_identifier | ApplePay tokenization attribute |
|
|
160
|
+
| force_validation | If true, trigger a non-$0 auth to validate cards not supporting $0 auth |
|
|
161
|
+
| force_validation_amount | Amount to use when force_validation is set |
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.0.
|
|
1
|
+
4.0.3
|
data/cybersource.yml
CHANGED
data/db/ddl.sql
CHANGED
|
@@ -57,7 +57,7 @@ CREATE TABLE `cybersource_responses` (
|
|
|
57
57
|
`kb_payment_transaction_id` varchar(255) DEFAULT NULL,
|
|
58
58
|
`transaction_type` varchar(255) DEFAULT NULL,
|
|
59
59
|
`payment_processor_account_id` varchar(255) DEFAULT NULL,
|
|
60
|
-
`message`
|
|
60
|
+
`message` text DEFAULT NULL,
|
|
61
61
|
`authorization` varchar(255) DEFAULT NULL,
|
|
62
62
|
`fraud_review` tinyint(1) DEFAULT NULL,
|
|
63
63
|
`test` tinyint(1) DEFAULT NULL,
|
data/db/schema.rb
CHANGED
|
@@ -58,7 +58,7 @@ ActiveRecord::Schema.define(:version => 20140410153635) do
|
|
|
58
58
|
t.string "kb_payment_transaction_id"
|
|
59
59
|
t.string "transaction_type"
|
|
60
60
|
t.string "payment_processor_account_id"
|
|
61
|
-
t.
|
|
61
|
+
t.text "message"
|
|
62
62
|
t.string "authorization"
|
|
63
63
|
t.boolean "fraud_review"
|
|
64
64
|
t.boolean "test"
|
data/lib/cybersource/api.rb
CHANGED
|
@@ -31,7 +31,18 @@ module Killbill #:nodoc:
|
|
|
31
31
|
add_required_options(kb_account_id, properties, options, context)
|
|
32
32
|
|
|
33
33
|
properties = merge_properties(properties, options)
|
|
34
|
-
super(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
|
|
34
|
+
auth_response = super(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
|
|
35
|
+
|
|
36
|
+
# Error 234 is "A problem exists with your CyberSource merchant configuration", most likely the processor used doesn't support $0 auth for this card type
|
|
37
|
+
if auth_response.gateway_error_code == '234' && to_cents(amount, currency) == 0
|
|
38
|
+
h_props = properties_to_hash(properties)
|
|
39
|
+
if ::Killbill::Plugin::ActiveMerchant::Utils.normalized(h_props, :force_validation)
|
|
40
|
+
force_validation_amount = (::Killbill::Plugin::ActiveMerchant::Utils.normalized(h_props, :force_validation_amount) || 1).to_f
|
|
41
|
+
auth_response = force_validation(auth_response, kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, force_validation_amount, currency, properties, context)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
auth_response
|
|
35
46
|
end
|
|
36
47
|
|
|
37
48
|
def capture_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
|
|
@@ -257,18 +268,31 @@ module Killbill #:nodoc:
|
|
|
257
268
|
super
|
|
258
269
|
|
|
259
270
|
merchant_reference_code = options[:order_id]
|
|
260
|
-
report =
|
|
271
|
+
report = get_report_for_kb_transaction(merchant_reference_code, kb_transaction, options, context)
|
|
261
272
|
return nil if report.nil? || report.empty?
|
|
262
273
|
|
|
263
|
-
logger.info "Skipping gateway call for existing
|
|
274
|
+
logger.info "Skipping gateway call for existing kb_transaction_id='#{kb_transaction.id}', merchant_reference_code='#{merchant_reference_code}'"
|
|
264
275
|
options[:skip_gw] = true
|
|
265
276
|
rescue => e
|
|
266
|
-
logger.warn "Error checking for duplicate payment
|
|
277
|
+
logger.warn "Error checking for duplicate payment for merchant_reference_code='#{merchant_reference_code}'\n#{e.backtrace.join("\n")}"
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# Duplicate check
|
|
281
|
+
def get_report_for_kb_transaction(merchant_reference_code, kb_transaction, options, context)
|
|
282
|
+
report_api = get_report_api(options, context)
|
|
283
|
+
return nil if report_api.nil? || !report_api.check_for_duplicates?
|
|
284
|
+
# kb_transaction is a Utils::LazyEvaluator, delay evaluation as much as possible
|
|
285
|
+
get_single_transaction_report(report_api, merchant_reference_code, kb_transaction.created_date)
|
|
267
286
|
end
|
|
268
287
|
|
|
288
|
+
# Janitor path
|
|
269
289
|
def get_report(merchant_reference_code, date, options, context)
|
|
270
|
-
report_api = get_report_api(context
|
|
271
|
-
return nil if report_api.nil?
|
|
290
|
+
report_api = get_report_api(options, context)
|
|
291
|
+
return nil if report_api.nil?
|
|
292
|
+
get_single_transaction_report(report_api, merchant_reference_code, date)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def get_single_transaction_report(report_api, merchant_reference_code, date)
|
|
272
296
|
report_api.single_transaction_report(merchant_reference_code, date.strftime('%Y%m%d'))
|
|
273
297
|
end
|
|
274
298
|
|
|
@@ -287,12 +311,57 @@ module Killbill #:nodoc:
|
|
|
287
311
|
::Killbill::Plugin::ActiveMerchant::Utils.normalize_property(properties, 'ignore_cvv')
|
|
288
312
|
end
|
|
289
313
|
|
|
290
|
-
def get_report_api(
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
314
|
+
def get_report_api(options, context)
|
|
315
|
+
return nil if options[:skip_gw] || options[:bypass_duplicate_check]
|
|
316
|
+
cybersource_config = config(context.tenant_id)[:cybersource]
|
|
317
|
+
return nil unless cybersource_config.is_a?(Array)
|
|
318
|
+
on_demand_config = cybersource_config.find { |c| c[:account_id].to_s == 'on_demand' }
|
|
319
|
+
return nil if on_demand_config.nil?
|
|
320
|
+
CyberSourceOnDemand.new(on_demand_config, logger)
|
|
321
|
+
rescue => e
|
|
322
|
+
@logger.warn("Unexpected exception while looking-up reporting API for kb_tenant_id='#{context.tenant_id}'\n#{e.backtrace.join("\n")}")
|
|
294
323
|
nil
|
|
295
324
|
end
|
|
325
|
+
|
|
326
|
+
# TODO: should this eventually be hardened and extracted into the base framework?
|
|
327
|
+
def force_validation(auth_response, kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
|
|
328
|
+
# Trigger a non-$0 auth
|
|
329
|
+
new_auth_response = nil
|
|
330
|
+
begin
|
|
331
|
+
# If duplicate checks are enabled, we need to bypass them (since a transaction for that merchant reference code was already attempted)
|
|
332
|
+
properties << build_property(:bypass_duplicate_check, true)
|
|
333
|
+
new_auth_response = authorize_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
|
|
334
|
+
rescue => e
|
|
335
|
+
# Note: state might be broken here (potentially two responses with the same kb_payment_transaction_id)
|
|
336
|
+
@logger.warn("Unexpected exception while forcing validation for kb_payment_id='#{kb_payment_id}', kb_payment_transaction_id='#{kb_payment_transaction_id}'\n#{e.backtrace.join("\n")}")
|
|
337
|
+
return auth_response
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# Void it right away on success (make sure we didn't skip the gateway call too)
|
|
341
|
+
if new_auth_response.status == :PROCESSED && !new_auth_response.first_payment_reference_id.blank?
|
|
342
|
+
begin
|
|
343
|
+
void_payment(kb_account_id, kb_payment_id, SecureRandom.uuid, kb_payment_method_id, properties, context)
|
|
344
|
+
rescue => e
|
|
345
|
+
@logger.warn("Unexpected exception while voiding forced validation for kb_payment_id='#{kb_payment_id}', kb_payment_transaction_id='#{kb_payment_transaction_id}'\n#{e.backtrace.join("\n")}")
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# Finally, clean up the state of the original (failed) auth
|
|
350
|
+
cybersource_response_id = find_value_from_properties(auth_response.properties, 'cybersourceResponseId')
|
|
351
|
+
if cybersource_response_id.nil?
|
|
352
|
+
@logger.warn "Unable to find cybersourceResponseId matching failed authorization for kb_payment_id='#{kb_payment_id}', kb_payment_transaction_id='#{kb_payment_transaction_id}'"
|
|
353
|
+
else
|
|
354
|
+
response = CybersourceResponse.find_by(:id => cybersource_response_id)
|
|
355
|
+
if response.nil?
|
|
356
|
+
@logger.warn "Unable to find response matching failed authorization for kb_payment_id='#{kb_payment_id}', kb_payment_transaction_id='#{kb_payment_transaction_id}'"
|
|
357
|
+
else
|
|
358
|
+
# Change the kb_payment_transaction_id to avoid confusing Kill Bill (there is no transaction row to update since the call wasn't successful)
|
|
359
|
+
response.update(:kb_payment_transaction_id => SecureRandom.uuid)
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
new_auth_response
|
|
364
|
+
end
|
|
296
365
|
end
|
|
297
366
|
end
|
|
298
367
|
end
|
|
@@ -3,17 +3,20 @@ module Killbill #:nodoc:
|
|
|
3
3
|
# See http://apps.cybersource.com/library/documentation/dev_guides/Reporting_Developers_Guide/reporting_dg.pdf
|
|
4
4
|
class CyberSourceOnDemand
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
# For convenience, re-use the ActiveMerchant connection code, as the configuration is global currently
|
|
7
|
+
# (see https://github.com/killbill/killbill-plugin-framework-ruby/issues/47)
|
|
8
|
+
include ActiveMerchant::PostsData
|
|
8
9
|
|
|
9
|
-
def initialize(
|
|
10
|
-
@
|
|
10
|
+
def initialize(config, logger)
|
|
11
|
+
@config = config
|
|
11
12
|
@logger = logger
|
|
13
|
+
|
|
14
|
+
configure_connection
|
|
12
15
|
end
|
|
13
16
|
|
|
14
17
|
def single_transaction_report(merchant_reference_code, target_date)
|
|
15
18
|
params = {
|
|
16
|
-
:merchantID => @
|
|
19
|
+
:merchantID => @config[:merchantID],
|
|
17
20
|
:merchantReferenceNumber => merchant_reference_code,
|
|
18
21
|
:targetDate => target_date,
|
|
19
22
|
:type => 'transaction',
|
|
@@ -23,14 +26,50 @@ module Killbill #:nodoc:
|
|
|
23
26
|
|
|
24
27
|
headers = {
|
|
25
28
|
# Don't use symbols or it will confuse Net/HTTP
|
|
26
|
-
'Authorization' => 'Basic ' + Base64.encode64("#{@
|
|
29
|
+
'Authorization' => 'Basic ' + Base64.encode64("#{@config[:username]}:#{@config[:password]}").chomp
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
data = URI.encode_www_form(params)
|
|
30
|
-
endpoint = @gateway.test? ? @@test_url : @@live_url
|
|
31
33
|
|
|
32
34
|
# Will raise ResponseError if the response code is > 300
|
|
33
|
-
CyberSourceOnDemandTransactionReport.new(
|
|
35
|
+
CyberSourceOnDemandTransactionReport.new(ssl_post(endpoint, data, headers), @logger)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def check_for_duplicates?
|
|
39
|
+
@config[:check_for_duplicates] == true
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def endpoint
|
|
45
|
+
@config[:test] == false ? live_url : test_url
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def live_url
|
|
49
|
+
@config[:live_url] || 'https://ebc.cybersource.com/ebc/Query'
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_url
|
|
53
|
+
@config[:test_url] || 'https://ebctest.cybersource.com/ebctest/Query'
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def configure_connection
|
|
57
|
+
if @config[:log_file]
|
|
58
|
+
self.wiredump_device = File.open(@config[:log_file], 'w')
|
|
59
|
+
else
|
|
60
|
+
log_method = @config[:quiet] ? :debug : :info
|
|
61
|
+
self.wiredump_device = ::Killbill::Plugin::ActiveMerchant::Utils::KBWiredumpDevice.new(@logger, log_method)
|
|
62
|
+
end
|
|
63
|
+
self.wiredump_device.sync = true
|
|
64
|
+
|
|
65
|
+
self.open_timeout = @config[:open_timeout] unless @config[:open_timeout].nil?
|
|
66
|
+
self.read_timeout = @config[:read_timeout] unless @config[:read_timeout].nil?
|
|
67
|
+
self.retry_safe = @config[:retry_safe] unless @config[:retry_safe].nil?
|
|
68
|
+
self.ssl_strict = @config[:ssl_strict] unless @config[:ssl_strict].nil?
|
|
69
|
+
self.ssl_version = @config[:ssl_version] unless @config[:ssl_version].nil?
|
|
70
|
+
self.max_retries = @config[:max_retries] unless @config[:max_retries].nil?
|
|
71
|
+
self.proxy_address = @config[:proxy_address] unless @config[:proxy_address].nil?
|
|
72
|
+
self.proxy_port = @config[:proxy_port] unless @config[:proxy_port].nil?
|
|
34
73
|
end
|
|
35
74
|
|
|
36
75
|
class CyberSourceOnDemandTransactionReport
|
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
module ActiveMerchant
|
|
2
2
|
module Billing
|
|
3
|
+
|
|
4
|
+
KB_PLUGIN_VERSION = Gem.loaded_specs['killbill-cybersource'].version.version rescue nil
|
|
5
|
+
|
|
3
6
|
class CyberSourceGateway
|
|
4
7
|
|
|
8
|
+
def initialize(options = {})
|
|
9
|
+
super
|
|
10
|
+
|
|
11
|
+
# Add missing response codes
|
|
12
|
+
@@response_codes[:r104] = 'The merchant reference code for this authorization request matches the merchant reference code of another authorization request that you sent within the past 15 minutes.'
|
|
13
|
+
@@response_codes[:r110] = 'Only a partial amount was approved'
|
|
14
|
+
end
|
|
15
|
+
|
|
5
16
|
# Add support for CreditCard objects
|
|
6
17
|
def build_credit_request(money, creditcard_or_reference, options)
|
|
7
18
|
xml = Builder::XmlMarkup.new :indent => 2
|
|
@@ -14,6 +25,67 @@ module ActiveMerchant
|
|
|
14
25
|
xml.target!
|
|
15
26
|
end
|
|
16
27
|
|
|
28
|
+
# Add support for commerceIndicator override
|
|
29
|
+
def add_auth_service(xml, payment_method, options)
|
|
30
|
+
if network_tokenization?(payment_method)
|
|
31
|
+
add_network_tokenization(xml, payment_method, options)
|
|
32
|
+
else
|
|
33
|
+
xml.tag! 'ccAuthService', {'run' => 'true'} do
|
|
34
|
+
# Let CyberSource figure it out otherwise (internet is the default unless tokens are used)
|
|
35
|
+
xml.tag!("commerceIndicator", options[:commerce_indicator]) unless options[:commerce_indicator].blank?
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Changes:
|
|
41
|
+
# * Add support for commerceIndicator override
|
|
42
|
+
# * Don't set paymentNetworkToken (needs to be set after businessRules)
|
|
43
|
+
def add_network_tokenization(xml, payment_method, options)
|
|
44
|
+
return unless network_tokenization?(payment_method)
|
|
45
|
+
|
|
46
|
+
case card_brand(payment_method).to_sym
|
|
47
|
+
when :visa
|
|
48
|
+
xml.tag! 'ccAuthService', {'run' => 'true'} do
|
|
49
|
+
xml.tag!("cavv", payment_method.payment_cryptogram)
|
|
50
|
+
xml.tag!("commerceIndicator", options[:commerce_indicator] || "vbv")
|
|
51
|
+
xml.tag!("xid", payment_method.payment_cryptogram)
|
|
52
|
+
end
|
|
53
|
+
when :mastercard
|
|
54
|
+
xml.tag! 'ucaf' do
|
|
55
|
+
xml.tag!("authenticationData", payment_method.payment_cryptogram)
|
|
56
|
+
xml.tag!("collectionIndicator", "2")
|
|
57
|
+
end
|
|
58
|
+
xml.tag! 'ccAuthService', {'run' => 'true'} do
|
|
59
|
+
xml.tag!("commerceIndicator", options[:commerce_indicator] || "spa")
|
|
60
|
+
end
|
|
61
|
+
when :american_express
|
|
62
|
+
cryptogram = Base64.decode64(payment_method.payment_cryptogram)
|
|
63
|
+
xml.tag! 'ccAuthService', {'run' => 'true'} do
|
|
64
|
+
xml.tag!("cavv", Base64.encode64(cryptogram[0...20]))
|
|
65
|
+
xml.tag!("commerceIndicator", options[:commerce_indicator] || "aesk")
|
|
66
|
+
xml.tag!("xid", Base64.encode64(cryptogram[20...40]))
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Changes:
|
|
72
|
+
# * Enable business rules for Apple Pay
|
|
73
|
+
# * Set paymentNetworkToken if needed (a bit of a hack to do it here, but it avoids having to override too much code)
|
|
74
|
+
def add_business_rules_data(xml, payment_method, options)
|
|
75
|
+
prioritized_options = [options, @options]
|
|
76
|
+
|
|
77
|
+
xml.tag! 'businessRules' do
|
|
78
|
+
xml.tag!('ignoreAVSResult', 'true') if extract_option(prioritized_options, :ignore_avs)
|
|
79
|
+
xml.tag!('ignoreCVResult', 'true') if extract_option(prioritized_options, :ignore_cvv)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
if network_tokenization?(payment_method)
|
|
83
|
+
xml.tag! 'paymentNetworkToken' do
|
|
84
|
+
xml.tag!('transactionType', "1")
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
17
89
|
# See https://github.com/killbill/killbill-cybersource-plugin/issues/4
|
|
18
90
|
def commit(request, options)
|
|
19
91
|
request = build_request(request, options)
|
|
@@ -24,14 +96,23 @@ module ActiveMerchant
|
|
|
24
96
|
end
|
|
25
97
|
response = parse(raw_response)
|
|
26
98
|
|
|
99
|
+
# Remove namespace when unnecessary (ActiveMerchant and our original code expect it that way)
|
|
100
|
+
response.keys.each do |k|
|
|
101
|
+
_, actual_key = k.to_s.split('_', 2)
|
|
102
|
+
if !actual_key.nil? && !response.has_key?(actual_key)
|
|
103
|
+
response[actual_key] = response[k]
|
|
104
|
+
response.delete(k)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
27
108
|
success = response[:decision] == 'ACCEPT'
|
|
28
|
-
authorization = success ? [options[:order_id], response[:requestID], response[:requestToken]].compact.join(
|
|
109
|
+
authorization = success ? [options[:order_id], response[:requestID], response[:requestToken]].compact.join(';') : nil
|
|
29
110
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
message = @@response_codes[('r' + response[:reasonCode]).to_sym] rescue response[:message]
|
|
111
|
+
message = nil
|
|
112
|
+
if response[:reasonCode].blank? && (response[:faultcode] == 'wsse:FailedCheck' || response[:faultcode] == 'wsse:InvalidSecurity' || response[:faultcode] == 'soap:Client' || response[:faultcode] == 'c:ServerError')
|
|
113
|
+
message = {:exception_message => response[:message], :payment_plugin_status => :CANCELED}.to_json
|
|
34
114
|
end
|
|
115
|
+
message ||= @@response_codes[('r' + response[:reasonCode].to_s).to_sym] || response[:message]
|
|
35
116
|
|
|
36
117
|
Response.new(success, message, response,
|
|
37
118
|
:test => test?,
|
|
@@ -40,6 +121,31 @@ module ActiveMerchant
|
|
|
40
121
|
:cvv_result => response[:cvCode]
|
|
41
122
|
)
|
|
42
123
|
end
|
|
124
|
+
|
|
125
|
+
def add_merchant_data(xml, options)
|
|
126
|
+
xml.tag! 'merchantID', @options[:login]
|
|
127
|
+
xml.tag! 'merchantReferenceCode', options[:order_id]
|
|
128
|
+
xml.tag! 'clientLibrary' ,'Kill Bill'
|
|
129
|
+
xml.tag! 'clientLibraryVersion', KB_PLUGIN_VERSION
|
|
130
|
+
xml.tag! 'clientEnvironment' , RUBY_PLATFORM
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def parse_element(reply, node)
|
|
134
|
+
if node.has_elements?
|
|
135
|
+
node.elements.each { |e| parse_element(reply, e) }
|
|
136
|
+
else
|
|
137
|
+
# The original ActiveMerchant implementation clobbers top level fields with values from the children
|
|
138
|
+
# Instead, always namespace the keys for children (cleanup is needed afterwards, see above)
|
|
139
|
+
is_top_level = node.parent.name == 'replyMessage' || node.parent.name == 'Fault'
|
|
140
|
+
key = node.name.to_sym
|
|
141
|
+
unless is_top_level
|
|
142
|
+
parent = node.parent.name + (node.parent.attributes['id'] ? '_' + node.parent.attributes['id'] : '')
|
|
143
|
+
key = (parent + '_' + node.name).to_sym
|
|
144
|
+
end
|
|
145
|
+
reply[key] = node.text
|
|
146
|
+
end
|
|
147
|
+
return reply
|
|
148
|
+
end
|
|
43
149
|
end
|
|
44
150
|
end
|
|
45
151
|
end
|
|
@@ -6,6 +6,9 @@ module Killbill #:nodoc:
|
|
|
6
6
|
|
|
7
7
|
has_one :cybersource_transaction
|
|
8
8
|
|
|
9
|
+
UNDEFINED_ERROR_CODES = [ 151, 152, 250 ]
|
|
10
|
+
CANCELED_ERROR_CODES = [ 101, 102, 104, 150, 207, 232, 234, 235, 236, 237, 238, 239, 240, 241, 243, 246, 247, 254 ]
|
|
11
|
+
|
|
9
12
|
def self.from_response(api_call, kb_account_id, kb_payment_id, kb_payment_transaction_id, transaction_type, payment_processor_account_id, kb_tenant_id, response, extra_params = {}, model = ::Killbill::Cybersource::CybersourceResponse)
|
|
10
13
|
super(api_call,
|
|
11
14
|
kb_account_id,
|
|
@@ -98,8 +101,23 @@ module Killbill #:nodoc:
|
|
|
98
101
|
|
|
99
102
|
t_info_plugin.properties << create_plugin_property('cybersourceResponseId', id)
|
|
100
103
|
|
|
104
|
+
set_correct_status(t_info_plugin)
|
|
105
|
+
|
|
101
106
|
t_info_plugin
|
|
102
107
|
end
|
|
108
|
+
|
|
109
|
+
def set_correct_status(t_info_plugin)
|
|
110
|
+
# Respect the existing status if the payment was successful, if overridden or if there is no error code
|
|
111
|
+
return if success || message.strip.start_with?('{') || gateway_error_code.blank?
|
|
112
|
+
|
|
113
|
+
if CANCELED_ERROR_CODES.include?(gateway_error_code.to_i)
|
|
114
|
+
t_info_plugin.status = :CANCELED
|
|
115
|
+
elsif UNDEFINED_ERROR_CODES.include?(gateway_error_code.to_i)
|
|
116
|
+
t_info_plugin.status = :UNDEFINED
|
|
117
|
+
else
|
|
118
|
+
t_info_plugin.status = :ERROR
|
|
119
|
+
end
|
|
120
|
+
end
|
|
103
121
|
end
|
|
104
122
|
end
|
|
105
123
|
end
|
data/pom.xml
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
<groupId>org.kill-bill.billing.plugin.ruby</groupId>
|
|
26
26
|
<artifactId>cybersource-plugin</artifactId>
|
|
27
27
|
<packaging>pom</packaging>
|
|
28
|
-
<version>4.0.
|
|
28
|
+
<version>4.0.3</version>
|
|
29
29
|
<name>cybersource-plugin</name>
|
|
30
30
|
<url>http://github.com/killbill/killbill-cybersource-plugin</url>
|
|
31
31
|
<description>Plugin for accessing Cybersource as a payment gateway</description>
|
|
@@ -24,6 +24,26 @@ describe Killbill::Cybersource::PaymentPlugin do
|
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
let(:expected_successful_params) do
|
|
28
|
+
{
|
|
29
|
+
:params_merchant_reference_code => 'b0a6cf9aa07f1a8495f89c364bbd6a9a',
|
|
30
|
+
:params_request_id => '2004333231260008401927',
|
|
31
|
+
:params_decision => 'ACCEPT',
|
|
32
|
+
:params_reason_code => '100',
|
|
33
|
+
:params_request_token => 'Afvvj7Ke2Fmsbq0wHFE2sM6R4GAptYZ0jwPSA+R9PhkyhFTb0KRjoE4+ynthZrG6tMBwjAtT',
|
|
34
|
+
:params_currency => 'USD',
|
|
35
|
+
:params_amount => '1.00',
|
|
36
|
+
:params_authorization_code => '123456',
|
|
37
|
+
:params_avs_code => 'Y',
|
|
38
|
+
:params_avs_code_raw => 'Y',
|
|
39
|
+
:params_cv_code => 'M',
|
|
40
|
+
:params_authorized_date_time => '2008-01-15T21:42:03Z',
|
|
41
|
+
:params_processor_response => '00',
|
|
42
|
+
:params_reconciliation_id => 'ABCDEF',
|
|
43
|
+
:params_subscription_id => 'XXYYZZ'
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
27
47
|
it 'should start and stop correctly' do
|
|
28
48
|
@plugin.stop_plugin
|
|
29
49
|
end
|
|
@@ -54,8 +74,8 @@ describe Killbill::Cybersource::PaymentPlugin do
|
|
|
54
74
|
request_body.should_not match('<ignoreCVResult>')
|
|
55
75
|
successful_purchase_response
|
|
56
76
|
end
|
|
57
|
-
|
|
58
|
-
|
|
77
|
+
purchase_with_token(:PROCESSED, [], expected_successful_params)
|
|
78
|
+
purchase_with_token(:PROCESSED, [build_property('ignore_avs', 'false'), build_property('ignore_cvv', 'false')], expected_successful_params)
|
|
59
79
|
end
|
|
60
80
|
|
|
61
81
|
it 'ignores AVS and CVN' do
|
|
@@ -64,7 +84,7 @@ describe Killbill::Cybersource::PaymentPlugin do
|
|
|
64
84
|
request_body.should match('<ignoreCVResult>')
|
|
65
85
|
successful_purchase_response
|
|
66
86
|
end
|
|
67
|
-
|
|
87
|
+
purchase_with_token(:PROCESSED, [build_property('ignore_avs', 'true'), build_property('ignore_cvv', 'true')], expected_successful_params)
|
|
68
88
|
end
|
|
69
89
|
|
|
70
90
|
it 'ignores AVS but not CVN' do
|
|
@@ -73,8 +93,8 @@ describe Killbill::Cybersource::PaymentPlugin do
|
|
|
73
93
|
request_body.should_not match('<ignoreCVResult>')
|
|
74
94
|
successful_purchase_response
|
|
75
95
|
end
|
|
76
|
-
|
|
77
|
-
|
|
96
|
+
purchase_with_token(:PROCESSED, [build_property('ignore_avs', 'true')], expected_successful_params)
|
|
97
|
+
purchase_with_token(:PROCESSED, [build_property('ignore_avs', 'true'), build_property('ignore_cvv', 'false')], expected_successful_params)
|
|
78
98
|
end
|
|
79
99
|
|
|
80
100
|
it 'ignores CVN but not AVS' do
|
|
@@ -83,8 +103,45 @@ describe Killbill::Cybersource::PaymentPlugin do
|
|
|
83
103
|
request_body.should match('<ignoreCVResult>')
|
|
84
104
|
successful_purchase_response
|
|
85
105
|
end
|
|
86
|
-
|
|
87
|
-
|
|
106
|
+
purchase_with_token(:PROCESSED, [build_property('ignore_cvv', 'true')], expected_successful_params)
|
|
107
|
+
purchase_with_token(:PROCESSED, [build_property('ignore_avs', 'false'), build_property('ignore_cvv', 'true')], expected_successful_params)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
context 'Override parameters' do
|
|
112
|
+
|
|
113
|
+
it 'has a default commerceIndicator' do
|
|
114
|
+
::ActiveMerchant::Billing::CyberSourceGateway.any_instance.stub(:ssl_post) do |host, request_body|
|
|
115
|
+
request_body.should_not match('<commerceIndicator>')
|
|
116
|
+
successful_purchase_response
|
|
117
|
+
end
|
|
118
|
+
purchase_with_token(:PROCESSED, [], expected_successful_params)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it 'can override commerceIndicator for card-on-file' do
|
|
122
|
+
::ActiveMerchant::Billing::CyberSourceGateway.any_instance.stub(:ssl_post) do |host, request_body|
|
|
123
|
+
request_body.should match('<commerceIndicator>recurring</commerceIndicator>')
|
|
124
|
+
successful_purchase_response
|
|
125
|
+
end
|
|
126
|
+
purchase_with_card(:PROCESSED, [build_property('commerce_indicator', 'recurring')], expected_successful_params)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
it 'has a default commerceIndicator for Apple Pay' do
|
|
130
|
+
::ActiveMerchant::Billing::CyberSourceGateway.any_instance.stub(:ssl_post) do |host, request_body|
|
|
131
|
+
request_body.should_not match('<commerceIndicator>internet</commerceIndicator>')
|
|
132
|
+
request_body.should match('<commerceIndicator>vbv</commerceIndicator>')
|
|
133
|
+
successful_purchase_response
|
|
134
|
+
end
|
|
135
|
+
purchase_with_network_tokenization(:PROCESSED, [], expected_successful_params)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
it 'can override commerceIndicator for Apple Pay' do
|
|
139
|
+
::ActiveMerchant::Billing::CyberSourceGateway.any_instance.stub(:ssl_post) do |host, request_body|
|
|
140
|
+
request_body.should_not match('<commerceIndicator>vbv</commerceIndicator>')
|
|
141
|
+
request_body.should match('<commerceIndicator>internet</commerceIndicator>')
|
|
142
|
+
successful_purchase_response
|
|
143
|
+
end
|
|
144
|
+
purchase_with_network_tokenization(:PROCESSED, [build_property('commerce_indicator', 'internet')], expected_successful_params)
|
|
88
145
|
end
|
|
89
146
|
end
|
|
90
147
|
|
|
@@ -92,17 +149,24 @@ describe Killbill::Cybersource::PaymentPlugin do
|
|
|
92
149
|
|
|
93
150
|
it 'handles expired passwords as CANCELED transactions' do
|
|
94
151
|
::ActiveMerchant::Billing::CyberSourceGateway.any_instance.stub(:ssl_post).and_return(password_expired_response)
|
|
95
|
-
|
|
152
|
+
purchase_with_token(:CANCELED).gateway_error.should == 'wsse:FailedCheck: Security Data : Merchant password has expired.'
|
|
96
153
|
end
|
|
97
154
|
|
|
98
155
|
it 'handles bad passwords as CANCELED transactions' do
|
|
99
156
|
::ActiveMerchant::Billing::CyberSourceGateway.any_instance.stub(:ssl_post).and_return(bad_password_response)
|
|
100
|
-
|
|
157
|
+
purchase_with_token(:CANCELED).gateway_error.should == 'wsse:FailedCheck: Security Data : UsernameToken authentication failed.'
|
|
101
158
|
end
|
|
102
159
|
|
|
103
160
|
it 'handles unsuccessful authorizations as ERROR transactions' do
|
|
104
161
|
::ActiveMerchant::Billing::CyberSourceGateway.any_instance.stub(:ssl_post).and_return(unsuccessful_authorization_response)
|
|
105
|
-
|
|
162
|
+
purchase_with_token(:ERROR).gateway_error.should == 'Invalid account number'
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it 'parses correctly authorization reversal errors' do
|
|
166
|
+
::ActiveMerchant::Billing::CyberSourceGateway.any_instance.stub(:ssl_post).and_return(unsuccessful_auth_reversal_response)
|
|
167
|
+
payment_response = purchase_with_token(:CANCELED)
|
|
168
|
+
payment_response.gateway_error.should == 'One or more fields contains invalid data'
|
|
169
|
+
payment_response.gateway_error_code.should == '102'
|
|
106
170
|
end
|
|
107
171
|
end
|
|
108
172
|
|
|
@@ -125,16 +189,43 @@ describe Killbill::Cybersource::PaymentPlugin do
|
|
|
125
189
|
t.destroy! unless t.nil?
|
|
126
190
|
end
|
|
127
191
|
|
|
128
|
-
def
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
kb_transaction_id = kb_payment.transactions[0].id
|
|
192
|
+
def purchase_with_card(expected_status = :PROCESSED, properties = [], expected_params = {})
|
|
193
|
+
properties << build_property('email', 'foo@bar.com')
|
|
194
|
+
properties << build_property('cc_number', '4111111111111111')
|
|
132
195
|
|
|
196
|
+
purchase(expected_status, properties, expected_params)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def purchase_with_token(expected_status = :PROCESSED, properties = [], expected_params = {})
|
|
133
200
|
properties << build_property('email', 'foo@bar.com')
|
|
134
201
|
properties << build_property('token', '1234')
|
|
135
202
|
|
|
203
|
+
purchase(expected_status, properties, expected_params)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def purchase_with_network_tokenization(expected_status = :PROCESSED, properties = [], expected_params = {})
|
|
207
|
+
properties << build_property('email', 'foo@bar.com')
|
|
208
|
+
properties << build_property('cc_number', '4111111111111111')
|
|
209
|
+
properties << build_property('brand', 'visa')
|
|
210
|
+
properties << build_property('eci', '05')
|
|
211
|
+
properties << build_property('payment_cryptogram', '111111111100cryptogram')
|
|
212
|
+
|
|
213
|
+
purchase(expected_status, properties, expected_params)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def purchase(expected_status = :PROCESSED, properties = [], expected_params = {})
|
|
217
|
+
kb_payment_id = SecureRandom.uuid
|
|
218
|
+
kb_payment = @plugin.kb_apis.proxied_services[:payment_api].add_payment(kb_payment_id)
|
|
219
|
+
kb_transaction_id = kb_payment.transactions[0].id
|
|
220
|
+
|
|
136
221
|
payment_response = @plugin.purchase_payment(SecureRandom.uuid, kb_payment_id, kb_transaction_id, SecureRandom.uuid, BigDecimal.new('100'), 'USD', properties, build_call_context)
|
|
137
222
|
payment_response.status.should eq(expected_status), payment_response.gateway_error
|
|
223
|
+
|
|
224
|
+
gw_response = Killbill::Cybersource::CybersourceResponse.last
|
|
225
|
+
expected_params.each do |k, v|
|
|
226
|
+
gw_response.send(k.to_sym).should == v
|
|
227
|
+
end
|
|
228
|
+
|
|
138
229
|
payment_response
|
|
139
230
|
end
|
|
140
231
|
|
|
@@ -142,7 +233,7 @@ describe Killbill::Cybersource::PaymentPlugin do
|
|
|
142
233
|
<<-XML
|
|
143
234
|
<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
|
|
144
235
|
<soap:Header>
|
|
145
|
-
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Timestamp-2636690"><wsu:Created>2008-01-15T21:42:03.343Z</wsu:Created></wsu:Timestamp></wsse:Security></soap:Header><soap:Body><c:replyMessage xmlns:c="urn:schemas-cybersource-com:transaction-data-1.26"><c:merchantReferenceCode>b0a6cf9aa07f1a8495f89c364bbd6a9a</c:merchantReferenceCode><c:requestID>2004333231260008401927</c:requestID><c:decision>ACCEPT</c:decision><c:reasonCode>100</c:reasonCode><c:requestToken>Afvvj7Ke2Fmsbq0wHFE2sM6R4GAptYZ0jwPSA+R9PhkyhFTb0KRjoE4+ynthZrG6tMBwjAtT</c:requestToken><c:purchaseTotals><c:currency>USD</c:currency></c:purchaseTotals><c:ccAuthReply><c:reasonCode>100</c:reasonCode><c:amount>1.00</c:amount><c:authorizationCode>123456</c:authorizationCode><c:avsCode>Y</c:avsCode><c:avsCodeRaw>Y</c:avsCodeRaw><c:cvCode>M</c:cvCode><c:cvCodeRaw>M</c:cvCodeRaw><c:authorizedDateTime>2008-01-15T21:42:03Z</c:authorizedDateTime><c:processorResponse>00</c:processorResponse><c:authFactorCode>U</c:authFactorCode></c:ccAuthReply></c:replyMessage></soap:Body></soap:Envelope>
|
|
236
|
+
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Timestamp-2636690"><wsu:Created>2008-01-15T21:42:03.343Z</wsu:Created></wsu:Timestamp></wsse:Security></soap:Header><soap:Body><c:replyMessage xmlns:c="urn:schemas-cybersource-com:transaction-data-1.26"><c:merchantReferenceCode>b0a6cf9aa07f1a8495f89c364bbd6a9a</c:merchantReferenceCode><c:requestID>2004333231260008401927</c:requestID><c:decision>ACCEPT</c:decision><c:reasonCode>100</c:reasonCode><c:requestToken>Afvvj7Ke2Fmsbq0wHFE2sM6R4GAptYZ0jwPSA+R9PhkyhFTb0KRjoE4+ynthZrG6tMBwjAtT</c:requestToken><c:purchaseTotals><c:currency>USD</c:currency></c:purchaseTotals><c:ccAuthReply><c:reasonCode>100</c:reasonCode><c:amount>1.00</c:amount><c:authorizationCode>123456</c:authorizationCode><c:avsCode>Y</c:avsCode><c:avsCodeRaw>Y</c:avsCodeRaw><c:cvCode>M</c:cvCode><c:cvCodeRaw>M</c:cvCodeRaw><c:authorizedDateTime>2008-01-15T21:42:03Z</c:authorizedDateTime><c:processorResponse>00</c:processorResponse><c:reconciliationID>ABCDEF</c:reconciliationID><c:authFactorCode>U</c:authFactorCode></c:ccAuthReply><c:paySubscriptionCreateReply><c:reasonCode>100</c:reasonCode><c:subscriptionID>XXYYZZ</c:subscriptionID></c:paySubscriptionCreateReply></c:replyMessage></soap:Body></soap:Envelope>
|
|
146
237
|
XML
|
|
147
238
|
end
|
|
148
239
|
|
|
@@ -169,4 +260,12 @@ describe Killbill::Cybersource::PaymentPlugin do
|
|
|
169
260
|
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Timestamp-28121162"><wsu:Created>2008-01-15T21:50:41.580Z</wsu:Created></wsu:Timestamp></wsse:Security></soap:Header><soap:Body><c:replyMessage xmlns:c="urn:schemas-cybersource-com:transaction-data-1.26"><c:merchantReferenceCode>a1efca956703a2a5037178a8a28f7357</c:merchantReferenceCode><c:requestID>2004338415330008402434</c:requestID><c:decision>REJECT</c:decision><c:reasonCode>231</c:reasonCode><c:requestToken>Afvvj7KfIgU12gooCFE2/DanQIApt+G1OgTSA+R9PTnyhFTb0KRjgFY+ynyIFNdoKKAghwgx</c:requestToken><c:ccAuthReply><c:reasonCode>231</c:reasonCode></c:ccAuthReply></c:replyMessage></soap:Body></soap:Envelope>
|
|
170
261
|
XML
|
|
171
262
|
end
|
|
263
|
+
|
|
264
|
+
def unsuccessful_auth_reversal_response
|
|
265
|
+
<<-XML
|
|
266
|
+
<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
|
|
267
|
+
<soap:Header>
|
|
268
|
+
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Timestamp-28121162"><wsu:Created>2008-01-15T21:50:41.580Z</wsu:Created></wsu:Timestamp></wsse:Security></soap:Header><soap:Body><c:replyMessage xmlns:c="urn:schemas-cybersource-com:transaction-data-1.26"><c:merchantReferenceCode>a1efca956703a2a5037178a8a28f7357</c:merchantReferenceCode><c:requestID>2004338415330008402434</c:requestID><c:decision>REJECT</c:decision><c:reasonCode>102</c:reasonCode><c:requestToken>Afvvj7KfIgU12gooCFE2/DanQIApt+G1OgTSA+R9PTnyhFTb0KRjgFY+ynyIFNdoKKAghwgx</c:requestToken><c:ccAuthReversalReply><c:reasonCode>102</c:reasonCode></c:ccAuthReversalReply><c:originalTransaction><c:amount>0.00</c:amount><c:reasonCode>100</c:reasonCode></c:originalTransaction></c:replyMessage></soap:Body></soap:Envelope>
|
|
269
|
+
XML
|
|
270
|
+
end
|
|
172
271
|
end
|
|
@@ -21,18 +21,19 @@ describe Killbill::Cybersource::PaymentPlugin do
|
|
|
21
21
|
@amount = BigDecimal.new('100')
|
|
22
22
|
@currency = 'USD'
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
1.upto(6) do
|
|
26
|
-
@kb_payment = @plugin.kb_apis.proxied_services[:payment_api].add_payment(kb_payment_id)
|
|
27
|
-
end
|
|
24
|
+
@kb_payment = setup_kb_payment(6)
|
|
28
25
|
end
|
|
29
26
|
|
|
30
27
|
after(:each) do
|
|
31
28
|
@plugin.stop_plugin
|
|
32
29
|
end
|
|
33
30
|
|
|
31
|
+
let(:report_api) do
|
|
32
|
+
@plugin.get_report_api({}, @call_context)
|
|
33
|
+
end
|
|
34
|
+
|
|
34
35
|
let(:with_report_api) do
|
|
35
|
-
|
|
36
|
+
report_api.present?
|
|
36
37
|
end
|
|
37
38
|
|
|
38
39
|
it 'should be able to charge a Credit Card directly and calls should be idempotent' do
|
|
@@ -43,12 +44,9 @@ describe Killbill::Cybersource::PaymentPlugin do
|
|
|
43
44
|
Killbill::Cybersource::CybersourceTransaction.all.size.should == 0
|
|
44
45
|
|
|
45
46
|
payment_response = @plugin.purchase_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[0].id, @pm.kb_payment_method_id, @amount, @currency, properties, @call_context)
|
|
46
|
-
payment_response
|
|
47
|
-
payment_response.amount.should == @amount
|
|
48
|
-
payment_response.transaction_type.should == :PURCHASE
|
|
47
|
+
check_response(payment_response, @amount, :PURCHASE, :PROCESSED, 'Successful transaction', '100')
|
|
49
48
|
payment_response.first_payment_reference_id.should_not be_nil
|
|
50
49
|
payment_response.second_payment_reference_id.should_not be_nil
|
|
51
|
-
payment_response.gateway_error_code.should_not be_nil
|
|
52
50
|
|
|
53
51
|
responses = Killbill::Cybersource::CybersourceResponse.all
|
|
54
52
|
responses.size.should == 2
|
|
@@ -60,8 +58,8 @@ describe Killbill::Cybersource::PaymentPlugin do
|
|
|
60
58
|
transactions.size.should == 1
|
|
61
59
|
transactions[0].api_call.should == 'purchase'
|
|
62
60
|
|
|
63
|
-
# Skip the rest of the test if the report API isn't configured
|
|
64
|
-
break unless with_report_api
|
|
61
|
+
# Skip the rest of the test if the report API isn't configured to check for duplicates
|
|
62
|
+
break unless with_report_api && report_api.check_for_duplicates?
|
|
65
63
|
|
|
66
64
|
payment_response = @plugin.purchase_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[0].id, @pm.kb_payment_method_id, @amount, @currency, @properties, @call_context)
|
|
67
65
|
payment_response.amount.should == @amount
|
|
@@ -88,9 +86,77 @@ describe Killbill::Cybersource::PaymentPlugin do
|
|
|
88
86
|
transactions[1].txn_id.should be_nil
|
|
89
87
|
end
|
|
90
88
|
|
|
89
|
+
it 'should be able to verify a Credit Card' do
|
|
90
|
+
# Valid card
|
|
91
|
+
properties = build_pm_properties
|
|
92
|
+
kb_payment = setup_kb_payment(2)
|
|
93
|
+
payment_response = @plugin.authorize_payment(@pm.kb_account_id, kb_payment.id, kb_payment.transactions[0].id, @pm.kb_payment_method_id, 0, @currency, properties, @call_context)
|
|
94
|
+
check_response(payment_response, 0, :AUTHORIZE, :PROCESSED, 'Successful transaction', '100')
|
|
95
|
+
payment_response.first_payment_reference_id.should_not be_nil
|
|
96
|
+
payment_response.second_payment_reference_id.should_not be_nil
|
|
97
|
+
|
|
98
|
+
# Note that you won't be able to void the $0 auth
|
|
99
|
+
payment_response = @plugin.void_payment(@pm.kb_account_id, kb_payment.id, kb_payment.transactions[1].id, @pm.kb_payment_method_id, @properties, @call_context)
|
|
100
|
+
check_response(payment_response, nil, :VOID, :CANCELED, 'One or more fields contains invalid data', '102')
|
|
101
|
+
|
|
102
|
+
# Invalid card
|
|
103
|
+
# See http://www.cybersource.com/developers/getting_started/test_and_manage/simple_order_api/HTML/General_testing_info/soapi_general_test.html
|
|
104
|
+
properties = build_pm_properties(nil, { :cc_exp_year => 1998 })
|
|
105
|
+
kb_payment = setup_kb_payment
|
|
106
|
+
payment_response = @plugin.authorize_payment(@pm.kb_account_id, kb_payment.id, kb_payment.transactions[0].id, @pm.kb_payment_method_id, 0, @currency, properties, @call_context)
|
|
107
|
+
check_response(payment_response, nil, :AUTHORIZE, :ERROR, 'Expired card', '202')
|
|
108
|
+
payment_response.first_payment_reference_id.should_not be_nil
|
|
109
|
+
payment_response.second_payment_reference_id.should be_nil
|
|
110
|
+
|
|
111
|
+
# Discover card (doesn't support $0 auth on Paymentech)
|
|
112
|
+
# See http://www.cybersource.com/developers/other_resources/quick_references/test_cc_numbers/
|
|
113
|
+
properties = build_pm_properties(nil, { :cc_number => '6011111111111117', :cc_type => :discover })
|
|
114
|
+
kb_payment = setup_kb_payment
|
|
115
|
+
payment_response = @plugin.authorize_payment(@pm.kb_account_id, kb_payment.id, kb_payment.transactions[0].id, @pm.kb_payment_method_id, 0, @currency, properties, @call_context)
|
|
116
|
+
check_response(payment_response, nil, :AUTHORIZE, :CANCELED, 'A problem exists with your CyberSource merchant configuration', '234')
|
|
117
|
+
payment_response.first_payment_reference_id.should_not be_nil
|
|
118
|
+
payment_response.second_payment_reference_id.should be_nil
|
|
119
|
+
# Verify the GET path
|
|
120
|
+
transaction_info_plugins = @plugin.get_payment_info(@pm.kb_account_id, kb_payment.id, @properties, @call_context)
|
|
121
|
+
transaction_info_plugins.size.should == 1
|
|
122
|
+
transaction_info_plugins.first.transaction_type.should eq(:AUTHORIZE)
|
|
123
|
+
transaction_info_plugins.first.status.should eq(:CANCELED)
|
|
124
|
+
|
|
125
|
+
# Force the validation on Discover
|
|
126
|
+
properties << build_property('force_validation', 'true')
|
|
127
|
+
kb_payment = setup_kb_payment
|
|
128
|
+
payment_response = @plugin.authorize_payment(@pm.kb_account_id, kb_payment.id, kb_payment.transactions[0].id, @pm.kb_payment_method_id, 0, @currency, properties, @call_context)
|
|
129
|
+
check_response(payment_response, 1, :AUTHORIZE, :PROCESSED, 'Successful transaction', '100')
|
|
130
|
+
payment_response.first_payment_reference_id.should_not be_nil
|
|
131
|
+
payment_response.second_payment_reference_id.should_not be_nil
|
|
132
|
+
# Verify the GET path
|
|
133
|
+
transaction_info_plugins = @plugin.get_payment_info(@pm.kb_account_id, kb_payment.id, @properties, @call_context)
|
|
134
|
+
transaction_info_plugins.size.should == 3
|
|
135
|
+
transaction_info_plugins[0].transaction_type.should eq(:AUTHORIZE)
|
|
136
|
+
transaction_info_plugins[0].status.should eq(:CANCELED)
|
|
137
|
+
transaction_info_plugins[0].kb_transaction_payment_id.should_not eq(kb_payment.transactions[0].id)
|
|
138
|
+
transaction_info_plugins[1].transaction_type.should eq(:AUTHORIZE)
|
|
139
|
+
transaction_info_plugins[1].status.should eq(:PROCESSED)
|
|
140
|
+
transaction_info_plugins[1].kb_transaction_payment_id.should eq(kb_payment.transactions[0].id)
|
|
141
|
+
transaction_info_plugins[2].transaction_type.should eq(:VOID)
|
|
142
|
+
transaction_info_plugins[2].status.should eq(:PROCESSED)
|
|
143
|
+
transaction_info_plugins[2].kb_transaction_payment_id.should_not eq(kb_payment.transactions[0].id)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
it 'should be able to bypass AVS and CVV rules with Apple Pay' do
|
|
147
|
+
properties = build_pm_properties(nil,
|
|
148
|
+
{
|
|
149
|
+
:payment_cryptogram => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
|
|
150
|
+
:ignore_avs => true,
|
|
151
|
+
:ignore_cvv => true
|
|
152
|
+
})
|
|
153
|
+
payment_response = @plugin.purchase_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[0].id, @pm.kb_payment_method_id, @amount, @currency, properties, @call_context)
|
|
154
|
+
check_response(payment_response, @amount, :PURCHASE, :PROCESSED, 'Successful transaction', '100')
|
|
155
|
+
end
|
|
156
|
+
|
|
91
157
|
it 'should be able to fix UNDEFINED payments' do
|
|
92
158
|
payment_response = @plugin.purchase_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[0].id, @pm.kb_payment_method_id, @amount, @currency, @properties, @call_context)
|
|
93
|
-
payment_response
|
|
159
|
+
check_response(payment_response, @amount, :PURCHASE, :PROCESSED, 'Successful transaction', '100')
|
|
94
160
|
|
|
95
161
|
# Force a transition to :UNDEFINED
|
|
96
162
|
Killbill::Cybersource::CybersourceTransaction.last.delete
|
|
@@ -149,71 +215,51 @@ describe Killbill::Cybersource::PaymentPlugin do
|
|
|
149
215
|
|
|
150
216
|
it 'should be able to charge and refund' do
|
|
151
217
|
payment_response = @plugin.purchase_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[0].id, @pm.kb_payment_method_id, @amount, @currency, @properties, @call_context)
|
|
152
|
-
payment_response
|
|
153
|
-
payment_response.amount.should == @amount
|
|
154
|
-
payment_response.transaction_type.should == :PURCHASE
|
|
218
|
+
check_response(payment_response, @amount, :PURCHASE, :PROCESSED, 'Successful transaction', '100')
|
|
155
219
|
|
|
156
220
|
# Try a full refund
|
|
157
221
|
refund_response = @plugin.refund_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[1].id, @pm.kb_payment_method_id, @amount, @currency, @properties, @call_context)
|
|
158
|
-
refund_response
|
|
159
|
-
refund_response.amount.should == @amount
|
|
160
|
-
refund_response.transaction_type.should == :REFUND
|
|
222
|
+
check_response(refund_response, @amount, :REFUND, :PROCESSED, 'Successful transaction', '100')
|
|
161
223
|
end
|
|
162
224
|
|
|
163
225
|
it 'should be able to auth, capture and refund' do
|
|
164
226
|
payment_response = @plugin.authorize_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[0].id, @pm.kb_payment_method_id, @amount, @currency, @properties, @call_context)
|
|
165
|
-
payment_response
|
|
166
|
-
payment_response.amount.should == @amount
|
|
167
|
-
payment_response.transaction_type.should == :AUTHORIZE
|
|
227
|
+
check_response(payment_response, @amount, :AUTHORIZE, :PROCESSED, 'Successful transaction', '100')
|
|
168
228
|
|
|
169
229
|
# Try multiple partial captures
|
|
170
230
|
partial_capture_amount = BigDecimal.new('10')
|
|
171
231
|
1.upto(3) do |i|
|
|
172
232
|
payment_response = @plugin.capture_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[i].id, @pm.kb_payment_method_id, partial_capture_amount, @currency, @properties, @call_context)
|
|
173
|
-
payment_response
|
|
174
|
-
payment_response.amount.should == partial_capture_amount
|
|
175
|
-
payment_response.transaction_type.should == :CAPTURE
|
|
233
|
+
check_response(payment_response, partial_capture_amount, :CAPTURE, :PROCESSED, 'Successful transaction', '100')
|
|
176
234
|
end
|
|
177
235
|
|
|
178
236
|
# Try a partial refund
|
|
179
237
|
refund_response = @plugin.refund_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[4].id, @pm.kb_payment_method_id, partial_capture_amount, @currency, @properties, @call_context)
|
|
180
|
-
refund_response
|
|
181
|
-
refund_response.amount.should == partial_capture_amount
|
|
182
|
-
refund_response.transaction_type.should == :REFUND
|
|
238
|
+
check_response(refund_response, partial_capture_amount, :REFUND, :PROCESSED, 'Successful transaction', '100')
|
|
183
239
|
|
|
184
240
|
# Try to capture again
|
|
185
241
|
payment_response = @plugin.capture_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[5].id, @pm.kb_payment_method_id, partial_capture_amount, @currency, @properties, @call_context)
|
|
186
|
-
payment_response
|
|
187
|
-
payment_response.amount.should == partial_capture_amount
|
|
188
|
-
payment_response.transaction_type.should == :CAPTURE
|
|
242
|
+
check_response(payment_response, partial_capture_amount, :CAPTURE, :PROCESSED, 'Successful transaction', '100')
|
|
189
243
|
end
|
|
190
244
|
|
|
191
245
|
it 'should be able to auth and void' do
|
|
192
246
|
payment_response = @plugin.authorize_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[0].id, @pm.kb_payment_method_id, @amount, @currency, @properties, @call_context)
|
|
193
|
-
payment_response
|
|
194
|
-
payment_response.amount.should == @amount
|
|
195
|
-
payment_response.transaction_type.should == :AUTHORIZE
|
|
247
|
+
check_response(payment_response, @amount, :AUTHORIZE, :PROCESSED, 'Successful transaction', '100')
|
|
196
248
|
|
|
197
249
|
payment_response = @plugin.void_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[1].id, @pm.kb_payment_method_id, @properties, @call_context)
|
|
198
|
-
payment_response
|
|
199
|
-
payment_response.transaction_type.should == :VOID
|
|
250
|
+
check_response(payment_response, nil, :VOID, :PROCESSED, 'Successful transaction', '100')
|
|
200
251
|
end
|
|
201
252
|
|
|
202
253
|
it 'should be able to auth, partial capture and void' do
|
|
203
254
|
payment_response = @plugin.authorize_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[0].id, @pm.kb_payment_method_id, @amount, @currency, @properties, @call_context)
|
|
204
|
-
payment_response
|
|
205
|
-
payment_response.amount.should == @amount
|
|
206
|
-
payment_response.transaction_type.should == :AUTHORIZE
|
|
255
|
+
check_response(payment_response, @amount, :AUTHORIZE, :PROCESSED, 'Successful transaction', '100')
|
|
207
256
|
|
|
208
257
|
partial_capture_amount = BigDecimal.new('10')
|
|
209
258
|
payment_response = @plugin.capture_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[1].id, @pm.kb_payment_method_id, partial_capture_amount, @currency, @properties, @call_context)
|
|
210
|
-
payment_response
|
|
211
|
-
payment_response.amount.should == partial_capture_amount
|
|
212
|
-
payment_response.transaction_type.should == :CAPTURE
|
|
259
|
+
check_response(payment_response, partial_capture_amount, :CAPTURE, :PROCESSED, 'Successful transaction', '100')
|
|
213
260
|
|
|
214
261
|
payment_response = @plugin.void_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[2].id, @pm.kb_payment_method_id, @properties, @call_context)
|
|
215
|
-
payment_response
|
|
216
|
-
payment_response.transaction_type.should == :VOID
|
|
262
|
+
check_response(payment_response, nil, :VOID, :PROCESSED, 'Successful transaction', '100')
|
|
217
263
|
Killbill::Cybersource::CybersourceResponse.last.params_amount.should == '10.00'
|
|
218
264
|
|
|
219
265
|
# From the CyberSource documentation:
|
|
@@ -221,25 +267,217 @@ describe Killbill::Cybersource::PaymentPlugin do
|
|
|
221
267
|
# your processor supports authorization reversal after void as described in "Authorization Reversal After Void," page 39, CyberSource recommends that you request an authorization reversal
|
|
222
268
|
# to release the hold on the unused credit card funds.
|
|
223
269
|
payment_response = @plugin.void_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[3].id, @pm.kb_payment_method_id, @properties, @call_context)
|
|
224
|
-
payment_response
|
|
225
|
-
payment_response.transaction_type.should == :VOID
|
|
270
|
+
check_response(payment_response, nil, :VOID, :PROCESSED, 'Successful transaction', '100')
|
|
226
271
|
Killbill::Cybersource::CybersourceResponse.last.params_amount.should == '100.00'
|
|
227
272
|
end
|
|
228
273
|
|
|
229
274
|
it 'should be able to credit' do
|
|
230
275
|
payment_response = @plugin.credit_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[0].id, @pm.kb_payment_method_id, @amount, @currency, @properties, @call_context)
|
|
231
|
-
payment_response
|
|
232
|
-
payment_response.amount.should == @amount
|
|
233
|
-
payment_response.transaction_type.should == :CREDIT
|
|
276
|
+
check_response(payment_response, @amount, :CREDIT, :PROCESSED, 'Successful transaction', '100')
|
|
234
277
|
end
|
|
235
278
|
|
|
236
279
|
# See https://github.com/killbill/killbill-cybersource-plugin/issues/4
|
|
237
|
-
it 'handles errors gracefully' do
|
|
280
|
+
it 'handles 500 errors gracefully' do
|
|
238
281
|
properties_with_no_expiration_year = build_pm_properties
|
|
239
282
|
cc_exp_year = properties_with_no_expiration_year.find { |prop| prop.key == 'ccExpirationYear' }
|
|
240
283
|
cc_exp_year.value = nil
|
|
241
284
|
|
|
242
|
-
|
|
243
|
-
payment_response.
|
|
285
|
+
kb_payment = setup_kb_payment
|
|
286
|
+
payment_response = @plugin.purchase_payment(@pm.kb_account_id, kb_payment.id, kb_payment.transactions[0].id, SecureRandom.uuid, @amount, @currency, properties_with_no_expiration_year, @call_context)
|
|
287
|
+
check_response(payment_response, nil, :PURCHASE, :CANCELED, '{"exception_message":"soap:Client: \\nXML parse error.\\n","payment_plugin_status":"CANCELED"}', nil)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# See http://www.cybersource.com/developers/getting_started/test_and_manage/simple_order_api/HTML/General_testing_info/soapi_general_test.html
|
|
291
|
+
it 'sets the correct transaction status' do
|
|
292
|
+
properties = build_pm_properties
|
|
293
|
+
|
|
294
|
+
kb_payment = setup_kb_payment
|
|
295
|
+
payment_response = @plugin.purchase_payment(@pm.kb_account_id, kb_payment.id, kb_payment.transactions[0].id, SecureRandom.uuid, -1, @currency, properties, @call_context)
|
|
296
|
+
check_response(payment_response, nil, :PURCHASE, :CANCELED, 'One or more fields contains invalid data', '102')
|
|
297
|
+
|
|
298
|
+
kb_payment = setup_kb_payment
|
|
299
|
+
payment_response = @plugin.purchase_payment(@pm.kb_account_id, kb_payment.id, kb_payment.transactions[0].id, SecureRandom.uuid, 100000000000, @currency, properties, @call_context)
|
|
300
|
+
check_response(payment_response, nil, :PURCHASE, :CANCELED, 'One or more fields contains invalid data', '102')
|
|
301
|
+
|
|
302
|
+
kb_payment = setup_kb_payment
|
|
303
|
+
bogus_properties = build_pm_properties(nil, {:cc_number => '4111111111111112'})
|
|
304
|
+
payment_response = @plugin.purchase_payment(@pm.kb_account_id, kb_payment.id, kb_payment.transactions[0].id, SecureRandom.uuid, @amount, @currency, bogus_properties, @call_context)
|
|
305
|
+
check_response(payment_response, nil, :PURCHASE, :ERROR, 'Invalid account number', '231')
|
|
306
|
+
|
|
307
|
+
kb_payment = setup_kb_payment
|
|
308
|
+
bogus_properties = build_pm_properties(nil, {:cc_number => '412345678912345678914'})
|
|
309
|
+
payment_response = @plugin.purchase_payment(@pm.kb_account_id, kb_payment.id, kb_payment.transactions[0].id, SecureRandom.uuid, @amount, @currency, bogus_properties, @call_context)
|
|
310
|
+
check_response(payment_response, nil, :PURCHASE, :ERROR, 'Invalid account number', '231')
|
|
311
|
+
|
|
312
|
+
kb_payment = setup_kb_payment
|
|
313
|
+
bogus_properties = build_pm_properties(nil, {:cc_exp_month => '13'})
|
|
314
|
+
payment_response = @plugin.purchase_payment(@pm.kb_account_id, kb_payment.id, kb_payment.transactions[0].id, SecureRandom.uuid, @amount, @currency, bogus_properties, @call_context)
|
|
315
|
+
check_response(payment_response, nil, :PURCHASE, :CANCELED, 'One or more fields contains invalid data', '102')
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
context 'Processors' do
|
|
319
|
+
|
|
320
|
+
# See http://www.cybersource.com/developers/getting_started/test_and_manage/simple_order_api/HTML/Paymentech/soapi_ptech_err.html
|
|
321
|
+
it 'handles Chase Paymentech Solutions errors' do
|
|
322
|
+
properties = build_pm_properties
|
|
323
|
+
|
|
324
|
+
%w(000 236 248 265 266 267 301 519 769 902 905 906).each do |expected_processor_response|
|
|
325
|
+
kb_payment = setup_kb_payment
|
|
326
|
+
amount = 2000 + expected_processor_response.to_i
|
|
327
|
+
payment_response = @plugin.purchase_payment(@pm.kb_account_id, kb_payment.id, kb_payment.transactions[0].id, SecureRandom.uuid, amount, @currency, properties, @call_context)
|
|
328
|
+
check_response(payment_response, nil, :PURCHASE, :CANCELED, 'General failure', '150', expected_processor_response)
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
%w(239 241 249 833).each do |expected_processor_response|
|
|
332
|
+
kb_payment = setup_kb_payment
|
|
333
|
+
amount = 2000 + expected_processor_response.to_i
|
|
334
|
+
payment_response = @plugin.purchase_payment(@pm.kb_account_id, kb_payment.id, kb_payment.transactions[0].id, SecureRandom.uuid, amount, @currency, properties, @call_context)
|
|
335
|
+
check_response(payment_response, nil, :PURCHASE, :CANCELED, 'A problem exists with your CyberSource merchant configuration', '234', expected_processor_response)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
{'201' => '231',
|
|
339
|
+
'202' => '233',
|
|
340
|
+
'203' => '233',
|
|
341
|
+
# Disable most of the checks by default (test lasts for 7 minutes otherwise)
|
|
342
|
+
=begin
|
|
343
|
+
'204' => '233',
|
|
344
|
+
'205' => '233',
|
|
345
|
+
'218' => '233',
|
|
346
|
+
'219' => '233',
|
|
347
|
+
'220' => '233',
|
|
348
|
+
'225' => '233',
|
|
349
|
+
'227' => '233',
|
|
350
|
+
'231' => '233',
|
|
351
|
+
'233' => '233',
|
|
352
|
+
'234' => '233',
|
|
353
|
+
'238' => '233',
|
|
354
|
+
'243' => '233',
|
|
355
|
+
'244' => '233',
|
|
356
|
+
'245' => '233',
|
|
357
|
+
'246' => '233',
|
|
358
|
+
'247' => '233',
|
|
359
|
+
'253' => '233',
|
|
360
|
+
'257' => '233',
|
|
361
|
+
'258' => '233',
|
|
362
|
+
'261' => '233',
|
|
363
|
+
'263' => '233',
|
|
364
|
+
'264' => '233',
|
|
365
|
+
'268' => '233',
|
|
366
|
+
'269' => '203',
|
|
367
|
+
'270' => '203',
|
|
368
|
+
'271' => '203',
|
|
369
|
+
'273' => '203',
|
|
370
|
+
'275' => '203',
|
|
371
|
+
'302' => '210',
|
|
372
|
+
'303' => '203',
|
|
373
|
+
'304' => '231',
|
|
374
|
+
'401' => '201',
|
|
375
|
+
'402' => '201',
|
|
376
|
+
'501' => '205',
|
|
377
|
+
'502' => '205',
|
|
378
|
+
'503' => '209',
|
|
379
|
+
'505' => '203',
|
|
380
|
+
'508' => '203',
|
|
381
|
+
'509' => '204',
|
|
382
|
+
'510' => '203',
|
|
383
|
+
'521' => '204',
|
|
384
|
+
'522' => '202',
|
|
385
|
+
'523' => '233',
|
|
386
|
+
'524' => '211',
|
|
387
|
+
'530' => '203',
|
|
388
|
+
'531' => '211',
|
|
389
|
+
'540' => '203',
|
|
390
|
+
'541' => '205',
|
|
391
|
+
'542' => '203',
|
|
392
|
+
'543' => '203',
|
|
393
|
+
'544' => '203',
|
|
394
|
+
'545' => '203',
|
|
395
|
+
'546' => '203',
|
|
396
|
+
'547' => '233',
|
|
397
|
+
'548' => '233',
|
|
398
|
+
'549' => '203',
|
|
399
|
+
'550' => '203',
|
|
400
|
+
'551' => '233',
|
|
401
|
+
'560' => '203',
|
|
402
|
+
'561' => '203',
|
|
403
|
+
'562' => '203',
|
|
404
|
+
'563' => '203',
|
|
405
|
+
'564' => '203',
|
|
406
|
+
'567' => '203',
|
|
407
|
+
'570' => '203',
|
|
408
|
+
'571' => '203',
|
|
409
|
+
'572' => '203',
|
|
410
|
+
'591' => '231',
|
|
411
|
+
'592' => '203',
|
|
412
|
+
'594' => '203',
|
|
413
|
+
'595' => '208',
|
|
414
|
+
'596' => '205',
|
|
415
|
+
'597' => '233',
|
|
416
|
+
'602' => '233',
|
|
417
|
+
'603' => '233',
|
|
418
|
+
'605' => '233',
|
|
419
|
+
'606' => '208',
|
|
420
|
+
'607' => '233',
|
|
421
|
+
'610' => '231',
|
|
422
|
+
'617' => '203',
|
|
423
|
+
'719' => '203',
|
|
424
|
+
'740' => '233',
|
|
425
|
+
'741' => '233',
|
|
426
|
+
'742' => '233',
|
|
427
|
+
'747' => '233',
|
|
428
|
+
'750' => '233',
|
|
429
|
+
'751' => '233',
|
|
430
|
+
'752' => '233',
|
|
431
|
+
'753' => '233',
|
|
432
|
+
'754' => '233',
|
|
433
|
+
'755' => '233',
|
|
434
|
+
'756' => '233',
|
|
435
|
+
'757' => '233',
|
|
436
|
+
'758' => '233',
|
|
437
|
+
'759' => '233',
|
|
438
|
+
'760' => '233',
|
|
439
|
+
'763' => '233',
|
|
440
|
+
'764' => '233',
|
|
441
|
+
'765' => '233',
|
|
442
|
+
'766' => '233',
|
|
443
|
+
'767' => '233',
|
|
444
|
+
'768' => '233',
|
|
445
|
+
'802' => '203',
|
|
446
|
+
'806' => '203',
|
|
447
|
+
=end
|
|
448
|
+
'811' => '209',
|
|
449
|
+
'813' => '203',
|
|
450
|
+
'825' => '231',
|
|
451
|
+
'834' => '203',
|
|
452
|
+
'903' => '203',
|
|
453
|
+
'904' => '203'}.each do |expected_processor_response, expected_reason_code|
|
|
454
|
+
kb_payment = setup_kb_payment
|
|
455
|
+
amount = 2000 + expected_processor_response.to_i
|
|
456
|
+
payment_response = @plugin.purchase_payment(@pm.kb_account_id, kb_payment.id, kb_payment.transactions[0].id, SecureRandom.uuid, amount, @currency, properties, @call_context)
|
|
457
|
+
expected_error = ::ActiveMerchant::Billing::CyberSourceGateway.class_variable_get(:@@response_codes)[('r' + expected_reason_code).to_sym]
|
|
458
|
+
check_response(payment_response, nil, :PURCHASE, :ERROR, expected_error, expected_reason_code, expected_processor_response)
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
private
|
|
464
|
+
|
|
465
|
+
def check_response(payment_response, amount, transaction_type, expected_status, expected_error, expected_error_code, expected_processor_response = nil)
|
|
466
|
+
payment_response.amount.should == amount
|
|
467
|
+
payment_response.transaction_type.should == transaction_type
|
|
468
|
+
payment_response.status.should eq(expected_status), payment_response.gateway_error
|
|
469
|
+
|
|
470
|
+
gw_response = Killbill::Cybersource::CybersourceResponse.last
|
|
471
|
+
gw_response.gateway_error.should == expected_error
|
|
472
|
+
gw_response.gateway_error_code.should == expected_error_code
|
|
473
|
+
gw_response.params_processor_response.should == expected_processor_response unless expected_processor_response.nil?
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
def setup_kb_payment(nb_transactions=1, kb_payment_id=SecureRandom.uuid)
|
|
477
|
+
kb_payment = nil
|
|
478
|
+
1.upto(nb_transactions) do
|
|
479
|
+
kb_payment = @plugin.kb_apis.proxied_services[:payment_api].add_payment(kb_payment_id)
|
|
480
|
+
end
|
|
481
|
+
kb_payment
|
|
244
482
|
end
|
|
245
483
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: killbill-cybersource
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.0.
|
|
4
|
+
version: 4.0.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kill Bill core team
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2016-
|
|
11
|
+
date: 2016-05-05 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: killbill
|
|
@@ -283,6 +283,7 @@ files:
|
|
|
283
283
|
- config.ru
|
|
284
284
|
- cybersource.yml
|
|
285
285
|
- db/ddl.sql
|
|
286
|
+
- db/migrate/20162519092522_enlarge_message.rb
|
|
286
287
|
- db/schema.rb
|
|
287
288
|
- killbill-cybersource.gemspec
|
|
288
289
|
- killbill.properties
|
|
@@ -324,7 +325,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
324
325
|
version: '0'
|
|
325
326
|
requirements: []
|
|
326
327
|
rubyforge_project:
|
|
327
|
-
rubygems_version: 2.
|
|
328
|
+
rubygems_version: 2.4.6
|
|
328
329
|
signing_key:
|
|
329
330
|
specification_version: 4
|
|
330
331
|
summary: Plugin to use Cybersource as a gateway.
|