killbill 3.2.4 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
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