killbill-litle 1.10.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,66 +0,0 @@
1
- configure do
2
- # Usage: rackup -Ilib -E test
3
- if development? or test?
4
- Killbill::Litle.initialize! unless Killbill::Litle.initialized
5
- end
6
- end
7
-
8
- helpers do
9
- def plugin
10
- Killbill::Litle::PrivatePaymentPlugin.instance
11
- end
12
-
13
- def required_parameter!(parameter_name, parameter_value, message='must be specified!')
14
- halt 400, "#{parameter_name} #{message}" if parameter_value.blank?
15
- end
16
- end
17
-
18
- after do
19
- # return DB connections to the Pool if required
20
- ActiveRecord::Base.connection.close
21
- end
22
-
23
- # http://127.0.0.1:9292/plugins/killbill-litle
24
- get '/plugins/killbill-litle' do
25
- kb_account_id = request.GET['kb_account_id']
26
- required_parameter! :kb_account_id, kb_account_id
27
-
28
- secure_page_url = Killbill::Litle.config[:litle][:secure_page_url]
29
- required_parameter! :secure_page_url, secure_page_url, 'is not configured'
30
-
31
- # Allow currency override if needed
32
- currency = request.GET['currency'] || plugin.get_currency(kb_account_id)
33
- paypage_id = Killbill::Litle.config[:litle][:paypage_id][currency.to_sym]
34
- required_parameter! :paypage_id, paypage_id, "is not configured for currency #{currency.to_sym}"
35
-
36
- locals = {
37
- :currency => currency,
38
- :secure_page_url => secure_page_url,
39
- :paypage_id => paypage_id,
40
- :kb_account_id => kb_account_id,
41
- :merchant_txn_id => request.GET['merchant_txn_id'] || '1',
42
- :order_id => request.GET['order_id'] || '1',
43
- :report_group => request.GET['report_group'] || 'Default Report Group',
44
- :success_page => params[:successPage],
45
- :failure_page => params[:failurePage]
46
- }
47
- erb :paypage, :views => File.expand_path(File.dirname(__FILE__) + '/../views'), :locals => locals
48
- end
49
-
50
- # curl -v http://127.0.0.1:9292/plugins/killbill-litle/1.0/pms/1
51
- get '/plugins/killbill-litle/1.0/pms/:id', :provides => 'json' do
52
- if pm = Killbill::Litle::LitlePaymentMethod.find_by_id(params[:id].to_i)
53
- pm.to_json
54
- else
55
- status 404
56
- end
57
- end
58
-
59
- # curl -v http://127.0.0.1:9292/plugins/killbill-litle/1.0/transactions/1
60
- get '/plugins/killbill-litle/1.0/transactions/:id', :provides => 'json' do
61
- if transaction = Killbill::Litle::LitleTransaction.find_by_id(params[:id].to_i)
62
- transaction.to_json
63
- else
64
- status 404
65
- end
66
- end
@@ -1,51 +0,0 @@
1
- require 'logger'
2
-
3
- module Killbill::Litle
4
- mattr_reader :logger
5
- mattr_reader :config
6
- mattr_reader :gateways
7
- mattr_reader :currency_conversions
8
- mattr_reader :kb_apis
9
- mattr_reader :initialized
10
- mattr_reader :test
11
-
12
- def self.initialize!(logger=Logger.new(STDOUT), conf_dir=File.expand_path('../../../', File.dirname(__FILE__)), kb_apis = nil)
13
- @@logger = logger
14
- @@kb_apis = kb_apis
15
-
16
- config_file = "#{conf_dir}/litle.yml"
17
- @@config = Properties.new(config_file)
18
- @@config.parse!
19
- @@test = @@config[:litle][:test]
20
-
21
- @@logger.log_level = Logger::DEBUG if (@@config[:logger] || {})[:debug]
22
-
23
- @@gateways = Killbill::Litle::Gateway.from_config(@@config[:litle])
24
-
25
- @@currency_conversions = @@config[:currency_conversions]
26
-
27
- if defined?(JRUBY_VERSION)
28
- # See https://github.com/jruby/activerecord-jdbc-adapter/issues/302
29
- require 'jdbc/mysql'
30
- Jdbc::MySQL.load_driver(:require) if Jdbc::MySQL.respond_to?(:load_driver)
31
- end
32
-
33
- ActiveRecord::Base.establish_connection(@@config[:database])
34
- ActiveRecord::Base.logger = @@logger
35
-
36
- @@initialized = true
37
- end
38
-
39
- def self.converted_currency(currency)
40
- currency_sym = currency.to_s.upcase.to_sym
41
- @@currency_conversions && @@currency_conversions[currency_sym]
42
- end
43
-
44
-
45
- def self.gateway_for_currency(currency)
46
- currency_sym = currency.to_s.upcase.to_sym
47
- gateway = @@gateways[currency_sym]
48
- raise "Gateway for #{currency} not configured!" if gateway.nil?
49
- gateway
50
- end
51
- end
@@ -1,23 +0,0 @@
1
- module Killbill::Litle
2
- class Properties
3
- def initialize(file = 'litle.yml')
4
- @config_file = Pathname.new(file).expand_path
5
- end
6
-
7
- def parse!
8
- raise "#{@config_file} is not a valid file" unless @config_file.file?
9
- @config = YAML.load_file(@config_file.to_s)
10
- validate!
11
- end
12
-
13
- def [](key)
14
- @config[key]
15
- end
16
-
17
- private
18
-
19
- def validate!
20
- raise "Bad configuration for Litle plugin. Config is #{@config.inspect}" if @config.blank? || !@config[:litle] || !@config[:litle][:merchant_id] || !@config[:litle][:password]
21
- end
22
- end
23
- end
@@ -1,32 +0,0 @@
1
- module Killbill::Litle
2
- class Gateway
3
- def self.from_config(config)
4
- if config[:test]
5
- ActiveMerchant::Billing::Base.mode = :test
6
- end
7
-
8
- if config[:log_file]
9
- ActiveMerchant::Billing::LitleGateway.wiredump_device = File.open(config[:log_file], 'w')
10
- ActiveMerchant::Billing::LitleGateway.wiredump_device.sync = true
11
- end
12
-
13
- gateways = {}
14
- config[:merchant_id].each do |currency, mid|
15
- gateways[currency.upcase.to_sym] = Gateway.new(currency, config[:username], config[:password], mid)
16
- end
17
- gateways
18
- end
19
-
20
- def initialize(currency, user, password, merchant_id)
21
- @currency = currency
22
- @gateway = ActiveMerchant::Billing::LitleGateway.new({:user => user,
23
- :password => password,
24
- :merchant_id => merchant_id
25
- })
26
- end
27
-
28
- def method_missing(m, *args, &block)
29
- @gateway.send(m, *args, &block)
30
- end
31
- end
32
- end
@@ -1,167 +0,0 @@
1
- module Killbill::Litle
2
- class LitlePaymentMethod < ActiveRecord::Base
3
- attr_accessible :kb_account_id,
4
- :kb_payment_method_id,
5
- :litle_token,
6
- :cc_first_name,
7
- :cc_last_name,
8
- :cc_type,
9
- :cc_exp_month,
10
- :cc_exp_year,
11
- :cc_last_4,
12
- :address1,
13
- :address2,
14
- :city,
15
- :state,
16
- :zip,
17
- :country
18
-
19
- alias_attribute :external_payment_method_id, :litle_token
20
-
21
- def self.from_kb_account_id(kb_account_id)
22
- find_all_by_kb_account_id_and_is_deleted(kb_account_id, false)
23
- end
24
-
25
- def self.from_kb_payment_method_id(kb_payment_method_id)
26
- payment_methods = find_all_by_kb_payment_method_id_and_is_deleted(kb_payment_method_id, false)
27
- raise "No payment method found for payment method #{kb_payment_method_id}" if payment_methods.empty?
28
- raise "Killbill payment method mapping to multiple active Litle tokens for payment method #{kb_payment_method_id}" if payment_methods.size > 1
29
- payment_methods[0]
30
- end
31
-
32
- def self.mark_as_deleted!(kb_payment_method_id)
33
- payment_method = from_kb_payment_method_id(kb_payment_method_id)
34
- payment_method.is_deleted = true
35
- payment_method.save!
36
- end
37
-
38
- # VisibleForTesting
39
- def self.search_query(search_key, offset = nil, limit = nil)
40
- t = self.arel_table
41
-
42
- # Exact match for litle_token, cc_type, cc_exp_month, cc_exp_year, cc_last_4, state and zip, partial match for the reset
43
- where_clause = t[:litle_token].eq(search_key)
44
- .or(t[:cc_type].eq(search_key))
45
- .or(t[:state].eq(search_key))
46
- .or(t[:zip].eq(search_key))
47
- .or(t[:cc_first_name].matches("%#{search_key}%"))
48
- .or(t[:cc_last_name].matches("%#{search_key}%"))
49
- .or(t[:address1].matches("%#{search_key}%"))
50
- .or(t[:address2].matches("%#{search_key}%"))
51
- .or(t[:city].matches("%#{search_key}%"))
52
- .or(t[:country].matches("%#{search_key}%"))
53
-
54
- # Coming from Kill Bill, search_key will always be a String. Check to see if it represents a numeric for numeric-only fields
55
- if search_key.is_a?(Numeric) or search_key.to_s =~ /\A[-+]?[0-9]*\.?[0-9]+\Z/
56
- where_clause = where_clause.or(t[:cc_exp_month].eq(search_key))
57
- .or(t[:cc_exp_year].eq(search_key))
58
- .or(t[:cc_last_4].eq(search_key))
59
- end
60
-
61
- query = t.where(where_clause)
62
- .order(t[:id])
63
-
64
- if offset.blank? and limit.blank?
65
- # true is for count distinct
66
- query.project(t[:id].count(true))
67
- else
68
- query.skip(offset) unless offset.blank?
69
- query.take(limit) unless limit.blank?
70
- query.project(t[Arel.star])
71
- # Not chainable
72
- query.distinct
73
- end
74
- query
75
- end
76
-
77
- def self.search(search_key, offset = 0, limit = 100)
78
- pagination = Killbill::Plugin::Model::Pagination.new
79
- pagination.current_offset = offset
80
- pagination.total_nb_records = self.count_by_sql(self.search_query(search_key))
81
- pagination.max_nb_records = self.count
82
- pagination.next_offset = (!pagination.total_nb_records.nil? && offset + limit >= pagination.total_nb_records) ? nil : offset + limit
83
- # Reduce the limit if the specified value is larger than the number of records
84
- actual_limit = [pagination.max_nb_records, limit].min
85
- pagination.iterator = StreamyResultSet.new(actual_limit) do |offset,limit|
86
- self.find_by_sql(self.search_query(search_key, offset, limit))
87
- .map(&:to_payment_method_response)
88
- end
89
- pagination
90
- end
91
-
92
- def to_payment_method_response
93
- properties = []
94
- properties << create_pm_kv_info('token', external_payment_method_id)
95
- properties << create_pm_kv_info('ccName', cc_name)
96
- properties << create_pm_kv_info('ccType', cc_type)
97
- properties << create_pm_kv_info('ccExpirationMonth', cc_exp_month)
98
- properties << create_pm_kv_info('ccExpirationYear', cc_exp_year)
99
- properties << create_pm_kv_info('ccLast4', cc_last_4)
100
- properties << create_pm_kv_info('address1', address1)
101
- properties << create_pm_kv_info('address2', address2)
102
- properties << create_pm_kv_info('city', city)
103
- properties << create_pm_kv_info('state', state)
104
- properties << create_pm_kv_info('zip', zip)
105
- properties << create_pm_kv_info('country', country)
106
-
107
- pm_plugin = Killbill::Plugin::Model::PaymentMethodPlugin.new
108
- pm_plugin.kb_payment_method_id = kb_payment_method_id
109
- pm_plugin.external_payment_method_id = external_payment_method_id
110
- pm_plugin.is_default_payment_method = is_default
111
- pm_plugin.properties = properties
112
- pm_plugin.type = 'CreditCard'
113
- pm_plugin.cc_name = cc_name
114
- pm_plugin.cc_type = cc_type
115
- pm_plugin.cc_expiration_month = cc_exp_month
116
- pm_plugin.cc_expiration_year = cc_exp_year
117
- pm_plugin.cc_last4 = cc_last_4
118
- pm_plugin.address1 = address1
119
- pm_plugin.address2 = address2
120
- pm_plugin.city = city
121
- pm_plugin.state = state
122
- pm_plugin.zip = zip
123
- pm_plugin.country = country
124
-
125
- pm_plugin
126
- end
127
-
128
- def to_payment_method_info_response
129
- pm_info_plugin = Killbill::Plugin::Model::PaymentMethodInfoPlugin.new
130
- pm_info_plugin.account_id = kb_account_id
131
- pm_info_plugin.payment_method_id = kb_payment_method_id
132
- pm_info_plugin.is_default = is_default
133
- pm_info_plugin.external_payment_method_id = external_payment_method_id
134
- pm_info_plugin
135
- end
136
-
137
- def is_default
138
- # No concept of default payment method in Litle
139
- false
140
- end
141
-
142
- def cc_name
143
- if cc_first_name and cc_last_name
144
- "#{cc_first_name} #{cc_last_name}"
145
- elsif cc_first_name
146
- cc_first_name
147
- elsif cc_last_name
148
- cc_last_name
149
- else
150
- nil
151
- end
152
- end
153
-
154
- def to_litle_card_token
155
- ActiveMerchant::Billing::LitleGateway::LitleCardToken.new(:token => litle_token, :month => cc_exp_month, :year => cc_exp_year)
156
- end
157
-
158
- private
159
-
160
- def create_pm_kv_info(key, value)
161
- prop = Killbill::Plugin::Model::PaymentMethodKVInfo.new
162
- prop.key = key
163
- prop.value = value
164
- prop
165
- end
166
- end
167
- end
@@ -1,199 +0,0 @@
1
- module Killbill::Litle
2
- class LitleResponse < ActiveRecord::Base
3
- has_one :litle_transaction
4
- attr_accessible :api_call,
5
- :kb_payment_id,
6
- :message,
7
- # Either litleToken (registerToken call) or litleTxnId
8
- :authorization,
9
- :fraud_review,
10
- :test,
11
- :params_litleonelineresponse_message,
12
- :params_litleonelineresponse_response,
13
- :params_litleonelineresponse_version,
14
- :params_litleonelineresponse_xmlns,
15
- :params_litleonelineresponse_saleresponse_customer_id,
16
- :params_litleonelineresponse_saleresponse_id,
17
- :params_litleonelineresponse_saleresponse_report_group,
18
- :params_litleonelineresponse_saleresponse_litle_txn_id,
19
- :params_litleonelineresponse_saleresponse_order_id,
20
- :params_litleonelineresponse_saleresponse_response,
21
- :params_litleonelineresponse_saleresponse_response_time,
22
- :params_litleonelineresponse_saleresponse_message,
23
- :params_litleonelineresponse_saleresponse_auth_code,
24
- :avs_result_code,
25
- :avs_result_message,
26
- :avs_result_street_match,
27
- :avs_result_postal_match,
28
- :cvv_result_code,
29
- :cvv_result_message,
30
- :success
31
-
32
- def litle_token
33
- authorization
34
- end
35
-
36
- def litle_txn_id
37
- potential_litle_txn_id = params_litleonelineresponse_saleresponse_litle_txn_id || authorization
38
- if potential_litle_txn_id.blank?
39
- nil
40
- else
41
- # Litle seems to return the precision sometimes along with the txnId (e.g. 053499651324799+19)
42
- # And sometimes it adds a ;credit
43
- # TODO Figure out WTF is going on here
44
- potential_litle_txn_id.to_s.split(';')[0].split('+')[0]
45
- end
46
- end
47
-
48
- def self.from_response(api_call, kb_payment_id, response)
49
- LitleResponse.new({
50
- :api_call => api_call,
51
- :kb_payment_id => kb_payment_id,
52
- :message => response.message,
53
- :authorization => response.authorization,
54
- :fraud_review => response.fraud_review?,
55
- :test => response.test?,
56
- :params_litleonelineresponse_message => extract(response, "litleOnlineResponse", "message"),
57
- :params_litleonelineresponse_response => extract(response, "litleOnlineResponse", "response"),
58
- :params_litleonelineresponse_version => extract(response, "litleOnlineResponse", "version"),
59
- :params_litleonelineresponse_xmlns => extract(response, "litleOnlineResponse", "xmlns"),
60
- :params_litleonelineresponse_saleresponse_customer_id => extract(response, "litleOnlineResponse", "saleResponse", "customerId"),
61
- :params_litleonelineresponse_saleresponse_id => extract(response, "litleOnlineResponse", "saleResponse", "id"),
62
- :params_litleonelineresponse_saleresponse_report_group => extract(response, "litleOnlineResponse", "saleResponse", "reportGroup"),
63
- :params_litleonelineresponse_saleresponse_litle_txn_id => extract(response, "litleOnlineResponse", "saleResponse", "litleTxnId"),
64
- :params_litleonelineresponse_saleresponse_order_id => extract(response, "litleOnlineResponse", "saleResponse", "orderId"),
65
- :params_litleonelineresponse_saleresponse_response => extract(response, "litleOnlineResponse", "saleResponse", "response"),
66
- :params_litleonelineresponse_saleresponse_response_time => extract(response, "litleOnlineResponse", "saleResponse", "responseTime"),
67
- :params_litleonelineresponse_saleresponse_message => extract(response, "litleOnlineResponse", "saleResponse", "message"),
68
- :params_litleonelineresponse_saleresponse_auth_code => extract(response, "litleOnlineResponse", "saleResponse", "authCode"),
69
- :avs_result_code => response.avs_result.kind_of?(ActiveMerchant::Billing::AVSResult) ? response.avs_result.code : response.avs_result['code'],
70
- :avs_result_message => response.avs_result.kind_of?(ActiveMerchant::Billing::AVSResult) ? response.avs_result.message : response.avs_result['message'],
71
- :avs_result_street_match => response.avs_result.kind_of?(ActiveMerchant::Billing::AVSResult) ? response.avs_result.street_match : response.avs_result['street_match'],
72
- :avs_result_postal_match => response.avs_result.kind_of?(ActiveMerchant::Billing::AVSResult) ? response.avs_result.postal_match : response.avs_result['postal_match'],
73
- :cvv_result_code => response.cvv_result.kind_of?(ActiveMerchant::Billing::CVVResult) ? response.cvv_result.code : response.cvv_result['code'],
74
- :cvv_result_message => response.cvv_result.kind_of?(ActiveMerchant::Billing::CVVResult) ? response.cvv_result.message : response.cvv_result['message'],
75
- :success => response.success?
76
- })
77
- end
78
-
79
- def to_payment_response
80
- to_killbill_response :payment
81
- end
82
-
83
- def to_refund_response
84
- to_killbill_response :refund
85
- end
86
-
87
- # VisibleForTesting
88
- def self.search_query(api_call, search_key, offset = nil, limit = nil)
89
- t = self.arel_table
90
-
91
- # Exact matches only
92
- where_clause = t[:params_litleonelineresponse_saleresponse_id].eq(search_key)
93
- .or(t[:params_litleonelineresponse_saleresponse_litle_txn_id].eq(search_key))
94
- .or(t[:params_litleonelineresponse_saleresponse_order_id].eq(search_key))
95
- .or(t[:params_litleonelineresponse_saleresponse_auth_code].eq(search_key))
96
-
97
- # Only search successful payments and refunds
98
- where_clause = where_clause.and(t[:api_call].eq(api_call))
99
- .and(t[:success].eq(true))
100
-
101
- query = t.where(where_clause)
102
- .order(t[:id])
103
-
104
- if offset.blank? and limit.blank?
105
- # true is for count distinct
106
- query.project(t[:id].count(true))
107
- else
108
- query.skip(offset) unless offset.blank?
109
- query.take(limit) unless limit.blank?
110
- query.project(t[Arel.star])
111
- # Not chainable
112
- query.distinct
113
- end
114
- query
115
- end
116
-
117
- def self.search(search_key, offset = 0, limit = 100, type = :payment)
118
- api_call = type == :payment ? 'charge' : 'refund'
119
- pagination = Killbill::Plugin::Model::Pagination.new
120
- pagination.current_offset = offset
121
- pagination.total_nb_records = self.count_by_sql(self.search_query(api_call, search_key))
122
- pagination.max_nb_records = self.where(:api_call => api_call, :success => true).count
123
- pagination.next_offset = (!pagination.total_nb_records.nil? && offset + limit >= pagination.total_nb_records) ? nil : offset + limit
124
- # Reduce the limit if the specified value is larger than the number of records
125
- actual_limit = [pagination.max_nb_records, limit].min
126
- pagination.iterator = StreamyResultSet.new(actual_limit) do |offset,limit|
127
- self.find_by_sql(self.search_query(api_call, search_key, offset, limit))
128
- .map { |x| type == :payment ? x.to_payment_response : x.to_refund_response }
129
- end
130
- pagination
131
- end
132
-
133
- private
134
-
135
- def to_killbill_response(type)
136
- if litle_transaction.nil?
137
- amount_in_cents = nil
138
- currency = nil
139
- created_date = created_at
140
- first_payment_reference_id = nil
141
- second_payment_reference_id = nil
142
- else
143
- amount_in_cents = litle_transaction.amount_in_cents
144
- currency = litle_transaction.currency
145
- created_date = litle_transaction.created_at
146
- first_reference_id = params_litleonelineresponse_saleresponse_id
147
- second_reference_id = litle_transaction.litle_txn_id
148
- end
149
-
150
- effective_date = params_litleonelineresponse_saleresponse_response_time || created_date
151
- gateway_error = message || params_litleonelineresponse_saleresponse_message
152
- gateway_error_code = params_litleonelineresponse_saleresponse_response
153
-
154
- if type == :payment
155
- p_info_plugin = Killbill::Plugin::Model::PaymentInfoPlugin.new
156
- p_info_plugin.kb_payment_id = kb_payment_id
157
- p_info_plugin.amount = Money.new(amount_in_cents, currency).to_d if currency
158
- p_info_plugin.currency = currency
159
- p_info_plugin.created_date = created_date
160
- p_info_plugin.effective_date = effective_date
161
- p_info_plugin.status = (success ? :PROCESSED : :ERROR)
162
- p_info_plugin.gateway_error = gateway_error
163
- p_info_plugin.gateway_error_code = gateway_error_code
164
- p_info_plugin.first_payment_reference_id = first_reference_id
165
- p_info_plugin.second_payment_reference_id = second_reference_id
166
- p_info_plugin
167
- else
168
- r_info_plugin = Killbill::Plugin::Model::RefundInfoPlugin.new
169
- r_info_plugin.kb_payment_id = kb_payment_id
170
- r_info_plugin.amount = Money.new(amount_in_cents, currency).to_d if currency
171
- r_info_plugin.currency = currency
172
- r_info_plugin.created_date = created_date
173
- r_info_plugin.effective_date = effective_date
174
- r_info_plugin.status = (success ? :PROCESSED : :ERROR)
175
- r_info_plugin.gateway_error = gateway_error
176
- r_info_plugin.gateway_error_code = gateway_error_code
177
- r_info_plugin.first_refund_reference_id = first_reference_id
178
- r_info_plugin.second_refund_reference_id = second_reference_id
179
- r_info_plugin
180
- end
181
- end
182
-
183
- def self.extract(response, key1, key2=nil, key3=nil)
184
- return nil if response.nil? || response.params.nil?
185
- level1 = response.params[key1]
186
-
187
- if level1.nil? or (key2.nil? and key3.nil?)
188
- return level1
189
- end
190
- level2 = level1[key2]
191
-
192
- if level2.nil? or key3.nil?
193
- return level2
194
- else
195
- return level2[key3]
196
- end
197
- end
198
- end
199
- end