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 +4 -4
- data/.travis.yml +1 -1
- data/Gemfile.lock +2 -2
- data/NEWS +7 -0
- data/Rakefile +9 -0
- data/VERSION +1 -1
- data/lib/cybersource/api.rb +47 -17
- data/lib/cybersource/cyber_source_on_demand.rb +2 -0
- data/lib/cybersource/models/response.rb +22 -1
- data/pom.xml +1 -1
- data/spec/cybersource/base_plugin_spec.rb +27 -1
- data/spec/cybersource/remote/integration_spec.rb +66 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7471ac43901ed2efb3ebeaa6b6ed62a28a38286f
|
4
|
+
data.tar.gz: 4a697cfdb6b8fad42f07c13df1a8ae1d4ce1f808
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 05e6ea3dba262e59947a316d7b26e4fa31388762ae3f79e308c11798cdeff366f64439b05c7241288918d4e807d9719ff95619dae073d81c28784b4221dab5d0
|
7
|
+
data.tar.gz: 61e4584909179d697d4ba137f4928b3a5a1d1af3dc0eff91289832c8f058d3a2390f6b00d01236b64c883f450c86e7d9231d4b0b74f9d6ef88ec20d80f3908c3
|
data/.travis.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
killbill-cybersource (4.0.
|
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.
|
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
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.
|
1
|
+
4.0.7
|
data/lib/cybersource/api.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
149
|
-
|
150
|
-
|
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
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
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
|
-
|
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
|
-
|
161
|
-
|
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
|
-
|
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?('{'))
|
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.
|
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.
|
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-
|
11
|
+
date: 2016-06-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: killbill
|