killbill 4.0.0 → 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +12 -3
- data/Jarfile +8 -7
- data/Jarfile.lock +9 -8
- data/README.md +131 -108
- data/gen_config/api.conf +1 -1
- data/gen_config/plugin_api.conf +7 -6
- data/generators/active_merchant/templates/Jarfile.rb +7 -7
- data/generators/active_merchant/templates/plugin.gemspec.rb +2 -3
- data/killbill.gemspec +5 -2
- data/lib/killbill.rb +26 -22
- data/lib/killbill/gen/api/admin_payment_api.rb +76 -0
- data/lib/killbill/gen/api/payment_api.rb +82 -0
- data/lib/killbill/gen/api/payment_gateway_api.rb +75 -0
- data/lib/killbill/gen/api/require_gen.rb +1 -0
- data/lib/killbill/gen/plugin-api/catalog_plugin_api.rb +76 -0
- data/lib/killbill/gen/plugin-api/on_failure_payment_routing_result.rb +65 -0
- data/lib/killbill/gen/plugin-api/on_success_payment_routing_result.rb +53 -0
- data/lib/killbill/gen/plugin-api/payment_routing_api_exception.rb +51 -0
- data/lib/killbill/gen/plugin-api/payment_routing_context.rb +246 -0
- data/lib/killbill/gen/plugin-api/payment_routing_plugin_api.rb +138 -0
- data/lib/killbill/gen/plugin-api/prior_payment_routing_result.rb +107 -0
- data/lib/killbill/gen/plugin-api/require_gen.rb +9 -0
- data/lib/killbill/gen/plugin-api/standalone_plugin_catalog.rb +174 -0
- data/lib/killbill/gen/plugin-api/versioned_plugin_catalog.rb +83 -0
- data/lib/killbill/helpers/active_merchant.rb +6 -4
- data/lib/killbill/helpers/active_merchant/active_record/models/response.rb +16 -4
- data/lib/killbill/helpers/active_merchant/configuration.rb +7 -2
- data/lib/killbill/helpers/active_merchant/gateway.rb +76 -5
- data/lib/killbill/helpers/active_merchant/payment_plugin.rb +12 -8
- data/lib/killbill/helpers/catalog.rb +15 -0
- data/lib/killbill/payment_control.rb +23 -0
- data/lib/killbill/version.rb +3 -0
- data/spec/killbill/helpers/configuration_spec.rb +18 -4
- data/spec/killbill/helpers/payment_plugin_spec.rb +140 -36
- data/spec/killbill/helpers/response_spec.rb +1 -1
- metadata +49 -5
- data/VERSION +0 -1
@@ -4,15 +4,16 @@ module Killbill
|
|
4
4
|
require 'active_merchant'
|
5
5
|
|
6
6
|
class Gateway
|
7
|
-
def self.wrap(gateway_builder, config)
|
8
|
-
Gateway.new(config, gateway_builder.call(config))
|
7
|
+
def self.wrap(gateway_builder, config, logger)
|
8
|
+
Gateway.new(config, gateway_builder.call(config), logger)
|
9
9
|
end
|
10
10
|
|
11
11
|
attr_reader :config
|
12
12
|
|
13
|
-
def initialize(config, am_gateway)
|
13
|
+
def initialize(config, am_gateway, logger)
|
14
14
|
@config = config
|
15
15
|
@gateway = am_gateway
|
16
|
+
@logger = logger
|
16
17
|
|
17
18
|
# Override urls if needed (there is no easy way to do it earlier, because AM uses class_attribute)
|
18
19
|
@gateway.class.test_url = @config[:test_url] unless @config[:test_url].nil?
|
@@ -57,19 +58,89 @@ module Killbill
|
|
57
58
|
end
|
58
59
|
|
59
60
|
def method_missing(m, *args, &block)
|
61
|
+
options = {}
|
62
|
+
|
60
63
|
# The options hash should be the last argument, iterate through all to be safe
|
61
64
|
args.reverse.each do |arg|
|
62
|
-
if arg.respond_to?(:has_key?)
|
63
|
-
|
65
|
+
if arg.respond_to?(:has_key?)
|
66
|
+
options = arg
|
67
|
+
return ::ActiveMerchant::Billing::Response.new(true, 'Skipped Gateway call') if Utils.normalized(arg, :skip_gw)
|
64
68
|
end
|
65
69
|
end
|
66
70
|
|
67
71
|
@gateway.send(m, *args, &block)
|
72
|
+
rescue ::ActiveMerchant::ConnectionError => e
|
73
|
+
# Need to unwrap it
|
74
|
+
until e.triggering_exception.nil?
|
75
|
+
e = e.triggering_exception
|
76
|
+
break unless e.is_a?(::ActiveMerchant::ConnectionError)
|
77
|
+
end
|
78
|
+
handle_exception(e, options)
|
79
|
+
rescue => e
|
80
|
+
handle_exception(e, options)
|
68
81
|
end
|
69
82
|
|
70
83
|
def respond_to?(method, include_private=false)
|
71
84
|
@gateway.respond_to?(method, include_private) || super
|
72
85
|
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
UNKNOWN_CONNECTION_ERRORS = [
|
90
|
+
# Corrupted stream (e.g. Zlib::BufError)
|
91
|
+
::ActiveMerchant::InvalidResponseError,
|
92
|
+
# We attempted a payment, but the gateway replied >= 300. This is gateway specific, hopefully the individual
|
93
|
+
# gateway implementation knows how to rescue from it and this is not risen
|
94
|
+
::ActiveMerchant::ResponseError,
|
95
|
+
# Should not be risen directly
|
96
|
+
::ActiveMerchant::RetriableConnectionError,
|
97
|
+
::ActiveMerchant::ActiveMerchantError
|
98
|
+
]
|
99
|
+
|
100
|
+
PROBABLY_UNKNOWN_CONNECTION_ERRORS = [
|
101
|
+
EOFError,
|
102
|
+
Errno::ECONNRESET,
|
103
|
+
Timeout::Error,
|
104
|
+
Errno::ETIMEDOUT
|
105
|
+
]
|
106
|
+
|
107
|
+
SAFE_CONNECTION_ERRORS = [
|
108
|
+
SocketError,
|
109
|
+
Errno::EHOSTUNREACH,
|
110
|
+
Errno::ECONNREFUSED,
|
111
|
+
::OpenSSL::SSL::SSLError,
|
112
|
+
# Invalid certificate (e.g. OpenSSL::X509::CertificateError)
|
113
|
+
::ActiveMerchant::ClientCertificateError
|
114
|
+
]
|
115
|
+
|
116
|
+
# See https://github.com/killbill/killbill-plugin-framework-ruby/issues/44
|
117
|
+
def handle_exception(e, options = {})
|
118
|
+
message = "#{e.class} #{e.message}"
|
119
|
+
|
120
|
+
if SAFE_CONNECTION_ERRORS.include?(e.class)
|
121
|
+
# Easy case: we didn't attempt the payment
|
122
|
+
@logger.warn("Connection error with the gateway: #{message}")
|
123
|
+
payment_plugin_status = :CANCELED
|
124
|
+
else
|
125
|
+
# For anything else, tell Kill Bill we don't know. If the gateway supports retrieving a payment status,
|
126
|
+
# the plugin should implement get_payment_info accordingly for the Janitor.
|
127
|
+
# Otherwise, the transaction will need to be fixed manually using the admin APIs.
|
128
|
+
|
129
|
+
# Note that PROBABLY_UNKNOWN_CONNECTION_ERRORS/UNKNOWN_CONNECTION_ERRORS are a bit _better_, as they can be expected and we don't have any control over them.
|
130
|
+
# Any other exception might be caused by a bug in our code!
|
131
|
+
if PROBABLY_UNKNOWN_CONNECTION_ERRORS.include?(e.class) || UNKNOWN_CONNECTION_ERRORS.include?(e.class)
|
132
|
+
@logger.warn("Unstable connection with the gateway: #{message}")
|
133
|
+
else
|
134
|
+
@logger.warn("Unexpected exception: #{message}")
|
135
|
+
end
|
136
|
+
|
137
|
+
# Allow clients to force a PLUGIN_FAILURE instead of UNKNOWN (the default is a conservative behavior)
|
138
|
+
payment_plugin_status = Utils.normalized(options, :connection_errors_safe) ? :CANCELED : :UNDEFINED
|
139
|
+
end
|
140
|
+
|
141
|
+
response_message = { :exception_class => e.class.to_s, :exception_message => e.message, :payment_plugin_status => payment_plugin_status }.to_json
|
142
|
+
::ActiveMerchant::Billing::Response.new(false, response_message)
|
143
|
+
end
|
73
144
|
end
|
74
145
|
end
|
75
146
|
end
|
@@ -406,7 +406,7 @@ module Killbill
|
|
406
406
|
end
|
407
407
|
|
408
408
|
# Filter before all gateways call
|
409
|
-
before_gateways(kb_transaction, last_transaction, payment_source, amount_in_cents, currency, options)
|
409
|
+
before_gateways(kb_transaction, last_transaction, payment_source, amount_in_cents, currency, options, context)
|
410
410
|
|
411
411
|
# Dispatch to the gateways. In most cases (non split settlements), we only dispatch to a single gateway account
|
412
412
|
gw_responses = []
|
@@ -424,14 +424,14 @@ module Killbill
|
|
424
424
|
gateway = lookup_gateway(payment_processor_account_id, context.tenant_id)
|
425
425
|
|
426
426
|
# Filter before each gateway call
|
427
|
-
before_gateway(gateway, kb_transaction, last_transaction, payment_source, amount_in_cents, currency, options)
|
427
|
+
before_gateway(gateway, kb_transaction, last_transaction, payment_source, amount_in_cents, currency, options, context)
|
428
428
|
|
429
429
|
# Perform the operation in the gateway
|
430
430
|
gw_response = gateway_call_proc.call(gateway, linked_transaction, payment_source, amount_in_cents, options)
|
431
431
|
response, transaction = save_response_and_transaction(gw_response, operation, kb_account_id, context.tenant_id, payment_processor_account_id, kb_payment_id, kb_payment_transaction_id, operation.upcase, amount_in_cents, currency)
|
432
432
|
|
433
433
|
# Filter after each gateway call
|
434
|
-
after_gateway(response, transaction, gw_response)
|
434
|
+
after_gateway(response, transaction, gw_response, context)
|
435
435
|
|
436
436
|
gw_responses << gw_response
|
437
437
|
responses << response
|
@@ -439,7 +439,7 @@ module Killbill
|
|
439
439
|
end
|
440
440
|
|
441
441
|
# Filter after all gateways call
|
442
|
-
after_gateways(responses, transactions, gw_responses)
|
442
|
+
after_gateways(responses, transactions, gw_responses, context)
|
443
443
|
|
444
444
|
# Merge data
|
445
445
|
merge_transaction_info_plugins(payment_processor_account_ids, responses, transactions)
|
@@ -453,18 +453,22 @@ module Killbill
|
|
453
453
|
kb_transaction
|
454
454
|
end
|
455
455
|
|
456
|
-
|
456
|
+
# Default nil value for context only for backward compatibility (Kill Bill 0.14.0)
|
457
|
+
def before_gateways(kb_transaction, last_transaction, payment_source, amount_in_cents, currency, options, context = nil)
|
457
458
|
end
|
458
459
|
|
459
|
-
|
460
|
+
# Default nil value for context only for backward compatibility (Kill Bill 0.14.0)
|
461
|
+
def after_gateways(response, transaction, gw_response, context = nil)
|
460
462
|
end
|
461
463
|
|
462
|
-
|
464
|
+
# Default nil value for context only for backward compatibility (Kill Bill 0.14.0)
|
465
|
+
def before_gateway(gateway, kb_transaction, last_transaction, payment_source, amount_in_cents, currency, options, context = nil)
|
463
466
|
# Can be used to implement idempotency for example: lookup the payment in the gateway
|
464
467
|
# and pass options[:skip_gw] if the payment has already been through
|
465
468
|
end
|
466
469
|
|
467
|
-
|
470
|
+
# Default nil value for context only for backward compatibility (Kill Bill 0.14.0)
|
471
|
+
def after_gateway(response, transaction, gw_response, context = nil)
|
468
472
|
end
|
469
473
|
|
470
474
|
def to_cents(amount, currency)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'killbill/plugin'
|
2
|
+
|
3
|
+
module Killbill
|
4
|
+
module Plugin
|
5
|
+
class CatalogPluginApi < Notification
|
6
|
+
|
7
|
+
class OperationUnsupported < NotImplementedError
|
8
|
+
end
|
9
|
+
|
10
|
+
def get_versioned_plugin_catalog(properties, context)
|
11
|
+
raise OperationUnsupported
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'killbill/plugin'
|
2
|
+
|
3
|
+
module Killbill
|
4
|
+
module Plugin
|
5
|
+
class PaymentRoutingPluginApi < Notification
|
6
|
+
|
7
|
+
class OperationUnsupportedByGatewayError < NotImplementedError
|
8
|
+
end
|
9
|
+
|
10
|
+
def prior_call(routing_context, properties)
|
11
|
+
raise OperationUnsupportedByGatewayError
|
12
|
+
end
|
13
|
+
|
14
|
+
def on_success_call(routing_context, properties)
|
15
|
+
raise OperationUnsupportedByGatewayError
|
16
|
+
end
|
17
|
+
|
18
|
+
def on_failure_call(routing_context, properties)
|
19
|
+
raise OperationUnsupportedByGatewayError
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -19,6 +19,16 @@ describe Killbill::Plugin::ActiveMerchant do
|
|
19
19
|
do_initialize_with_config_path!(nil)
|
20
20
|
end
|
21
21
|
|
22
|
+
# See https://github.com/killbill/killbill-plugin-framework-ruby/issues/46
|
23
|
+
it 'handles gracefully mis-configurations' do
|
24
|
+
do_initialize_without_config(Proc.new { |config| config[:login] })
|
25
|
+
|
26
|
+
do_common_checks
|
27
|
+
|
28
|
+
gw = ::Killbill::Plugin::ActiveMerchant.gateways(call_context.tenant_id)
|
29
|
+
gw.should be_empty
|
30
|
+
end
|
31
|
+
|
22
32
|
it 'should support multi-tenancy configurations' do
|
23
33
|
do_initialize!(<<-eos)
|
24
34
|
:login: admin
|
@@ -211,17 +221,21 @@ describe Killbill::Plugin::ActiveMerchant do
|
|
211
221
|
end
|
212
222
|
end
|
213
223
|
|
214
|
-
def
|
224
|
+
def do_initialize_without_config(gw_builder = Proc.new { |config| config })
|
225
|
+
do_initialize_with_config_path!(nil, gw_builder, svcs_with_per_tenant_config(':nothing:'))
|
226
|
+
end
|
227
|
+
|
228
|
+
def do_initialize_with_config_path!(path = nil, gw_builder = Proc.new { |config| config }, per_tenant_config = svcs_with_per_tenant_config)
|
215
229
|
::Killbill::Plugin::ActiveMerchant.initialize!(gw_builder,
|
216
230
|
:test,
|
217
231
|
logger,
|
218
232
|
:KEY,
|
219
233
|
path,
|
220
|
-
::Killbill::Plugin::KillbillApi.new('test',
|
234
|
+
::Killbill::Plugin::KillbillApi.new('test', per_tenant_config))
|
221
235
|
end
|
222
236
|
|
223
|
-
def svcs_with_per_tenant_config
|
224
|
-
per_tenant_config
|
237
|
+
def svcs_with_per_tenant_config(per_tenant_config = nil)
|
238
|
+
per_tenant_config ||=<<-oes
|
225
239
|
:test:
|
226
240
|
:login: admin2
|
227
241
|
:password: password2
|
@@ -311,16 +311,14 @@ describe Killbill::Plugin::ActiveMerchant::PaymentPlugin do
|
|
311
311
|
end
|
312
312
|
|
313
313
|
context 'with a dummy gateway' do
|
314
|
-
let(:gateway) {
|
314
|
+
let(:gateway) { plugin.lookup_gateway(:default, @call_context.tenant_id) }
|
315
315
|
|
316
316
|
let(:plugin) do
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
plugin.kb_apis = kb_apis
|
323
|
-
plugin.logger = logger
|
317
|
+
jplugin.delegate_plugin
|
318
|
+
end
|
319
|
+
|
320
|
+
let(:jplugin) do
|
321
|
+
ze_jplugin = nil
|
324
322
|
|
325
323
|
plugin_config = {
|
326
324
|
:test => [
|
@@ -328,14 +326,20 @@ describe Killbill::Plugin::ActiveMerchant::PaymentPlugin do
|
|
328
326
|
]
|
329
327
|
}
|
330
328
|
with_plugin_yaml_config('test.yml', plugin_config) do |file|
|
331
|
-
|
332
|
-
|
329
|
+
ze_jplugin = ::Killbill::Plugin::Api::PaymentPluginApi.new('DummyRecordingGatewayPlugin',
|
330
|
+
{
|
331
|
+
'payment_api' => payment_api,
|
332
|
+
'tenant_user_api' => tenant_api,
|
333
|
+
'logger' => logger,
|
334
|
+
'conf_dir' => File.dirname(file),
|
335
|
+
'root' => File.dirname(file)
|
336
|
+
})
|
333
337
|
|
334
338
|
# Start the plugin here - since the config file will be deleted
|
335
|
-
|
339
|
+
ze_jplugin.start_plugin
|
336
340
|
end
|
337
341
|
|
338
|
-
|
342
|
+
ze_jplugin
|
339
343
|
end
|
340
344
|
|
341
345
|
after(:each) do
|
@@ -347,51 +351,46 @@ describe Killbill::Plugin::ActiveMerchant::PaymentPlugin do
|
|
347
351
|
end
|
348
352
|
|
349
353
|
it 'sets the kb_payment_transaction_id as order_id by default' do
|
350
|
-
|
351
|
-
|
352
|
-
kb_payment_transaction_id = SecureRandom.uuid
|
353
|
-
plugin.purchase_payment(@kb_account_id, @kb_payment_id, kb_payment_transaction_id, @kb_payment_method_id, @amount_in_cents, @currency, [], @call_context)
|
354
|
+
ptip = trigger_purchase
|
354
355
|
|
355
356
|
sent_options = gateway.call_stack[-1][:options]
|
356
357
|
sent_options.size.should == 11
|
357
358
|
sent_options[:currency].should == @currency
|
358
|
-
sent_options[:description].should == "Kill Bill purchase for #{
|
359
|
-
sent_options[:order_id].should ==
|
359
|
+
sent_options[:description].should == "Kill Bill purchase for #{ptip.kb_transaction_payment_id}"
|
360
|
+
sent_options[:order_id].should == ptip.kb_transaction_payment_id
|
360
361
|
end
|
361
362
|
|
362
363
|
it 'sets the kb_payment_transaction_id as order_id if specified' do
|
363
|
-
plugin.add_payment_method(@kb_account_id, @kb_payment_method_id, @payment_method_props, true, [], @call_context)
|
364
|
-
|
365
364
|
property = ::Killbill::Plugin::Model::PluginProperty.new
|
366
365
|
property.key = 'external_key_as_order_id'
|
367
366
|
property.value = 'false'
|
367
|
+
properties = [property]
|
368
368
|
|
369
|
-
|
370
|
-
plugin.purchase_payment(@kb_account_id, @kb_payment_id, kb_payment_transaction_id, @kb_payment_method_id, @amount_in_cents, @currency, [property], @call_context)
|
369
|
+
ptip = trigger_purchase(properties)
|
371
370
|
|
372
371
|
sent_options = gateway.call_stack[-1][:options]
|
373
372
|
sent_options.size.should == 12
|
374
373
|
sent_options[:currency].should == @currency
|
375
|
-
sent_options[:description].should == "Kill Bill purchase for #{
|
376
|
-
sent_options[:order_id].should ==
|
374
|
+
sent_options[:description].should == "Kill Bill purchase for #{ptip.kb_transaction_payment_id}"
|
375
|
+
sent_options[:order_id].should == ptip.kb_transaction_payment_id
|
377
376
|
end
|
378
377
|
|
379
378
|
it 'sets the payment transaction external key as order_id if specified' do
|
380
|
-
plugin.add_payment_method(@kb_account_id, @kb_payment_method_id, @payment_method_props, true, [], @call_context)
|
381
|
-
|
382
379
|
property = ::Killbill::Plugin::Model::PluginProperty.new
|
383
380
|
property.key = 'external_key_as_order_id'
|
384
381
|
property.value = 'true'
|
382
|
+
properties = [property]
|
385
383
|
|
386
384
|
kb_payment_transaction_id = SecureRandom.uuid
|
387
385
|
kb_payment_transaction_external_key = SecureRandom.uuid
|
388
|
-
payment_api.add_payment(@kb_payment_id,
|
389
|
-
|
386
|
+
payment_api.add_payment(@kb_payment_id, kb_payment_transaction_id, kb_payment_transaction_external_key, :PURCHASE)
|
387
|
+
|
388
|
+
ptip = trigger_purchase(properties, kb_payment_transaction_id)
|
390
389
|
|
391
390
|
sent_options = gateway.call_stack[-1][:options]
|
392
391
|
sent_options.size.should == 12
|
393
392
|
sent_options[:currency].should == @currency
|
394
|
-
sent_options[:description].should == "Kill Bill purchase for #{
|
393
|
+
sent_options[:description].should == "Kill Bill purchase for #{ptip.kb_transaction_payment_id}"
|
395
394
|
sent_options[:order_id].should == kb_payment_transaction_external_key
|
396
395
|
end
|
397
396
|
|
@@ -403,47 +402,152 @@ describe Killbill::Plugin::ActiveMerchant::PaymentPlugin do
|
|
403
402
|
|
404
403
|
::ActiveRecord::Base.connection_pool.active_connection?.should == false
|
405
404
|
end
|
405
|
+
|
406
|
+
# Regression tests for the Kill Bill API conventions
|
407
|
+
# TODO Go through Java generated code
|
408
|
+
|
409
|
+
it 'returns ERROR if the payment transaction went through but failed' do
|
410
|
+
gateway.next_success = false
|
411
|
+
|
412
|
+
# Verify the purchase call for the Kill Bill payment state machine and the get_payment_info call for the Janitor
|
413
|
+
ptip = trigger_purchase
|
414
|
+
verify_purchase_status(ptip, :ERROR)
|
415
|
+
|
416
|
+
# Check debugging fields
|
417
|
+
ptip.gateway_error.should == 'false'
|
418
|
+
end
|
419
|
+
|
420
|
+
it 'returns UNDEFINED for plugin bugs' do
|
421
|
+
gateway.next_exception = NoMethodError.new("undefined method `split' for 12:Fixnum")
|
422
|
+
|
423
|
+
# Verify the purchase call for the Kill Bill payment state machine and the get_payment_info call for the Janitor
|
424
|
+
ptip = trigger_purchase
|
425
|
+
verify_purchase_status(ptip, :UNDEFINED)
|
426
|
+
|
427
|
+
# Check debugging fields
|
428
|
+
ptip.gateway_error.should == "undefined method `split' for 12:Fixnum"
|
429
|
+
end
|
430
|
+
|
431
|
+
# Specific ActiveMerchant errors handling
|
432
|
+
# See https://github.com/Shopify/active_merchant/blob/2e7eebe38020db4d262b91778797910ede2f31be/lib/active_merchant/network_connection_retries.rb#L21-L34
|
433
|
+
|
434
|
+
it 'returns CANCELED if the payment was not attempted' do
|
435
|
+
{
|
436
|
+
Errno::ECONNREFUSED => 'The remote server refused the connection',
|
437
|
+
SocketError => 'The connection to the remote server could not be established',
|
438
|
+
Errno::EHOSTUNREACH => 'The connection to the remote server could not be established',
|
439
|
+
OpenSSL::SSL::SSLError => 'The SSL connection to the remote server could not be established',
|
440
|
+
::ActiveMerchant::ClientCertificateError => 'The remote server did not accept the provided SSL certificate'
|
441
|
+
}.each do |ek, msg|
|
442
|
+
gateway.next_exception = ::ActiveMerchant::ConnectionError.new(msg, ek.new(msg))
|
443
|
+
|
444
|
+
# Verify the purchase call for the Kill Bill payment state machine and the get_payment_info call for the Janitor
|
445
|
+
ptip = trigger_purchase
|
446
|
+
verify_purchase_status(ptip, :CANCELED)
|
447
|
+
|
448
|
+
# Check debugging fields
|
449
|
+
ptip.gateway_error.ends_with?(msg).should be_true
|
450
|
+
ptip.gateway_error_code.should == ek.to_s
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
it 'returns UNDEFINED if we are not sure' do
|
455
|
+
{
|
456
|
+
EOFError => 'The remote server dropped the connection',
|
457
|
+
Errno::ECONNRESET => 'The remote server reset the connection',
|
458
|
+
Timeout::Error => 'The connection to the remote server timed out',
|
459
|
+
Errno::ETIMEDOUT => 'The connection to the remote server timed out',
|
460
|
+
::ActiveMerchant::InvalidResponseError => 'The remote server replied with an invalid response'
|
461
|
+
}.each do |ek, msg|
|
462
|
+
gateway.next_exception = ::ActiveMerchant::ConnectionError.new(msg, ek.new(msg))
|
463
|
+
|
464
|
+
# Verify the purchase call for the Kill Bill payment state machine and the get_payment_info call for the Janitor
|
465
|
+
ptip = trigger_purchase
|
466
|
+
verify_purchase_status(ptip, :UNDEFINED)
|
467
|
+
|
468
|
+
# Check debugging fields
|
469
|
+
ptip.gateway_error.ends_with?(msg).should be_true
|
470
|
+
ptip.gateway_error_code.should == ek.to_s
|
471
|
+
end
|
472
|
+
end
|
406
473
|
end
|
407
474
|
|
408
475
|
private
|
409
476
|
|
410
|
-
def
|
477
|
+
def trigger_purchase(purchase_properties=[], kb_payment_transaction_id=SecureRandom.uuid)
|
478
|
+
plugin.get_payment_method_detail(@kb_account_id, @kb_payment_method_id, [], @call_context) rescue plugin.add_payment_method(@kb_account_id, @kb_payment_method_id, @payment_method_props, true, [], @call_context)
|
479
|
+
plugin.purchase_payment(@kb_account_id, @kb_payment_id, kb_payment_transaction_id, @kb_payment_method_id, @amount_in_cents, @currency, purchase_properties, @call_context)
|
480
|
+
end
|
481
|
+
|
482
|
+
def verify_purchase_status(t_info_plugin, status)
|
483
|
+
verify_transaction_info_plugin(t_info_plugin, t_info_plugin.kb_transaction_payment_id, :PURCHASE, nil, 'default', status)
|
484
|
+
end
|
485
|
+
|
486
|
+
def verify_transaction_info_plugin(t_info_plugin, kb_transaction_id, type, transaction_nb, payment_processor_account_id='default', status=:PROCESSED)
|
411
487
|
t_info_plugin.kb_payment_id.should == @kb_payment_id
|
412
488
|
t_info_plugin.kb_transaction_payment_id.should == kb_transaction_id
|
413
489
|
t_info_plugin.transaction_type.should == type
|
414
|
-
if type == :VOID
|
490
|
+
if type == :VOID || status != :PROCESSED
|
415
491
|
t_info_plugin.amount.should be_nil
|
416
492
|
t_info_plugin.currency.should be_nil
|
417
493
|
else
|
418
494
|
t_info_plugin.amount.should == @amount_in_cents
|
419
495
|
t_info_plugin.currency.should == @currency
|
420
496
|
end
|
421
|
-
t_info_plugin.status.should ==
|
497
|
+
t_info_plugin.status.should == status
|
422
498
|
|
423
499
|
# Verify we routed to the right gateway
|
424
500
|
(t_info_plugin.properties.find { |kv| kv.key.to_s == 'payment_processor_account_id' }).value.to_s.should == payment_processor_account_id
|
425
501
|
|
426
502
|
transactions = plugin.get_payment_info(@kb_account_id, @kb_payment_id, [], @call_context)
|
427
|
-
transactions.size.should == transaction_nb
|
428
|
-
transactions[
|
503
|
+
transactions.size.should == transaction_nb unless transaction_nb.nil?
|
504
|
+
transactions[-1].to_json.should == t_info_plugin.to_json
|
505
|
+
end
|
506
|
+
|
507
|
+
class DummyRecordingGatewayPlugin < ::Killbill::Plugin::ActiveMerchant::PaymentPlugin
|
508
|
+
|
509
|
+
def initialize
|
510
|
+
super(Proc.new { |config| DummyRecordingGateway.new },
|
511
|
+
:test,
|
512
|
+
::Killbill::Test::TestPaymentMethod,
|
513
|
+
::Killbill::Test::TestTransaction,
|
514
|
+
::Killbill::Test::TestResponse)
|
515
|
+
end
|
429
516
|
end
|
430
517
|
|
431
518
|
class DummyRecordingGateway < ::ActiveMerchant::Billing::Gateway
|
432
519
|
|
433
520
|
attr_reader :call_stack
|
521
|
+
attr_writer :next_success, :next_exception
|
434
522
|
|
435
523
|
def initialize
|
436
524
|
@call_stack = []
|
525
|
+
@next_success = true
|
526
|
+
@next_exception = nil
|
437
527
|
end
|
438
528
|
|
439
529
|
def purchase(money, paysource, options = {})
|
530
|
+
success = before_purchase
|
440
531
|
@call_stack << {:money => money, :source => paysource, :options => options}
|
441
|
-
::ActiveMerchant::Billing::Response.new(
|
532
|
+
::ActiveMerchant::Billing::Response.new(success, success.to_s, {:authorized_amount => money}, :test => true, :authorization => '12345')
|
442
533
|
end
|
443
534
|
|
444
535
|
def store(paysource, options = {})
|
445
536
|
@call_stack << {:source => paysource, :options => options}
|
446
|
-
::ActiveMerchant::Billing::Response.new(true, 'Success!', {:billingid => '1'}, :test => true, :authorization => 12345)
|
537
|
+
::ActiveMerchant::Billing::Response.new(true, 'Success!', {:billingid => '1'}, :test => true, :authorization => '12345')
|
538
|
+
end
|
539
|
+
|
540
|
+
# Testing helpers
|
541
|
+
|
542
|
+
def before_purchase
|
543
|
+
unless @next_exception.nil?
|
544
|
+
e = @next_exception
|
545
|
+
@next_exception = nil
|
546
|
+
raise e
|
547
|
+
end
|
548
|
+
s = @next_success
|
549
|
+
@next_success = true
|
550
|
+
s
|
447
551
|
end
|
448
552
|
end
|
449
553
|
end
|