killbill-litle 1.0.1
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.
- data/.gitignore +36 -0
- data/.travis.yml +18 -0
- data/Gemfile +3 -0
- data/Jarfile +3 -0
- data/README.md +7 -0
- data/Rakefile +17 -0
- data/VERSION +1 -0
- data/config.ru +4 -0
- data/db/schema.rb +53 -0
- data/killbill-litle.gemspec +45 -0
- data/killbill.properties +3 -0
- data/lib/litle/api.rb +119 -0
- data/lib/litle/config/application.rb +54 -0
- data/lib/litle/config/configuration.rb +30 -0
- data/lib/litle/config/properties.rb +23 -0
- data/lib/litle/litle/gateway.rb +25 -0
- data/lib/litle/litle_utils.rb +22 -0
- data/lib/litle/models/litle_payment_method.rb +32 -0
- data/lib/litle/models/litle_response.rb +122 -0
- data/lib/litle/models/litle_transaction.rb +13 -0
- data/lib/litle/private_api.rb +34 -0
- data/lib/litle/views/paypage.erb +126 -0
- data/lib/litle.rb +27 -0
- data/pom.xml +35 -0
- data/release.sh +11 -0
- data/spec/litle/base_plugin_spec.rb +29 -0
- data/spec/litle/integration_spec.rb +85 -0
- data/spec/litle/utils_spec.rb +10 -0
- data/spec/spec_helper.rb +35 -0
- metadata +276 -0
data/.gitignore
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
*.swp
|
4
|
+
.bundle
|
5
|
+
.config
|
6
|
+
coverage
|
7
|
+
InstalledFiles
|
8
|
+
lib/bundler/man
|
9
|
+
pkg
|
10
|
+
rdoc
|
11
|
+
spec/reports
|
12
|
+
test/tmp
|
13
|
+
test/version_tmp
|
14
|
+
tmp
|
15
|
+
|
16
|
+
# YARD artifacts
|
17
|
+
.yardoc
|
18
|
+
_yardoc
|
19
|
+
doc/
|
20
|
+
|
21
|
+
.jbundler
|
22
|
+
Jarfile.lock
|
23
|
+
Gemfile.lock
|
24
|
+
|
25
|
+
.DS_Store
|
26
|
+
|
27
|
+
# Build directory
|
28
|
+
killbill-litle/
|
29
|
+
|
30
|
+
# Config file
|
31
|
+
litle.yml
|
32
|
+
|
33
|
+
# Testing database
|
34
|
+
test.db
|
35
|
+
|
36
|
+
target
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Jarfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
[](https://travis-ci.org/killbill/killbill-litle-plugin)
|
2
|
+
[](https://codeclimate.com/github/killbill/killbill-litle-plugin)
|
3
|
+
|
4
|
+
killbill-litle-plugin
|
5
|
+
=====================
|
6
|
+
|
7
|
+
Plugin to use Litle & Co. as a gateway
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
|
3
|
+
# Install tasks to build and release the plugin
|
4
|
+
require 'bundler/setup'
|
5
|
+
Bundler::GemHelper.install_tasks
|
6
|
+
|
7
|
+
# Install test tasks
|
8
|
+
require 'rspec/core/rake_task'
|
9
|
+
desc "Run RSpec"
|
10
|
+
RSpec::Core::RakeTask.new
|
11
|
+
|
12
|
+
# Install tasks to package the plugin for Killbill
|
13
|
+
require 'killbill/rake_task'
|
14
|
+
Killbill::PluginHelper.install_tasks
|
15
|
+
|
16
|
+
# Run tests by default
|
17
|
+
task :default => :spec
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.1
|
data/config.ru
ADDED
data/db/schema.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
ActiveRecord::Schema.define(:version => 20130311153635) do
|
4
|
+
create_table "litle_payment_methods", :force => true do |t|
|
5
|
+
t.string "kb_account_id", :null => false
|
6
|
+
t.string "kb_payment_method_id" # NULL before Killbill knows about it
|
7
|
+
t.string "litle_token", :null => false
|
8
|
+
t.boolean "is_deleted", :null => false, :default => false
|
9
|
+
t.datetime "created_at", :null => false
|
10
|
+
t.datetime "updated_at", :null => false
|
11
|
+
end
|
12
|
+
|
13
|
+
create_table "litle_transactions", :force => true do |t|
|
14
|
+
t.integer "litle_response_id", :null => false
|
15
|
+
t.string "api_call", :null => false
|
16
|
+
t.string "kb_payment_id", :null => false
|
17
|
+
t.string "litle_txn_id", :null => false
|
18
|
+
t.integer "amount_in_cents", :null => false
|
19
|
+
t.datetime "created_at", :null => false
|
20
|
+
t.datetime "updated_at", :null => false
|
21
|
+
end
|
22
|
+
|
23
|
+
create_table "litle_responses", :force => true do |t|
|
24
|
+
t.string "api_call", :null => false
|
25
|
+
t.string "kb_payment_id"
|
26
|
+
t.string "message"
|
27
|
+
t.string "authorization"
|
28
|
+
t.boolean "fraud_review"
|
29
|
+
t.boolean "test"
|
30
|
+
t.string "params_litleonelineresponse_message"
|
31
|
+
t.string "params_litleonelineresponse_response"
|
32
|
+
t.string "params_litleonelineresponse_version"
|
33
|
+
t.string "params_litleonelineresponse_xmlns"
|
34
|
+
t.string "params_litleonelineresponse_saleresponse_customer_id"
|
35
|
+
t.string "params_litleonelineresponse_saleresponse_id"
|
36
|
+
t.string "params_litleonelineresponse_saleresponse_report_group"
|
37
|
+
t.string "params_litleonelineresponse_saleresponse_litle_txn_id"
|
38
|
+
t.string "params_litleonelineresponse_saleresponse_order_id"
|
39
|
+
t.string "params_litleonelineresponse_saleresponse_response"
|
40
|
+
t.string "params_litleonelineresponse_saleresponse_response_time"
|
41
|
+
t.string "params_litleonelineresponse_saleresponse_message"
|
42
|
+
t.string "params_litleonelineresponse_saleresponse_auth_code"
|
43
|
+
t.string "avs_result_code"
|
44
|
+
t.string "avs_result_message"
|
45
|
+
t.string "avs_result_street_match"
|
46
|
+
t.string "avs_result_postal_match"
|
47
|
+
t.string "cvv_result_code"
|
48
|
+
t.string "cvv_result_message"
|
49
|
+
t.boolean "success"
|
50
|
+
t.datetime "created_at", :null => false
|
51
|
+
t.datetime "updated_at", :null => false
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
version = File.read(File.expand_path('../VERSION', __FILE__)).strip
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'killbill-litle'
|
5
|
+
s.version = version
|
6
|
+
s.summary = 'Plugin to use Litle & Co. as a gateway.'
|
7
|
+
s.description = 'Killbill payment plugin for Litle & Co.'
|
8
|
+
|
9
|
+
s.required_ruby_version = '>= 1.9.3'
|
10
|
+
|
11
|
+
s.license = 'Apache License (2.0)'
|
12
|
+
|
13
|
+
s.author = 'Killbill core team'
|
14
|
+
s.email = 'killbilling-users@googlegroups.com'
|
15
|
+
s.homepage = 'http://www.killbilling.org'
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.bindir = 'bin'
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
|
+
s.require_paths = ["lib"]
|
22
|
+
|
23
|
+
s.rdoc_options << '--exclude' << '.'
|
24
|
+
|
25
|
+
s.add_dependency 'killbill', '~> 1.0.12'
|
26
|
+
s.add_dependency 'activemerchant', '~> 1.31.1'
|
27
|
+
s.add_dependency 'activerecord', '~> 3.2.1'
|
28
|
+
s.add_dependency 'sinatra', '~> 1.3.4'
|
29
|
+
# LitleOnline gem dependencies
|
30
|
+
s.add_dependency 'LitleOnline', '~> 8.16.0'
|
31
|
+
s.add_dependency 'xml-mapping', '~> 0.9.1'
|
32
|
+
s.add_dependency 'xml-object', '~> 0.9.93'
|
33
|
+
if defined?(JRUBY_VERSION)
|
34
|
+
s.add_dependency 'activerecord-jdbcmysql-adapter', '~> 1.2.9'
|
35
|
+
end
|
36
|
+
|
37
|
+
s.add_development_dependency 'jbundler', '~> 0.4.1'
|
38
|
+
s.add_development_dependency 'rake', '>= 10.0.0'
|
39
|
+
s.add_development_dependency 'rspec', '~> 2.12.0'
|
40
|
+
if defined?(JRUBY_VERSION)
|
41
|
+
s.add_development_dependency 'activerecord-jdbcsqlite3-adapter', '~> 1.2.6'
|
42
|
+
else
|
43
|
+
s.add_development_dependency 'sqlite3', '~> 1.3.7'
|
44
|
+
end
|
45
|
+
end
|
data/killbill.properties
ADDED
data/lib/litle/api.rb
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
module Killbill::Litle
|
2
|
+
class PaymentPlugin < Killbill::Plugin::Payment
|
3
|
+
def start_plugin
|
4
|
+
Killbill::Litle.initialize! "#{@root}/litle.yml", @logger
|
5
|
+
@gateway = Killbill::Litle.gateway
|
6
|
+
|
7
|
+
super
|
8
|
+
|
9
|
+
@logger.info "Killbill::Litle::PaymentPlugin started"
|
10
|
+
end
|
11
|
+
|
12
|
+
# return DB connections to the Pool if required
|
13
|
+
def after_request
|
14
|
+
ActiveRecord::Base.connection.close
|
15
|
+
end
|
16
|
+
|
17
|
+
def process_payment(kb_account_id, kb_payment_id, kb_payment_method_id, amount_in_cents, currency, options = {})
|
18
|
+
# Required argument
|
19
|
+
# Note! The field is limited to 25 chars, so we convert the UUID (in hex) to base64
|
20
|
+
options[:order_id] ||= Utils.compact_uuid kb_payment_id
|
21
|
+
|
22
|
+
# Set a default report group
|
23
|
+
options[:merchant] ||= report_group_for_currency(currency)
|
24
|
+
# Retrieve the Litle token
|
25
|
+
token = get_token(kb_payment_method_id)
|
26
|
+
|
27
|
+
# Go to Litle
|
28
|
+
litle_response = @gateway.purchase amount_in_cents, token, options
|
29
|
+
response = save_response_and_transaction litle_response, :charge, kb_payment_id, amount_in_cents
|
30
|
+
|
31
|
+
response.to_payment_response
|
32
|
+
end
|
33
|
+
|
34
|
+
def process_refund(kb_account_id, kb_payment_id, amount_in_cents, currency, options = {})
|
35
|
+
# Find one successful charge which amount is at least the amount we are trying to refund
|
36
|
+
litle_transaction = LitleTransaction.where("litle_transactions.amount_in_cents >= ?", amount_in_cents).find_last_by_api_call_and_kb_payment_id(:charge, kb_payment_id)
|
37
|
+
raise "Unable to find Litle transaction id for payment #{kb_payment_id}" if litle_transaction.nil?
|
38
|
+
|
39
|
+
# Set a default report group
|
40
|
+
options[:merchant] ||= report_group_for_currency(currency)
|
41
|
+
|
42
|
+
# Go to Litle
|
43
|
+
litle_response = @gateway.credit amount_in_cents, litle_transaction.litle_txn_id, options
|
44
|
+
response = save_response_and_transaction litle_response, :refund, kb_payment_id, amount_in_cents
|
45
|
+
|
46
|
+
response.to_refund_response
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_payment_info(kb_account_id, kb_payment_id, options = {})
|
50
|
+
# We assume the payment is immutable in Litle and only look at our tables since there
|
51
|
+
# doesn't seem to be a Litle API to fetch details for a given transaction.
|
52
|
+
# TODO How can we support Authorization/Sale Recycling?
|
53
|
+
litle_transaction = LitleTransaction.from_kb_payment_id(kb_payment_id)
|
54
|
+
|
55
|
+
litle_transaction.litle_response.to_payment_response
|
56
|
+
end
|
57
|
+
|
58
|
+
def add_payment_method(kb_account_id, kb_payment_method_id, payment_method_props, set_default, options = {})
|
59
|
+
# Set a default report group
|
60
|
+
options[:merchant] ||= report_group_for_account(kb_account_id)
|
61
|
+
|
62
|
+
cc = ActiveMerchant::Billing::CreditCard.new(:number => payment_method_props.value_string('number'), :description => kb_payment_method_id)
|
63
|
+
litle_response = @gateway.store cc, options
|
64
|
+
response = save_response_and_transaction litle_response, :add_payment_method
|
65
|
+
|
66
|
+
LitlePaymentMethod.create :kb_account_id => kb_account_id, :kb_payment_method_id => kb_payment_method_id, :litle_token => response.litle_token
|
67
|
+
end
|
68
|
+
|
69
|
+
def delete_payment_method(kb_account_id, kb_payment_method_id, options = {})
|
70
|
+
LitlePaymentMethod.mark_as_deleted! kb_payment_method_id
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_payment_method_detail(kb_account_id, kb_payment_method_id, options = {})
|
74
|
+
LitlePaymentMethod.from_kb_payment_method_id(kb_payment_method_id).to_payment_method_response
|
75
|
+
end
|
76
|
+
|
77
|
+
def get_payment_methods(kb_account_id, refresh_from_gateway = false, options = {})
|
78
|
+
LitlePaymentMethod.from_kb_account_id(kb_account_id).collect { |pm| pm.to_payment_method_response }
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def report_group_for_account(kb_account_id)
|
84
|
+
=begin
|
85
|
+
account = account_user_api.get_account_by_id(kb_account_id)
|
86
|
+
currency = account.get_currency
|
87
|
+
report_group_for_currency(currency)
|
88
|
+
rescue APINotAvailableError
|
89
|
+
"Default Report Group"
|
90
|
+
=end
|
91
|
+
# STEPH hack until we support making API calls-- with context
|
92
|
+
report_group_for_currency('USD')
|
93
|
+
end
|
94
|
+
|
95
|
+
def report_group_for_currency(currency)
|
96
|
+
"Report Group for #{currency}"
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
def get_token(kb_payment_method_id)
|
101
|
+
LitlePaymentMethod.from_kb_payment_method_id(kb_payment_method_id).litle_token
|
102
|
+
end
|
103
|
+
|
104
|
+
def save_response_and_transaction(litle_response, api_call, kb_payment_id=nil, amount_in_cents=0)
|
105
|
+
@logger.warn "Unsuccessful #{api_call}: #{litle_response.message}" unless litle_response.success?
|
106
|
+
|
107
|
+
# Save the response to our logs
|
108
|
+
response = LitleResponse.from_response(api_call, kb_payment_id, litle_response)
|
109
|
+
response.save!
|
110
|
+
|
111
|
+
if response.success and !response.litle_txn_id.blank?
|
112
|
+
# Record the transaction
|
113
|
+
transaction = response.create_litle_transaction!(:amount_in_cents => amount_in_cents, :api_call => api_call, :kb_payment_id => kb_payment_id, :litle_txn_id => response.litle_txn_id)
|
114
|
+
@logger.debug "Recorded transaction: #{transaction.inspect}"
|
115
|
+
end
|
116
|
+
response
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,54 @@
|
|
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
|
+
end
|
13
|
+
|
14
|
+
# http://127.0.0.1:9292/plugins/killbill-litle
|
15
|
+
get '/plugins/killbill-litle' do
|
16
|
+
locals = {
|
17
|
+
:secure_page_url => Killbill::Litle.config[:litle][:secure_page_url],
|
18
|
+
:paypage_id => Killbill::Litle.config[:litle][:paypage_id],
|
19
|
+
:kb_account_id => request.GET['kb_account_id'] || '1',
|
20
|
+
:merchant_txn_id => request.GET['merchant_txn_id'] || '1',
|
21
|
+
:order_id => request.GET['order_id'] || '1',
|
22
|
+
:report_group => request.GET['report_group'] || 'Default Report Group',
|
23
|
+
}
|
24
|
+
erb :paypage, :views => File.expand_path(File.dirname(__FILE__) + '/../views'), :locals => locals
|
25
|
+
end
|
26
|
+
|
27
|
+
post '/plugins/killbill-litle/checkout' do
|
28
|
+
data = request.POST
|
29
|
+
|
30
|
+
begin
|
31
|
+
pm = plugin.register_token! data['kb_account_id'], data['response_paypage_registration_id']
|
32
|
+
redirect "/plugins/killbill-litle/1.0/pms/#{pm.id}"
|
33
|
+
rescue => e
|
34
|
+
halt 500, {'Content-Type' => 'text/plain'}, "Error: #{e}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# curl -v http://127.0.0.1:9292/plugins/killbill-litle/1.0/pms/1
|
39
|
+
get '/plugins/killbill-litle/1.0/pms/:id', :provides => 'json' do
|
40
|
+
if pm = Killbill::Litle::LitlePaymentMethod.find_by_id(params[:id].to_i)
|
41
|
+
pm.to_json
|
42
|
+
else
|
43
|
+
status 404
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# curl -v http://127.0.0.1:9292/plugins/killbill-litle/1.0/transactions/1
|
48
|
+
get '/plugins/killbill-litle/1.0/transactions/:id', :provides => 'json' do
|
49
|
+
if transaction = Killbill::Litle::LitleTransaction.find_by_id(params[:id].to_i)
|
50
|
+
transaction.to_json
|
51
|
+
else
|
52
|
+
status 404
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Killbill::Litle
|
4
|
+
mattr_reader :logger
|
5
|
+
mattr_reader :config
|
6
|
+
mattr_reader :gateway
|
7
|
+
mattr_reader :initialized
|
8
|
+
mattr_reader :test
|
9
|
+
|
10
|
+
def self.initialize!(config_file='litle.yml', logger=Logger.new(STDOUT))
|
11
|
+
@@logger = logger
|
12
|
+
|
13
|
+
@@config = Properties.new(config_file)
|
14
|
+
@@config.parse!
|
15
|
+
@@test = @@config[:litle][:test]
|
16
|
+
|
17
|
+
@@gateway = Killbill::Litle::Gateway.instance
|
18
|
+
@@gateway.configure(@@config[:litle])
|
19
|
+
|
20
|
+
if defined?(JRUBY_VERSION)
|
21
|
+
# See https://github.com/jruby/activerecord-jdbc-adapter/issues/302
|
22
|
+
require 'jdbc/mysql'
|
23
|
+
Jdbc::MySQL.load_driver(:require) if Jdbc::MySQL.respond_to?(:load_driver)
|
24
|
+
end
|
25
|
+
|
26
|
+
ActiveRecord::Base.establish_connection(@@config[:database])
|
27
|
+
|
28
|
+
@@initialized = true
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,23 @@
|
|
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
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Killbill::Litle
|
2
|
+
class Gateway
|
3
|
+
include Singleton
|
4
|
+
|
5
|
+
def configure(config)
|
6
|
+
if config[:test]
|
7
|
+
ActiveMerchant::Billing::Base.mode = :test
|
8
|
+
end
|
9
|
+
|
10
|
+
if config[:log_file]
|
11
|
+
ActiveMerchant::Billing::LitleGateway.wiredump_device = File.open(config[:log_file], 'w')
|
12
|
+
ActiveMerchant::Billing::LitleGateway.wiredump_device.sync = true
|
13
|
+
end
|
14
|
+
|
15
|
+
@gateway = ActiveMerchant::Billing::LitleGateway.new({ :user => config[:username],
|
16
|
+
:merchant_id => config[:merchant_id],
|
17
|
+
:password => config[:password]
|
18
|
+
})
|
19
|
+
end
|
20
|
+
|
21
|
+
def method_missing(m, *args, &block)
|
22
|
+
@gateway.send(m, *args, &block)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Integer
|
2
|
+
def base(b)
|
3
|
+
self < b ? [self] : (self/b).base(b) + [self%b]
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
module Killbill::Litle
|
8
|
+
class Utils
|
9
|
+
# Use base 62 to be safe on the Litle side
|
10
|
+
BASE62 = ('0'..'9').to_a + ('A'..'Z').to_a + ('a'..'z').to_a
|
11
|
+
|
12
|
+
def self.compact_uuid(uuid)
|
13
|
+
uuid = uuid.gsub(/-/, '')
|
14
|
+
uuid.hex.base(62).map{ |i| BASE62[i].chr } * ''
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.unpack_uuid(base62_uuid)
|
18
|
+
as_hex = base62_uuid.split(//).inject(0) { |i,e| i*62 + BASE62.index(e[0]) }
|
19
|
+
("%x" % as_hex).insert(8, "-").insert(13, "-").insert(18, "-").insert(23, "-")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Killbill::Litle
|
2
|
+
class LitlePaymentMethod < ActiveRecord::Base
|
3
|
+
attr_accessible :kb_account_id, :kb_payment_method_id, :litle_token
|
4
|
+
|
5
|
+
def self.from_kb_account_id(kb_account_id)
|
6
|
+
find_all_by_kb_account_id_and_is_deleted(kb_account_id, false)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.from_kb_payment_method_id(kb_payment_method_id)
|
10
|
+
payment_methods = find_all_by_kb_payment_method_id_and_is_deleted(kb_payment_method_id, false)
|
11
|
+
raise "No payment method found for payment method #{kb_payment_method_id}" if payment_methods.empty?
|
12
|
+
raise "Killbill payment method mapping to multiple active Litle tokens for payment method #{kb_payment_method_id}" if payment_methods.size > 1
|
13
|
+
payment_methods[0]
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.mark_as_deleted!(kb_payment_method_id)
|
17
|
+
payment_method = from_kb_payment_method_id(kb_payment_method_id)
|
18
|
+
payment_method.is_deleted = true
|
19
|
+
payment_method.save!
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_payment_method_response
|
23
|
+
external_payment_method_id = litle_token
|
24
|
+
# No concept of default payment method in Litle
|
25
|
+
is_default = false
|
26
|
+
# No extra information is stored in Litle
|
27
|
+
properties = []
|
28
|
+
|
29
|
+
Killbill::Plugin::PaymentMethodResponse.new external_payment_method_id, is_default, properties
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Killbill::Litle
|
4
|
+
class LitleResponse < ActiveRecord::Base
|
5
|
+
has_one :litle_transaction
|
6
|
+
attr_accessible :api_call,
|
7
|
+
:kb_payment_id,
|
8
|
+
:message,
|
9
|
+
# Either litleToken (registerToken call) or litleTxnId
|
10
|
+
:authorization,
|
11
|
+
:fraud_review,
|
12
|
+
:test,
|
13
|
+
:params_litleonelineresponse_message,
|
14
|
+
:params_litleonelineresponse_response,
|
15
|
+
:params_litleonelineresponse_version,
|
16
|
+
:params_litleonelineresponse_xmlns,
|
17
|
+
:params_litleonelineresponse_saleresponse_customer_id,
|
18
|
+
:params_litleonelineresponse_saleresponse_id,
|
19
|
+
:params_litleonelineresponse_saleresponse_report_group,
|
20
|
+
:params_litleonelineresponse_saleresponse_litle_txn_id,
|
21
|
+
:params_litleonelineresponse_saleresponse_order_id,
|
22
|
+
:params_litleonelineresponse_saleresponse_response,
|
23
|
+
:params_litleonelineresponse_saleresponse_response_time,
|
24
|
+
:params_litleonelineresponse_saleresponse_message,
|
25
|
+
:params_litleonelineresponse_saleresponse_auth_code,
|
26
|
+
:avs_result_code,
|
27
|
+
:avs_result_message,
|
28
|
+
:avs_result_street_match,
|
29
|
+
:avs_result_postal_match,
|
30
|
+
:cvv_result_code,
|
31
|
+
:cvv_result_message,
|
32
|
+
:success
|
33
|
+
|
34
|
+
def litle_token
|
35
|
+
authorization
|
36
|
+
end
|
37
|
+
|
38
|
+
def litle_txn_id
|
39
|
+
potential_litle_txn_id = params_litleonelineresponse_saleresponse_litle_txn_id || authorization
|
40
|
+
if potential_litle_txn_id.blank?
|
41
|
+
nil
|
42
|
+
else
|
43
|
+
# Litle seems to return the precision sometimes along with the txnId (e.g. 053499651324799+19)
|
44
|
+
("%f" % potential_litle_txn_id.split('+')[0]).to_i
|
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 Killbill::Plugin::PaymentResponse
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_refund_response
|
84
|
+
to_killbill_response Killbill::Plugin::RefundResponse
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def to_killbill_response(klass)
|
90
|
+
if litle_transaction.nil?
|
91
|
+
amount_in_cents = nil
|
92
|
+
created_date = created_at
|
93
|
+
else
|
94
|
+
amount_in_cents = litle_transaction.amount_in_cents
|
95
|
+
created_date = litle_transaction.created_at
|
96
|
+
end
|
97
|
+
|
98
|
+
effective_date = params_litleonelineresponse_saleresponse_response_time || created_date
|
99
|
+
status = message == 'Approved' ? Killbill::Plugin::PaymentStatus::SUCCESS : Killbill::Plugin::PaymentStatus::ERROR
|
100
|
+
gateway_error = params_litleonelineresponse_saleresponse_message
|
101
|
+
gateway_error_code = params_litleonelineresponse_saleresponse_response
|
102
|
+
|
103
|
+
klass.new(amount_in_cents, created_date, effective_date, status, gateway_error, gateway_error_code)
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.extract(response, key1, key2=nil, key3=nil)
|
107
|
+
return nil if response.nil? || response.params.nil?
|
108
|
+
level1 = response.params[key1]
|
109
|
+
|
110
|
+
if level1.nil? or (key2.nil? and key3.nil?)
|
111
|
+
return level1
|
112
|
+
end
|
113
|
+
level2 = level1[key2]
|
114
|
+
|
115
|
+
if level2.nil? or key3.nil?
|
116
|
+
return level2
|
117
|
+
else
|
118
|
+
return level2[key3]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Killbill::Litle
|
2
|
+
class LitleTransaction < ActiveRecord::Base
|
3
|
+
belongs_to :litle_response
|
4
|
+
attr_accessible :amount_in_cents, :api_call, :kb_payment_id, :litle_txn_id
|
5
|
+
|
6
|
+
def self.from_kb_payment_id(kb_payment_id)
|
7
|
+
litle_transactions = find_all_by_api_call_and_kb_payment_id(:charge, kb_payment_id)
|
8
|
+
raise "Unable to find Litle transaction id for payment #{kb_payment_id}" if litle_transactions.empty?
|
9
|
+
raise "Killbill payment mapping to multiple Litle transactions for payment #{kb_payment_id}" if litle_transactions.size > 1
|
10
|
+
litle_transactions[0]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|