killbill 3.2.4 → 4.0.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.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -2
  3. data/.travis.yml +26 -5
  4. data/Gemfile +1 -1
  5. data/Gemfile.head +5 -0
  6. data/Gemfile.lock +125 -0
  7. data/Jarfile +9 -9
  8. data/Jarfile.lock +54 -0
  9. data/NEWS +3 -0
  10. data/README.md +13 -0
  11. data/VERSION +1 -1
  12. data/generators/active_merchant/templates/.gitignore.rb +2 -3
  13. data/generators/active_merchant/templates/.travis.yml.rb +22 -3
  14. data/generators/active_merchant/templates/Gemfile.head.rb +5 -0
  15. data/generators/active_merchant/templates/Gemfile.rb +1 -1
  16. data/generators/active_merchant/templates/Jarfile.rb +9 -8
  17. data/generators/active_merchant/templates/LICENSE.rb +2 -2
  18. data/generators/active_merchant/templates/config.yml.rb +16 -6
  19. data/generators/active_merchant/templates/lib/plugin.rb +0 -1
  20. data/generators/active_merchant/templates/plugin.gemspec.rb +17 -15
  21. data/generators/active_merchant/templates/pom.xml.rb +1 -1
  22. data/generators/active_merchant/templates/release.sh.rb +34 -14
  23. data/generators/active_merchant/templates/spec/base_plugin_spec.rb +5 -7
  24. data/generators/active_merchant/templates/spec/integration_spec.rb +7 -18
  25. data/killbill.gemspec +21 -15
  26. data/lib/killbill/ext/active_merchant/typhoeus_connection.rb +3 -4
  27. data/lib/killbill/gen/api/account.rb +1 -1
  28. data/lib/killbill/gen/api/account_data.rb +1 -1
  29. data/lib/killbill/gen/api/audit_log.rb +2 -2
  30. data/lib/killbill/gen/api/audit_user_api.rb +3 -3
  31. data/lib/killbill/gen/api/block.rb +1 -1
  32. data/lib/killbill/gen/api/blocking_state.rb +1 -1
  33. data/lib/killbill/gen/api/call_context.rb +2 -2
  34. data/lib/killbill/gen/api/control_tag.rb +2 -2
  35. data/lib/killbill/gen/api/currency_conversion.rb +1 -1
  36. data/lib/killbill/gen/api/currency_conversion_api.rb +2 -2
  37. data/lib/killbill/gen/api/custom_field.rb +1 -1
  38. data/lib/killbill/gen/api/custom_field_user_api.rb +2 -2
  39. data/lib/killbill/gen/api/dry_run_arguments.rb +22 -3
  40. data/lib/killbill/gen/api/duration.rb +1 -1
  41. data/lib/killbill/gen/api/entitlement.rb +3 -3
  42. data/lib/killbill/gen/api/entitlement_ao_status_dry_run.rb +3 -3
  43. data/lib/killbill/gen/api/entitlement_api.rb +25 -7
  44. data/lib/killbill/gen/api/fixed.rb +1 -1
  45. data/lib/killbill/gen/api/invoice.rb +1 -1
  46. data/lib/killbill/gen/api/invoice_formatter.rb +2 -2
  47. data/lib/killbill/gen/api/invoice_item.rb +2 -2
  48. data/lib/killbill/gen/api/invoice_item_formatter.rb +2 -2
  49. data/lib/killbill/gen/api/invoice_payment.rb +3 -3
  50. data/lib/killbill/gen/api/invoice_user_api.rb +2 -2
  51. data/lib/killbill/gen/api/migration_plan.rb +1 -1
  52. data/lib/killbill/gen/api/mutable_account_data.rb +1 -1
  53. data/lib/killbill/gen/api/payment.rb +1 -1
  54. data/lib/killbill/gen/api/payment_api.rb +11 -11
  55. data/lib/killbill/gen/api/payment_transaction.rb +4 -4
  56. data/lib/killbill/gen/api/plan.rb +1 -1
  57. data/lib/killbill/gen/api/plan_change_result.rb +2 -2
  58. data/lib/killbill/gen/api/plan_phase.rb +1 -1
  59. data/lib/killbill/gen/api/plan_phase_price_override.rb +93 -0
  60. data/lib/killbill/gen/api/plan_phase_price_overrides_with_call_context.rb +77 -0
  61. data/lib/killbill/gen/api/plan_phase_specifier.rb +3 -3
  62. data/lib/killbill/gen/api/plan_specifier.rb +2 -2
  63. data/lib/killbill/gen/api/price.rb +1 -1
  64. data/lib/killbill/gen/api/product.rb +1 -1
  65. data/lib/killbill/gen/api/rate.rb +2 -2
  66. data/lib/killbill/gen/api/record_id_api.rb +1 -1
  67. data/lib/killbill/gen/api/recurring.rb +1 -1
  68. data/lib/killbill/gen/api/refund.rb +2 -2
  69. data/lib/killbill/gen/api/require_gen.rb +2 -0
  70. data/lib/killbill/gen/api/static_catalog.rb +2 -2
  71. data/lib/killbill/gen/api/subscription.rb +3 -3
  72. data/lib/killbill/gen/api/subscription_event.rb +3 -3
  73. data/lib/killbill/gen/api/tag.rb +1 -1
  74. data/lib/killbill/gen/api/tag_definition.rb +1 -1
  75. data/lib/killbill/gen/api/tag_user_api.rb +6 -6
  76. data/lib/killbill/gen/api/tiered_block.rb +1 -1
  77. data/lib/killbill/gen/api/usage.rb +3 -3
  78. data/lib/killbill/gen/plugin-api/currency_plugin_api.rb +1 -1
  79. data/lib/killbill/gen/plugin-api/ext_bus_event.rb +2 -2
  80. data/lib/killbill/gen/plugin-api/payment_transaction_info_plugin.rb +3 -3
  81. data/lib/killbill/helpers/active_merchant.rb +2 -2
  82. data/lib/killbill/helpers/active_merchant/active_record.rb +1 -1
  83. data/lib/killbill/helpers/active_merchant/active_record/active_record_helper.rb +14 -0
  84. data/lib/killbill/helpers/active_merchant/active_record/models/helpers.rb +8 -0
  85. data/lib/killbill/helpers/active_merchant/active_record/models/payment_method.rb +10 -8
  86. data/lib/killbill/helpers/active_merchant/active_record/models/response.rb +16 -3
  87. data/lib/killbill/helpers/active_merchant/active_record/models/streamy_result_set.rb +1 -3
  88. data/lib/killbill/helpers/active_merchant/active_record/models/transaction.rb +2 -0
  89. data/lib/killbill/helpers/active_merchant/configuration.rb +119 -27
  90. data/lib/killbill/helpers/active_merchant/gateway.rb +40 -27
  91. data/lib/killbill/helpers/active_merchant/killbill_spec_helper.rb +63 -45
  92. data/lib/killbill/helpers/active_merchant/payment_plugin.rb +121 -95
  93. data/lib/killbill/helpers/active_merchant/private_payment_plugin.rb +1 -1
  94. data/lib/killbill/helpers/active_merchant/properties.rb +9 -2
  95. data/lib/killbill/helpers/active_merchant/sinatra.rb +4 -8
  96. data/lib/killbill/helpers/active_merchant/utils.rb +44 -0
  97. data/lib/killbill/helpers/properties_helper.rb +41 -0
  98. data/lib/killbill/http_servlet.rb +11 -4
  99. data/lib/killbill/killbill_api.rb +6 -4
  100. data/lib/killbill/rake_task.rb +542 -102
  101. data/spec/killbill/helpers/configuration_spec.rb +117 -33
  102. data/spec/killbill/helpers/payment_method_spec.rb +20 -17
  103. data/spec/killbill/helpers/payment_plugin_spec.rb +401 -201
  104. data/spec/killbill/helpers/private_payment_plugin_spec.rb +3 -16
  105. data/spec/killbill/helpers/response_spec.rb +92 -22
  106. data/spec/killbill/helpers/streamy_result_set_spec.rb +3 -2
  107. data/spec/killbill/helpers/test_payment_method.rb +9 -0
  108. data/spec/killbill/helpers/test_response.rb +11 -0
  109. data/spec/killbill/helpers/test_schema.rb +6 -6
  110. data/spec/killbill/helpers/test_transaction.rb +11 -0
  111. data/spec/killbill/helpers/transaction_spec.rb +3 -13
  112. data/spec/killbill/helpers/utils_spec.rb +127 -69
  113. data/spec/spec_helper.rb +69 -18
  114. metadata +77 -71
  115. data/lib/killbill/ext/active_merchant/jdbc_connection.rb +0 -92
  116. data/lib/killbill/ext/active_merchant/proxy_support.rb +0 -58
  117. data/spec/killbill/helpers/gateway_spec.rb +0 -36
@@ -4,30 +4,7 @@ module Killbill
4
4
  require 'active_merchant'
5
5
 
6
6
  class Gateway
7
- def self.wrap(gateway_builder, logger, config)
8
- ::ActiveMerchant::Billing::Gateway.logger = logger
9
-
10
- if config[:test]
11
- ::ActiveMerchant::Billing::Base.mode = :test
12
- end
13
-
14
- if config[:log_file]
15
- ::ActiveMerchant::Billing::Gateway.wiredump_device = File.open(config[:log_file], 'w')
16
- else
17
- log_method = config[:quiet] ? :debug : :info
18
- ::ActiveMerchant::Billing::Gateway.wiredump_device = ::Killbill::Plugin::ActiveMerchant::Utils::KBWiredumpDevice.new(logger, log_method)
19
- end
20
- ::ActiveMerchant::Billing::Gateway.wiredump_device.sync = true
21
-
22
- ::ActiveMerchant::Billing::Gateway.open_timeout = config[:open_timeout] unless config[:open_timeout].nil?
23
- ::ActiveMerchant::Billing::Gateway.read_timeout = config[:read_timeout] unless config[:read_timeout].nil?
24
- ::ActiveMerchant::Billing::Gateway.retry_safe = config[:retry_safe] unless config[:retry_safe].nil?
25
- ::ActiveMerchant::Billing::Gateway.ssl_strict = config[:ssl_strict] unless config[:ssl_strict].nil?
26
- ::ActiveMerchant::Billing::Gateway.ssl_version = config[:ssl_version] unless config[:ssl_version].nil?
27
- ::ActiveMerchant::Billing::Gateway.max_retries = config[:max_retries] unless config[:max_retries].nil?
28
- ::ActiveMerchant::Billing::Gateway.proxy_address = config[:proxy_address] unless config[:proxy_address].nil?
29
- ::ActiveMerchant::Billing::Gateway.proxy_port = config[:proxy_port] unless config[:proxy_port].nil?
30
-
7
+ def self.wrap(gateway_builder, config)
31
8
  Gateway.new(config, gateway_builder.call(config))
32
9
  end
33
10
 
@@ -36,17 +13,53 @@ module Killbill
36
13
  def initialize(config, am_gateway)
37
14
  @config = config
38
15
  @gateway = am_gateway
16
+
17
+ # Override urls if needed (there is no easy way to do it earlier, because AM uses class_attribute)
18
+ @gateway.class.test_url = @config[:test_url] unless @config[:test_url].nil?
19
+ @gateway.class.live_url = @config[:live_url] unless @config[:live_url].nil?
20
+ end
21
+
22
+ #
23
+ # Materialize the most common operations - this is for performance reasons
24
+ #
25
+
26
+ def store(*args, &block)
27
+ method_missing(:store, *args, &block)
28
+ end
29
+
30
+ def unstore(*args, &block)
31
+ method_missing(:unstore, *args, &block)
32
+ end
33
+
34
+ def authorize(*args, &block)
35
+ method_missing(:authorize, *args, &block)
39
36
  end
40
37
 
41
38
  # Unfortunate name...
42
- def capture(money, authorization, options = {})
43
- method_missing(:capture, money, authorization, options)
39
+ def capture(*args, &block)
40
+ method_missing(:capture, *args, &block)
41
+ end
42
+
43
+ def purchase(*args, &block)
44
+ method_missing(:purchase, *args, &block)
45
+ end
46
+
47
+ def void(*args, &block)
48
+ method_missing(:void, *args, &block)
49
+ end
50
+
51
+ def credit(*args, &block)
52
+ method_missing(:credit, *args, &block)
53
+ end
54
+
55
+ def refund(*args, &block)
56
+ method_missing(:refund, *args, &block)
44
57
  end
45
58
 
46
59
  def method_missing(m, *args, &block)
47
60
  # The options hash should be the last argument, iterate through all to be safe
48
61
  args.reverse.each do |arg|
49
- if arg.respond_to?(:has_key?) && arg.has_key?(:skip_gw)
62
+ if arg.respond_to?(:has_key?) && Utils.normalized(arg, :skip_gw)
50
63
  return ::ActiveMerchant::Billing::Response.new(true, 'Skipped Gateway call')
51
64
  end
52
65
  end
@@ -2,29 +2,30 @@ module Killbill
2
2
  module Plugin
3
3
  module ActiveMerchant
4
4
  module RSpec
5
+ include ::Killbill::Plugin::PropertiesHelper
5
6
 
6
- def create_payment_method(payment_method_model=::Killbill::Plugin::ActiveMerchant::ActiveRecord::PaymentMethod, kb_account_id=nil, kb_tenant_id=nil, properties = [], options = {})
7
+ def create_payment_method(payment_method_model=::Killbill::Plugin::ActiveMerchant::ActiveRecord::PaymentMethod, kb_account_id=nil, kb_tenant_id=nil, properties = [], options = {}, set_default = true, plugin = @plugin)
7
8
  kb_payment_method_id = SecureRandom.uuid
8
9
 
9
10
  if kb_account_id.nil?
10
11
  kb_account_id = SecureRandom.uuid
11
12
 
12
13
  # Create a new account
13
- create_kb_account kb_account_id
14
+ create_kb_account(kb_account_id, plugin.kb_apis.proxied_services[:account_user_api])
14
15
  end
15
16
 
16
- context = @plugin.kb_apis.create_context(kb_tenant_id)
17
- account = @plugin.kb_apis.account_user_api.get_account_by_id(kb_account_id, context)
17
+ context = plugin.kb_apis.create_context(kb_tenant_id)
18
+ account = plugin.kb_apis.account_user_api.get_account_by_id(kb_account_id, context)
18
19
 
19
20
  # The rest is pure Ruby
20
21
  context = context.to_ruby(context)
21
22
 
22
23
  # Generate a token
23
- pm_properties = build_pm_properties(account, options)
24
+ pm_properties = build_pm_properties(account, options, set_default)
24
25
 
25
26
  info = Killbill::Plugin::Model::PaymentMethodPlugin.new
26
27
  info.properties = pm_properties
27
- payment_method = @plugin.add_payment_method(kb_account_id, kb_payment_method_id, info, true, properties, context)
28
+ payment_method = plugin.add_payment_method(kb_account_id, kb_payment_method_id, info, true, properties, context)
28
29
 
29
30
  pm = payment_method_model.from_kb_payment_method_id(kb_payment_method_id, context.tenant_id)
30
31
  pm.should == payment_method
@@ -34,47 +35,47 @@ module Killbill
34
35
  pm
35
36
  end
36
37
 
37
- def build_pm_properties(account = nil, overrides = {})
38
- cc_number = (overrides.delete(:cc_number) || '4242424242424242')
39
- cc_first_name = (overrides.delete(:cc_first_name) || 'John')
40
- cc_last_name = (overrides.delete(:cc_last_name) || 'Doe')
41
- cc_type = (overrides.delete(:cc_type) || 'Visa')
42
- cc_exp_month = (overrides.delete(:cc_exp_month) || 12)
43
- cc_exp_year = (overrides.delete(:cc_exp_year) || 2017)
44
- cc_last_4 = (overrides.delete(:cc_last_4) || 4242)
45
- address1 = (overrides.delete(:address1) || '5, oakriu road')
46
- address2 = (overrides.delete(:address2) || 'apt. 298')
47
- city = (overrides.delete(:city) || 'Gdio Foia')
48
- state = (overrides.delete(:state) || 'FL')
49
- zip = (overrides.delete(:zip) || 49302)
50
- country = (overrides.delete(:country) || 'US')
51
- cc_verification_value = (overrides.delete(:cc_verification_value) || 1234)
38
+ def build_pm_properties(account = nil, overrides = {}, set_defaults = true)
39
+ cc_number = (overrides.delete(:cc_number) || (set_defaults ? '4242424242424242' : nil))
40
+ cc_first_name = (overrides.delete(:cc_first_name) || (set_defaults ? 'John' : nil))
41
+ cc_last_name = (overrides.delete(:cc_last_name) || (set_defaults ? 'Doe' : nil))
42
+ cc_type = (overrides.delete(:cc_type) || (set_defaults ? 'Visa' : nil))
43
+ cc_exp_month = (overrides.delete(:cc_exp_month) || (set_defaults ? 12 : nil))
44
+ cc_exp_year = (overrides.delete(:cc_exp_year) || (set_defaults ? 2017 : nil))
45
+ cc_last_4 = (overrides.delete(:cc_last_4) || (set_defaults ? 4242 : nil))
46
+ address1 = (overrides.delete(:address1) || (set_defaults ? '5, oakriu road' : nil))
47
+ address2 = (overrides.delete(:address2) || (set_defaults ? 'apt. 298' : nil))
48
+ city = (overrides.delete(:city) || (set_defaults ? 'Gdio Foia' : nil))
49
+ state = (overrides.delete(:state) || (set_defaults ? 'FL' : nil))
50
+ zip = (overrides.delete(:zip) || (set_defaults ? 49302 : nil))
51
+ country = (overrides.delete(:country) || (set_defaults ? 'US' : nil))
52
+ cc_verification_value = (overrides.delete(:cc_verification_value) || (set_defaults ? 1234 : nil))
52
53
 
53
54
  properties = []
54
- properties << create_pm_kv_info('ccNumber', cc_number)
55
- properties << create_pm_kv_info('ccFirstName', cc_first_name)
56
- properties << create_pm_kv_info('ccLastName', cc_last_name)
57
- properties << create_pm_kv_info('ccType', cc_type)
58
- properties << create_pm_kv_info('ccExpirationMonth', cc_exp_month)
59
- properties << create_pm_kv_info('ccExpirationYear', cc_exp_year)
60
- properties << create_pm_kv_info('ccLast4', cc_last_4)
61
- properties << create_pm_kv_info('email', account.nil? ? Time.now.to_i.to_s + '-test@tester.com' : account.email) # Required by e.g. CyberSource
62
- properties << create_pm_kv_info('address1', address1)
63
- properties << create_pm_kv_info('address2', address2)
64
- properties << create_pm_kv_info('city', city)
65
- properties << create_pm_kv_info('state', state)
66
- properties << create_pm_kv_info('zip', zip)
67
- properties << create_pm_kv_info('country', country)
68
- properties << create_pm_kv_info('ccVerificationValue', cc_verification_value)
55
+ properties << build_property('ccNumber', cc_number)
56
+ properties << build_property('ccFirstName', cc_first_name)
57
+ properties << build_property('ccLastName', cc_last_name)
58
+ properties << build_property('ccType', cc_type)
59
+ properties << build_property('ccExpirationMonth', cc_exp_month)
60
+ properties << build_property('ccExpirationYear', cc_exp_year)
61
+ properties << build_property('ccLast4', cc_last_4)
62
+ properties << build_property('email', account.nil? ? Time.now.to_i.to_s + '-test@tester.com' : account.email) # Required by e.g. CyberSource
63
+ properties << build_property('address1', address1)
64
+ properties << build_property('address2', address2)
65
+ properties << build_property('city', city)
66
+ properties << build_property('state', state)
67
+ properties << build_property('zip', zip)
68
+ properties << build_property('country', country)
69
+ properties << build_property('ccVerificationValue', cc_verification_value)
69
70
 
70
71
  overrides.each do |key, value|
71
- properties << create_pm_kv_info(key, value)
72
+ properties << build_property(key, value)
72
73
  end
73
74
 
74
75
  properties
75
76
  end
76
77
 
77
- def create_kb_account(kb_account_id)
78
+ def create_kb_account(kb_account_id, account_api = @account_api)
78
79
  external_key = Time.now.to_i.to_s + '-test'
79
80
  email = external_key + '@tester.com'
80
81
 
@@ -85,16 +86,33 @@ module Killbill
85
86
  account.name = 'Integration spec'
86
87
  account.currency = :USD
87
88
 
88
- @account_api.accounts << account
89
+ account_api.accounts << account
89
90
 
90
91
  return external_key, kb_account_id
91
92
  end
92
93
 
93
- def create_pm_kv_info(key, value)
94
- prop = ::Killbill::Plugin::Model::PluginProperty.new
95
- prop.key = key
96
- prop.value = value
97
- prop
94
+ def build_call_context(tenant_id = '00000011-0022-0033-0044-000000000055')
95
+ call_context = ::Killbill::Plugin::Model::CallContext.new
96
+ call_context.tenant_id = tenant_id
97
+ call_context.to_ruby(call_context)
98
+ end
99
+
100
+ def build_plugin(model, name, conf_dir = '.')
101
+ plugin = model.new
102
+
103
+ svcs = {
104
+ :account_user_api => ::Killbill::Plugin::ActiveMerchant::RSpec::FakeJavaUserAccountApi.new,
105
+ :payment_api => ::Killbill::Plugin::ActiveMerchant::RSpec::FakeJavaPaymentApi.new,
106
+ :tenant_user_api => ::Killbill::Plugin::ActiveMerchant::RSpec::FakeJavaTenantUserApi.new
107
+ }
108
+ plugin.kb_apis = ::Killbill::Plugin::KillbillApi.new(name, svcs)
109
+
110
+ plugin.logger = ::Logger.new(STDOUT)
111
+ plugin.logger.level = ::Logger::INFO
112
+ plugin.conf_dir = File.expand_path(conf_dir)
113
+ plugin.root = "/foo/killbill-#{name}/0.0.1"
114
+
115
+ plugin
98
116
  end
99
117
 
100
118
  class FakeJavaUserAccountApi
@@ -149,7 +167,7 @@ module Killbill
149
167
 
150
168
  attr_accessor :per_tenant_config
151
169
 
152
- def initialize(per_tenant_config)
170
+ def initialize(per_tenant_config = {})
153
171
  @per_tenant_config = per_tenant_config
154
172
  end
155
173
 
@@ -8,6 +8,8 @@ module Killbill
8
8
  require 'offsite_payments'
9
9
 
10
10
  class PaymentPlugin < ::Killbill::Plugin::Payment
11
+ include ::Killbill::Plugin::ActiveMerchant::ActiveRecordHelper
12
+ include ::Killbill::Plugin::PropertiesHelper
11
13
 
12
14
  def initialize(gateway_builder, identifier, payment_method_model, transaction_model, response_model)
13
15
  super()
@@ -17,11 +19,9 @@ module Killbill
17
19
  @payment_method_model = payment_method_model
18
20
  @transaction_model = transaction_model
19
21
  @response_model = response_model
20
-
21
22
  end
22
23
 
23
24
  def start_plugin
24
-
25
25
  @logger.progname = "#{@identifier.to_s}-plugin"
26
26
 
27
27
  @config_key_name = "PLUGIN_CONFIG_#{@plugin_name}".to_sym
@@ -37,19 +37,11 @@ module Killbill
37
37
  @logger.info "#{@identifier} payment plugin started"
38
38
  end
39
39
 
40
- # return DB connections to the Pool if required
41
40
  def after_request
42
- pool = ::ActiveRecord::Base.connection_pool
43
- return unless pool.active_connection?
44
-
45
- connection = ::ActiveRecord::Base.connection
46
- pool.remove(connection)
47
- connection.disconnect!
48
-
49
- @logger.debug { "after_request: pool.active_connection? = #{pool.active_connection?}, connection.active? = #{connection.active?}, pool.connections.size = #{pool.connections.size}, connections = #{pool.connections.inspect}" }
41
+ # return DB connections to the Pool if required
42
+ close_connection(@logger)
50
43
  end
51
44
 
52
-
53
45
  def on_event(event)
54
46
  if (event.event_type == :TENANT_CONFIG_CHANGE || event.event_type == :TENANT_CONFIG_DELETION) &&
55
47
  event.meta_data.to_sym == @config_key_name
@@ -147,8 +139,8 @@ module Killbill
147
139
 
148
140
  def get_payment_info(kb_account_id, kb_payment_id, properties, context)
149
141
  # We assume the payment is immutable in the Gateway and only look at our tables
150
- @transaction_model.transactions_from_kb_payment_id(kb_payment_id, context.tenant_id).collect do |transaction|
151
- transaction.send("#{@identifier}_response").to_transaction_info_plugin(transaction)
142
+ @response_model.from_kb_payment_id(@transaction_model, kb_payment_id, context.tenant_id).collect do |response|
143
+ response.to_transaction_info_plugin(response.send("#{@identifier}_transaction"))
152
144
  end
153
145
  end
154
146
 
@@ -163,8 +155,10 @@ module Killbill
163
155
  options[:set_default] ||= set_default
164
156
  options[:order_id] ||= kb_payment_method_id
165
157
 
158
+ should_skip_gw = Utils.normalized(options, :skip_gw)
159
+
166
160
  # Registering a card or a token
167
- if options[:skip_gw]
161
+ if should_skip_gw
168
162
  # If nothing is passed, that's fine - we probably just want a placeholder row in the plugin
169
163
  payment_source = get_payment_source(nil, all_properties, options, context) rescue nil
170
164
  else
@@ -172,21 +166,31 @@ module Killbill
172
166
  end
173
167
 
174
168
  # Go to the gateway
175
- payment_processor_account_id = options[:payment_processor_account_id] || :default
169
+ payment_processor_account_id = Utils.normalized(options, :payment_processor_account_id) || :default
176
170
  gateway = lookup_gateway(payment_processor_account_id, context.tenant_id)
177
171
  gw_response = gateway.store(payment_source, options)
178
172
  response, transaction = save_response_and_transaction(gw_response, :add_payment_method, kb_account_id, context.tenant_id, payment_processor_account_id)
179
173
 
180
174
  if response.success
181
175
  # If we have skipped the call to the gateway, we still need to store the payment method (either a token or the full credit card)
182
- if options[:skip_gw]
176
+ if should_skip_gw
183
177
  cc_or_token = payment_source
184
178
  else
185
179
  # response.authorization may be a String combination separated by ; - don't split it! Some plugins expect it as-is (they split it themselves)
186
180
  cc_or_token = response.authorization
187
181
  end
188
182
 
189
- payment_method = @payment_method_model.from_response(kb_account_id, kb_payment_method_id, context.tenant_id, cc_or_token, gw_response, options, {}, @payment_method_model)
183
+ attributes = properties_to_hash(all_properties)
184
+ # Note: keep the same keys as in build_am_credit_card below
185
+ extra_params = {
186
+ :cc_first_name => Utils.normalized(attributes, :cc_first_name),
187
+ :cc_last_name => Utils.normalized(attributes, :cc_last_name),
188
+ :cc_type => Utils.normalized(attributes, :cc_type),
189
+ :cc_exp_month => Utils.normalized(attributes, :cc_expiration_month),
190
+ :cc_exp_year => Utils.normalized(attributes, :cc_expiration_year),
191
+ :cc_last_4 => Utils.normalized(attributes, :cc_last_4)
192
+ }
193
+ payment_method = @payment_method_model.from_response(kb_account_id, kb_payment_method_id, context.tenant_id, cc_or_token, gw_response, options, extra_params, @payment_method_model)
190
194
  payment_method.save!
191
195
  payment_method
192
196
  else
@@ -200,10 +204,12 @@ module Killbill
200
204
  pm = @payment_method_model.from_kb_payment_method_id(kb_payment_method_id, context.tenant_id)
201
205
 
202
206
  # Delete the card
203
- payment_processor_account_id = options[:payment_processor_account_id] || :default
207
+ payment_processor_account_id = Utils.normalized(options, :payment_processor_account_id) || :default
204
208
  gateway = lookup_gateway(payment_processor_account_id, context.tenant_id)
205
- if options[:customer_id]
206
- gw_response = gateway.unstore(options[:customer_id], pm.token, options)
209
+
210
+ customer_id = Utils.normalized(options, :customer_id)
211
+ if customer_id
212
+ gw_response = gateway.unstore(customer_id, pm.token, options)
207
213
  else
208
214
  gw_response = gateway.unstore(pm.token, options)
209
215
  end
@@ -266,10 +272,12 @@ module Killbill
266
272
 
267
273
  # The remaining elements in payment_methods are not in our table (this should never happen?!)
268
274
  payment_methods.each do |payment_method_info_plugin|
269
- pm = @payment_method_model.create :kb_account_id => kb_account_id,
275
+ pm = @payment_method_model.create(:kb_account_id => kb_account_id,
270
276
  :kb_payment_method_id => payment_method_info_plugin.payment_method_id,
271
277
  :kb_tenant_id => context.tenant_id,
272
- :token => payment_method_info_plugin.external_payment_method_id
278
+ :token => payment_method_info_plugin.external_payment_method_id,
279
+ :created_at => Time.now.utc,
280
+ :updated_at => Time.now.utc)
273
281
  end
274
282
  end
275
283
 
@@ -370,12 +378,12 @@ module Killbill
370
378
  # * payment_source should probably be retrieved per gateway
371
379
  # * amount per gateway should be retrieved from the options
372
380
  def dispatch_to_gateways(operation, kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context, gateway_call_proc, linked_transaction_proc=nil)
373
- kb_transaction = get_kb_transaction(kb_payment_id, kb_payment_transaction_id, context.tenant_id)
381
+ kb_transaction = Utils::LazyEvaluator.new { get_kb_transaction(kb_payment_id, kb_payment_transaction_id, context.tenant_id) }
374
382
  amount_in_cents = amount.nil? ? nil : to_cents(amount, currency)
375
383
 
376
384
  # Setup options for ActiveMerchant
377
385
  options = properties_to_hash(properties)
378
- options[:order_id] ||= kb_transaction.external_key
386
+ options[:order_id] ||= (Utils.normalized(options, :external_key_as_order_id) ? kb_transaction.external_key : kb_payment_transaction_id)
379
387
  options[:currency] ||= currency.to_s.upcase unless currency.nil?
380
388
  options[:description] ||= "Kill Bill #{operation.to_s} for #{kb_payment_transaction_id}"
381
389
 
@@ -388,7 +396,7 @@ module Killbill
388
396
  end
389
397
 
390
398
  # Retrieve the previous transaction for the same operation and payment id - this is useful to detect dups for example
391
- last_transaction = @transaction_model.send("#{operation.to_s}s_from_kb_payment_id", kb_payment_id, context.tenant_id).last
399
+ last_transaction = Utils::LazyEvaluator.new { @transaction_model.send("#{operation.to_s}s_from_kb_payment_id", kb_payment_id, context.tenant_id).last }
392
400
 
393
401
  # Retrieve the linked transaction (authorization to capture, purchase to refund, etc.)
394
402
  linked_transaction = nil
@@ -404,7 +412,13 @@ module Killbill
404
412
  gw_responses = []
405
413
  responses = []
406
414
  transactions = []
407
- payment_processor_account_ids = options[:payment_processor_account_ids].nil? ? [options[:payment_processor_account_id] || :default] : options[:payment_processor_account_ids].split(',')
415
+
416
+ payment_processor_account_ids = Utils.normalized(options, :payment_processor_account_ids)
417
+ if !payment_processor_account_ids
418
+ payment_processor_account_ids = [Utils.normalized(options, :payment_processor_account_id) || :default]
419
+ else
420
+ payment_processor_account_ids = payment_processor_account_ids.split(',')
421
+ end
408
422
  payment_processor_account_ids.each do |payment_processor_account_id|
409
423
  # Find the gateway
410
424
  gateway = lookup_gateway(payment_processor_account_id, context.tenant_id)
@@ -459,53 +473,51 @@ module Killbill
459
473
  end
460
474
 
461
475
  def get_payment_source(kb_payment_method_id, properties, options, context)
462
- # Use ccNumber for the real number (if stored locally) or an in-house token (proxy tokenization). It is assumed the rest
463
- # of the card details are filled (expiration dates, etc.).
464
- cc_number = find_value_from_properties(properties, 'ccNumber')
476
+ attributes = properties_to_hash(properties, options)
477
+
478
+ # Use ccNumber for:
479
+ # * the real number
480
+ # * in-house token (e.g. proxy tokenization)
481
+ # * token from a token service provider (e.g. ApplePay)
482
+ # If not specified, the rest of the card details will be retrieved from the locally stored payment method (if available)
483
+ cc_number = Utils.normalized(attributes, :cc_number)
465
484
  # Use token for the token stored in an external vault. The token itself should be enough to process payments.
466
- cc_or_token = find_value_from_properties(properties, 'token') || find_value_from_properties(properties, 'cardId')
467
-
468
- if cc_number.blank? and cc_or_token.blank?
469
- # Lookup existing token
470
- pm = @payment_method_model.from_kb_payment_method_id(kb_payment_method_id, context.tenant_id)
471
- if pm.token.nil?
472
- # Real credit card
473
- cc_or_token = ::ActiveMerchant::Billing::CreditCard.new(
474
- :number => pm.cc_number,
475
- :brand => pm.cc_type,
476
- :month => pm.cc_exp_month,
477
- :year => pm.cc_exp_year,
478
- :verification_value => pm.cc_verification_value,
479
- :first_name => pm.cc_first_name,
480
- :last_name => pm.cc_last_name
481
- )
485
+ token = Utils.normalized(attributes, :token) || Utils.normalized(attributes, :card_id) || Utils.normalized(attributes, :payment_data)
486
+
487
+ if token.blank?
488
+ pm = nil
489
+ begin
490
+ pm = @payment_method_model.from_kb_payment_method_id(kb_payment_method_id, context.tenant_id)
491
+ rescue => e
492
+ raise e if cc_number.blank?
493
+ end unless kb_payment_method_id.nil?
494
+
495
+ if cc_number.blank? && !pm.nil?
496
+ # Lookup existing token
497
+ if pm.token.nil?
498
+ # Real credit card
499
+ cc_or_token = build_am_credit_card(pm.cc_number, attributes, pm)
500
+ else
501
+ # Tokenized card
502
+ cc_or_token = pm.token
503
+ end
482
504
  else
483
- # Tokenized card
484
- cc_or_token = pm.token
505
+ # Real credit card or network tokenization
506
+ cc_or_token = build_am_credit_card(cc_number, attributes, pm)
485
507
  end
486
- elsif !cc_number.blank? and cc_or_token.blank?
487
- # Real credit card
488
- cc_or_token = ::ActiveMerchant::Billing::CreditCard.new(
489
- :number => cc_number,
490
- :brand => find_value_from_properties(properties, 'ccType'),
491
- :month => find_value_from_properties(properties, 'ccExpirationMonth'),
492
- :year => find_value_from_properties(properties, 'ccExpirationYear'),
493
- :verification_value => find_value_from_properties(properties, 'ccVerificationValue'),
494
- :first_name => find_value_from_properties(properties, 'ccFirstName'),
495
- :last_name => find_value_from_properties(properties, 'ccLastName')
496
- )
497
508
  else
498
509
  # Use specified token
510
+ cc_or_token = build_am_token(token, attributes)
499
511
  end
500
512
 
501
513
  options[:billing_address] ||= {
502
- :email => find_value_from_properties(properties, 'email'),
503
- :address1 => find_value_from_properties(properties, 'address1'),
504
- :address2 => find_value_from_properties(properties, 'address2'),
505
- :city => find_value_from_properties(properties, 'city'),
506
- :zip => find_value_from_properties(properties, 'zip'),
507
- :state => find_value_from_properties(properties, 'state'),
508
- :country => find_value_from_properties(properties, 'country')
514
+ :email => Utils.normalized(attributes, :email),
515
+ :address1 => Utils.normalized(attributes, :address1) || (pm.nil? ? nil : pm.address1),
516
+ :address2 => Utils.normalized(attributes, :address2) || (pm.nil? ? nil : pm.address2),
517
+ :city => Utils.normalized(attributes, :city) || (pm.nil? ? nil : pm.city),
518
+ :zip => Utils.normalized(attributes, :zip) || (pm.nil? ? nil : pm.zip),
519
+ :state => Utils.normalized(attributes, :state) || (pm.nil? ? nil : pm.state),
520
+ :country => Utils.normalized(attributes, :country) || (pm.nil? ? nil : pm.country)
509
521
  }
510
522
 
511
523
  # To make various gateway implementations happy...
@@ -514,10 +526,49 @@ module Killbill
514
526
  cc_or_token
515
527
  end
516
528
 
517
- def find_value_from_properties(properties, key)
518
- return nil if key.nil?
519
- prop = (properties.find { |kv| kv.key.to_s == key.to_s })
520
- prop.nil? ? nil : prop.value
529
+ def build_am_credit_card(cc_number, attributes, pm=nil)
530
+ card_attributes = {
531
+ :number => cc_number,
532
+ :brand => Utils.normalized(attributes, :cc_type) || (pm.nil? ? nil : pm.cc_type),
533
+ :month => Utils.normalized(attributes, :cc_expiration_month) || (pm.nil? ? nil : pm.cc_exp_month),
534
+ :year => Utils.normalized(attributes, :cc_expiration_year) || (pm.nil? ? nil : pm.cc_exp_year),
535
+ :verification_value => Utils.normalized(attributes, :cc_verification_value) || (pm.nil? ? nil : pm.cc_verification_value),
536
+ :first_name => Utils.normalized(attributes, :cc_first_name) || (pm.nil? ? nil : pm.cc_first_name),
537
+ :last_name => Utils.normalized(attributes, :cc_last_name) || (pm.nil? ? nil : pm.cc_last_name)
538
+ }
539
+ tokenization_attributes = {
540
+ :eci => Utils.normalized(attributes, :eci),
541
+ :payment_cryptogram => Utils.normalized(attributes, :payment_cryptogram),
542
+ :transaction_id => Utils.normalized(attributes, :transaction_id)
543
+ }
544
+
545
+ if tokenization_attributes[:eci].nil? &&
546
+ tokenization_attributes[:payment_cryptogram].nil? &&
547
+ tokenization_attributes[:transaction_id].nil?
548
+ ::ActiveMerchant::Billing::CreditCard.new(card_attributes)
549
+ else
550
+ # NetworkTokenizationCreditCard is exactly like a credit card but with EMV/3DS standard payment network tokenization data
551
+ ::ActiveMerchant::Billing::NetworkTokenizationCreditCard.new(card_attributes.merge(tokenization_attributes))
552
+ end
553
+ end
554
+
555
+ def build_am_token(token, attributes)
556
+ token_attributes = {
557
+ :payment_instrument_name => Utils.normalized(attributes, :payment_instrument_name),
558
+ :payment_network => Utils.normalized(attributes, :payment_network),
559
+ :transaction_identifier => Utils.normalized(attributes, :transaction_identifier)
560
+ }
561
+
562
+ if token_attributes[:payment_instrument_name].nil? &&
563
+ token_attributes[:payment_network].nil? &&
564
+ token_attributes[:transaction_identifier].nil?
565
+ token
566
+ else
567
+ # Use ActiveSupport since ActiveMerchant does the same
568
+ payment_data = ::ActiveSupport::JSON.decode(token) rescue token
569
+ # PaymentToken is meant for modeling proprietary payment token structures (like stripe and apple_pay)
570
+ ::ActiveMerchant::Billing::ApplePayPaymentToken.new(payment_data, token_attributes)
571
+ end
521
572
  end
522
573
 
523
574
  def account_currency(kb_account_id, kb_tenant_id)
@@ -545,31 +596,6 @@ module Killbill
545
596
  ::Killbill::Plugin::ActiveMerchant.config
546
597
  end
547
598
 
548
- def hash_to_properties(options)
549
- merge_properties([], options)
550
- end
551
-
552
- def properties_to_hash(properties, options = {})
553
- merged = {}
554
- (properties || []).each do |p|
555
- merged[p.key.to_sym] = p.value
556
- end
557
- merged.merge(options)
558
- end
559
-
560
- def merge_properties(properties, options)
561
- merged = properties_to_hash(properties, options)
562
-
563
- properties = []
564
- merged.each do |k, v|
565
- p = ::Killbill::Plugin::Model::PluginProperty.new
566
- p.key = k
567
- p.value = v
568
- properties << p
569
- end
570
- properties
571
- end
572
-
573
599
  def get_active_merchant_module
574
600
  ::OffsitePayments.integration(@identifier.to_s.camelize)
575
601
  end