killbill 4.0.0 → 4.1.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 +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
|