killbill 3.1.11 → 3.1.12

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