killbill 3.1.11 → 3.1.12

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/Jarfile +6 -6
  3. data/NEWS +6 -0
  4. data/VERSION +1 -1
  5. data/generators/active_merchant/templates/Jarfile.rb +7 -7
  6. data/generators/active_merchant/templates/db/ddl.sql.rb +2 -0
  7. data/generators/active_merchant/templates/db/schema.rb +2 -0
  8. data/generators/active_merchant/templates/lib/application.rb +1 -5
  9. data/generators/active_merchant/templates/lib/models/response.rb +2 -1
  10. data/generators/active_merchant/templates/lib/plugin.rb +1 -0
  11. data/generators/active_merchant/templates/lib/private_api.rb +7 -0
  12. data/generators/active_merchant/templates/plugin.gemspec.rb +3 -2
  13. data/generators/active_merchant/templates/spec/integration_spec.rb +103 -2
  14. data/killbill.gemspec +4 -1
  15. data/lib/killbill/gen/api/dry_run_arguments.rb +100 -0
  16. data/lib/killbill/gen/api/invoice_user_api.rb +5 -5
  17. data/lib/killbill/gen/api/killbill_api.rb +19 -18
  18. data/lib/killbill/gen/api/payment_options.rb +17 -5
  19. data/lib/killbill/gen/api/require_gen.rb +5 -0
  20. data/lib/killbill/gen/api/rolled_up_unit.rb +63 -0
  21. data/lib/killbill/gen/api/rolled_up_usage.rb +31 -35
  22. data/lib/killbill/gen/api/static_catalog.rb +8 -8
  23. data/lib/killbill/gen/api/subscription_usage_record.rb +75 -0
  24. data/lib/killbill/gen/api/unit_usage_record.rb +74 -0
  25. data/lib/killbill/gen/api/usage_record.rb +66 -0
  26. data/lib/killbill/gen/api/usage_user_api.rb +22 -56
  27. data/lib/killbill/gen/plugin-api/currency_plugin_api.rb +19 -18
  28. data/lib/killbill/gen/plugin-api/ext_bus_event.rb +19 -18
  29. data/lib/killbill/gen/plugin-api/gateway_notification.rb +19 -18
  30. data/lib/killbill/gen/plugin-api/hosted_payment_page_form_descriptor.rb +19 -18
  31. data/lib/killbill/gen/plugin-api/notification_plugin_api.rb +19 -18
  32. data/lib/killbill/gen/plugin-api/payment_method_info_plugin.rb +19 -18
  33. data/lib/killbill/gen/plugin-api/payment_plugin_api.rb +19 -18
  34. data/lib/killbill/gen/plugin-api/payment_plugin_api_exception.rb +19 -18
  35. data/lib/killbill/gen/plugin-api/payment_transaction_info_plugin.rb +19 -18
  36. data/lib/killbill/gen/plugin-api/require_gen.rb +19 -18
  37. data/lib/killbill/helpers/active_merchant/active_record/models/payment_method.rb +4 -0
  38. data/lib/killbill/helpers/active_merchant/active_record/models/response.rb +65 -34
  39. data/lib/killbill/helpers/active_merchant/active_record/models/streamy_result_set.rb +14 -2
  40. data/lib/killbill/helpers/active_merchant/active_record/models/transaction.rb +9 -5
  41. data/lib/killbill/helpers/active_merchant/killbill_spec_helper.rb +28 -23
  42. data/lib/killbill/helpers/active_merchant/payment_plugin.rb +66 -36
  43. data/lib/killbill/helpers/active_merchant/private_payment_plugin.rb +18 -31
  44. data/lib/killbill/helpers/active_merchant/sinatra.rb +7 -2
  45. data/lib/killbill/helpers/active_merchant/utils.rb +12 -0
  46. data/spec/killbill/helpers/killbill_spec_helper_spec.rb +24 -0
  47. data/spec/killbill/helpers/payment_method_spec.rb +5 -0
  48. data/spec/killbill/helpers/payment_plugin_spec.rb +33 -2
  49. data/spec/killbill/helpers/private_payment_plugin_spec.rb +83 -0
  50. data/spec/killbill/helpers/response_spec.rb +47 -24
  51. data/spec/killbill/helpers/streamy_result_set_spec.rb +38 -0
  52. data/spec/killbill/helpers/test_schema.rb +2 -0
  53. metadata +57 -8
  54. data/lib/killbill/gen/api/payment_attempt.rb +0 -117
  55. data/lib/killbill/gen/api/payment_method_kv_info.rb +0 -71
  56. data/lib/killbill/gen/plugin-api/billing_address.rb +0 -85
  57. data/lib/killbill/gen/plugin-api/customer.rb +0 -73
@@ -1,21 +1,22 @@
1
- ###################################################################################
2
- # #
3
- # Copyright 2010-2013 Ning, Inc. #
4
- # Copyright 2014 The Billing Project, LLC #
5
- # #
6
- # The Billing Project licenses this file to you under the Apache License, #
7
- # version 2.0 (the "License"); you may not use this file except in #
8
- # compliance with the License. You may obtain a copy of the License at: #
9
- # #
10
- # http://www.apache.org/licenses/LICENSE-2.0 #
11
- # #
12
- # Unless required by applicable law or agreed to in writing, software #
13
- # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT #
14
- # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the #
15
- # License for the specific language governing permissions and limitations #
16
- # under the License. #
17
- # #
18
- ###################################################################################
1
+ #############################################################################################
2
+ # #
3
+ # Copyright 2010-2013 Ning, Inc. #
4
+ # Copyright 2014 Groupon, Inc. #
5
+ # Copyright 2014 The Billing Project, LLC #
6
+ # #
7
+ # The Billing Project licenses this file to you under the Apache License, version 2.0 #
8
+ # (the "License"); you may not use this file except in compliance with the #
9
+ # License. You may obtain a copy of the License at: #
10
+ # #
11
+ # http://www.apache.org/licenses/LICENSE-2.0 #
12
+ # #
13
+ # Unless required by applicable law or agreed to in writing, software #
14
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT #
15
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the #
16
+ # License for the specific language governing permissions and limitations #
17
+ # under the License. #
18
+ # #
19
+ #############################################################################################
19
20
 
20
21
 
21
22
  #
@@ -1,21 +1,22 @@
1
- ###################################################################################
2
- # #
3
- # Copyright 2010-2013 Ning, Inc. #
4
- # Copyright 2014 The Billing Project, LLC #
5
- # #
6
- # The Billing Project licenses this file to you under the Apache License, #
7
- # version 2.0 (the "License"); you may not use this file except in #
8
- # compliance with the License. You may obtain a copy of the License at: #
9
- # #
10
- # http://www.apache.org/licenses/LICENSE-2.0 #
11
- # #
12
- # Unless required by applicable law or agreed to in writing, software #
13
- # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT #
14
- # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the #
15
- # License for the specific language governing permissions and limitations #
16
- # under the License. #
17
- # #
18
- ###################################################################################
1
+ #############################################################################################
2
+ # #
3
+ # Copyright 2010-2013 Ning, Inc. #
4
+ # Copyright 2014 Groupon, Inc. #
5
+ # Copyright 2014 The Billing Project, LLC #
6
+ # #
7
+ # The Billing Project licenses this file to you under the Apache License, version 2.0 #
8
+ # (the "License"); you may not use this file except in compliance with the #
9
+ # License. You may obtain a copy of the License at: #
10
+ # #
11
+ # http://www.apache.org/licenses/LICENSE-2.0 #
12
+ # #
13
+ # Unless required by applicable law or agreed to in writing, software #
14
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT #
15
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the #
16
+ # License for the specific language governing permissions and limitations #
17
+ # under the License. #
18
+ # #
19
+ #############################################################################################
19
20
 
20
21
 
21
22
  #
@@ -48,6 +48,10 @@ module Killbill
48
48
  end
49
49
  end
50
50
 
51
+ def self.from_kb_account_id_and_token(token, kb_account_id, kb_tenant_id)
52
+ from_kb_account_id(kb_account_id, kb_tenant_id).where("token = #{@@quotes_cache[token]}")
53
+ end
54
+
51
55
  def self.from_kb_payment_method_id(kb_payment_method_id, kb_tenant_id)
52
56
  if kb_tenant_id.nil?
53
57
  payment_methods = where("kb_payment_method_id = #{@@quotes_cache[kb_payment_method_id]} AND kb_tenant_id is NULL AND is_deleted = #{@@quotes_cache[false]}")
@@ -14,40 +14,43 @@ module Killbill
14
14
 
15
15
  self.abstract_class = true
16
16
 
17
- def self.from_response(api_call, kb_account_id, kb_payment_id, kb_payment_transaction_id, transaction_type, kb_tenant_id, response, extra_params = {}, model = Response)
17
+ @@quotes_cache = build_quotes_cache
18
+
19
+ def self.from_response(api_call, kb_account_id, kb_payment_id, kb_payment_transaction_id, transaction_type, payment_processor_account_id, kb_tenant_id, response, extra_params = {}, model = Response)
18
20
  # Under high load, Rails sometimes fails to set timestamps. Unclear why...
19
21
  current_time = Time.now.utc
20
22
  model.new({
21
- :api_call => api_call,
22
- :kb_account_id => kb_account_id,
23
- :kb_payment_id => kb_payment_id,
24
- :kb_payment_transaction_id => kb_payment_transaction_id,
25
- :transaction_type => transaction_type,
26
- :kb_tenant_id => kb_tenant_id,
27
- :message => response.message,
28
- :authorization => response.authorization,
29
- :fraud_review => response.fraud_review?,
30
- :test => response.test?,
31
- :avs_result_code => response.avs_result.kind_of?(::ActiveMerchant::Billing::AVSResult) ? response.avs_result.code : response.avs_result['code'],
32
- :avs_result_message => response.avs_result.kind_of?(::ActiveMerchant::Billing::AVSResult) ? response.avs_result.message : response.avs_result['message'],
33
- :avs_result_street_match => response.avs_result.kind_of?(::ActiveMerchant::Billing::AVSResult) ? response.avs_result.street_match : response.avs_result['street_match'],
34
- :avs_result_postal_match => response.avs_result.kind_of?(::ActiveMerchant::Billing::AVSResult) ? response.avs_result.postal_match : response.avs_result['postal_match'],
35
- :cvv_result_code => response.cvv_result.kind_of?(::ActiveMerchant::Billing::CVVResult) ? response.cvv_result.code : response.cvv_result['code'],
36
- :cvv_result_message => response.cvv_result.kind_of?(::ActiveMerchant::Billing::CVVResult) ? response.cvv_result.message : response.cvv_result['message'],
37
- :success => response.success?,
38
- :created_at => current_time,
39
- :updated_at => current_time
23
+ :api_call => api_call,
24
+ :kb_account_id => kb_account_id,
25
+ :kb_payment_id => kb_payment_id,
26
+ :kb_payment_transaction_id => kb_payment_transaction_id,
27
+ :transaction_type => transaction_type,
28
+ :payment_processor_account_id => payment_processor_account_id,
29
+ :kb_tenant_id => kb_tenant_id,
30
+ :message => response.message,
31
+ :authorization => response.authorization,
32
+ :fraud_review => response.fraud_review?,
33
+ :test => response.test?,
34
+ :avs_result_code => response.avs_result.kind_of?(::ActiveMerchant::Billing::AVSResult) ? response.avs_result.code : response.avs_result['code'],
35
+ :avs_result_message => response.avs_result.kind_of?(::ActiveMerchant::Billing::AVSResult) ? response.avs_result.message : response.avs_result['message'],
36
+ :avs_result_street_match => response.avs_result.kind_of?(::ActiveMerchant::Billing::AVSResult) ? response.avs_result.street_match : response.avs_result['street_match'],
37
+ :avs_result_postal_match => response.avs_result.kind_of?(::ActiveMerchant::Billing::AVSResult) ? response.avs_result.postal_match : response.avs_result['postal_match'],
38
+ :cvv_result_code => response.cvv_result.kind_of?(::ActiveMerchant::Billing::CVVResult) ? response.cvv_result.code : response.cvv_result['code'],
39
+ :cvv_result_message => response.cvv_result.kind_of?(::ActiveMerchant::Billing::CVVResult) ? response.cvv_result.message : response.cvv_result['message'],
40
+ :success => response.success?,
41
+ :created_at => current_time,
42
+ :updated_at => current_time
40
43
  }.merge!(extra_params))
41
44
  end
42
45
 
43
- def self.create_response_and_transaction(identifier, transaction_model, api_call, kb_account_id, kb_payment_id, kb_payment_transaction_id, transaction_type, kb_tenant_id, gw_response, amount_in_cents, currency, extra_params = {}, model = Response)
46
+ def self.create_response_and_transaction(identifier, transaction_model, api_call, kb_account_id, kb_payment_id, kb_payment_transaction_id, transaction_type, payment_processor_account_id, kb_tenant_id, gw_response, amount_in_cents, currency, extra_params = {}, model = Response)
44
47
  response, transaction, exception = nil
45
48
 
46
49
  # Rails wraps all create/save calls in a transaction. To speed things up, create a single transaction for both rows.
47
50
  # This has a small gotcha in the unhappy path though (see below).
48
51
  transaction do
49
52
  # Save the response to our logs
50
- response = from_response(api_call, kb_account_id, kb_payment_id, kb_payment_transaction_id, transaction_type, kb_tenant_id, gw_response, extra_params, model)
53
+ response = from_response(api_call, kb_account_id, kb_payment_id, kb_payment_transaction_id, transaction_type, payment_processor_account_id, kb_tenant_id, gw_response, extra_params, model)
51
54
  response.save!(shared_activerecord_options)
52
55
 
53
56
  transaction = nil
@@ -58,19 +61,20 @@ module Killbill
58
61
  begin
59
62
  # Originally, we used response.send("build_#{identifier}_transaction"), but the ActiveRecord magic was adding
60
63
  # about 20% overhead - instead, we now construct the transaction record manually
61
- transaction = transaction_model.new(:kb_account_id => kb_account_id,
62
- :kb_tenant_id => kb_tenant_id,
63
- :amount_in_cents => amount_in_cents,
64
- :currency => currency,
65
- :api_call => api_call,
66
- :kb_payment_id => kb_payment_id,
67
- :kb_payment_transaction_id => kb_payment_transaction_id,
68
- :transaction_type => transaction_type,
69
- :txn_id => txn_id,
70
- "#{identifier}_response_id" => response.id,
64
+ transaction = transaction_model.new(:kb_account_id => kb_account_id,
65
+ :kb_tenant_id => kb_tenant_id,
66
+ :amount_in_cents => amount_in_cents,
67
+ :currency => currency,
68
+ :api_call => api_call,
69
+ :kb_payment_id => kb_payment_id,
70
+ :kb_payment_transaction_id => kb_payment_transaction_id,
71
+ :transaction_type => transaction_type,
72
+ :payment_processor_account_id => payment_processor_account_id,
73
+ :txn_id => txn_id,
74
+ "#{identifier}_response_id" => response.id,
71
75
  # See Response#from_response
72
- :created_at => response.created_at,
73
- :updated_at => response.updated_at)
76
+ :created_at => response.created_at,
77
+ :updated_at => response.updated_at)
74
78
  transaction.save!(shared_activerecord_options)
75
79
  rescue => e
76
80
  exception = e
@@ -109,6 +113,7 @@ module Killbill
109
113
  t_info_plugin.second_payment_reference_id = second_reference_id
110
114
 
111
115
  properties = []
116
+ properties << create_plugin_property('payment_processor_account_id', payment_processor_account_id)
112
117
  properties << create_plugin_property('message', message)
113
118
  properties << create_plugin_property('authorization', authorization)
114
119
  properties << create_plugin_property('fraudReview', fraud_review)
@@ -217,6 +222,32 @@ module Killbill
217
222
  prop.value = value
218
223
  prop
219
224
  end
225
+
226
+ class << self
227
+ [:kb_payment_id, :kb_payment_transaction_id].each do |attribute|
228
+ define_method("responses_from_#{attribute.to_s}") do |transaction_type, attribute_value, kb_tenant_id, how_many = :multiple|
229
+ if kb_tenant_id.nil?
230
+ if transaction_type.nil?
231
+ transactions = where("kb_tenant_id is NULL AND #{attribute.to_s} = ?", attribute_value).order(:created_at)
232
+ else
233
+ transactions = where("transaction_type = #{@@quotes_cache[transaction_type]} AND kb_tenant_id is NULL AND #{attribute.to_s} = #{@@quotes_cache[attribute_value]}").order(:created_at)
234
+ end
235
+ else
236
+ if transaction_type.nil?
237
+ transactions = where("kb_tenant_id = #{@@quotes_cache[kb_tenant_id]} AND #{attribute.to_s} = #{@@quotes_cache[attribute_value]}").order(:created_at)
238
+ else
239
+ transactions = where("transaction_type = #{@@quotes_cache[transaction_type]} AND kb_tenant_id = #{@@quotes_cache[kb_tenant_id]} AND #{attribute.to_s} = #{@@quotes_cache[attribute_value]}").order(:created_at)
240
+ end
241
+ end
242
+ if how_many == :single
243
+ raise "Kill Bill #{attribute} = #{attribute_value} mapping to multiple plugin transactions" if transactions.size > 1
244
+ transactions[0]
245
+ else
246
+ transactions
247
+ end
248
+ end
249
+ end
250
+ end
220
251
  end
221
252
  end
222
253
  end
@@ -21,15 +21,27 @@ module Killbill
21
21
  # Optimization: bail out if no more results
22
22
  break if result.nil? || result.empty?
23
23
  end if @batch > 0
24
+ ensure
24
25
  # Make sure to return DB connections to the Pool
25
- ::ActiveRecord::Base.connection.close
26
+ close_connection
26
27
  end
27
28
 
28
29
  def to_a
29
30
  super.to_a.flatten
30
31
  end
32
+
33
+ private
34
+
35
+ def close_connection
36
+ pool = ::ActiveRecord::Base.connection_pool
37
+ return unless pool.active_connection?
38
+
39
+ connection = ::ActiveRecord::Base.connection
40
+ pool.remove(connection)
41
+ connection.disconnect!
42
+ end
31
43
  end
32
44
  end
33
45
  end
34
46
  end
35
- end
47
+ end
@@ -41,11 +41,15 @@ module Killbill
41
41
  transaction_from_kb_payment_transaction_id(nil, kb_payment_transaction_id, kb_tenant_id, :single)
42
42
  end
43
43
 
44
- def find_candidate_transaction_for_refund(kb_payment_id, kb_tenant_id, amount_in_cents)
45
- begin
46
- do_find_candidate_transaction_for_refund(:authorize, kb_payment_id, kb_tenant_id, amount_in_cents)
47
- rescue
48
- do_find_candidate_transaction_for_refund(:purchase, kb_payment_id, kb_tenant_id, amount_in_cents)
44
+ def find_candidate_transaction_for_refund(kb_payment_id, kb_tenant_id, amount_in_cents, transaction_type = nil)
45
+ if transaction_type.nil?
46
+ begin
47
+ do_find_candidate_transaction_for_refund(:authorize, kb_payment_id, kb_tenant_id, amount_in_cents)
48
+ rescue
49
+ do_find_candidate_transaction_for_refund(:purchase, kb_payment_id, kb_tenant_id, amount_in_cents)
50
+ end
51
+ else
52
+ do_find_candidate_transaction_for_refund(transaction_type, kb_payment_id, kb_tenant_id, amount_in_cents)
49
53
  end
50
54
  end
51
55
 
@@ -3,7 +3,7 @@ module Killbill
3
3
  module ActiveMerchant
4
4
  module RSpec
5
5
 
6
- def create_payment_method(payment_method_model=::Killbill::Plugin::ActiveMerchant::ActiveRecord::PaymentMethod, kb_account_id=nil, kb_tenant_id=nil)
6
+ def create_payment_method(payment_method_model=::Killbill::Plugin::ActiveMerchant::ActiveRecord::PaymentMethod, kb_account_id=nil, kb_tenant_id=nil, properties = [], options = {})
7
7
  kb_payment_method_id = SecureRandom.uuid
8
8
 
9
9
  if kb_account_id.nil?
@@ -13,20 +13,20 @@ module Killbill
13
13
  create_kb_account kb_account_id
14
14
  end
15
15
 
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)
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)
18
18
 
19
19
  # The rest is pure Ruby
20
- context = context.to_ruby(context)
20
+ context = context.to_ruby(context)
21
21
 
22
22
  # Generate a token
23
- properties = build_pm_properties(account)
23
+ pm_properties = build_pm_properties(account, options)
24
24
 
25
25
  info = Killbill::Plugin::Model::PaymentMethodPlugin.new
26
- info.properties = properties
27
- payment_method = @plugin.add_payment_method(kb_account_id, kb_payment_method_id, info, true, [], context)
26
+ info.properties = pm_properties
27
+ payment_method = @plugin.add_payment_method(kb_account_id, kb_payment_method_id, info, true, properties, context)
28
28
 
29
- pm = payment_method_model.from_kb_payment_method_id kb_payment_method_id, context.tenant_id
29
+ pm = payment_method_model.from_kb_payment_method_id(kb_payment_method_id, context.tenant_id)
30
30
  pm.should == payment_method
31
31
  pm.kb_account_id.should == kb_account_id
32
32
  pm.kb_payment_method_id.should == kb_payment_method_id
@@ -34,21 +34,21 @@ module Killbill
34
34
  pm
35
35
  end
36
36
 
37
- def build_pm_properties(account = nil)
38
- cc_number = '4242424242424242'
39
- cc_first_name = 'John'
40
- cc_last_name = 'Doe'
41
- cc_type = 'Visa'
42
- cc_exp_month = 12
43
- cc_exp_year = 2017
44
- cc_last_4 = 4242
45
- address1 = '5, oakriu road'
46
- address2 = 'apt. 298'
47
- city = 'Gdio Foia'
48
- state = 'FL'
49
- zip = 49302
50
- country = 'US'
51
- cc_verification_value = 1234
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)
52
52
 
53
53
  properties = []
54
54
  properties << create_pm_kv_info('ccNumber', cc_number)
@@ -66,6 +66,11 @@ module Killbill
66
66
  properties << create_pm_kv_info('zip', zip)
67
67
  properties << create_pm_kv_info('country', country)
68
68
  properties << create_pm_kv_info('ccVerificationValue', cc_verification_value)
69
+
70
+ overrides.each do |key, value|
71
+ properties << create_pm_kv_info(key, value)
72
+ end
73
+
69
74
  properties
70
75
  end
71
76
 
@@ -1,9 +1,11 @@
1
1
  module Killbill
2
2
  module Plugin
3
3
  module ActiveMerchant
4
+ require 'active_merchant'
4
5
  require 'active_record'
5
6
  require 'monetize'
6
7
  require 'money'
8
+ require 'offsite_payments'
7
9
 
8
10
  class PaymentPlugin < ::Killbill::Plugin::Payment
9
11
 
@@ -44,7 +46,7 @@ module Killbill
44
46
  end
45
47
 
46
48
  def authorize_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
47
- gateway_call_proc = Proc.new do |gateway, payment_source, amount_in_cents, options|
49
+ gateway_call_proc = Proc.new do |gateway, linked_transaction, payment_source, amount_in_cents, options|
48
50
  gateway.authorize(amount_in_cents, payment_source, options)
49
51
  end
50
52
 
@@ -52,18 +54,22 @@ module Killbill
52
54
  end
53
55
 
54
56
  def capture_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
55
- gateway_call_proc = Proc.new do |gateway, payment_source, amount_in_cents, options|
57
+ gateway_call_proc = Proc.new do |gateway, linked_transaction, payment_source, amount_in_cents, options|
58
+ gateway.capture(amount_in_cents, linked_transaction.txn_id, options)
59
+ end
60
+
61
+ linked_transaction_proc = Proc.new do |amount_in_cents, options|
56
62
  # TODO We use the last transaction at the moment, is it good enough?
57
63
  last_authorization = @transaction_model.authorizations_from_kb_payment_id(kb_payment_id, context.tenant_id).last
58
64
  raise "Unable to retrieve last authorization for operation=capture, kb_payment_id=#{kb_payment_id}, kb_payment_transaction_id=#{kb_payment_transaction_id}, kb_payment_method_id=#{kb_payment_method_id}" if last_authorization.nil?
59
- gateway.capture(amount_in_cents, last_authorization.txn_id, options)
65
+ last_authorization
60
66
  end
61
67
 
62
- dispatch_to_gateways(:capture, kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context, gateway_call_proc)
68
+ dispatch_to_gateways(:capture, kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context, gateway_call_proc, linked_transaction_proc)
63
69
  end
64
70
 
65
71
  def purchase_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
66
- gateway_call_proc = Proc.new do |gateway, payment_source, amount_in_cents, options|
72
+ gateway_call_proc = Proc.new do |gateway, linked_transaction, payment_source, amount_in_cents, options|
67
73
  gateway.purchase(amount_in_cents, payment_source, options)
68
74
  end
69
75
 
@@ -71,31 +77,41 @@ module Killbill
71
77
  end
72
78
 
73
79
  def void_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, properties, context)
74
- gateway_call_proc = Proc.new do |gateway, payment_source, amount_in_cents, options|
75
- # If an authorization is being voided, we're performing an 'auth_reversal', otherwise,
76
- # we're voiding an unsettled capture or purchase (which often needs to happen within 24 hours).
77
- last_transaction = @transaction_model.purchases_from_kb_payment_id(kb_payment_id, context.tenant_id).last
78
- if last_transaction.nil?
79
- last_transaction = @transaction_model.captures_from_kb_payment_id(kb_payment_id, context.tenant_id).last
80
+ gateway_call_proc = Proc.new do |gateway, linked_transaction, payment_source, amount_in_cents, options|
81
+ authorization = linked_transaction.txn_id
82
+
83
+ # Go to the gateway - while some gateways implementations are smart and have void support 'auth_reversal' and 'void' (e.g. Litle),
84
+ # others (e.g. CyberSource) implement different methods
85
+ linked_transaction.transaction_type == 'AUTHORIZE' && gateway.respond_to?(:auth_reversal) ? gateway.auth_reversal(linked_transaction.amount_in_cents, authorization, options) : gateway.void(authorization, options)
86
+ end
87
+
88
+ linked_transaction_proc = Proc.new do |amount_in_cents, options|
89
+ linked_transaction_type = find_value_from_properties(properties, :linked_transaction_type)
90
+ if linked_transaction_type.nil?
91
+ # Default behavior to search for the last transaction
92
+ # If an authorization is being voided, we're performing an 'auth_reversal', otherwise,
93
+ # we're voiding an unsettled capture or purchase (which often needs to happen within 24 hours).
94
+ last_transaction = @transaction_model.purchases_from_kb_payment_id(kb_payment_id, context.tenant_id).last
80
95
  if last_transaction.nil?
81
- last_transaction = @transaction_model.authorizations_from_kb_payment_id(kb_payment_id, context.tenant_id).last
96
+ last_transaction = @transaction_model.captures_from_kb_payment_id(kb_payment_id, context.tenant_id).last
82
97
  if last_transaction.nil?
83
- raise ArgumentError.new("Kill Bill payment #{kb_payment_id} has no auth, capture or purchase, thus cannot be voided")
98
+ last_transaction = @transaction_model.authorizations_from_kb_payment_id(kb_payment_id, context.tenant_id).last
99
+ if last_transaction.nil?
100
+ raise ArgumentError.new("Kill Bill payment #{kb_payment_id} has no auth, capture or purchase, thus cannot be voided")
101
+ end
84
102
  end
85
103
  end
104
+ else
105
+ last_transaction = @transaction_model.send("#{linked_transaction_type.to_s}s_from_kb_payment_id", kb_payment_id, context.tenant_id).last
86
106
  end
87
- authorization = last_transaction.txn_id
88
-
89
- # Go to the gateway - while some gateways implementations are smart and have void support 'auth_reversal' and 'void' (e.g. Litle),
90
- # others (e.g. CyberSource) implement different methods
91
- last_transaction.transaction_type == 'AUTHORIZE' && gateway.respond_to?(:auth_reversal) ? gateway.auth_reversal(last_transaction.amount_in_cents, authorization, options) : gateway.void(authorization, options)
107
+ last_transaction
92
108
  end
93
109
 
94
- dispatch_to_gateways(:void, kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, nil, nil, properties, context, gateway_call_proc)
110
+ dispatch_to_gateways(:void, kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, nil, nil, properties, context, gateway_call_proc, linked_transaction_proc)
95
111
  end
96
112
 
97
113
  def credit_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
98
- gateway_call_proc = Proc.new do |gateway, payment_source, amount_in_cents, options|
114
+ gateway_call_proc = Proc.new do |gateway, linked_transaction, payment_source, amount_in_cents, options|
99
115
  gateway.credit(amount_in_cents, payment_source, options)
100
116
  end
101
117
 
@@ -103,13 +119,18 @@ module Killbill
103
119
  end
104
120
 
105
121
  def refund_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
106
- gateway_call_proc = Proc.new do |gateway, payment_source, amount_in_cents, options|
107
- transaction = @transaction_model.find_candidate_transaction_for_refund(kb_payment_id, context.tenant_id, amount_in_cents)
108
- raise "Unable to retrieve transaction to refund for operation=capture, kb_payment_id=#{kb_payment_id}, kb_payment_transaction_id=#{kb_payment_transaction_id}, kb_payment_method_id=#{kb_payment_method_id}" if transaction.nil?
109
- gateway.refund(amount_in_cents, transaction.txn_id, options)
122
+ gateway_call_proc = Proc.new do |gateway, linked_transaction, payment_source, amount_in_cents, options|
123
+ gateway.refund(amount_in_cents, linked_transaction.txn_id, options)
110
124
  end
111
125
 
112
- dispatch_to_gateways(:refund, kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context, gateway_call_proc)
126
+ linked_transaction_proc = Proc.new do |amount_in_cents, options|
127
+ linked_transaction_type = find_value_from_properties(properties, :linked_transaction_type)
128
+ transaction = @transaction_model.find_candidate_transaction_for_refund(kb_payment_id, context.tenant_id, amount_in_cents, linked_transaction_type)
129
+ raise "Unable to retrieve transaction to refund for operation=refund, kb_payment_id=#{kb_payment_id}, kb_payment_transaction_id=#{kb_payment_transaction_id}, kb_payment_method_id=#{kb_payment_method_id}" if transaction.nil?
130
+ transaction
131
+ end
132
+
133
+ dispatch_to_gateways(:refund, kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context, gateway_call_proc, linked_transaction_proc)
113
134
  end
114
135
 
115
136
  def get_payment_info(kb_account_id, kb_payment_id, properties, context)
@@ -134,9 +155,10 @@ module Killbill
134
155
  payment_source = get_payment_source(nil, all_properties, options, context)
135
156
 
136
157
  # Go to the gateway
137
- gateway = lookup_gateway(options[:payment_processor_account_id] || :default)
138
- gw_response = gateway.store(payment_source, options)
139
- response, transaction = save_response_and_transaction gw_response, :add_payment_method, kb_account_id, context.tenant_id
158
+ payment_processor_account_id = options[:payment_processor_account_id] || :default
159
+ gateway = lookup_gateway(payment_processor_account_id)
160
+ gw_response = gateway.store(payment_source, options)
161
+ response, transaction = save_response_and_transaction(gw_response, :add_payment_method, kb_account_id, context.tenant_id, payment_processor_account_id)
140
162
 
141
163
  if response.success
142
164
  # If we have skipped the call to the gateway, we still need to store the payment method
@@ -161,13 +183,14 @@ module Killbill
161
183
  pm = @payment_method_model.from_kb_payment_method_id(kb_payment_method_id, context.tenant_id)
162
184
 
163
185
  # Delete the card
164
- gateway = lookup_gateway(options[:payment_processor_account_id] || :default)
186
+ payment_processor_account_id = options[:payment_processor_account_id] || :default
187
+ gateway = lookup_gateway(payment_processor_account_id)
165
188
  if options[:customer_id]
166
189
  gw_response = gateway.unstore(options[:customer_id], pm.token, options)
167
190
  else
168
191
  gw_response = gateway.unstore(pm.token, options)
169
192
  end
170
- response, transaction = save_response_and_transaction gw_response, :delete_payment_method, kb_account_id, context.tenant_id
193
+ response, transaction = save_response_and_transaction(gw_response, :delete_payment_method, kb_account_id, context.tenant_id, payment_processor_account_id)
171
194
 
172
195
  if response.success
173
196
  @payment_method_model.mark_as_deleted! kb_payment_method_id, context.tenant_id
@@ -329,7 +352,7 @@ module Killbill
329
352
  # TODO Split settlements is partially implemented. Left to be done:
330
353
  # * payment_source should probably be retrieved per gateway
331
354
  # * amount per gateway should be retrieved from the options
332
- 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)
355
+ 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)
333
356
  kb_transaction = get_kb_transaction(kb_payment_id, kb_payment_transaction_id, context.tenant_id)
334
357
  amount_in_cents = amount.nil? ? nil : to_cents(amount, currency)
335
358
 
@@ -350,6 +373,13 @@ module Killbill
350
373
  # Retrieve the previous transaction for the same operation and payment id - this is useful to detect dups for example
351
374
  last_transaction = @transaction_model.send("#{operation.to_s}s_from_kb_payment_id", kb_payment_id, context.tenant_id).last
352
375
 
376
+ # Retrieve the linked transaction (authorization to capture, purchase to refund, etc.)
377
+ linked_transaction = nil
378
+ unless linked_transaction_proc.nil?
379
+ linked_transaction = linked_transaction_proc.call(amount_in_cents, options)
380
+ options[:payment_processor_account_id] ||= linked_transaction.payment_processor_account_id
381
+ end
382
+
353
383
  # Filter before all gateways call
354
384
  before_gateways(kb_transaction, last_transaction, payment_source, amount_in_cents, currency, options)
355
385
 
@@ -366,8 +396,8 @@ module Killbill
366
396
  before_gateway(gateway, kb_transaction, last_transaction, payment_source, amount_in_cents, currency, options)
367
397
 
368
398
  # Perform the operation in the gateway
369
- gw_response = gateway_call_proc.call(gateway, payment_source, amount_in_cents, options)
370
- response, transaction = save_response_and_transaction(gw_response, operation, kb_account_id, context.tenant_id, kb_payment_id, kb_payment_transaction_id, operation.upcase, amount_in_cents, currency)
399
+ gw_response = gateway_call_proc.call(gateway, linked_transaction, payment_source, amount_in_cents, options)
400
+ 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)
371
401
 
372
402
  # Filter after each gateway call
373
403
  after_gateway(response, transaction, gw_response)
@@ -460,10 +490,10 @@ module Killbill
460
490
  account.currency
461
491
  end
462
492
 
463
- def save_response_and_transaction(gw_response, api_call, kb_account_id, kb_tenant_id, kb_payment_id=nil, kb_payment_transaction_id=nil, transaction_type=nil, amount_in_cents=0, currency=nil)
493
+ def save_response_and_transaction(gw_response, api_call, kb_account_id, kb_tenant_id, payment_processor_account_id, kb_payment_id=nil, kb_payment_transaction_id=nil, transaction_type=nil, amount_in_cents=0, currency=nil)
464
494
  @logger.warn "Unsuccessful #{api_call}: #{gw_response.message}" unless gw_response.success?
465
495
 
466
- response, transaction = @response_model.create_response_and_transaction(@identifier, @transaction_model, api_call, kb_account_id, kb_payment_id, kb_payment_transaction_id, transaction_type, kb_tenant_id, gw_response, amount_in_cents, currency, {}, @response_model)
496
+ response, transaction = @response_model.create_response_and_transaction(@identifier, @transaction_model, api_call, kb_account_id, kb_payment_id, kb_payment_transaction_id, transaction_type, payment_processor_account_id, kb_tenant_id, gw_response, amount_in_cents, currency, {}, @response_model)
467
497
 
468
498
  @logger.debug "Recorded transaction: #{transaction.inspect}" unless transaction.nil?
469
499
 
@@ -506,7 +536,7 @@ module Killbill
506
536
  end
507
537
 
508
538
  def get_active_merchant_module
509
- ::ActiveMerchant::Billing::Integrations.const_get(@identifier.to_s.camelize)
539
+ ::OffsitePayments.integration(@identifier.to_s.camelize)
510
540
  end
511
541
 
512
542
  def merge_transaction_info_plugins(payment_processor_account_ids, responses, transactions)