killbill-cybersource 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +24 -21
- data/Jarfile +8 -7
- data/Jarfile.lock +11 -10
- data/VERSION +1 -1
- data/killbill-cybersource.gemspec +1 -1
- data/lib/cybersource/api.rb +77 -21
- data/lib/cybersource/cyber_source_on_demand.rb +113 -15
- data/lib/cybersource/ext/active_merchant/active_merchant.rb +28 -0
- data/lib/cybersource/models/response.rb +71 -17
- data/lib/cybersource.rb +2 -0
- data/pom.xml +1 -1
- data/spec/cybersource/cyber_source_on_demand_spec.rb +276 -0
- data/spec/cybersource/remote/integration_spec.rb +66 -0
- metadata +12 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bfa04a9b0d7a032fbd01ed8e985c0a77682c54f4
|
4
|
+
data.tar.gz: baabc471f68b9130025b82c0045438246178c6b9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 50e85249fc100dd9006445ea729cdb7fce42e16d9b39504fcad05d3e206f5c2d7d174ec75c97eaec72218f83ce0227e70ccb33df3c7194886c9502661f6bdc01
|
7
|
+
data.tar.gz: 7265f9d257964c162e754f0153eeb84a40324a848f3e23c872fc9ef59e283a846717eb24f6f0bb9bad86657710814ee2bedf2c8157f494fb84bc6f750b0bf23e
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
killbill-cybersource (
|
4
|
+
killbill-cybersource (2.0.0)
|
5
5
|
actionpack (~> 4.1.0)
|
6
6
|
actionview (~> 4.1.0)
|
7
7
|
activemerchant (~> 1.48.0)
|
@@ -9,7 +9,7 @@ PATH
|
|
9
9
|
activerecord-bogacs (~> 0.3)
|
10
10
|
activerecord-jdbc-adapter (~> 1.3)
|
11
11
|
jruby-openssl (~> 0.9.6)
|
12
|
-
killbill (~> 4.
|
12
|
+
killbill (~> 4.4.0)
|
13
13
|
monetize (~> 1.1.0)
|
14
14
|
money (~> 6.5.1)
|
15
15
|
offsite_payments (~> 2.1.0)
|
@@ -19,13 +19,13 @@ PATH
|
|
19
19
|
GEM
|
20
20
|
remote: https://rubygems.org/
|
21
21
|
specs:
|
22
|
-
actionpack (4.1.
|
23
|
-
actionview (= 4.1.
|
24
|
-
activesupport (= 4.1.
|
22
|
+
actionpack (4.1.12)
|
23
|
+
actionview (= 4.1.12)
|
24
|
+
activesupport (= 4.1.12)
|
25
25
|
rack (~> 1.5.2)
|
26
26
|
rack-test (~> 0.6.2)
|
27
|
-
actionview (4.1.
|
28
|
-
activesupport (= 4.1.
|
27
|
+
actionview (4.1.12)
|
28
|
+
activesupport (= 4.1.12)
|
29
29
|
builder (~> 3.1)
|
30
30
|
erubis (~> 2.7.0)
|
31
31
|
active_utils (3.0.0)
|
@@ -36,18 +36,18 @@ GEM
|
|
36
36
|
builder (>= 2.1.2, < 4.0.0)
|
37
37
|
i18n (>= 0.6.9)
|
38
38
|
nokogiri (~> 1.4)
|
39
|
-
activemodel (4.1.
|
40
|
-
activesupport (= 4.1.
|
39
|
+
activemodel (4.1.12)
|
40
|
+
activesupport (= 4.1.12)
|
41
41
|
builder (~> 3.1)
|
42
|
-
activerecord (4.1.
|
43
|
-
activemodel (= 4.1.
|
44
|
-
activesupport (= 4.1.
|
42
|
+
activerecord (4.1.12)
|
43
|
+
activemodel (= 4.1.12)
|
44
|
+
activesupport (= 4.1.12)
|
45
45
|
arel (~> 5.0.0)
|
46
46
|
activerecord-bogacs (0.3.0)
|
47
47
|
thread_safe (~> 0.3)
|
48
|
-
activerecord-jdbc-adapter (1.3.
|
48
|
+
activerecord-jdbc-adapter (1.3.17)
|
49
49
|
activerecord (>= 2.2)
|
50
|
-
activesupport (4.1.
|
50
|
+
activesupport (4.1.12)
|
51
51
|
i18n (~> 0.6, >= 0.6.9)
|
52
52
|
json (~> 1.7, >= 1.7.7)
|
53
53
|
minitest (~> 5.1)
|
@@ -57,24 +57,24 @@ GEM
|
|
57
57
|
builder (3.2.2)
|
58
58
|
diff-lcs (1.1.3)
|
59
59
|
erubis (2.7.0)
|
60
|
-
ethon (0.7.
|
60
|
+
ethon (0.7.4)
|
61
61
|
ffi (>= 1.3.0)
|
62
|
-
ffi (1.9.
|
62
|
+
ffi (1.9.10-java)
|
63
63
|
i18n (0.7.0)
|
64
64
|
jbundler (0.4.3)
|
65
65
|
maven-tools (~> 0.32.1)
|
66
66
|
ruby-maven (~> 3.0.4)
|
67
67
|
jdbc-mariadb (1.1.8)
|
68
|
-
jdbc-sqlite3 (3.8.
|
68
|
+
jdbc-sqlite3 (3.8.10.1)
|
69
69
|
jruby-openssl (0.9.7-java)
|
70
|
-
json (1.8.
|
71
|
-
killbill (4.
|
70
|
+
json (1.8.3-java)
|
71
|
+
killbill (4.4.0)
|
72
72
|
rack (>= 1.5.2)
|
73
73
|
sinatra (~> 1.3.4)
|
74
74
|
typhoeus (~> 0.6.9)
|
75
75
|
tzinfo (~> 1.2.0)
|
76
76
|
maven-tools (0.32.5)
|
77
|
-
minitest (5.
|
77
|
+
minitest (5.7.0)
|
78
78
|
monetize (1.1.0)
|
79
79
|
money (~> 6.5.0)
|
80
80
|
money (6.5.1)
|
@@ -88,7 +88,7 @@ GEM
|
|
88
88
|
i18n (~> 0.5)
|
89
89
|
money (>= 5.0.0, < 7.0.0)
|
90
90
|
nokogiri (~> 1.4)
|
91
|
-
rack (1.5.
|
91
|
+
rack (1.5.5)
|
92
92
|
rack-protection (1.5.3)
|
93
93
|
rack
|
94
94
|
rack-test (0.6.3)
|
@@ -127,3 +127,6 @@ DEPENDENCIES
|
|
127
127
|
killbill-cybersource!
|
128
128
|
rake (>= 10.0.0)
|
129
129
|
rspec (~> 2.12.0)
|
130
|
+
|
131
|
+
BUNDLED WITH
|
132
|
+
1.10.5
|
data/Jarfile
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
-
jar 'org.kill-bill.billing:killbill-api', '0.
|
2
|
-
jar 'org.kill-bill.billing.plugin:killbill-plugin-api-currency', '0.
|
3
|
-
jar 'org.kill-bill.billing.plugin:killbill-plugin-api-invoice', '0.
|
4
|
-
jar 'org.kill-bill.billing.plugin:killbill-plugin-api-notification', '0.
|
5
|
-
jar 'org.kill-bill.billing.plugin:killbill-plugin-api-payment', '0.
|
6
|
-
jar 'org.kill-bill.billing.plugin:killbill-plugin-api-routing', '0.
|
7
|
-
jar 'org.kill-bill.billing:killbill-
|
1
|
+
jar 'org.kill-bill.billing:killbill-api', '0.18'
|
2
|
+
jar 'org.kill-bill.billing.plugin:killbill-plugin-api-currency', '0.11'
|
3
|
+
jar 'org.kill-bill.billing.plugin:killbill-plugin-api-invoice', '0.11'
|
4
|
+
jar 'org.kill-bill.billing.plugin:killbill-plugin-api-notification', '0.11'
|
5
|
+
jar 'org.kill-bill.billing.plugin:killbill-plugin-api-payment', '0.11'
|
6
|
+
jar 'org.kill-bill.billing.plugin:killbill-plugin-api-routing', '0.11'
|
7
|
+
jar 'org.kill-bill.billing.plugin:killbill-plugin-api-catalog', '0.11'
|
8
|
+
jar 'org.kill-bill.billing:killbill-util:tests', '0.14.0'
|
8
9
|
jar 'org.mockito:mockito-all', '1.10.19'
|
9
10
|
jar 'javax.servlet:javax.servlet-api', '3.1.0'
|
data/Jarfile.lock
CHANGED
@@ -1,12 +1,15 @@
|
|
1
|
-
org.
|
1
|
+
org.bouncycastle:bcpkix-jdk15on:jar:1.50
|
2
|
+
org.bouncycastle:bcprov-jdk15on:jar:1.50
|
3
|
+
org.kill-bill.billing:killbill-api:jar:0.18
|
2
4
|
com.fasterxml.jackson.core:jackson-annotations:jar:2.4.3
|
3
5
|
joda-time:joda-time:jar:2.3
|
4
|
-
org.kill-bill.billing.plugin:killbill-plugin-api-currency:jar:0.
|
5
|
-
org.kill-bill.billing.plugin:killbill-plugin-api-invoice:jar:0.
|
6
|
-
org.kill-bill.billing.plugin:killbill-plugin-api-notification:jar:0.
|
7
|
-
org.kill-bill.billing.plugin:killbill-plugin-api-payment:jar:0.
|
8
|
-
org.kill-bill.billing.plugin:killbill-plugin-api-routing:jar:0.
|
9
|
-
org.kill-bill.billing:killbill-
|
6
|
+
org.kill-bill.billing.plugin:killbill-plugin-api-currency:jar:0.11
|
7
|
+
org.kill-bill.billing.plugin:killbill-plugin-api-invoice:jar:0.11
|
8
|
+
org.kill-bill.billing.plugin:killbill-plugin-api-notification:jar:0.11
|
9
|
+
org.kill-bill.billing.plugin:killbill-plugin-api-payment:jar:0.11
|
10
|
+
org.kill-bill.billing.plugin:killbill-plugin-api-routing:jar:0.11
|
11
|
+
org.kill-bill.billing.plugin:killbill-plugin-api-catalog:jar:0.11
|
12
|
+
org.kill-bill.billing:killbill-util:jar:tests:0.14.0
|
10
13
|
com.fasterxml.jackson.core:jackson-databind:jar:2.4.3
|
11
14
|
com.fasterxml.jackson.core:jackson-core:jar:2.4.3
|
12
15
|
com.fasterxml.jackson.dataformat:jackson-dataformat-csv:jar:2.4.3
|
@@ -32,7 +35,7 @@ aopalliance:aopalliance:jar:1.0
|
|
32
35
|
com.google.inject.extensions:guice-multibindings:jar:3.0
|
33
36
|
org.jdbi:jdbi:jar:2.62
|
34
37
|
org.joda:joda-money:jar:0.9
|
35
|
-
org.kill-bill.billing:killbill-internal-api:jar:0.
|
38
|
+
org.kill-bill.billing:killbill-internal-api:jar:0.14.0
|
36
39
|
org.kill-bill.billing:killbill-platform-api:jar:0.2
|
37
40
|
org.kill-bill.billing:killbill-platform-base:jar:0.2
|
38
41
|
com.google.code.findbugs:annotations:jar:3.0.0
|
@@ -54,5 +57,3 @@ org.slf4j:slf4j-api:jar:1.7.12
|
|
54
57
|
org.weakref:jmxutils:jar:1.12
|
55
58
|
org.mockito:mockito-all:jar:1.10.19
|
56
59
|
javax.servlet:javax.servlet-api:jar:3.1.0
|
57
|
-
org.bouncycastle:bcpkix-jdk15on:jar:1.50
|
58
|
-
org.bouncycastle:bcprov-jdk15on:jar:1.50
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
2.0.0
|
data/lib/cybersource/api.rb
CHANGED
@@ -22,14 +22,6 @@ module Killbill #:nodoc:
|
|
22
22
|
#
|
23
23
|
end
|
24
24
|
|
25
|
-
def start_plugin
|
26
|
-
super
|
27
|
-
gateway = lookup_gateway(:on_demand)
|
28
|
-
@report_api = CyberSourceOnDemand.new(gateway, logger)
|
29
|
-
rescue => e
|
30
|
-
@report_api = nil
|
31
|
-
end
|
32
|
-
|
33
25
|
def authorize_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
|
34
26
|
# Pass extra parameters for the gateway here
|
35
27
|
options = {}
|
@@ -95,7 +87,64 @@ module Killbill #:nodoc:
|
|
95
87
|
options = {}
|
96
88
|
|
97
89
|
properties = merge_properties(properties, options)
|
98
|
-
super(kb_account_id, kb_payment_id, properties, context)
|
90
|
+
transaction_info_plugins = super(kb_account_id, kb_payment_id, properties, context)
|
91
|
+
|
92
|
+
# Should never happen...
|
93
|
+
return [] if transaction_info_plugins.nil?
|
94
|
+
|
95
|
+
# Note: this won't handle the case where we don't have any record in the DB. While this should very rarely happen
|
96
|
+
# (see Killbill::Plugin::ActiveMerchant::Gateway), we could use the CyberSource Payment Batch Detail Report to fix it.
|
97
|
+
|
98
|
+
options = properties_to_hash(properties)
|
99
|
+
|
100
|
+
stale = false
|
101
|
+
transaction_info_plugins.each do |transaction_info_plugin|
|
102
|
+
# We only need to fix the UNDEFINED ones
|
103
|
+
next unless transaction_info_plugin.status == :UNDEFINED
|
104
|
+
|
105
|
+
cybersource_response_id = find_value_from_properties(transaction_info_plugin.properties, 'cybersourceResponseId')
|
106
|
+
next if cybersource_response_id.nil?
|
107
|
+
|
108
|
+
report_date = transaction_info_plugin.created_date
|
109
|
+
authorization = find_value_from_properties(transaction_info_plugin.properties, 'authorization')
|
110
|
+
|
111
|
+
order_id = Killbill::Plugin::ActiveMerchant::Utils.normalized(options, :order_id)
|
112
|
+
# authorization is very likely nil, as we didn't get an answer from the gateway in the first place
|
113
|
+
order_id ||= authorization.split(';')[0] unless authorization.nil?
|
114
|
+
|
115
|
+
# Retrieve the report from CyberSource
|
116
|
+
if order_id.nil?
|
117
|
+
# order_id undetermined - try the defaults (see PaymentPlugin#dispatch_to_gateways)
|
118
|
+
report = get_report(transaction_info_plugin.kb_transaction_payment_id, report_date, options, context)
|
119
|
+
if report.nil?
|
120
|
+
kb_transaction = get_kb_transaction(kb_payment_id, transaction_info_plugin.kb_transaction_payment_id, context.tenant_id)
|
121
|
+
report = get_report(kb_transaction.external_key, report_date, options, context)
|
122
|
+
end
|
123
|
+
else
|
124
|
+
report = get_report(order_id, report_date, options, context)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Report API not configured or skip_gw=true
|
128
|
+
next if report.nil?
|
129
|
+
|
130
|
+
# Report not found
|
131
|
+
if report.empty?
|
132
|
+
logger.info("Unable to fix UNDEFINED transaction #{transaction_info_plugin.kb_transaction_payment_id} (not found in CyberSource)")
|
133
|
+
next
|
134
|
+
end
|
135
|
+
|
136
|
+
# Update our rows
|
137
|
+
response = CybersourceResponse.find_by(:id => cybersource_response_id)
|
138
|
+
next if response.nil?
|
139
|
+
|
140
|
+
logger.info("Fixing UNDEFINED transaction #{transaction_info_plugin.kb_transaction_payment_id}: success? = #{report.response.success?}")
|
141
|
+
|
142
|
+
response.update_and_create_transaction(report.response)
|
143
|
+
stale = true
|
144
|
+
end
|
145
|
+
|
146
|
+
# If we updated the state, re-fetch the latest data
|
147
|
+
stale ? super(kb_account_id, kb_payment_id, properties, context) : transaction_info_plugins
|
99
148
|
end
|
100
149
|
|
101
150
|
def search_payments(search_key, offset, limit, properties, context)
|
@@ -183,25 +232,25 @@ module Killbill #:nodoc:
|
|
183
232
|
end
|
184
233
|
|
185
234
|
# Make calls idempotent
|
186
|
-
def before_gateway(gateway, kb_transaction, last_transaction, payment_source, amount_in_cents, currency, options)
|
235
|
+
def before_gateway(gateway, kb_transaction, last_transaction, payment_source, amount_in_cents, currency, options, context)
|
187
236
|
super
|
188
|
-
return if @report_api.nil? || options[:skip_gw]
|
189
237
|
|
190
238
|
merchant_reference_code = options[:order_id]
|
191
|
-
report
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
!report['Report']['Requests']['Request'].nil? &&
|
197
|
-
report['Report']['Requests']['Request']['MerchantReferenceNumber'] == merchant_reference_code
|
198
|
-
logger.info "Skipping gateway call for existing transaction #{kb_transaction.id}, merchant reference code #{merchant_reference_code}"
|
199
|
-
options[:skip_gw] = true
|
200
|
-
end
|
239
|
+
report = get_report(merchant_reference_code, kb_transaction.created_date, options, context)
|
240
|
+
return nil if report.nil? || report.empty?
|
241
|
+
|
242
|
+
logger.info "Skipping gateway call for existing transaction #{kb_transaction.id}, merchant reference code #{merchant_reference_code}"
|
243
|
+
options[:skip_gw] = true
|
201
244
|
rescue => e
|
202
245
|
logger.warn "Error checking for duplicate payment: #{e.message}"
|
203
246
|
end
|
204
247
|
|
248
|
+
def get_report(merchant_reference_code, date, options, context)
|
249
|
+
report_api = get_report_api(context.tenant_id)
|
250
|
+
return nil if report_api.nil? || options[:skip_gw]
|
251
|
+
report_api.single_transaction_report(merchant_reference_code, date.strftime('%Y%m%d'))
|
252
|
+
end
|
253
|
+
|
205
254
|
def add_required_options(kb_account_id, properties, options, context)
|
206
255
|
if options[:email].nil?
|
207
256
|
email = find_value_from_properties(properties, 'email')
|
@@ -213,6 +262,13 @@ module Killbill #:nodoc:
|
|
213
262
|
options[:email] = email
|
214
263
|
end
|
215
264
|
end
|
265
|
+
|
266
|
+
def get_report_api(kb_tenant_id)
|
267
|
+
gateway = lookup_gateway(:on_demand, kb_tenant_id)
|
268
|
+
CyberSourceOnDemand.new(gateway, logger)
|
269
|
+
rescue
|
270
|
+
nil
|
271
|
+
end
|
216
272
|
end
|
217
273
|
end
|
218
274
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module Killbill #:nodoc:
|
2
2
|
module Cybersource #:nodoc:
|
3
|
+
# See http://apps.cybersource.com/library/documentation/dev_guides/Reporting_Developers_Guide/reporting_dg.pdf
|
3
4
|
class CyberSourceOnDemand
|
4
5
|
|
5
6
|
@@live_url = 'https://ebc.cybersource.com/ebc/Query'
|
@@ -7,17 +8,17 @@ module Killbill #:nodoc:
|
|
7
8
|
|
8
9
|
def initialize(gateway, logger)
|
9
10
|
@gateway = gateway
|
10
|
-
@logger
|
11
|
+
@logger = logger
|
11
12
|
end
|
12
13
|
|
13
14
|
def single_transaction_report(merchant_reference_code, target_date)
|
14
15
|
params = {
|
15
|
-
:merchantID
|
16
|
+
:merchantID => @gateway.config[:merchantID],
|
16
17
|
:merchantReferenceNumber => merchant_reference_code,
|
17
|
-
:targetDate
|
18
|
-
:type
|
19
|
-
:subtype
|
20
|
-
:versionNumber
|
18
|
+
:targetDate => target_date,
|
19
|
+
:type => 'transaction',
|
20
|
+
:subtype => 'transactionDetail',
|
21
|
+
:versionNumber => '1.7',
|
21
22
|
}
|
22
23
|
|
23
24
|
headers = {
|
@@ -25,21 +26,118 @@ module Killbill #:nodoc:
|
|
25
26
|
'Authorization' => 'Basic ' + Base64.encode64("#{@gateway.config[:username]}:#{@gateway.config[:password]}").chomp
|
26
27
|
}
|
27
28
|
|
28
|
-
data
|
29
|
+
data = URI.encode_www_form(params)
|
29
30
|
endpoint = @gateway.test? ? @@test_url : @@live_url
|
30
31
|
|
31
32
|
# Will raise ResponseError if the response code is > 300
|
32
|
-
|
33
|
+
CyberSourceOnDemandTransactionReport.new(@gateway.ssl_post(endpoint, data, headers), @logger)
|
33
34
|
end
|
34
35
|
|
35
|
-
|
36
|
+
class CyberSourceOnDemandTransactionReport
|
36
37
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
38
|
+
attr_reader :response
|
39
|
+
|
40
|
+
def initialize(xml_report, logger)
|
41
|
+
@logger = logger
|
42
|
+
@hash_report = parse_xml(xml_report)
|
43
|
+
parse
|
44
|
+
end
|
45
|
+
|
46
|
+
def success?
|
47
|
+
@response.success?
|
48
|
+
end
|
49
|
+
|
50
|
+
def empty?
|
51
|
+
@response.params['merchantReferenceCode'].nil?
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def parse
|
57
|
+
report = parse_report
|
58
|
+
request = parse_request(report)
|
59
|
+
payment_data = !request.nil? ? request['PaymentData'] : nil
|
60
|
+
profile = !request.nil? && !request['ProfileList'].nil? ? request['ProfileList']['Profile'] : nil
|
61
|
+
|
62
|
+
test = parse_test(report)
|
63
|
+
success, message = parse_success_message(request)
|
64
|
+
merchant_reference_code = extract(request, 'MerchantReferenceNumber')
|
65
|
+
request_id = extract(request, 'RequestID') || extract(payment_data, 'PaymentRequestID')
|
66
|
+
request_token = nil
|
67
|
+
|
68
|
+
# ActiveMerchant specific
|
69
|
+
authorization = "#{merchant_reference_code};#{request_id};#{request_token}"
|
70
|
+
|
71
|
+
# See CybersourceResponse
|
72
|
+
params = {
|
73
|
+
'merchantReferenceCode' => merchant_reference_code,
|
74
|
+
'requestID' => request_id,
|
75
|
+
'decision' => extract(profile, 'ProfileDecision'),
|
76
|
+
'reasonCode' => nil,
|
77
|
+
'requestToken' => request_token,
|
78
|
+
'currency' => extract(payment_data, 'CurrencyCode'),
|
79
|
+
'amount' => extract(payment_data, 'Amount'),
|
80
|
+
'authorizationCode' => extract(payment_data, 'AuthorizationCode'),
|
81
|
+
'avsCode' => extract(payment_data, 'AVSResultMapped'),
|
82
|
+
'avsCodeRaw' => extract(payment_data, 'AVSResult'),
|
83
|
+
'cvCode' => nil,
|
84
|
+
'authorizedDateTime' => nil,
|
85
|
+
'processorResponse' => nil,
|
86
|
+
'reconciliationID' => extract(request, 'TransactionReferenceNumber'),
|
87
|
+
'subscriptionID' => extract(request, 'SubscriptionID')
|
88
|
+
}
|
89
|
+
|
90
|
+
@response = ::ActiveMerchant::Billing::Response.new(success,
|
91
|
+
message,
|
92
|
+
params,
|
93
|
+
:test => test,
|
94
|
+
:authorization => authorization,
|
95
|
+
:avs_result => {:code => params['avsCode']},
|
96
|
+
:cvv_result => params['cvCode'])
|
97
|
+
end
|
98
|
+
|
99
|
+
def parse_report
|
100
|
+
!@hash_report.nil? ? @hash_report['Report'] : nil
|
101
|
+
end
|
102
|
+
|
103
|
+
def parse_test(report)
|
104
|
+
!report.nil? && !report['xmlns'].nil? && report['xmlns'].starts_with?('https://ebctest.cybersource.com')
|
105
|
+
end
|
106
|
+
|
107
|
+
def parse_request(report)
|
108
|
+
# Assume the report contains a single request
|
109
|
+
!report.nil? && !report['Requests'].nil? ? report['Requests']['Request'] : nil
|
110
|
+
end
|
111
|
+
|
112
|
+
# Note: for now, we only look at the response from CyberSource.
|
113
|
+
# It would be nice to take into account the processor response too.
|
114
|
+
def parse_success_message(request)
|
115
|
+
success = false
|
116
|
+
msg = nil
|
117
|
+
return [success, msg] if request.nil? || request['ApplicationReplies'].nil? || request['ApplicationReplies']['ApplicationReply'].nil? || request['ApplicationReplies']['ApplicationReply'].empty?
|
118
|
+
|
119
|
+
application_replies = request['ApplicationReplies']['ApplicationReply'].is_a?(Hash) ? [request['ApplicationReplies']['ApplicationReply']] : request['ApplicationReplies']['ApplicationReply']
|
120
|
+
|
121
|
+
success = true
|
122
|
+
application_replies.each do |application_reply|
|
123
|
+
success &&= (application_reply['RCode'].to_s == '1')
|
124
|
+
# Last message by convention
|
125
|
+
msg = application_reply['RMsg']
|
126
|
+
end
|
127
|
+
[success, msg]
|
128
|
+
end
|
129
|
+
|
130
|
+
def extract(hash, key)
|
131
|
+
!hash.nil? ? hash[key] : nil
|
132
|
+
end
|
133
|
+
|
134
|
+
def parse_xml(body)
|
135
|
+
# Thanks ActiveSupport!
|
136
|
+
Hash.from_xml(body)
|
137
|
+
rescue # Parser error - request failed
|
138
|
+
@logger.warn "Error checking for duplicate payment, CyberSource response: #{!body.nil? && body.respond_to?(:message) ? body.message : body}"
|
139
|
+
nil
|
140
|
+
end
|
43
141
|
end
|
44
142
|
end
|
45
143
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ActiveMerchant
|
2
|
+
module Billing
|
3
|
+
class CyberSourceGateway
|
4
|
+
|
5
|
+
# See https://github.com/killbill/killbill-cybersource-plugin/issues/4
|
6
|
+
def commit(request, options)
|
7
|
+
request = build_request(request, options)
|
8
|
+
begin
|
9
|
+
raw_response = ssl_post(test? ? self.test_url : self.live_url, request)
|
10
|
+
rescue ResponseError => e
|
11
|
+
raw_response = e.response.body
|
12
|
+
end
|
13
|
+
response = parse(raw_response)
|
14
|
+
|
15
|
+
success = response[:decision] == 'ACCEPT'
|
16
|
+
message = @@response_codes[('r' + response[:reasonCode]).to_sym] rescue response[:message]
|
17
|
+
authorization = success ? [options[:order_id], response[:requestID], response[:requestToken]].compact.join(";") : nil
|
18
|
+
|
19
|
+
Response.new(success, message, response,
|
20
|
+
:test => test?,
|
21
|
+
:authorization => authorization,
|
22
|
+
:avs_result => {:code => response[:avsCode]},
|
23
|
+
:cvv_result => response[:cvCode]
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -15,26 +15,72 @@ module Killbill #:nodoc:
|
|
15
15
|
payment_processor_account_id,
|
16
16
|
kb_tenant_id,
|
17
17
|
response,
|
18
|
-
|
19
|
-
:params_merchant_reference_code => extract(response, 'merchantReferenceCode'),
|
20
|
-
:params_request_id => extract(response, 'requestID'),
|
21
|
-
:params_decision => extract(response, 'decision'),
|
22
|
-
:params_reason_code => extract(response, 'reasonCode'),
|
23
|
-
:params_request_token => extract(response, 'requestToken'),
|
24
|
-
:params_currency => extract(response, 'currency'),
|
25
|
-
:params_amount => extract(response, 'amount'),
|
26
|
-
:params_authorization_code => extract(response, 'authorizationCode'),
|
27
|
-
:params_avs_code => extract(response, 'avsCode'),
|
28
|
-
:params_avs_code_raw => extract(response, 'avsCodeRaw'),
|
29
|
-
:params_cv_code => extract(response, 'cvCode'),
|
30
|
-
:params_authorized_date_time => extract(response, 'authorizedDateTime'),
|
31
|
-
:params_processor_response => extract(response, 'processorResponse'),
|
32
|
-
:params_reconciliation_id => extract(response, 'reconciliationID'),
|
33
|
-
:params_subscription_id => extract(response, 'subscriptionID'),
|
34
|
-
}.merge!(extra_params),
|
18
|
+
cybersource_response_params(response).merge!(extra_params),
|
35
19
|
model)
|
36
20
|
end
|
37
21
|
|
22
|
+
def self.cybersource_response_params(response)
|
23
|
+
{
|
24
|
+
:params_merchant_reference_code => extract(response, 'merchantReferenceCode'),
|
25
|
+
:params_request_id => extract(response, 'requestID'),
|
26
|
+
:params_decision => extract(response, 'decision'),
|
27
|
+
:params_reason_code => extract(response, 'reasonCode'),
|
28
|
+
:params_request_token => extract(response, 'requestToken'),
|
29
|
+
:params_currency => extract(response, 'currency'),
|
30
|
+
:params_amount => extract(response, 'amount'),
|
31
|
+
:params_authorization_code => extract(response, 'authorizationCode'),
|
32
|
+
:params_avs_code => extract(response, 'avsCode'),
|
33
|
+
:params_avs_code_raw => extract(response, 'avsCodeRaw'),
|
34
|
+
:params_cv_code => extract(response, 'cvCode'),
|
35
|
+
:params_authorized_date_time => extract(response, 'authorizedDateTime'),
|
36
|
+
:params_processor_response => extract(response, 'processorResponse'),
|
37
|
+
:params_reconciliation_id => extract(response, 'reconciliationID'),
|
38
|
+
:params_subscription_id => extract(response, 'subscriptionID'),
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
def update_and_create_transaction(gw_response)
|
43
|
+
updated_attributes = {
|
44
|
+
:message => gw_response.message,
|
45
|
+
:authorization => gw_response.authorization,
|
46
|
+
:fraud_review => gw_response.fraud_review?,
|
47
|
+
:test => gw_response.test?,
|
48
|
+
:avs_result_code => gw_response.avs_result.kind_of?(::ActiveMerchant::Billing::AVSResult) ? gw_response.avs_result.code : gw_response.avs_result['code'],
|
49
|
+
:avs_result_message => gw_response.avs_result.kind_of?(::ActiveMerchant::Billing::AVSResult) ? gw_response.avs_result.message : gw_response.avs_result['message'],
|
50
|
+
:avs_result_street_match => gw_response.avs_result.kind_of?(::ActiveMerchant::Billing::AVSResult) ? gw_response.avs_result.street_match : gw_response.avs_result['street_match'],
|
51
|
+
:avs_result_postal_match => gw_response.avs_result.kind_of?(::ActiveMerchant::Billing::AVSResult) ? gw_response.avs_result.postal_match : gw_response.avs_result['postal_match'],
|
52
|
+
:cvv_result_code => gw_response.cvv_result.kind_of?(::ActiveMerchant::Billing::CVVResult) ? gw_response.cvv_result.code : gw_response.cvv_result['code'],
|
53
|
+
:cvv_result_message => gw_response.cvv_result.kind_of?(::ActiveMerchant::Billing::CVVResult) ? gw_response.cvv_result.message : gw_response.cvv_result['message'],
|
54
|
+
:success => gw_response.success?,
|
55
|
+
:updated_at => Time.now.utc
|
56
|
+
}.merge(CybersourceResponse.cybersource_response_params(gw_response))
|
57
|
+
|
58
|
+
# Keep original values as much as possible
|
59
|
+
updated_attributes.delete_if { |k, v| v.blank? }
|
60
|
+
|
61
|
+
# Update the response row
|
62
|
+
update!(updated_attributes)
|
63
|
+
|
64
|
+
# Create the transaction row if needed (cannot have been created before or the state wouldn't have been UNDEFINED)
|
65
|
+
if gw_response.success?
|
66
|
+
amount = gw_response.params['amount']
|
67
|
+
currency = gw_response.params['currency']
|
68
|
+
amount_in_cents = amount.nil? ? nil : ::Monetize.from_numeric(amount.to_f, currency).cents.to_i
|
69
|
+
build_cybersource_transaction(:kb_account_id => kb_account_id,
|
70
|
+
:kb_tenant_id => kb_tenant_id,
|
71
|
+
:amount_in_cents => amount_in_cents,
|
72
|
+
:currency => currency,
|
73
|
+
:api_call => api_call,
|
74
|
+
:kb_payment_id => kb_payment_id,
|
75
|
+
:kb_payment_transaction_id => kb_payment_transaction_id,
|
76
|
+
:transaction_type => transaction_type,
|
77
|
+
:payment_processor_account_id => payment_processor_account_id,
|
78
|
+
:txn_id => txn_id,
|
79
|
+
:created_at => updated_at,
|
80
|
+
:updated_at => updated_at).save!
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
38
84
|
def first_reference_id
|
39
85
|
params_request_id
|
40
86
|
end
|
@@ -46,6 +92,14 @@ module Killbill #:nodoc:
|
|
46
92
|
def gateway_error_code
|
47
93
|
params_reason_code
|
48
94
|
end
|
95
|
+
|
96
|
+
def to_transaction_info_plugin(transaction=nil)
|
97
|
+
t_info_plugin = super(transaction)
|
98
|
+
|
99
|
+
t_info_plugin.properties << create_plugin_property('cybersourceResponseId', id)
|
100
|
+
|
101
|
+
t_info_plugin
|
102
|
+
end
|
49
103
|
end
|
50
104
|
end
|
51
105
|
end
|
data/lib/cybersource.rb
CHANGED
@@ -18,6 +18,8 @@ require 'yaml'
|
|
18
18
|
require 'killbill'
|
19
19
|
require 'killbill/helpers/active_merchant'
|
20
20
|
|
21
|
+
require 'cybersource/ext/active_merchant/active_merchant.rb'
|
22
|
+
|
21
23
|
require 'cybersource/cyber_source_on_demand'
|
22
24
|
require 'cybersource/api'
|
23
25
|
require 'cybersource/private_api'
|
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>
|
28
|
+
<version>2.0.0</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>
|
@@ -0,0 +1,276 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Killbill::Cybersource::CyberSourceOnDemand do
|
4
|
+
|
5
|
+
it 'parses a transaction detail report with a single ApplicationReply correctly' do
|
6
|
+
xml_report = <<eos
|
7
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
8
|
+
<!DOCTYPE Report SYSTEM "https://ebctest.cybersource.com/ebctest/reports/dtd/tdr_1_3.dtd">
|
9
|
+
<Report xmlns="https://ebctest.cybersource.com/ebctest/reports/dtd/tdr_1_3.dtd"
|
10
|
+
Name="Transaction Detail"
|
11
|
+
Version="1.3"
|
12
|
+
MerchantID="testMerchant"
|
13
|
+
ReportStartDate="2008-09-10 21:46:41.765-08:00"
|
14
|
+
ReportEndDate="2008-09-10 21:46:41.765-08:00">
|
15
|
+
<Requests>
|
16
|
+
<Request MerchantReferenceNumber="33038191"
|
17
|
+
RequestDate="2008-09-10T14:00:08-08:00"
|
18
|
+
RequestID="2210804330010167904567"
|
19
|
+
SubscriptionID=""
|
20
|
+
Source="SCMP API"
|
21
|
+
User="merchant123"
|
22
|
+
TransactionReferenceNumber="0001094522"
|
23
|
+
PredecessorRequestID="7904567221330010160804">
|
24
|
+
<BillTo>
|
25
|
+
<FirstName>JANE</FirstName>
|
26
|
+
<LastName>Smith</LastName>
|
27
|
+
<Address1>1295 Charleston Rd</Address1>
|
28
|
+
<Address2>Suite 2</Address2>
|
29
|
+
<City>Mountain View</City>
|
30
|
+
<State>CA</State>
|
31
|
+
<Zip>06513</Zip>
|
32
|
+
<Email>null@cybersource.com</Email>
|
33
|
+
<Country>US</Country>
|
34
|
+
</BillTo>
|
35
|
+
<ShipTo>
|
36
|
+
<FirstName>JANE</FirstName>
|
37
|
+
<LastName>SMITH</LastName>
|
38
|
+
<Address1>1295 Charleston Rd</Address1>
|
39
|
+
<Address2>Suite 2</Address2>
|
40
|
+
<City>Mountain View</City>
|
41
|
+
<State>CA</State>
|
42
|
+
<Zip>94043</Zip>
|
43
|
+
<Country>US</Country>
|
44
|
+
</ShipTo>
|
45
|
+
<PaymentMethod>
|
46
|
+
<Card>
|
47
|
+
<AccountSuffix>1111</AccountSuffix>
|
48
|
+
<ExpirationMonth>11</ExpirationMonth>
|
49
|
+
<ExpirationYear>2011</ExpirationYear>
|
50
|
+
<CardType>Visa</CardType>
|
51
|
+
</Card>
|
52
|
+
</PaymentMethod>
|
53
|
+
<LineItems>
|
54
|
+
<LineItem Number="0">
|
55
|
+
<FulfillmentType/>
|
56
|
+
<Quantity>1</Quantity>
|
57
|
+
<UnitPrice>1.56</UnitPrice>
|
58
|
+
<TaxAmount>0.25</TaxAmount>
|
59
|
+
<MerchantProductSKU>testdl</MerchantProductSKU>
|
60
|
+
<ProductName>PName1</ProductName>
|
61
|
+
<ProductCode>electronic_software</ProductCode>
|
62
|
+
</LineItem>
|
63
|
+
</LineItems>
|
64
|
+
<ApplicationReplies>
|
65
|
+
<ApplicationReply Name="ics_bill">
|
66
|
+
<RCode>1</RCode>
|
67
|
+
<RFlag>SOK</RFlag>
|
68
|
+
<RMsg>Request was processed successfully.</RMsg>
|
69
|
+
</ApplicationReply>
|
70
|
+
</ApplicationReplies>
|
71
|
+
<PaymentData>
|
72
|
+
<PaymentProcessor>vital</PaymentProcessor>
|
73
|
+
<Amount>1.81</Amount>
|
74
|
+
<CurrencyCode>eur</CurrencyCode>
|
75
|
+
<TotalTaxAmount>0.25</TotalTaxAmount>
|
76
|
+
<EventType>TRANSMITTED</EventType>
|
77
|
+
</PaymentData>
|
78
|
+
</Request>
|
79
|
+
</Requests>
|
80
|
+
</Report>
|
81
|
+
eos
|
82
|
+
report = Killbill::Cybersource::CyberSourceOnDemand::CyberSourceOnDemandTransactionReport.new(xml_report, Logger.new(STDOUT))
|
83
|
+
response = report.response
|
84
|
+
response.success?.should be_true
|
85
|
+
response.message.should == 'Request was processed successfully.'
|
86
|
+
response.params['merchantReferenceCode'].should == '33038191'
|
87
|
+
response.params['requestID'].should == '2210804330010167904567'
|
88
|
+
response.params['decision'].should be_nil
|
89
|
+
response.params['reasonCode'].should be_nil
|
90
|
+
response.params['requestToken'].should be_nil
|
91
|
+
response.params['currency'].should == 'eur'
|
92
|
+
response.params['amount'].should == '1.81'
|
93
|
+
response.params['authorizationCode'].should be_nil
|
94
|
+
response.params['avsCode'].should be_nil
|
95
|
+
response.params['avsCodeRaw'].should be_nil
|
96
|
+
response.params['cvCode'].should be_nil
|
97
|
+
response.params['authorizedDateTime'].should be_nil
|
98
|
+
response.params['processorResponse'].should be_nil
|
99
|
+
response.params['reconciliationID'].should == '0001094522'
|
100
|
+
response.params['subscriptionID'].should == ''
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'parses a transaction detail report with multiple ApplicationReplies correctly' do
|
104
|
+
xml_report = <<eos
|
105
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
106
|
+
<!DOCTYPE Report SYSTEM "https://ebctest.cybersource.com/ebctest/reports/dtd/tdr_1_6.dtd">
|
107
|
+
<Report xmlns="https://ebctest.cybersource.com/ebctest/reports/dtd/tdr_1_6.dtd"
|
108
|
+
Name="Transaction Detail"
|
109
|
+
Version="1.6"
|
110
|
+
MerchantID="ok_go"
|
111
|
+
ReportStartDate="2009-05-26T18:30:00-08:00"
|
112
|
+
ReportEndDate="2009-05-27T18:30:00-08:00">
|
113
|
+
<Requests>
|
114
|
+
<Request RequestID="2434465504100167904567"
|
115
|
+
RequestDate="2009-05-27T17:49:10+05:30"
|
116
|
+
MerchantReferenceNumber="1234"
|
117
|
+
Source="SCMP API"
|
118
|
+
User=""
|
119
|
+
SubscriptionID=""
|
120
|
+
TransactionReferenceNumber="00013791KV8BZF3P">
|
121
|
+
<BillTo>
|
122
|
+
<FirstName>sample</FirstName>
|
123
|
+
<LastName>merchant</LastName>
|
124
|
+
<Address1>11 Lico Ave</Address1>
|
125
|
+
<City>Big City</City>
|
126
|
+
<State>CA</State>
|
127
|
+
<Zip>99999</Zip>
|
128
|
+
<Email>smerchant@example.com</Email>
|
129
|
+
<Country>US</Country>
|
130
|
+
<Phone/>
|
131
|
+
</BillTo>
|
132
|
+
<ShipTo>
|
133
|
+
<City>xyz</City>
|
134
|
+
<Zip>95117</Zip>
|
135
|
+
</ShipTo>
|
136
|
+
<PaymentMethod>
|
137
|
+
<Card>
|
138
|
+
<AccountSuffix>7392</AccountSuffix>
|
139
|
+
<ExpirationMonth>12</ExpirationMonth>
|
140
|
+
<ExpirationYear>2009</ExpirationYear>
|
141
|
+
<CardType>Visa</CardType>
|
142
|
+
</Card>
|
143
|
+
</PaymentMethod>
|
144
|
+
<LineItems>
|
145
|
+
<LineItem Number="0">
|
146
|
+
<FulfillmentType>P</FulfillmentType>
|
147
|
+
<Quantity>1</Quantity>
|
148
|
+
<UnitPrice>2.00</UnitPrice>
|
149
|
+
<TaxAmount>0.00</TaxAmount>
|
150
|
+
<ProductCode>default</ProductCode>
|
151
|
+
</LineItem>
|
152
|
+
</LineItems>
|
153
|
+
<ApplicationReplies>
|
154
|
+
<ApplicationReply Name="ics_auth">
|
155
|
+
<RCode>1</RCode>
|
156
|
+
<RFlag>SOK</RFlag>
|
157
|
+
<RMsg>Request was processed successfully.</RMsg>
|
158
|
+
</ApplicationReply>
|
159
|
+
<ApplicationReply Name="ics_decision">
|
160
|
+
<RCode>0</RCode>
|
161
|
+
<RFlag>DREVIEW</RFlag>
|
162
|
+
<RMsg>Decision is REVIEW.</RMsg>
|
163
|
+
</ApplicationReply>
|
164
|
+
<ApplicationReply Name="ics_decision_early">
|
165
|
+
<RCode>1</RCode>
|
166
|
+
<RFlag/>
|
167
|
+
</ApplicationReply>
|
168
|
+
<ApplicationReply Name="ics_score">
|
169
|
+
<RCode>1</RCode>
|
170
|
+
<RFlag>DSCORE</RFlag>
|
171
|
+
<RMsg>Score exceeds threshold. Score = 84</RMsg>
|
172
|
+
</ApplicationReply>
|
173
|
+
</ApplicationReplies>
|
174
|
+
<PaymentData>
|
175
|
+
<PaymentRequestID>2434465504100167904567</PaymentRequestID>
|
176
|
+
<PaymentProcessor>smartpay</PaymentProcessor>
|
177
|
+
<Amount>2.00</Amount>
|
178
|
+
<CurrencyCode>USD</CurrencyCode>
|
179
|
+
<TotalTaxAmount>0.00</TotalTaxAmount>
|
180
|
+
<AuthorizationType>O</AuthorizationType>
|
181
|
+
<AuthorizationCode>888888</AuthorizationCode>
|
182
|
+
<AVSResult>I1</AVSResult>
|
183
|
+
<AVSResultMapped>X</AVSResultMapped>
|
184
|
+
<GrandTotal>2.00</GrandTotal>
|
185
|
+
<ACHVerificationResult>100</ACHVerificationResult>
|
186
|
+
</PaymentData>
|
187
|
+
<MerchantDefinedData>
|
188
|
+
<field1 name="mdd1">ca</field1>
|
189
|
+
</MerchantDefinedData>
|
190
|
+
<RiskData>
|
191
|
+
<Factors>C,Y,Z</Factors>
|
192
|
+
<HostSeverity>1</HostSeverity>
|
193
|
+
<Score>84</Score>
|
194
|
+
<TimeLocal>2009-05-27T10:49:10</TimeLocal>
|
195
|
+
<AppliedThreshold>20</AppliedThreshold>
|
196
|
+
<AppliedTimeHedge>normal</AppliedTimeHedge>
|
197
|
+
<AppliedVelocityHedge>high</AppliedVelocityHedge>
|
198
|
+
<AppliedHostHedge>normal</AppliedHostHedge>
|
199
|
+
<AppliedCategoryGift>n</AppliedCategoryGift>
|
200
|
+
<AppliedCategoryTime/>
|
201
|
+
<AppliedAVS>X</AppliedAVS>
|
202
|
+
<BinAccountType>CN</BinAccountType>
|
203
|
+
<BinScheme>Visa Credit</BinScheme>
|
204
|
+
<BinIssuer>Sample issuer</BinIssuer>
|
205
|
+
<BinCountry>us</BinCountry>
|
206
|
+
<InfoCodes>
|
207
|
+
<InfoCode>
|
208
|
+
<CodeType>address</CodeType>
|
209
|
+
<CodeValue>MM-C,MM-Z</CodeValue>
|
210
|
+
</InfoCode>
|
211
|
+
<InfoCode>
|
212
|
+
<CodeType>velocity</CodeType>
|
213
|
+
<CodeValue>VEL-CC</CodeValue>
|
214
|
+
</InfoCode>
|
215
|
+
</InfoCodes>
|
216
|
+
</RiskData>
|
217
|
+
<ProfileList>
|
218
|
+
<Profile Name="Default Profile">
|
219
|
+
<ProfileMode>Active</ProfileMode>
|
220
|
+
<ProfileDecision>ACCEPT</ProfileDecision>
|
221
|
+
<RuleList>
|
222
|
+
<Rule>
|
223
|
+
<RuleName>sample rule name</RuleName>
|
224
|
+
<RuleDecision>IGNORE</RuleDecision>
|
225
|
+
</Rule>
|
226
|
+
</RuleList>
|
227
|
+
</Profile>
|
228
|
+
</ProfileList>
|
229
|
+
<TravelData>
|
230
|
+
<TripInfo>
|
231
|
+
<CompleteRoute>AB-CD:EF-GH</CompleteRoute>
|
232
|
+
<JourneyType>round trip</JourneyType>
|
233
|
+
<DepartureDateTime>sample date & time</DepartureDateTime>
|
234
|
+
</TripInfo>
|
235
|
+
<PassengerInfo>
|
236
|
+
<Passenger Number="0">
|
237
|
+
<PassengerFirstName>jane</PassengerFirstName>
|
238
|
+
<PassengerLastName>doe</PassengerLastName>
|
239
|
+
<PassengerID>Sing-001</PassengerID>
|
240
|
+
</Passenger>
|
241
|
+
<Passenger Number="1">
|
242
|
+
<PassengerFirstName>john</PassengerFirstName>
|
243
|
+
<PassengerLastName>doe</PassengerLastName>
|
244
|
+
<PassengerID>sing-002</PassengerID>
|
245
|
+
<PassengerStatus>Adult</PassengerStatus>
|
246
|
+
<PassengerType>Gold</PassengerType>
|
247
|
+
<PassengerPhone>9995551212</PassengerPhone>
|
248
|
+
<PassengerEmail>jdoe@example.com</PassengerEmail>
|
249
|
+
</Passenger>
|
250
|
+
</PassengerInfo>
|
251
|
+
</TravelData>
|
252
|
+
</Request>
|
253
|
+
</Requests>
|
254
|
+
</Report>
|
255
|
+
eos
|
256
|
+
report = Killbill::Cybersource::CyberSourceOnDemand::CyberSourceOnDemandTransactionReport.new(xml_report, Logger.new(STDOUT))
|
257
|
+
response = report.response
|
258
|
+
response.success?.should be_false
|
259
|
+
response.message.should == 'Score exceeds threshold. Score = 84'
|
260
|
+
response.params['merchantReferenceCode'].should == '1234'
|
261
|
+
response.params['requestID'].should == '2434465504100167904567'
|
262
|
+
response.params['decision'].should == 'ACCEPT'
|
263
|
+
response.params['reasonCode'].should be_nil
|
264
|
+
response.params['requestToken'].should be_nil
|
265
|
+
response.params['currency'].should == 'USD'
|
266
|
+
response.params['amount'].should == '2.00'
|
267
|
+
response.params['authorizationCode'].should == '888888'
|
268
|
+
response.params['avsCode'].should == 'X'
|
269
|
+
response.params['avsCodeRaw'].should == 'I1'
|
270
|
+
response.params['cvCode'].should be_nil
|
271
|
+
response.params['authorizedDateTime'].should be_nil
|
272
|
+
response.params['processorResponse'].should be_nil
|
273
|
+
response.params['reconciliationID'].should == '00013791KV8BZF3P'
|
274
|
+
response.params['subscriptionID'].should == ''
|
275
|
+
end
|
276
|
+
end
|
@@ -81,6 +81,62 @@ describe Killbill::Cybersource::PaymentPlugin do
|
|
81
81
|
transactions[1].txn_id.should be_nil
|
82
82
|
end
|
83
83
|
|
84
|
+
it 'should be able to fix UNDEFINED payments' do
|
85
|
+
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)
|
86
|
+
payment_response.status.should eq(:PROCESSED), payment_response.gateway_error
|
87
|
+
|
88
|
+
# Force a transition to :UNDEFINED
|
89
|
+
Killbill::Cybersource::CybersourceTransaction.last.delete
|
90
|
+
response = Killbill::Cybersource::CybersourceResponse.last
|
91
|
+
response.update(:message => {:payment_plugin_status => 'UNDEFINED'}.to_json)
|
92
|
+
|
93
|
+
skip_gw = Killbill::Plugin::Model::PluginProperty.new
|
94
|
+
skip_gw.key = 'skip_gw'
|
95
|
+
skip_gw.value = 'true'
|
96
|
+
properties_with_skip_gw = @properties.clone
|
97
|
+
properties_with_skip_gw << skip_gw
|
98
|
+
|
99
|
+
# Set skip_gw=true, to avoid calling the report API
|
100
|
+
transaction_info_plugins = @plugin.get_payment_info(@pm.kb_account_id, @kb_payment.id, properties_with_skip_gw, @call_context)
|
101
|
+
transaction_info_plugins.size.should == 1
|
102
|
+
transaction_info_plugins.first.status.should eq(:UNDEFINED)
|
103
|
+
|
104
|
+
# Fix it
|
105
|
+
transaction_info_plugins = @plugin.get_payment_info(@pm.kb_account_id, @kb_payment.id, @properties, @call_context)
|
106
|
+
transaction_info_plugins.size.should == 1
|
107
|
+
transaction_info_plugins.first.status.should eq(:PROCESSED)
|
108
|
+
|
109
|
+
# Set skip_gw=true, to check the local state
|
110
|
+
transaction_info_plugins = @plugin.get_payment_info(@pm.kb_account_id, @kb_payment.id, properties_with_skip_gw, @call_context)
|
111
|
+
transaction_info_plugins.size.should == 1
|
112
|
+
transaction_info_plugins.first.status.should eq(:PROCESSED)
|
113
|
+
|
114
|
+
# Compare the state of the old and new response
|
115
|
+
new_response = Killbill::Cybersource::CybersourceResponse.last
|
116
|
+
new_response.id.should == response.id
|
117
|
+
new_response.api_call.should == 'purchase'
|
118
|
+
new_response.kb_tenant_id.should == @call_context.tenant_id
|
119
|
+
new_response.kb_account_id.should == @pm.kb_account_id
|
120
|
+
new_response.kb_payment_id.should == @kb_payment.id
|
121
|
+
new_response.kb_payment_transaction_id.should == @kb_payment.transactions[0].id
|
122
|
+
new_response.transaction_type.should == 'PURCHASE'
|
123
|
+
new_response.payment_processor_account_id.should == 'default'
|
124
|
+
# The report API doesn't give us the token
|
125
|
+
new_response.authorization.split(';')[0..1].should == response.authorization.split(';')[0..1]
|
126
|
+
new_response.test.should be_true
|
127
|
+
new_response.params_merchant_reference_code.should == response.params_merchant_reference_code
|
128
|
+
new_response.params_decision.should == response.params_decision
|
129
|
+
new_response.params_request_token.should == response.params_request_token
|
130
|
+
new_response.params_currency.should == response.params_currency
|
131
|
+
new_response.params_amount.should == response.params_amount
|
132
|
+
new_response.params_authorization_code.should == response.params_authorization_code
|
133
|
+
new_response.params_avs_code.should == response.params_avs_code
|
134
|
+
new_response.params_avs_code_raw.should == response.params_avs_code_raw
|
135
|
+
new_response.params_reconciliation_id.should == response.params_reconciliation_id
|
136
|
+
new_response.success.should be_true
|
137
|
+
new_response.message.should == 'Request was processed successfully.'
|
138
|
+
end
|
139
|
+
|
84
140
|
it 'should be able to charge and refund' do
|
85
141
|
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)
|
86
142
|
payment_response.status.should eq(:PROCESSED), payment_response.gateway_error
|
@@ -156,4 +212,14 @@ describe Killbill::Cybersource::PaymentPlugin do
|
|
156
212
|
payment_response.amount.should == @amount
|
157
213
|
payment_response.transaction_type.should == :CREDIT
|
158
214
|
end
|
215
|
+
|
216
|
+
# See https://github.com/killbill/killbill-cybersource-plugin/issues/4
|
217
|
+
it 'handles errors gracefully' do
|
218
|
+
properties_with_no_expiration_year = build_pm_properties
|
219
|
+
cc_exp_year = properties_with_no_expiration_year.find { |prop| prop.key == 'ccExpirationYear' }
|
220
|
+
cc_exp_year.value = nil
|
221
|
+
|
222
|
+
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)
|
223
|
+
payment_response.status.should eq(:ERROR), payment_response.gateway_error
|
224
|
+
end
|
159
225
|
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
|
+
version: 2.0.0
|
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: 2015-
|
11
|
+
date: 2015-07-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: killbill
|
@@ -16,12 +16,12 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ~>
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 4.
|
19
|
+
version: 4.4.0
|
20
20
|
requirement: !ruby/object:Gem::Requirement
|
21
21
|
requirements:
|
22
22
|
- - ~>
|
23
23
|
- !ruby/object:Gem::Version
|
24
|
-
version: 4.
|
24
|
+
version: 4.4.0
|
25
25
|
prerelease: false
|
26
26
|
type: :runtime
|
27
27
|
- !ruby/object:Gem::Dependency
|
@@ -290,6 +290,7 @@ files:
|
|
290
290
|
- lib/cybersource/api.rb
|
291
291
|
- lib/cybersource/application.rb
|
292
292
|
- lib/cybersource/cyber_source_on_demand.rb
|
293
|
+
- lib/cybersource/ext/active_merchant/active_merchant.rb
|
293
294
|
- lib/cybersource/models/payment_method.rb
|
294
295
|
- lib/cybersource/models/response.rb
|
295
296
|
- lib/cybersource/models/transaction.rb
|
@@ -298,6 +299,7 @@ files:
|
|
298
299
|
- pom.xml
|
299
300
|
- release.sh
|
300
301
|
- spec/cybersource/base_plugin_spec.rb
|
302
|
+
- spec/cybersource/cyber_source_on_demand_spec.rb
|
301
303
|
- spec/cybersource/remote/integration_spec.rb
|
302
304
|
- spec/spec_helper.rb
|
303
305
|
homepage: http://killbill.io
|
@@ -322,8 +324,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
322
324
|
version: '0'
|
323
325
|
requirements: []
|
324
326
|
rubyforge_project:
|
325
|
-
rubygems_version: 2.
|
327
|
+
rubygems_version: 2.4.6
|
326
328
|
signing_key:
|
327
329
|
specification_version: 4
|
328
330
|
summary: Plugin to use Cybersource as a gateway.
|
329
|
-
test_files:
|
331
|
+
test_files:
|
332
|
+
- spec/cybersource/base_plugin_spec.rb
|
333
|
+
- spec/cybersource/cyber_source_on_demand_spec.rb
|
334
|
+
- spec/cybersource/remote/integration_spec.rb
|
335
|
+
- spec/spec_helper.rb
|