killbill-cybersource 4.0.6 → 4.0.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8478664d561af5bbb03aa2fe320f238995d914b4
4
- data.tar.gz: 798bc353760c87feb1b0402acc058e1224fe5d2b
3
+ metadata.gz: 7471ac43901ed2efb3ebeaa6b6ed62a28a38286f
4
+ data.tar.gz: 4a697cfdb6b8fad42f07c13df1a8ae1d4ce1f808
5
5
  SHA512:
6
- metadata.gz: 4297a35bd9a0c3d11904073530cb5ef32e453e960bf0a16ace61ecb688581091b2ba213aece6f33cdf871023c0d52ec36f739c00cb7d1212fcf6e516ca96f5be
7
- data.tar.gz: 38b18109fb61c1955c07dc13e7cde92938d547d7f270f5309cf9943ccac505a23abe93b142e17694069c092d23b59006329de981e0c30d9f18455fbd686f54f7
6
+ metadata.gz: 05e6ea3dba262e59947a316d7b26e4fa31388762ae3f79e308c11798cdeff366f64439b05c7241288918d4e807d9719ff95619dae073d81c28784b4221dab5d0
7
+ data.tar.gz: 61e4584909179d697d4ba137f4928b3a5a1d1af3dc0eff91289832c8f058d3a2390f6b00d01236b64c883f450c86e7d9231d4b0b74f9d6ef88ec20d80f3908c3
data/.travis.yml CHANGED
@@ -6,7 +6,7 @@ cache: bundler
6
6
  before_script:
7
7
  - bundle exec jbundle install
8
8
 
9
- script: 'bundle exec rake test:spec test:remote:spec'
9
+ script: 'bundle exec rake test:ci:spec'
10
10
 
11
11
  notifications:
12
12
  email:
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- killbill-cybersource (4.0.6)
4
+ killbill-cybersource (4.0.7)
5
5
  actionpack (~> 4.1.0)
6
6
  actionview (~> 4.1.0)
7
7
  activemerchant (~> 1.48.0)
@@ -80,7 +80,7 @@ GEM
80
80
  jdbc-sqlite3 (3.8.11.2)
81
81
  jruby-openssl (0.9.16-java)
82
82
  json (1.8.3-java)
83
- killbill (7.0.3)
83
+ killbill (7.0.6)
84
84
  rack (>= 1.5.2)
85
85
  sinatra (~> 1.3.4)
86
86
  typhoeus (~> 0.6.9)
data/NEWS CHANGED
@@ -1,3 +1,10 @@
1
+ 4.0.7
2
+ Fix auth reversal in non-USD
3
+ Cancel old UNDEFINED transactions that cannot be found in CyberSource
4
+
5
+ 4.0.6
6
+ Fix NPE when message is null in the response
7
+
1
8
  4.0.5
2
9
  Fix ApplePay MasterCard implementation
3
10
 
data/Rakefile CHANGED
@@ -20,6 +20,15 @@ namespace :test do
20
20
  task.pattern = './spec/*/remote/*_spec.rb'
21
21
  end
22
22
  end
23
+
24
+ namespace :ci do
25
+ desc 'Run RSpec CI tests'
26
+ RSpec::Core::RakeTask.new do |task|
27
+ task.name = 'spec'
28
+ task.pattern = './spec/**/*_spec.rb'
29
+ task.rspec_opts = '--tag ~ci_skip'
30
+ end
31
+ end
23
32
  end
24
33
 
25
34
  # Install tasks to package the plugin for Killbill
data/VERSION CHANGED
@@ -1 +1 @@
1
- 4.0.6
1
+ 4.0.7
@@ -2,6 +2,7 @@ module Killbill #:nodoc:
2
2
  module Cybersource #:nodoc:
3
3
  class PaymentPlugin < ::Killbill::Plugin::ActiveMerchant::PaymentPlugin
4
4
 
5
+ SEVEN_DAYS_AGO = (7 * 86400)
5
6
  SIXTY_DAYS_AGO = (60 * 86400)
6
7
 
7
8
  def initialize
@@ -121,7 +122,10 @@ module Killbill #:nodoc:
121
122
  next unless transaction_info_plugin.status == :UNDEFINED
122
123
 
123
124
  cybersource_response_id = find_value_from_properties(transaction_info_plugin.properties, 'cybersourceResponseId')
124
- next if cybersource_response_id.nil?
125
+ if cybersource_response_id.nil?
126
+ logger.warn("Unable to fix UNDEFINED kb_transaction_id='#{transaction_info_plugin.kb_transaction_payment_id}' (cybersource_response_id not specified)")
127
+ next
128
+ end
125
129
 
126
130
  report_date = transaction_info_plugin.created_date
127
131
  authorization = find_value_from_properties(transaction_info_plugin.properties, 'authorization')
@@ -142,23 +146,34 @@ module Killbill #:nodoc:
142
146
  report = get_report(order_id, report_date, options, context)
143
147
  end
144
148
 
145
- # Report API not configured or skip_gw=true
149
+ # Report API not configured, connection problem or skip_gw=true
146
150
  next if report.nil?
147
151
 
148
- # Report not found
149
- if report.empty?
150
- logger.info("Unable to fix UNDEFINED transaction #{transaction_info_plugin.kb_transaction_payment_id} (not found in CyberSource)")
152
+ threshold = (Killbill::Plugin::ActiveMerchant::Utils.normalized(options, :cancel_threshold) || SEVEN_DAYS_AGO).to_i
153
+ should_cancel_payment = (now - report_date) >= threshold
154
+ if report.empty? && !should_cancel_payment
155
+ # We'll retry later
156
+ logger.info("Unable to fix UNDEFINED kb_transaction_id='#{transaction_info_plugin.kb_transaction_payment_id}' (not found in CyberSource)")
151
157
  next
152
- end
153
-
154
- # Update our rows
155
- response = CybersourceResponse.find_by(:id => cybersource_response_id)
156
- next if response.nil?
158
+ else
159
+ # Update our rows
160
+ response = CybersourceResponse.find_by(:id => cybersource_response_id)
161
+ if response.nil?
162
+ logger.warn("Unable to fix UNDEFINED kb_transaction_id='#{transaction_info_plugin.kb_transaction_payment_id}' (CyberSource response='#{cybersource_response_id}' not found)")
163
+ next
164
+ end
157
165
 
158
- logger.info("Fixing UNDEFINED transaction #{transaction_info_plugin.kb_transaction_payment_id}: success? = #{report.response.success?}")
166
+ if should_cancel_payment
167
+ # At this point, it's safe to assume the payment never happened
168
+ logger.info("Canceling UNDEFINED kb_transaction_id='#{transaction_info_plugin.kb_transaction_payment_id}'")
169
+ response.cancel
170
+ else
171
+ logger.info("Fixing UNDEFINED kb_transaction_id='#{transaction_info_plugin.kb_transaction_payment_id}', success='#{report.response.success?}'")
172
+ response.update_and_create_transaction(report.response)
173
+ end
159
174
 
160
- response.update_and_create_transaction(report.response)
161
- stale = true
175
+ stale = true
176
+ end
162
177
  end
163
178
 
164
179
  # If we updated the state, re-fetch the latest data
@@ -258,8 +273,6 @@ module Killbill #:nodoc:
258
273
 
259
274
  threshold = (Killbill::Plugin::ActiveMerchant::Utils.normalized(options, :auto_credit_threshold) || SIXTY_DAYS_AGO).to_i
260
275
 
261
- # we might want a 'util' function to make the conversion joda DateTime to a ruby Time object
262
- now = Time.parse(@clock.get_clock.get_utc_now.to_s)
263
276
  (now - transaction.created_at) >= threshold
264
277
  end
265
278
 
@@ -292,8 +305,20 @@ module Killbill #:nodoc:
292
305
  get_single_transaction_report(report_api, merchant_reference_code, date)
293
306
  end
294
307
 
295
- def get_single_transaction_report(report_api, merchant_reference_code, date)
296
- report_api.single_transaction_report(merchant_reference_code, date.strftime('%Y%m%d'))
308
+ def get_single_transaction_report(report_api, merchant_reference_code, date, fuzzy_date=true)
309
+ if fuzzy_date
310
+ report = get_single_transaction_report(report_api, merchant_reference_code, date, false)
311
+ report = get_single_transaction_report(report_api, merchant_reference_code, date - 1.day, false) if report.nil? || report.empty?
312
+ report = get_single_transaction_report(report_api, merchant_reference_code, date + 1.day, false) if report.nil? || report.empty?
313
+ return report
314
+ end
315
+
316
+ begin
317
+ report_api.single_transaction_report(merchant_reference_code, date.strftime('%Y%m%d'))
318
+ rescue => e
319
+ logger.warn "Error retrieving report for merchant_reference_code='#{merchant_reference_code}', target_date='#{date}': #{e.message}\n#{e.backtrace.join("\n")}"
320
+ nil
321
+ end
297
322
  end
298
323
 
299
324
  def add_required_options(kb_account_id, properties, options, context)
@@ -364,6 +389,11 @@ module Killbill #:nodoc:
364
389
 
365
390
  new_auth_response
366
391
  end
392
+
393
+ def now
394
+ # We might want a 'util' function to make the conversion Joda DateTime to a Ruby Time object
395
+ Time.parse(@clock.get_clock.get_utc_now.to_s)
396
+ end
367
397
  end
368
398
  end
369
399
  end
@@ -31,6 +31,8 @@ module Killbill #:nodoc:
31
31
 
32
32
  data = URI.encode_www_form(params)
33
33
 
34
+ @logger.info "Retrieving report for merchant_reference_code='#{merchant_reference_code}', target_date='#{target_date}', merchant_id='#{@config[:merchantID]}'"
35
+
34
36
  # Will raise ResponseError if the response code is > 300
35
37
  CyberSourceOnDemandTransactionReport.new(ssl_post(endpoint, data, headers), @logger)
36
38
  end
@@ -42,6 +42,27 @@ module Killbill #:nodoc:
42
42
  }
43
43
  end
44
44
 
45
+ def cancel
46
+ begin
47
+ error_details = JSON.parse(message)
48
+ original_message = nil
49
+ rescue
50
+ error_details = {}
51
+ original_message = message
52
+ end
53
+ error_details['original_message'] = original_message unless original_message.blank?
54
+ error_details['payment_plugin_status'] = 'CANCELED'
55
+
56
+ updated_attributes = {
57
+ :message => error_details.to_json,
58
+ :success => false,
59
+ :updated_at => Time.now.utc
60
+ }
61
+
62
+ # Update the response row
63
+ update!(updated_attributes)
64
+ end
65
+
45
66
  def update_and_create_transaction(gw_response)
46
67
  updated_attributes = {
47
68
  :message => gw_response.message,
@@ -108,7 +129,7 @@ module Killbill #:nodoc:
108
129
 
109
130
  def set_correct_status(t_info_plugin)
110
131
  # Respect the existing status if the payment was successful, if overridden or if there is no error code
111
- return if success || (message && message.strip.start_with?('{')) || gateway_error_code.blank?
132
+ return if success || (message && message.strip.start_with?('{')) || gateway_error_code.blank?
112
133
 
113
134
  if CANCELED_ERROR_CODES.include?(gateway_error_code.to_i)
114
135
  t_info_plugin.status = :CANCELED
data/pom.xml CHANGED
@@ -25,7 +25,7 @@
25
25
  <groupId>org.kill-bill.billing.plugin.ruby</groupId>
26
26
  <artifactId>cybersource-plugin</artifactId>
27
27
  <packaging>pom</packaging>
28
- <version>4.0.6</version>
28
+ <version>4.0.7</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>
@@ -61,7 +61,7 @@ describe Killbill::Cybersource::PaymentPlugin do
61
61
 
62
62
  with_transaction(kb_payment_id, :AUTHORIZE, 61.days.ago, context) do
63
63
  @plugin.should_credit?(kb_payment_id, context, {:disable_auto_credit => true}).should be_false
64
- @plugin.should_credit?(kb_payment_id, context, {:auto_credit_threshold => 61 * 86400}).should be_false
64
+ @plugin.should_credit?(kb_payment_id, context, {:auto_credit_threshold => 61 * 86400 + 10}).should be_false
65
65
  @plugin.should_credit?(kb_payment_id, context).should be_true
66
66
  end
67
67
  end
@@ -168,6 +168,32 @@ describe Killbill::Cybersource::PaymentPlugin do
168
168
  payment_response.gateway_error.should == 'One or more fields contains invalid data'
169
169
  payment_response.gateway_error_code.should == '102'
170
170
  end
171
+
172
+ it 'cancels UNDEFINED transactions with a JSON message' do
173
+ response = Killbill::Cybersource::CybersourceResponse.create(:api_call => 'authorization',
174
+ :message => '{"exception_message":"Timeout","payment_plugin_status":"UNDEFINED"}',
175
+ :created_at => Time.now,
176
+ :updated_at => Time.now)
177
+ response.cancel
178
+ response.message.should == '{"exception_message":"Timeout","payment_plugin_status":"CANCELED"}'
179
+ end
180
+
181
+ it 'cancels UNDEFINED transactions with a plain test message' do
182
+ response = Killbill::Cybersource::CybersourceResponse.create(:api_call => 'authorization',
183
+ :message => 'Internal error',
184
+ :created_at => Time.now,
185
+ :updated_at => Time.now)
186
+ response.cancel
187
+ response.message.should == '{"original_message":"Internal error","payment_plugin_status":"CANCELED"}'
188
+ end
189
+
190
+ it 'cancels UNDEFINED transactions with no message' do
191
+ response = Killbill::Cybersource::CybersourceResponse.create(:api_call => 'authorization',
192
+ :created_at => Time.now,
193
+ :updated_at => Time.now)
194
+ response.cancel
195
+ response.message.should == '{"payment_plugin_status":"CANCELED"}'
196
+ end
171
197
  end
172
198
 
173
199
  private
@@ -61,6 +61,9 @@ describe Killbill::Cybersource::PaymentPlugin do
61
61
  # Skip the rest of the test if the report API isn't configured to check for duplicates
62
62
  break unless with_report_api && report_api.check_for_duplicates?
63
63
 
64
+ # The report API can be delayed
65
+ await { !@plugin.get_single_transaction_report(report_api, @kb_payment.transactions[0].id, Time.now.utc).empty? }
66
+
64
67
  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)
65
68
  payment_response.amount.should == @amount
66
69
  payment_response.status.should == :PROCESSED
@@ -203,6 +206,9 @@ describe Killbill::Cybersource::PaymentPlugin do
203
206
 
204
207
  # Skip if the report API isn't configured
205
208
  if with_report_api
209
+ # The report API can be delayed
210
+ await { !@plugin.get_single_transaction_report(report_api, @kb_payment.transactions[0].id, Time.now.utc).empty? }
211
+
206
212
  # Fix it
207
213
  transaction_info_plugins = @plugin.get_payment_info(@pm.kb_account_id, @kb_payment.id, @properties, @call_context)
208
214
  transaction_info_plugins.size.should == 1
@@ -240,6 +246,50 @@ describe Killbill::Cybersource::PaymentPlugin do
240
246
  new_response.message.should == (with_report_api ? 'Request was processed successfully.' : '{"payment_plugin_status":"UNDEFINED"}')
241
247
  end
242
248
 
249
+ it 'should eventually cancel UNDEFINED payments' do
250
+ response = Killbill::Cybersource::CybersourceResponse.create(:api_call => 'authorization',
251
+ :kb_account_id => @pm.kb_account_id,
252
+ :kb_payment_id => @kb_payment.id,
253
+ :kb_payment_transaction_id => @kb_payment.transactions[0].id,
254
+ :kb_tenant_id => @call_context.tenant_id,
255
+ :message => '{"exception_message":"Timeout","payment_plugin_status":"UNDEFINED"}',
256
+ :created_at => Time.now,
257
+ :updated_at => Time.now)
258
+
259
+ # Set skip_gw=true, to avoid calling the report API
260
+ skip_gw = Killbill::Plugin::Model::PluginProperty.new
261
+ skip_gw.key = 'skip_gw'
262
+ skip_gw.value = 'true'
263
+ properties_with_cancel_threshold = @properties.clone
264
+ properties_with_cancel_threshold << skip_gw
265
+ transaction_info_plugins = @plugin.get_payment_info(@pm.kb_account_id, @kb_payment.id, properties_with_cancel_threshold, @call_context)
266
+ transaction_info_plugins.size.should == 1
267
+ transaction_info_plugins.first.status.should eq(:UNDEFINED)
268
+
269
+ # Call the reporting API (if configured) and verify the state still cannot be fixed
270
+ transaction_info_plugins = @plugin.get_payment_info(@pm.kb_account_id, @kb_payment.id, @properties, @call_context)
271
+ transaction_info_plugins.size.should == 1
272
+ transaction_info_plugins.first.status.should eq(:UNDEFINED)
273
+
274
+ # Transition to CANCEL won't work if the reporting API isn't configured
275
+ break unless with_report_api
276
+
277
+ # Force a transition to CANCEL
278
+ cancel_threshold = Killbill::Plugin::Model::PluginProperty.new
279
+ cancel_threshold.key = 'cancel_threshold'
280
+ cancel_threshold.value = '0'
281
+ properties_with_cancel_threshold = @properties.clone
282
+ properties_with_cancel_threshold << cancel_threshold
283
+ transaction_info_plugins = @plugin.get_payment_info(@pm.kb_account_id, @kb_payment.id, properties_with_cancel_threshold, @call_context)
284
+ transaction_info_plugins.size.should == 1
285
+ transaction_info_plugins.first.status.should eq(:CANCELED)
286
+
287
+ # Verify the state is sticky
288
+ transaction_info_plugins = @plugin.get_payment_info(@pm.kb_account_id, @kb_payment.id, @properties, @call_context)
289
+ transaction_info_plugins.size.should == 1
290
+ transaction_info_plugins.first.status.should eq(:CANCELED)
291
+ end
292
+
243
293
  it 'should be able to charge and refund' do
244
294
  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)
245
295
  check_response(payment_response, @amount, :PURCHASE, :PROCESSED, 'Successful transaction', '100')
@@ -277,6 +327,14 @@ describe Killbill::Cybersource::PaymentPlugin do
277
327
  check_response(payment_response, nil, :VOID, :PROCESSED, 'Successful transaction', '100')
278
328
  end
279
329
 
330
+ it 'should be able to auth and void in CAD', :ci_skip => true do
331
+ payment_response = @plugin.authorize_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[0].id, @pm.kb_payment_method_id, @amount, 'CAD', @properties, @call_context)
332
+ check_response(payment_response, @amount, :AUTHORIZE, :PROCESSED, 'Successful transaction', '100')
333
+
334
+ payment_response = @plugin.void_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[1].id, @pm.kb_payment_method_id, @properties, @call_context)
335
+ check_response(payment_response, nil, :VOID, :PROCESSED, 'Successful transaction', '100')
336
+ end
337
+
280
338
  it 'should be able to auth, partial capture and void' do
281
339
  payment_response = @plugin.authorize_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[0].id, @pm.kb_payment_method_id, @amount, @currency, @properties, @call_context)
282
340
  check_response(payment_response, @amount, :AUTHORIZE, :PROCESSED, 'Successful transaction', '100')
@@ -507,4 +565,12 @@ describe Killbill::Cybersource::PaymentPlugin do
507
565
  end
508
566
  kb_payment
509
567
  end
568
+
569
+ def await(timeout=15)
570
+ timeout.times do
571
+ return if block_given? && yield
572
+ sleep(1)
573
+ end
574
+ fail('Timeout')
575
+ end
510
576
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: killbill-cybersource
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.6
4
+ version: 4.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kill Bill core team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-05-11 00:00:00.000000000 Z
11
+ date: 2016-06-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: killbill