killbill-cybersource 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|