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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d96b3cde985847f615dfb6827472f74fa18286f9
4
- data.tar.gz: 7061ae8a789e1122e79109548133334695ff8f37
3
+ metadata.gz: bfa04a9b0d7a032fbd01ed8e985c0a77682c54f4
4
+ data.tar.gz: baabc471f68b9130025b82c0045438246178c6b9
5
5
  SHA512:
6
- metadata.gz: 7fbd21f7d2483df9594fddaf45249528162dc8f8ea374f94dda8ada1343edf12b1179d995d469426b2ea06e19f6c05ec61d6049249c77da5fac1be4fb27fcb4b
7
- data.tar.gz: a3db194b14bfcf50f66ba93dec2815f12048ad008013237f1f6a9a7ea578246af78cdaa9aacbe7eaa6bdacd7eb8d8e34386337bb1f2b6ed8567b4d0810457a95
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 (1.0.0)
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.0.0)
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.10)
23
- actionview (= 4.1.10)
24
- activesupport (= 4.1.10)
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.10)
28
- activesupport (= 4.1.10)
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.10)
40
- activesupport (= 4.1.10)
39
+ activemodel (4.1.12)
40
+ activesupport (= 4.1.12)
41
41
  builder (~> 3.1)
42
- activerecord (4.1.10)
43
- activemodel (= 4.1.10)
44
- activesupport (= 4.1.10)
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.16)
48
+ activerecord-jdbc-adapter (1.3.17)
49
49
  activerecord (>= 2.2)
50
- activesupport (4.1.10)
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.3)
60
+ ethon (0.7.4)
61
61
  ffi (>= 1.3.0)
62
- ffi (1.9.8-java)
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.7)
68
+ jdbc-sqlite3 (3.8.10.1)
69
69
  jruby-openssl (0.9.7-java)
70
- json (1.8.2-java)
71
- killbill (4.0.0)
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.6.1)
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.3)
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.14'
2
- jar 'org.kill-bill.billing.plugin:killbill-plugin-api-currency', '0.9'
3
- jar 'org.kill-bill.billing.plugin:killbill-plugin-api-invoice', '0.9'
4
- jar 'org.kill-bill.billing.plugin:killbill-plugin-api-notification', '0.9'
5
- jar 'org.kill-bill.billing.plugin:killbill-plugin-api-payment', '0.9'
6
- jar 'org.kill-bill.billing.plugin:killbill-plugin-api-routing', '0.9'
7
- jar 'org.kill-bill.billing:killbill-util:tests', '0.13.7'
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.kill-bill.billing:killbill-api:jar:0.14
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.9
5
- org.kill-bill.billing.plugin:killbill-plugin-api-invoice:jar:0.9
6
- org.kill-bill.billing.plugin:killbill-plugin-api-notification:jar:0.9
7
- org.kill-bill.billing.plugin:killbill-plugin-api-payment:jar:0.9
8
- org.kill-bill.billing.plugin:killbill-plugin-api-routing:jar:0.9
9
- org.kill-bill.billing:killbill-util:jar:tests:0.13.7
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.13.7
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.0.0
1
+ 2.0.0
@@ -22,7 +22,7 @@ Gem::Specification.new do |s|
22
22
 
23
23
  s.rdoc_options << '--exclude' << '.'
24
24
 
25
- s.add_dependency 'killbill', '~> 4.0.0'
25
+ s.add_dependency 'killbill', '~> 4.4.0'
26
26
 
27
27
  s.add_dependency 'sinatra', '~> 1.3.4'
28
28
  s.add_dependency 'thread_safe', '~> 0.3.4'
@@ -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 = @report_api.single_transaction_report(merchant_reference_code, kb_transaction.created_date.strftime('%Y%m%d'))
192
-
193
- if !report.nil? &&
194
- !report['Report'].nil? &&
195
- !report['Report']['Requests'].nil? &&
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 = 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 => @gateway.config[:merchantID],
16
+ :merchantID => @gateway.config[:merchantID],
16
17
  :merchantReferenceNumber => merchant_reference_code,
17
- :targetDate => target_date,
18
- :type => 'transaction',
19
- :subtype => 'transactionDetail',
20
- :versionNumber => '1.7',
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 = URI.encode_www_form(params)
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
- parse(@gateway.ssl_post(endpoint, data, headers))
33
+ CyberSourceOnDemandTransactionReport.new(@gateway.ssl_post(endpoint, data, headers), @logger)
33
34
  end
34
35
 
35
- private
36
+ class CyberSourceOnDemandTransactionReport
36
37
 
37
- def parse(body)
38
- # Thanks ActiveSupport!
39
- Hash.from_xml(body)
40
- rescue # Parser error - request failed
41
- @logger.warn "Error checking for duplicate payment, CyberSource response: #{body}"
42
- nil
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>1.0.0</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 &amp; 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: 1.0.0
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-05-08 00:00:00.000000000 Z
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.0.0
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.0.0
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.1.9
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