killbill-cybersource 4.0.6 → 4.0.7

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: 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