killbill-stripe 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +39 -0
- data/.travis.yml +19 -0
- data/Gemfile +3 -0
- data/Jarfile +6 -0
- data/NEWS +2 -0
- data/README.md +21 -0
- data/Rakefile +30 -0
- data/VERSION +1 -0
- data/config.ru +4 -0
- data/db/ddl.sql +99 -0
- data/db/schema.rb +99 -0
- data/killbill-stripe.gemspec +42 -0
- data/killbill.properties +3 -0
- data/lib/stripe/api.rb +248 -0
- data/lib/stripe/config/application.rb +76 -0
- data/lib/stripe/config/configuration.rb +38 -0
- data/lib/stripe/config/properties.rb +23 -0
- data/lib/stripe/models/stripe_payment_method.rb +182 -0
- data/lib/stripe/models/stripe_response.rb +242 -0
- data/lib/stripe/models/stripe_transaction.rb +44 -0
- data/lib/stripe/private_api.rb +52 -0
- data/lib/stripe/stripe/gateway.rb +24 -0
- data/lib/stripe/stripe_utils.rb +27 -0
- data/lib/stripe/views/stripejs.erb +88 -0
- data/lib/stripe.rb +30 -0
- data/pom.xml +44 -0
- data/release.sh +41 -0
- data/spec/spec_helper.rb +37 -0
- data/spec/stripe/base_plugin_spec.rb +85 -0
- data/spec/stripe/remote/integration_spec.rb +208 -0
- data/spec/stripe/stripe_payment_method_spec.rb +97 -0
- data/spec/stripe/stripe_response_spec.rb +84 -0
- metadata +222 -0
@@ -0,0 +1,76 @@
|
|
1
|
+
configure do
|
2
|
+
# Usage: rackup -Ilib -E test
|
3
|
+
if development? or test?
|
4
|
+
Killbill::Stripe.initialize! unless Killbill::Stripe.initialized
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
helpers do
|
9
|
+
def plugin
|
10
|
+
Killbill::Stripe::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-stripe
|
24
|
+
get '/plugins/killbill-stripe' do
|
25
|
+
kb_account_id = request.GET['kb_account_id']
|
26
|
+
required_parameter! :kb_account_id, kb_account_id
|
27
|
+
|
28
|
+
# URL to Stripe.js
|
29
|
+
stripejs_url = Killbill::Stripe.config[:stripe][:stripejs_url] || 'https://js.stripe.com/v2/'
|
30
|
+
required_parameter! :stripejs_url, stripejs_url, 'is not configured'
|
31
|
+
|
32
|
+
# Public API key
|
33
|
+
publishable_key = Killbill::Stripe.config[:stripe][:api_publishable_key]
|
34
|
+
required_parameter! :publishable_key, publishable_key, 'is not configured'
|
35
|
+
|
36
|
+
# Redirect
|
37
|
+
success_page = params[:successPage] || '/plugins/killbill-stripe'
|
38
|
+
required_parameter! :success_page, success_page, 'is not specified'
|
39
|
+
|
40
|
+
locals = {
|
41
|
+
:stripejs_url => stripejs_url,
|
42
|
+
:publishable_key => publishable_key,
|
43
|
+
:kb_account_id => kb_account_id,
|
44
|
+
:success_page => success_page
|
45
|
+
}
|
46
|
+
erb :stripejs, :views => File.expand_path(File.dirname(__FILE__) + '/../views'), :locals => locals
|
47
|
+
end
|
48
|
+
|
49
|
+
# This is mainly for testing. Your application should redirect from the Stripe.js checkout above
|
50
|
+
# to a custom endpoint where you call the Kill Bill add payment method JAX-RS API.
|
51
|
+
# If you really want to use this endpoint, you'll have to call the Kill Bill refresh payment methods API
|
52
|
+
# to get a Kill Bill payment method id assigned.
|
53
|
+
post '/plugins/killbill-stripe' do
|
54
|
+
pm = plugin.add_payment_method params
|
55
|
+
|
56
|
+
status 201
|
57
|
+
redirect '/plugins/killbill-stripe/1.0/pms/' + pm.id.to_s
|
58
|
+
end
|
59
|
+
|
60
|
+
# curl -v http://127.0.0.1:9292/plugins/killbill-stripe/1.0/pms/1
|
61
|
+
get '/plugins/killbill-stripe/1.0/pms/:id', :provides => 'json' do
|
62
|
+
if pm = Killbill::Stripe::StripePaymentMethod.find_by_id(params[:id].to_i)
|
63
|
+
pm.to_json
|
64
|
+
else
|
65
|
+
status 404
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# curl -v http://127.0.0.1:9292/plugins/killbill-stripe/1.0/transactions/1
|
70
|
+
get '/plugins/killbill-stripe/1.0/transactions/:id', :provides => 'json' do
|
71
|
+
if transaction = Killbill::Stripe::StripeTransaction.find_by_id(params[:id].to_i)
|
72
|
+
transaction.to_json
|
73
|
+
else
|
74
|
+
status 404
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Killbill::Stripe
|
4
|
+
mattr_reader :logger
|
5
|
+
mattr_reader :config
|
6
|
+
mattr_reader :gateway
|
7
|
+
mattr_reader :kb_apis
|
8
|
+
mattr_reader :stripe_payment_description
|
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}/stripe.yml"
|
17
|
+
@@config = Properties.new(config_file)
|
18
|
+
@@config.parse!
|
19
|
+
@@test = @@config[:stripe][:test]
|
20
|
+
|
21
|
+
@@logger.log_level = Logger::DEBUG if (@@config[:logger] || {})[:debug]
|
22
|
+
|
23
|
+
@@stripe_payment_description = @@config[:stripe][:payment_description]
|
24
|
+
|
25
|
+
@@gateway = Killbill::Stripe::Gateway.from_config(@@config[:stripe])
|
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
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Killbill::Stripe
|
2
|
+
class Properties
|
3
|
+
def initialize(file = 'stripe.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 Stripe plugin. Config is #{@config.inspect}" if @config.blank? || !@config[:stripe] || !@config[:stripe][:api_secret_key]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
module Killbill::Stripe
|
2
|
+
class StripePaymentMethod < ActiveRecord::Base
|
3
|
+
attr_accessible :kb_account_id,
|
4
|
+
:kb_payment_method_id,
|
5
|
+
:stripe_customer_id,
|
6
|
+
:stripe_card_id_or_token,
|
7
|
+
:cc_first_name,
|
8
|
+
:cc_last_name,
|
9
|
+
:cc_type,
|
10
|
+
:cc_exp_month,
|
11
|
+
:cc_exp_year,
|
12
|
+
:cc_last_4,
|
13
|
+
:address1,
|
14
|
+
:address2,
|
15
|
+
:city,
|
16
|
+
:state,
|
17
|
+
:zip,
|
18
|
+
:country
|
19
|
+
|
20
|
+
alias_attribute :external_payment_method_id, :stripe_card_id_or_token
|
21
|
+
|
22
|
+
def self.from_kb_account_id(kb_account_id)
|
23
|
+
find_all_by_kb_account_id_and_is_deleted(kb_account_id, false)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.stripe_customer_id_from_kb_account_id(kb_account_id)
|
27
|
+
pms = from_kb_account_id(kb_account_id)
|
28
|
+
return nil if pms.empty?
|
29
|
+
|
30
|
+
stripe_customer_ids = Set.new
|
31
|
+
pms.each { |pm| stripe_customer_ids << pm.stripe_customer_id }
|
32
|
+
raise "No Stripe customer id found for account #{kb_account_id}" if stripe_customer_ids.empty?
|
33
|
+
raise "Killbill account #{kb_account_id} mapping to multiple Stripe customers: #{stripe_customer_ids}" if stripe_customer_ids.size > 1
|
34
|
+
stripe_customer_ids.first
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.from_kb_payment_method_id(kb_payment_method_id)
|
38
|
+
payment_methods = find_all_by_kb_payment_method_id_and_is_deleted(kb_payment_method_id, false)
|
39
|
+
raise "No payment method found for payment method #{kb_payment_method_id}" if payment_methods.empty?
|
40
|
+
raise "Killbill payment method mapping to multiple active Stripe tokens for payment method #{kb_payment_method_id}" if payment_methods.size > 1
|
41
|
+
payment_methods[0]
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.mark_as_deleted!(kb_payment_method_id)
|
45
|
+
payment_method = from_kb_payment_method_id(kb_payment_method_id)
|
46
|
+
payment_method.is_deleted = true
|
47
|
+
payment_method.save!
|
48
|
+
end
|
49
|
+
|
50
|
+
# VisibleForTesting
|
51
|
+
def self.search_query(search_key, offset = nil, limit = nil)
|
52
|
+
t = self.arel_table
|
53
|
+
|
54
|
+
# Exact match for kb_account_id, kb_payment_method_id, stripe_customer_id, stripe_card_id_or_token, cc_type, cc_exp_month,
|
55
|
+
# cc_exp_year, cc_last_4, state and zip, partial match for the reset
|
56
|
+
where_clause = t[:kb_account_id].eq(search_key)
|
57
|
+
.or(t[:kb_payment_method_id].eq(search_key))
|
58
|
+
.or(t[:stripe_customer_id].eq(search_key))
|
59
|
+
.or(t[:stripe_card_id_or_token].eq(search_key))
|
60
|
+
.or(t[:cc_type].eq(search_key))
|
61
|
+
.or(t[:state].eq(search_key))
|
62
|
+
.or(t[:zip].eq(search_key))
|
63
|
+
.or(t[:cc_first_name].matches("%#{search_key}%"))
|
64
|
+
.or(t[:cc_last_name].matches("%#{search_key}%"))
|
65
|
+
.or(t[:address1].matches("%#{search_key}%"))
|
66
|
+
.or(t[:address2].matches("%#{search_key}%"))
|
67
|
+
.or(t[:city].matches("%#{search_key}%"))
|
68
|
+
.or(t[:country].matches("%#{search_key}%"))
|
69
|
+
|
70
|
+
# Coming from Kill Bill, search_key will always be a String. Check to see if it represents a numeric for numeric-only fields
|
71
|
+
if search_key.is_a?(Numeric) or search_key.to_s =~ /\A[-+]?[0-9]*\.?[0-9]+\Z/
|
72
|
+
where_clause = where_clause.or(t[:cc_exp_month].eq(search_key))
|
73
|
+
.or(t[:cc_exp_year].eq(search_key))
|
74
|
+
.or(t[:cc_last_4].eq(search_key))
|
75
|
+
end
|
76
|
+
|
77
|
+
# Remove garbage payment methods (added in the plugin but not reconcilied with Kill Bill yet)
|
78
|
+
query = t.where(where_clause)
|
79
|
+
.where(t[:kb_payment_method_id].not_eq(nil))
|
80
|
+
.order(t[:id])
|
81
|
+
|
82
|
+
if offset.blank? and limit.blank?
|
83
|
+
# true is for count distinct
|
84
|
+
query.project(t[:id].count(true))
|
85
|
+
else
|
86
|
+
query.skip(offset) unless offset.blank?
|
87
|
+
query.take(limit) unless limit.blank?
|
88
|
+
query.project(t[Arel.star])
|
89
|
+
# Not chainable
|
90
|
+
query.distinct
|
91
|
+
end
|
92
|
+
query
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.search(search_key, offset = 0, limit = 100)
|
96
|
+
pagination = Killbill::Plugin::Model::Pagination.new
|
97
|
+
pagination.current_offset = offset
|
98
|
+
pagination.total_nb_records = self.count_by_sql(self.search_query(search_key))
|
99
|
+
pagination.max_nb_records = self.count
|
100
|
+
pagination.next_offset = (!pagination.total_nb_records.nil? && offset + limit >= pagination.total_nb_records) ? nil : offset + limit
|
101
|
+
# Reduce the limit if the specified value is larger than the number of records
|
102
|
+
actual_limit = [pagination.max_nb_records, limit].min
|
103
|
+
pagination.iterator = StreamyResultSet.new(actual_limit) do |offset,limit|
|
104
|
+
self.find_by_sql(self.search_query(search_key, offset, limit))
|
105
|
+
.map(&:to_payment_method_response)
|
106
|
+
end
|
107
|
+
pagination
|
108
|
+
end
|
109
|
+
|
110
|
+
def to_payment_method_response
|
111
|
+
properties = []
|
112
|
+
properties << create_pm_kv_info('token', external_payment_method_id)
|
113
|
+
properties << create_pm_kv_info('ccName', cc_name)
|
114
|
+
properties << create_pm_kv_info('ccType', cc_type)
|
115
|
+
properties << create_pm_kv_info('ccExpirationMonth', cc_exp_month)
|
116
|
+
properties << create_pm_kv_info('ccExpirationYear', cc_exp_year)
|
117
|
+
properties << create_pm_kv_info('ccLast4', cc_last_4)
|
118
|
+
properties << create_pm_kv_info('address1', address1)
|
119
|
+
properties << create_pm_kv_info('address2', address2)
|
120
|
+
properties << create_pm_kv_info('city', city)
|
121
|
+
properties << create_pm_kv_info('state', state)
|
122
|
+
properties << create_pm_kv_info('zip', zip)
|
123
|
+
properties << create_pm_kv_info('country', country)
|
124
|
+
|
125
|
+
pm_plugin = Killbill::Plugin::Model::PaymentMethodPlugin.new
|
126
|
+
pm_plugin.kb_payment_method_id = kb_payment_method_id
|
127
|
+
pm_plugin.external_payment_method_id = external_payment_method_id
|
128
|
+
pm_plugin.is_default_payment_method = is_default
|
129
|
+
pm_plugin.properties = properties
|
130
|
+
pm_plugin.type = 'CreditCard'
|
131
|
+
pm_plugin.cc_name = cc_name
|
132
|
+
pm_plugin.cc_type = cc_type
|
133
|
+
pm_plugin.cc_expiration_month = cc_exp_month
|
134
|
+
pm_plugin.cc_expiration_year = cc_exp_year
|
135
|
+
pm_plugin.cc_last4 = cc_last_4
|
136
|
+
pm_plugin.address1 = address1
|
137
|
+
pm_plugin.address2 = address2
|
138
|
+
pm_plugin.city = city
|
139
|
+
pm_plugin.state = state
|
140
|
+
pm_plugin.zip = zip
|
141
|
+
pm_plugin.country = country
|
142
|
+
|
143
|
+
pm_plugin
|
144
|
+
end
|
145
|
+
|
146
|
+
def to_payment_method_info_response
|
147
|
+
pm_info_plugin = Killbill::Plugin::Model::PaymentMethodInfoPlugin.new
|
148
|
+
pm_info_plugin.account_id = kb_account_id
|
149
|
+
pm_info_plugin.payment_method_id = kb_payment_method_id
|
150
|
+
pm_info_plugin.is_default = is_default
|
151
|
+
pm_info_plugin.external_payment_method_id = external_payment_method_id
|
152
|
+
pm_info_plugin
|
153
|
+
end
|
154
|
+
|
155
|
+
def is_default
|
156
|
+
# There is a concept of default credit card in Stripe but it's not exposed by the API
|
157
|
+
# Return false to let Kill Bill knows it's authoritative on the matter
|
158
|
+
false
|
159
|
+
end
|
160
|
+
|
161
|
+
def cc_name
|
162
|
+
if cc_first_name and cc_last_name
|
163
|
+
"#{cc_first_name} #{cc_last_name}"
|
164
|
+
elsif cc_first_name
|
165
|
+
cc_first_name
|
166
|
+
elsif cc_last_name
|
167
|
+
cc_last_name
|
168
|
+
else
|
169
|
+
nil
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
private
|
174
|
+
|
175
|
+
def create_pm_kv_info(key, value)
|
176
|
+
prop = Killbill::Plugin::Model::PaymentMethodKVInfo.new
|
177
|
+
prop.key = key
|
178
|
+
prop.value = value
|
179
|
+
prop
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,242 @@
|
|
1
|
+
module Killbill::Stripe
|
2
|
+
class StripeResponse < ActiveRecord::Base
|
3
|
+
has_one :stripe_transaction
|
4
|
+
attr_accessible :api_call,
|
5
|
+
:kb_payment_id,
|
6
|
+
:message,
|
7
|
+
:authorization,
|
8
|
+
:fraud_review,
|
9
|
+
:test,
|
10
|
+
:params_id,
|
11
|
+
:params_object,
|
12
|
+
:params_created,
|
13
|
+
:params_livemode,
|
14
|
+
:params_paid,
|
15
|
+
:params_amount,
|
16
|
+
:params_currency,
|
17
|
+
:params_refunded,
|
18
|
+
:params_card_id,
|
19
|
+
:params_card_object,
|
20
|
+
:params_card_last4,
|
21
|
+
:params_card_type,
|
22
|
+
:params_card_exp_month,
|
23
|
+
:params_card_exp_year,
|
24
|
+
:params_card_fingerprint,
|
25
|
+
:params_card_customer,
|
26
|
+
:params_card_country,
|
27
|
+
:params_card_name,
|
28
|
+
:params_card_address_line1,
|
29
|
+
:params_card_address_line2,
|
30
|
+
:params_card_address_city,
|
31
|
+
:params_card_address_state,
|
32
|
+
:params_card_address_zip,
|
33
|
+
:params_card_address_country,
|
34
|
+
:params_card_cvc_check,
|
35
|
+
:params_card_address_line1_check,
|
36
|
+
:params_card_address_zip_check,
|
37
|
+
:params_captured,
|
38
|
+
:params_refunds,
|
39
|
+
:params_balance_transaction,
|
40
|
+
:params_failure_message,
|
41
|
+
:params_failure_code,
|
42
|
+
:params_amount_refunded,
|
43
|
+
:params_customer,
|
44
|
+
:params_invoice,
|
45
|
+
:params_description,
|
46
|
+
:params_dispute,
|
47
|
+
:params_metadata,
|
48
|
+
:params_error_type,
|
49
|
+
:params_error_message,
|
50
|
+
:avs_result_code,
|
51
|
+
:avs_result_message,
|
52
|
+
:avs_result_street_match,
|
53
|
+
:avs_result_postal_match,
|
54
|
+
:cvv_result_code,
|
55
|
+
:cvv_result_message,
|
56
|
+
:success
|
57
|
+
|
58
|
+
def stripe_txn_id
|
59
|
+
params_id || authorization
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.from_response(api_call, kb_payment_id, response)
|
63
|
+
StripeResponse.new({
|
64
|
+
:api_call => api_call,
|
65
|
+
:kb_payment_id => kb_payment_id,
|
66
|
+
:message => response.message,
|
67
|
+
:authorization => response.authorization,
|
68
|
+
:fraud_review => response.fraud_review?,
|
69
|
+
:test => response.test?,
|
70
|
+
:params_id => extract(response, "id"),
|
71
|
+
:params_object => extract(response, "object"),
|
72
|
+
:params_created => extract(response, "created"),
|
73
|
+
:params_livemode => extract(response, "livemode"),
|
74
|
+
:params_paid => extract(response, "paid"),
|
75
|
+
:params_amount => extract(response, "amount"),
|
76
|
+
:params_currency => extract(response, "currency"),
|
77
|
+
:params_refunded => extract(response, "refunded"),
|
78
|
+
:params_card_id => extract(response, "card", "id"),
|
79
|
+
:params_card_object => extract(response, "card", "object"),
|
80
|
+
:params_card_last4 => extract(response, "card", "last4"),
|
81
|
+
:params_card_type => extract(response, "card", "type"),
|
82
|
+
:params_card_exp_month => extract(response, "card", "exp_month"),
|
83
|
+
:params_card_exp_year => extract(response, "card", "exp_year"),
|
84
|
+
:params_card_fingerprint => extract(response, "card", "fingerprint"),
|
85
|
+
:params_card_customer => extract(response, "card", "customer"),
|
86
|
+
:params_card_country => extract(response, "card", "country"),
|
87
|
+
:params_card_name => extract(response, "card", "name"),
|
88
|
+
:params_card_address_line1 => extract(response, "card", "address_line1"),
|
89
|
+
:params_card_address_line2 => extract(response, "card", "address_line2"),
|
90
|
+
:params_card_address_city => extract(response, "card", "address_city"),
|
91
|
+
:params_card_address_state => extract(response, "card", "address_state"),
|
92
|
+
:params_card_address_zip => extract(response, "card", "address_zip"),
|
93
|
+
:params_card_address_country => extract(response, "card", "address_country"),
|
94
|
+
:params_card_cvc_check => extract(response, "card", "cvc_check"),
|
95
|
+
:params_card_address_line1_check => extract(response, "card", "address_line1_check"),
|
96
|
+
:params_card_address_zip_check => extract(response, "card", "address_zip_check"),
|
97
|
+
:params_captured => extract(response, "captured"),
|
98
|
+
:params_refunds => extract(response, "refunds"),
|
99
|
+
:params_balance_transaction => extract(response, "balance_transaction"),
|
100
|
+
:params_failure_message => extract(response, "failure_message"),
|
101
|
+
:params_failure_code => extract(response, "failure_code"),
|
102
|
+
:params_amount_refunded => extract(response, "amount_refunded"),
|
103
|
+
:params_customer => extract(response, "customer"),
|
104
|
+
:params_invoice => extract(response, "invoice"),
|
105
|
+
:params_description => extract(response, "description"),
|
106
|
+
:params_dispute => extract(response, "dispute"),
|
107
|
+
:params_metadata => extract(response, "metadata"),
|
108
|
+
:params_error_type => extract(response, "error", "type"),
|
109
|
+
:params_error_message => extract(response, "error", "message"),
|
110
|
+
:avs_result_code => response.avs_result.kind_of?(ActiveMerchant::Billing::AVSResult) ? response.avs_result.code : response.avs_result['code'],
|
111
|
+
:avs_result_message => response.avs_result.kind_of?(ActiveMerchant::Billing::AVSResult) ? response.avs_result.message : response.avs_result['message'],
|
112
|
+
:avs_result_street_match => response.avs_result.kind_of?(ActiveMerchant::Billing::AVSResult) ? response.avs_result.street_match : response.avs_result['street_match'],
|
113
|
+
:avs_result_postal_match => response.avs_result.kind_of?(ActiveMerchant::Billing::AVSResult) ? response.avs_result.postal_match : response.avs_result['postal_match'],
|
114
|
+
:cvv_result_code => response.cvv_result.kind_of?(ActiveMerchant::Billing::CVVResult) ? response.cvv_result.code : response.cvv_result['code'],
|
115
|
+
:cvv_result_message => response.cvv_result.kind_of?(ActiveMerchant::Billing::CVVResult) ? response.cvv_result.message : response.cvv_result['message'],
|
116
|
+
:success => response.success?
|
117
|
+
})
|
118
|
+
end
|
119
|
+
|
120
|
+
def to_payment_response
|
121
|
+
to_killbill_response :payment
|
122
|
+
end
|
123
|
+
|
124
|
+
def to_refund_response
|
125
|
+
to_killbill_response :refund
|
126
|
+
end
|
127
|
+
|
128
|
+
# VisibleForTesting
|
129
|
+
def self.search_query(api_call, search_key, offset = nil, limit = nil)
|
130
|
+
t = self.arel_table
|
131
|
+
|
132
|
+
# Exact matches only
|
133
|
+
where_clause = t[:authorization].eq(search_key)
|
134
|
+
.or(t[:params_id].eq(search_key))
|
135
|
+
.or(t[:params_card_id].eq(search_key))
|
136
|
+
|
137
|
+
# Only search successful payments and refunds
|
138
|
+
where_clause = where_clause.and(t[:api_call].eq(api_call))
|
139
|
+
.and(t[:success].eq(true))
|
140
|
+
|
141
|
+
query = t.where(where_clause)
|
142
|
+
.order(t[:id])
|
143
|
+
|
144
|
+
if offset.blank? and limit.blank?
|
145
|
+
# true is for count distinct
|
146
|
+
query.project(t[:id].count(true))
|
147
|
+
else
|
148
|
+
query.skip(offset) unless offset.blank?
|
149
|
+
query.take(limit) unless limit.blank?
|
150
|
+
query.project(t[Arel.star])
|
151
|
+
# Not chainable
|
152
|
+
query.distinct
|
153
|
+
end
|
154
|
+
query
|
155
|
+
end
|
156
|
+
|
157
|
+
def self.search(search_key, offset = 0, limit = 100, type = :payment)
|
158
|
+
api_call = type == :payment ? 'charge' : 'refund'
|
159
|
+
pagination = Killbill::Plugin::Model::Pagination.new
|
160
|
+
pagination.current_offset = offset
|
161
|
+
pagination.total_nb_records = self.count_by_sql(self.search_query(api_call, search_key))
|
162
|
+
pagination.max_nb_records = self.where(:api_call => api_call, :success => true).count
|
163
|
+
pagination.next_offset = (!pagination.total_nb_records.nil? && offset + limit >= pagination.total_nb_records) ? nil : offset + limit
|
164
|
+
# Reduce the limit if the specified value is larger than the number of records
|
165
|
+
actual_limit = [pagination.max_nb_records, limit].min
|
166
|
+
pagination.iterator = StreamyResultSet.new(actual_limit) do |offset,limit|
|
167
|
+
self.find_by_sql(self.search_query(api_call, search_key, offset, limit))
|
168
|
+
.map { |x| type == :payment ? x.to_payment_response : x.to_refund_response }
|
169
|
+
end
|
170
|
+
pagination
|
171
|
+
end
|
172
|
+
|
173
|
+
private
|
174
|
+
|
175
|
+
def to_killbill_response(type)
|
176
|
+
if stripe_transaction.nil?
|
177
|
+
amount_in_cents = nil
|
178
|
+
currency = nil
|
179
|
+
created_date = created_at
|
180
|
+
first_payment_reference_id = nil
|
181
|
+
second_payment_reference_id = nil
|
182
|
+
else
|
183
|
+
amount_in_cents = stripe_transaction.amount_in_cents
|
184
|
+
currency = stripe_transaction.currency
|
185
|
+
created_date = stripe_transaction.created_at
|
186
|
+
first_reference_id = params_balance_transaction
|
187
|
+
second_reference_id = stripe_transaction.stripe_txn_id
|
188
|
+
end
|
189
|
+
|
190
|
+
unless params_created.blank?
|
191
|
+
effective_date = DateTime.strptime(params_created.to_s, "%s") rescue nil
|
192
|
+
end
|
193
|
+
effective_date ||= created_date
|
194
|
+
gateway_error = message || params_error_message
|
195
|
+
gateway_error_code = params_error_type
|
196
|
+
|
197
|
+
if type == :payment
|
198
|
+
p_info_plugin = Killbill::Plugin::Model::PaymentInfoPlugin.new
|
199
|
+
p_info_plugin.kb_payment_id = kb_payment_id
|
200
|
+
p_info_plugin.amount = Money.new(amount_in_cents, currency).to_d if currency
|
201
|
+
p_info_plugin.currency = currency
|
202
|
+
p_info_plugin.created_date = created_date
|
203
|
+
p_info_plugin.effective_date = effective_date
|
204
|
+
p_info_plugin.status = (success ? :PROCESSED : :ERROR)
|
205
|
+
p_info_plugin.gateway_error = gateway_error
|
206
|
+
p_info_plugin.gateway_error_code = gateway_error_code
|
207
|
+
p_info_plugin.first_payment_reference_id = first_reference_id
|
208
|
+
p_info_plugin.second_payment_reference_id = second_reference_id
|
209
|
+
p_info_plugin
|
210
|
+
else
|
211
|
+
r_info_plugin = Killbill::Plugin::Model::RefundInfoPlugin.new
|
212
|
+
r_info_plugin.kb_payment_id = kb_payment_id
|
213
|
+
r_info_plugin.amount = Money.new(amount_in_cents, currency).to_d if currency
|
214
|
+
r_info_plugin.currency = currency
|
215
|
+
r_info_plugin.created_date = created_date
|
216
|
+
r_info_plugin.effective_date = effective_date
|
217
|
+
r_info_plugin.status = (success ? :PROCESSED : :ERROR)
|
218
|
+
r_info_plugin.gateway_error = gateway_error
|
219
|
+
r_info_plugin.gateway_error_code = gateway_error_code
|
220
|
+
r_info_plugin.first_refund_reference_id = first_reference_id
|
221
|
+
r_info_plugin.second_refund_reference_id = second_reference_id
|
222
|
+
r_info_plugin
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def self.extract(response, key1, key2=nil, key3=nil)
|
227
|
+
return nil if response.nil? || response.params.nil?
|
228
|
+
level1 = response.params[key1]
|
229
|
+
|
230
|
+
if level1.nil? or (key2.nil? and key3.nil?)
|
231
|
+
return level1
|
232
|
+
end
|
233
|
+
level2 = level1[key2]
|
234
|
+
|
235
|
+
if level2.nil? or key3.nil?
|
236
|
+
return level2
|
237
|
+
else
|
238
|
+
return level2[key3]
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|