Class: Killbill::Plugin::ActiveMerchant::ActiveRecord::Response

Inherits:
ActiveRecord::Base
  • Object
show all
Extended by:
Helpers
Defined in:
lib/killbill/helpers/active_merchant/active_record/models/response.rb

Constant Summary collapse

@@quotes_cache =
build_quotes_cache

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Helpers

build_quotes_cache, extract, shared_activerecord_options, with_connection, with_connection_and_transaction

Class Method Details

.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) ⇒ Object



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/killbill/helpers/active_merchant/active_record/models/response.rb', line 50

def self.create_response_and_transaction(identifier, transaction_model, api_call, , kb_payment_id, kb_payment_transaction_id, transaction_type, , kb_tenant_id, gw_response, amount_in_cents, currency, extra_params = {}, model = Response)
  response, transaction, exception = nil

  # Rails wraps all create/save calls in a transaction. To speed things up, create a single transaction for both rows.
  # This has a small gotcha in the unhappy path though (see below).
  with_connection_and_transaction do
    # Save the response to our logs
    response = from_response(api_call, , kb_payment_id, kb_payment_transaction_id, transaction_type, , kb_tenant_id, gw_response, extra_params, model)
    response.save!(shared_activerecord_options)

    transaction = nil
    txn_id      = response.txn_id
    if response.success and !kb_payment_id.blank?
      # Record the transaction
      # Note that we want to avoid throwing an exception here because we don't want to rollback the response row
      begin
        # Originally, we used response.send("build_#{identifier}_transaction"), but the ActiveRecord magic was adding
        # about 20% overhead - instead, we now construct the transaction record manually
        transaction = transaction_model.new(:kb_account_id                => ,
                                            :kb_tenant_id                 => kb_tenant_id,
                                            :amount_in_cents              => amount_in_cents,
                                            :currency                     => currency,
                                            :api_call                     => api_call,
                                            :kb_payment_id                => kb_payment_id,
                                            :kb_payment_transaction_id    => kb_payment_transaction_id,
                                            :transaction_type             => transaction_type,
                                            :payment_processor_account_id => ,
                                            :txn_id                       => txn_id,
                                            "#{identifier}_response_id"   => response.id,
                                            # See Response#from_response
                                            :created_at                   => response.created_at,
                                            :updated_at                   => response.updated_at)
        transaction.save!(shared_activerecord_options)
      rescue => e
        exception = e
      end
    end
  end

  raise exception unless exception.nil?

  return response, transaction
end

.from_kb_payment_id(transaction_model, kb_payment_id, kb_tenant_id) ⇒ Object



94
95
96
97
98
99
100
# File 'lib/killbill/helpers/active_merchant/active_record/models/response.rb', line 94

def self.from_kb_payment_id(transaction_model, kb_payment_id, kb_tenant_id)
  association = transaction_model.table_name.singularize.to_sym
  # Use eager_load to force Rails to issue a single query (see https://github.com/killbill/killbill-plugin-framework-ruby/issues/32)
  eager_load(association)
      .where(:kb_payment_id => kb_payment_id, :kb_tenant_id => kb_tenant_id)
      .order(:created_at)
end

.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) ⇒ Object



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/killbill/helpers/active_merchant/active_record/models/response.rb', line 20

def self.from_response(api_call, , kb_payment_id, kb_payment_transaction_id, transaction_type, , kb_tenant_id, response, extra_params = {}, model = Response)
  # Under high load, Rails sometimes fails to set timestamps. Unclear why...
  # But regardless, for performance reasons, we want to set these timestamps ourselves
  # See ActiveRecord::Timestamp
  current_time = Time.now.utc
  remove_sensitive_data_and_compact(extra_params)
  model.new({
                :api_call                     => api_call,
                :kb_account_id                => ,
                :kb_payment_id                => kb_payment_id,
                :kb_payment_transaction_id    => kb_payment_transaction_id,
                :transaction_type             => transaction_type,
                :payment_processor_account_id => ,
                :kb_tenant_id                 => kb_tenant_id,
                :message                      => response.message,
                :authorization                => response.authorization,
                :fraud_review                 => response.fraud_review?,
                :test                         => response.test?,
                :avs_result_code              => response.avs_result.kind_of?(::ActiveMerchant::Billing::AVSResult) ? response.avs_result.code : response.avs_result['code'],
                :avs_result_message           => response.avs_result.kind_of?(::ActiveMerchant::Billing::AVSResult) ? response.avs_result.message : response.avs_result['message'],
                :avs_result_street_match      => response.avs_result.kind_of?(::ActiveMerchant::Billing::AVSResult) ? response.avs_result.street_match : response.avs_result['street_match'],
                :avs_result_postal_match      => response.avs_result.kind_of?(::ActiveMerchant::Billing::AVSResult) ? response.avs_result.postal_match : response.avs_result['postal_match'],
                :cvv_result_code              => response.cvv_result.kind_of?(::ActiveMerchant::Billing::CVVResult) ? response.cvv_result.code : response.cvv_result['code'],
                :cvv_result_message           => response.cvv_result.kind_of?(::ActiveMerchant::Billing::CVVResult) ? response.cvv_result.message : response.cvv_result['message'],
                :success                      => response.success?,
                :created_at                   => current_time,
                :updated_at                   => current_time
            }.merge!(extra_params))
end

.max_nb_recordsObject



221
222
223
# File 'lib/killbill/helpers/active_merchant/active_record/models/response.rb', line 221

def self.max_nb_records
  self.where(:success => true).count
end

.remove_sensitive_data_and_compact(extra_params) ⇒ Object



211
212
213
214
# File 'lib/killbill/helpers/active_merchant/active_record/models/response.rb', line 211

def self.remove_sensitive_data_and_compact(extra_params)
  extra_params.compact!
  extra_params.delete_if { |k, _| sensitive_fields.include?(k) }
end

.search(search_key, kb_tenant_id, offset = 0, limit = 100) ⇒ Object



197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/killbill/helpers/active_merchant/active_record/models/response.rb', line 197

def self.search(search_key, kb_tenant_id, offset = 0, limit = 100)
  pagination                  = ::Killbill::Plugin::Model::Pagination.new
  pagination.current_offset   = offset
  pagination.total_nb_records = self.count_by_sql(self.search_query(search_key, kb_tenant_id))
  pagination.max_nb_records   = self.max_nb_records
  pagination.next_offset      = (!pagination.total_nb_records.nil? && offset + limit >= pagination.total_nb_records) ? nil : offset + limit
  # Reduce the limit if the specified value is larger than the number of records
  actual_limit                = [pagination.max_nb_records, limit].min
  pagination.iterator         = ::Killbill::Plugin::ActiveMerchant::ActiveRecord::StreamyResultSet.new(actual_limit) do |offset, limit|
    self.find_by_sql(self.search_query(search_key, kb_tenant_id, offset, limit)).map { |x| x.to_transaction_info_plugin }
  end
  pagination
end

.search_query(search_key, kb_tenant_id, offset = nil, limit = nil) ⇒ Object

VisibleForTesting



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/killbill/helpers/active_merchant/active_record/models/response.rb', line 174

def self.search_query(search_key, kb_tenant_id, offset = nil, limit = nil)
  t = self.arel_table

  if kb_tenant_id.nil?
    query = t.where(search_where_clause(t, search_key))
  else
    query = t.where(search_where_clause(t, search_key).and(t[:kb_tenant_id].eq(kb_tenant_id)))
  end

  if offset.blank? and limit.blank?
    # true is for count distinct
    query.project(t[:id].count(true))
  else
    query.order(t[:id])
    query.skip(offset) unless offset.blank?
    query.take(limit) unless limit.blank?
    query.project(t[Arel.star])
    # Not chainable
    query.distinct
  end
  query
end

.search_where_clause(t, search_key) ⇒ Object

Override in your plugin if needed



160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/killbill/helpers/active_merchant/active_record/models/response.rb', line 160

def self.search_where_clause(t, search_key)
  # Exact matches only
  where_clause = t[:kb_payment_id].eq(search_key)
                                  .or(t[:kb_payment_transaction_id].eq(search_key))
                                  .or(t[:message].eq(search_key))
                                  .or(t[:authorization].eq(search_key))

  # Only search successful payments and refunds
  where_clause = where_clause.and(t[:success].eq(true))

  where_clause
end

.sensitive_fieldsObject

Override in your plugin if needed



217
218
219
# File 'lib/killbill/helpers/active_merchant/active_record/models/response.rb', line 217

def self.sensitive_fields
  []
end

Instance Method Details

#authorizationObject



261
262
263
# File 'lib/killbill/helpers/active_merchant/active_record/models/response.rb', line 261

def authorization
  read_attribute(column_for_attribute('authorization').name)
end

#authorization=(auth) ⇒ Object

authorization was the old name (reserved on PostgreSQL) - make sure we support both column names for backward compatibility



257
258
259
# File 'lib/killbill/helpers/active_merchant/active_record/models/response.rb', line 257

def authorization=(auth)
  write_attribute(column_for_attribute('authorization').name, auth)
end

#column_for_attribute(name) ⇒ Object



265
266
267
# File 'lib/killbill/helpers/active_merchant/active_record/models/response.rb', line 265

def column_for_attribute(name)
  name == 'authorization' ? (super('authorisation') || super('authorization')) : super(name)
end

#effective_dateObject

Override in your plugin if needed



241
242
243
# File 'lib/killbill/helpers/active_merchant/active_record/models/response.rb', line 241

def effective_date
  created_at
end

#first_reference_idObject

Override in your plugin if needed



231
232
233
# File 'lib/killbill/helpers/active_merchant/active_record/models/response.rb', line 231

def first_reference_id
  nil
end

#gateway_errorObject

Override in your plugin if needed



246
247
248
# File 'lib/killbill/helpers/active_merchant/active_record/models/response.rb', line 246

def gateway_error
  message
end

#gateway_error_codeObject

Override in your plugin if needed



251
252
253
# File 'lib/killbill/helpers/active_merchant/active_record/models/response.rb', line 251

def gateway_error_code
  nil
end

#second_reference_idObject

Override in your plugin if needed



236
237
238
# File 'lib/killbill/helpers/active_merchant/active_record/models/response.rb', line 236

def second_reference_id
  nil
end

#to_transaction_info_plugin(transaction = nil) ⇒ Object



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/killbill/helpers/active_merchant/active_record/models/response.rb', line 102

def to_transaction_info_plugin(transaction=nil)
  error_details = {}

  if transaction.nil?
    amount_in_cents = nil
    currency        = nil
    created_date    = created_at
    # See Killbill::Plugin::ActiveMerchant::Gateway
    error_details   = JSON.parse(message) rescue {}
  else
    amount_in_cents = transaction.amount_in_cents
    currency        = transaction.currency
    created_date    = transaction.created_at
  end

  # See https://github.com/killbill/killbill-plugin-framework-ruby/issues/43
  # Note: status could also be :PENDING, but it would be handled only in the plugins which need it
  if !error_details['payment_plugin_status'].blank?
    status = error_details['payment_plugin_status'].to_sym
  else
    # Note: (success && transaction.nil?) _could_ happen (see above), but it would be an issue on our side
    # (the payment did go through in the gateway).
    status = success ? :PROCESSED : :ERROR
  end

  t_info_plugin                             = Killbill::Plugin::Model::PaymentTransactionInfoPlugin.new
  t_info_plugin.kb_payment_id               = kb_payment_id
  t_info_plugin.kb_transaction_payment_id   = kb_payment_transaction_id
  t_info_plugin.transaction_type            = transaction_type.nil? ? nil : transaction_type.to_sym
  t_info_plugin.amount                      = Money.new(amount_in_cents, currency).to_d if currency
  t_info_plugin.currency                    = currency
  t_info_plugin.created_date                = created_date
  t_info_plugin.effective_date              = effective_date
  t_info_plugin.status                      = status
  t_info_plugin.gateway_error               = error_details['exception_message'] || gateway_error
  t_info_plugin.gateway_error_code          = error_details['exception_class'] || gateway_error_code
  t_info_plugin.first_payment_reference_id  = first_reference_id
  t_info_plugin.second_payment_reference_id = second_reference_id

  properties = []
  properties << create_plugin_property('payment_processor_account_id', )
  properties << create_plugin_property('message', message)
  properties << create_plugin_property('authorization', authorization)
  properties << create_plugin_property('fraudReview', fraud_review)
  properties << create_plugin_property('test', self.read_attribute(:test))
  properties << create_plugin_property('avsResultCode', avs_result_code)
  properties << create_plugin_property('avsResultMessage', avs_result_message)
  properties << create_plugin_property('avsResultStreetMatch', avs_result_street_match)
  properties << create_plugin_property('avsResultPostalMatch', avs_result_postal_match)
  properties << create_plugin_property('cvvResultCode', cvv_result_code)
  properties << create_plugin_property('cvvResultMessage', cvv_result_message)
  properties << create_plugin_property('success', success)
  t_info_plugin.properties = properties

  t_info_plugin
end

#txn_idObject

Override in your plugin if needed



226
227
228
# File 'lib/killbill/helpers/active_merchant/active_record/models/response.rb', line 226

def txn_id
  authorization
end