killbill-cybersource 4.0.2 → 4.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|