killbill-litle 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,18 @@
1
+ language: ruby
2
+
3
+ notifications:
4
+ email:
5
+ - killbilling-dev@googlegroups.com
6
+
7
+ rvm:
8
+ - jruby-19mode
9
+ - jruby-head
10
+
11
+ jdk:
12
+ - openjdk7
13
+ - oraclejdk7
14
+ - openjdk6
15
+
16
+ matrix:
17
+ allow_failures:
18
+ - rvm: jruby-head
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/Jarfile ADDED
@@ -0,0 +1,3 @@
1
+ jar 'com.ning.billing:killbill-api', '0.1.63'
2
+ jar 'com.ning.billing:killbill-util:tests', '0.1.63'
3
+ jar 'javax.servlet:javax.servlet-api', '3.0.1'
data/README.md ADDED
@@ -0,0 +1,7 @@
1
+ [![Build Status](https://travis-ci.org/killbill/killbill-litle-plugin.png)](https://travis-ci.org/killbill/killbill-litle-plugin)
2
+ [![Code Climate](https://codeclimate.com/github/killbill/killbill-litle-plugin.png)](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
@@ -0,0 +1,4 @@
1
+ require 'litle'
2
+ require 'litle/config/application'
3
+
4
+ run Sinatra::Application
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
@@ -0,0 +1,3 @@
1
+ mainClass=Killbill::Litle::PaymentPlugin
2
+ require=litle
3
+ pluginType=PAYMENT
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