killbill-stripe 0.1.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.
- 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
|