killbill-litle 1.10.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/litle.rb CHANGED
@@ -1,29 +1,99 @@
1
+ require 'openssl'
2
+ require 'action_controller'
1
3
  require 'active_record'
2
- require 'activemerchant'
4
+ require 'action_view'
5
+ require 'active_merchant'
6
+ require 'active_support'
3
7
  require 'bigdecimal'
4
8
  require 'money'
9
+ require 'monetize'
10
+ require 'offsite_payments'
5
11
  require 'pathname'
6
12
  require 'sinatra'
7
13
  require 'singleton'
8
14
  require 'yaml'
9
15
 
10
16
  require 'killbill'
11
-
12
- require 'litle/config/configuration'
13
- require 'litle/config/properties'
17
+ require 'killbill/helpers/active_merchant'
14
18
 
15
19
  require 'litle/api'
16
20
  require 'litle/private_api'
17
21
 
18
- require 'litle/models/litle_payment_method'
19
- require 'litle/models/litle_response'
20
- require 'litle/models/litle_transaction'
22
+ require 'litle/models/payment_method'
23
+ require 'litle/models/response'
24
+ require 'litle/models/transaction'
25
+
26
+ # TODO submit patch
27
+ module ActiveMerchant #:nodoc:
28
+ module Billing #:nodoc:
29
+ class LitleGateway < Gateway
30
+ def register_token_request(paypage_registration_id, options = {})
31
+ request = build_xml_request do |doc|
32
+ add_authentication(doc)
33
+ doc.registerTokenRequest(transaction_attributes(options)) do
34
+ doc.orderId((options[:order_id] || '')[0..24])
35
+ doc.paypageRegistrationId(paypage_registration_id)
36
+ end
37
+ end
38
+
39
+ commit(:registerToken, request)
40
+ end
41
+
42
+ def add_auth_purchase_params(doc, money, payment_method, options)
43
+ doc.orderId(truncated_order_id(options))
44
+ doc.amount(money)
45
+ add_order_source(doc, payment_method, options)
46
+ add_billing_address(doc, payment_method, options)
47
+ add_shipping_address(doc, payment_method, options)
48
+ # Pass options to add_payment_method
49
+ add_payment_method(doc, payment_method, options)
50
+ add_pos(doc, payment_method)
51
+ add_descriptor(doc, options)
52
+ end
53
+
54
+ # Add support for PayPage registration ids
55
+ alias old_add_payment_method add_payment_method
56
+
57
+ def add_payment_method(doc, payment_method, options = {})
58
+ if options.has_key?(:paypageRegistrationId)
59
+ doc.paypage do
60
+ doc.paypageRegistrationId(options[:paypage_registration_id])
61
+ doc.expDate(exp_date(payment_method))
62
+ doc.cardValidationNum(payment_method.verification_value)
63
+ end
64
+ else
65
+ old_add_payment_method(doc, payment_method)
66
+ end
67
+ end
68
+
69
+ # Extract attributes
70
+ def parse(kind, xml)
71
+ parsed = {}
72
+
73
+ doc = Nokogiri::XML(xml).remove_namespaces!
74
+ response_nodes = doc.xpath("//litleOnlineResponse/#{kind}Response")
75
+ return {} unless !response_nodes.nil? && response_nodes.size == 1
76
+ response_node = response_nodes[0]
77
+
78
+ # Extract children elements
79
+ response_node.elements.each do |node|
80
+ if (node.elements.empty?)
81
+ parsed[node.name.to_sym] = node.text
82
+ else
83
+ node.elements.each do |childnode|
84
+ name = "#{node.name}_#{childnode.name}"
85
+ parsed[name.to_sym] = childnode.text
86
+ end
87
+ end
88
+ end
21
89
 
22
- require 'litle/litle_utils'
23
- require 'litle/litle/gateway'
90
+ # Extract attributes
91
+ response_node.keys.each do |key|
92
+ parsed[key.to_sym] ||= response_node[key]
93
+ end
24
94
 
25
- class Object
26
- def blank?
27
- respond_to?(:empty?) ? empty? : !self
95
+ parsed
96
+ end
97
+ end
28
98
  end
29
99
  end
data/lib/litle/api.rb CHANGED
@@ -1,250 +1,190 @@
1
- module Killbill::Litle
2
- class PaymentPlugin < Killbill::Plugin::Payment
3
- def start_plugin
4
- Killbill::Litle.initialize! @logger, @conf_dir, @kb_apis
5
-
6
- super
7
-
8
- @logger.info 'Killbill::Litle::PaymentPlugin started'
9
- end
1
+ module Killbill #:nodoc:
2
+ module Litle #:nodoc:
3
+ class PaymentPlugin < ::Killbill::Plugin::ActiveMerchant::PaymentPlugin
4
+
5
+ def initialize
6
+ gateway_builder = Proc.new do |config|
7
+ ::ActiveMerchant::Billing::LitleGateway.new :login => config[:login],
8
+ :password => config[:password],
9
+ :merchant_id => config[:merchant_id]
10
+ end
10
11
 
11
- # return DB connections to the Pool if required
12
- def after_request
13
- ActiveRecord::Base.connection.close
14
- end
12
+ super(gateway_builder,
13
+ :litle,
14
+ ::Killbill::Litle::LitlePaymentMethod,
15
+ ::Killbill::Litle::LitleTransaction,
16
+ ::Killbill::Litle::LitleResponse)
17
+ end
15
18
 
16
- def process_payment(kb_account_id, kb_payment_id, kb_payment_method_id, amount, currency, call_context = nil, options = {})
17
- # Use Money to compute the amount in cents, as it depends on the currency (1 cent of BTC is 1 Satoshi, not 0.01 BTC)
18
- amount_in_cents = Money.new_with_amount(amount, currency).cents.to_i
19
+ def on_event(event)
20
+ # Require to deal with per tenant configuration invalidation
21
+ super(event)
22
+ #
23
+ # Custom event logic could be added below...
24
+ #
25
+ end
19
26
 
20
- # If the payment was already made, just return the status
21
27
  # TODO Should we set the Litle Id field to check for dups (https://www.litle.com/mc-secure/DupeChecking_V1.2.pdf)?
22
- litle_transaction = LitleTransaction.from_kb_payment_id(kb_payment_id) rescue nil
23
- return litle_transaction.litle_response.to_payment_response unless litle_transaction.nil?
24
-
25
- # Required argument
26
- # Note! The field is limited to 25 chars, so we convert the UUID (in hex) to base64
27
- options[:order_id] ||= Utils.compact_uuid kb_payment_id
28
-
29
- # Set the account identifier to the kb_account_id
30
- options[:customer] ||= kb_account_id
31
28
 
32
- # Set a default report group
33
- options[:merchant] ||= report_group_for_currency(currency)
34
- # Retrieve the Litle payment method
35
- litle_pm = LitlePaymentMethod.from_kb_payment_method_id(kb_payment_method_id)
29
+ def authorize_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
30
+ # Pass extra parameters for the gateway here
31
+ options = {}
36
32
 
37
- # Check for currency conversion
38
- actual_amount, actual_currency = convert_amount_currency_if_required(amount_in_cents, currency, kb_payment_id)
33
+ paypage_registration_id = find_value_from_properties(properties, 'paypageRegistrationId')
34
+ options[:paypage_registration_id] = paypage_registration_id unless paypage_registration_id.blank?
39
35
 
40
- # Go to Litle
41
- gateway = Killbill::Litle.gateway_for_currency(actual_currency)
42
- litle_response = gateway.purchase actual_amount, litle_pm.to_litle_card_token, options
43
- response = save_response_and_transaction litle_response, :charge, kb_payment_id, actual_amount, actual_currency
36
+ properties = merge_properties(properties, options)
37
+ super(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
38
+ end
44
39
 
45
- response.to_payment_response
46
- end
40
+ def capture_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
41
+ # Pass extra parameters for the gateway here
42
+ options = {}
47
43
 
44
+ properties = merge_properties(properties, options)
45
+ super(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
46
+ end
48
47
 
49
- def get_payment_info(kb_account_id, kb_payment_id, tenant_context = nil, 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)
48
+ def purchase_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
49
+ # Pass extra parameters for the gateway here
50
+ options = {}
54
51
 
55
- litle_transaction.litle_response.to_payment_response
56
- end
52
+ properties = merge_properties(properties, options)
53
+ super(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
54
+ end
57
55
 
58
- def process_refund(kb_account_id, kb_payment_id, amount, currency, call_context = nil, options = {})
59
- # Use Money to compute the amount in cents, as it depends on the currency (1 cent of BTC is 1 Satoshi, not 0.01 BTC)
60
- amount_in_cents = Money.new_with_amount(amount, currency).cents.to_i
56
+ def void_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, properties, context)
57
+ # Pass extra parameters for the gateway here
58
+ options = {}
61
59
 
62
- # Check for currency conversion
63
- actual_amount, actual_currency = convert_amount_currency_if_required(amount_in_cents, currency, kb_payment_id)
60
+ properties = merge_properties(properties, options)
61
+ super(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, properties, context)
62
+ end
64
63
 
65
- litle_transaction = LitleTransaction.find_candidate_transaction_for_refund(kb_payment_id, actual_amount)
64
+ def credit_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
65
+ # Pass extra parameters for the gateway here
66
+ options = {}
66
67
 
67
- # Set a default report group
68
- options[:merchant] ||= report_group_for_currency(actual_currency)
68
+ properties = merge_properties(properties, options)
69
+ super(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
70
+ end
69
71
 
70
- # Go to Litle
71
- gateway = Killbill::Litle.gateway_for_currency(actual_currency)
72
- litle_response = gateway.credit actual_amount, litle_transaction.litle_txn_id, options
73
- response = save_response_and_transaction litle_response, :refund, kb_payment_id, actual_amount, actual_currency
74
- response.to_refund_response
75
- end
72
+ def refund_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
73
+ # Pass extra parameters for the gateway here
74
+ options = {}
76
75
 
77
- def get_refund_info(kb_account_id, kb_payment_id, tenant_context = nil, options = {})
78
- # We assume the refund is immutable in Litle and only look at our tables since there
79
- # doesn't seem to be a Litle API to fetch details for a given transaction.
80
- litle_transactions = LitleTransaction.refunds_from_kb_payment_id(kb_payment_id)
76
+ properties = merge_properties(properties, options)
77
+ super(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
78
+ end
81
79
 
82
- litle_transactions.map { |t| t.litle_response.to_refund_response }
83
- end
80
+ def get_payment_info(kb_account_id, kb_payment_id, properties, context)
81
+ # Pass extra parameters for the gateway here
82
+ options = {}
84
83
 
85
- def add_payment_method(kb_account_id, kb_payment_method_id, payment_method_props, set_default, call_context = nil, options = {})
86
- # Set a default report group
87
- options[:merchant] ||= report_group_for_account(kb_account_id)
88
-
89
- # TODO Add support for real credit cards
90
- token = find_value_from_payment_method_props payment_method_props, 'paypageRegistrationId'
91
-
92
- currency = account_currency(kb_account_id)
93
- gateway = Killbill::Litle.gateway_for_currency(currency)
94
- litle_response = gateway.store token, options
95
- response = save_response_and_transaction litle_response, :add_payment_method
96
-
97
- if response.success
98
- LitlePaymentMethod.create :kb_account_id => kb_account_id,
99
- :kb_payment_method_id => kb_payment_method_id,
100
- :litle_token => response.litle_token,
101
- :cc_first_name => find_value_from_payment_method_props(payment_method_props, 'ccFirstName'),
102
- :cc_last_name => find_value_from_payment_method_props(payment_method_props, 'ccLastName'),
103
- :cc_type => find_value_from_payment_method_props(payment_method_props, 'ccType'),
104
- :cc_exp_month => find_value_from_payment_method_props(payment_method_props, 'ccExpMonth'),
105
- :cc_exp_year => find_value_from_payment_method_props(payment_method_props, 'ccExpYear'),
106
- :cc_last_4 => find_value_from_payment_method_props(payment_method_props, 'ccLast4'),
107
- :address1 => find_value_from_payment_method_props(payment_method_props, 'address1'),
108
- :address2 => find_value_from_payment_method_props(payment_method_props, 'address2'),
109
- :city => find_value_from_payment_method_props(payment_method_props, 'city'),
110
- :state => find_value_from_payment_method_props(payment_method_props, 'state'),
111
- :zip => find_value_from_payment_method_props(payment_method_props, 'zip'),
112
- :country => find_value_from_payment_method_props(payment_method_props, 'country')
113
- else
114
- raise response.message
84
+ properties = merge_properties(properties, options)
85
+ super(kb_account_id, kb_payment_id, properties, context)
115
86
  end
116
- end
117
-
118
- def delete_payment_method(kb_account_id, kb_payment_method_id, call_context = nil, options = {})
119
- LitlePaymentMethod.mark_as_deleted! kb_payment_method_id
120
- end
121
87
 
122
- def get_payment_method_detail(kb_account_id, kb_payment_method_id, tenant_context = nil, options = {})
123
- LitlePaymentMethod.from_kb_payment_method_id(kb_payment_method_id).to_payment_method_response
124
- end
88
+ def search_payments(search_key, offset, limit, properties, context)
89
+ # Pass extra parameters for the gateway here
90
+ options = {}
125
91
 
126
- def set_default_payment_method(kb_account_id, kb_payment_method_id, call_context = nil, options = {})
127
- # No-op
128
- end
129
-
130
- def get_payment_methods(kb_account_id, refresh_from_gateway = false, call_context = nil, options = {})
131
- LitlePaymentMethod.from_kb_account_id(kb_account_id).collect { |pm| pm.to_payment_method_info_response }
132
- end
92
+ properties = merge_properties(properties, options)
93
+ super(search_key, offset, limit, properties, context)
94
+ end
133
95
 
134
- def reset_payment_methods(kb_account_id, payment_methods)
135
- return if payment_methods.nil?
136
-
137
- litle_pms = LitlePaymentMethod.from_kb_account_id(kb_account_id)
138
-
139
- payment_methods.delete_if do |payment_method_info_plugin|
140
- should_be_deleted = false
141
- litle_pms.each do |litle_pm|
142
- # Do litle_pm and payment_method_info_plugin represent the same Litle payment method?
143
- if litle_pm.external_payment_method_id == payment_method_info_plugin.external_payment_method_id
144
- # Do we already have a kb_payment_method_id?
145
- if litle_pm.kb_payment_method_id == payment_method_info_plugin.payment_method_id
146
- should_be_deleted = true
147
- break
148
- elsif litle_pm.kb_payment_method_id.nil?
149
- # We didn't have the kb_payment_method_id - update it
150
- litle_pm.kb_payment_method_id = payment_method_info_plugin.payment_method_id
151
- should_be_deleted = litle_pm.save
152
- break
153
- # Otherwise the same token points to 2 different kb_payment_method_id. This should never happen,
154
- # but we cowardly will insert a second row below
155
- end
156
- end
96
+ def add_payment_method(kb_account_id, kb_payment_method_id, payment_method_props, set_default, properties, context)
97
+ # paypageRegistrationId is passed via properties
98
+ paypage_registration_id = find_value_from_properties(properties, 'paypageRegistrationId')
99
+ # paypageRegistrationId is passed from the json body
100
+ paypage_registration_id = find_value_from_properties(payment_method_props.properties, 'paypageRegistrationId') if paypage_registration_id.nil?
101
+
102
+ # Pass extra parameters for the gateway here
103
+ options = {}
104
+
105
+ # Register the token
106
+ unless paypage_registration_id.nil?
107
+ payment_processor_account_id = options[:payment_processor_account_id] || :default
108
+ gateway = lookup_gateway(payment_processor_account_id, context.tenant_id)
109
+ litle_response = gateway.register_token_request(paypage_registration_id, options)
110
+ response, _ = save_response_and_transaction(litle_response, :register_token_request, kb_account_id, context.tenant_id, payment_processor_account_id)
111
+ raise response.message unless response.success
112
+
113
+ options[:skip_gw] = true
114
+ options[:token] = litle_response.authorization
157
115
  end
158
116
 
159
- should_be_deleted
117
+ properties = merge_properties(properties, options)
118
+ super(kb_account_id, kb_payment_method_id, payment_method_props, set_default, properties, context)
160
119
  end
161
120
 
162
- # The remaining elements in payment_methods are not in our table (this should never happen?!)
163
- payment_methods.each do |payment_method_info_plugin|
164
- LitlePaymentMethod.create :kb_account_id => payment_method_info_plugin.account_id,
165
- :kb_payment_method_id => payment_method_info_plugin.payment_method_id,
166
- :litle_token => payment_method_info_plugin.external_payment_method_id
167
- end
168
- end
121
+ def delete_payment_method(kb_account_id, kb_payment_method_id, properties, context)
122
+ # Pass extra parameters for the gateway here
123
+ options = {}
169
124
 
170
- def search_payments(search_key, offset = 0, limit = 100, call_context = nil, options = {})
171
- LitleResponse.search(search_key, offset, limit, :payment)
172
- end
125
+ properties = merge_properties(properties, options)
126
+ super(kb_account_id, kb_payment_method_id, properties, context)
127
+ end
173
128
 
174
- def search_refunds(search_key, offset = 0, limit = 100, call_context = nil, options = {})
175
- LitleResponse.search(search_key, offset, limit, :refund)
176
- end
129
+ def get_payment_method_detail(kb_account_id, kb_payment_method_id, properties, context)
130
+ # Pass extra parameters for the gateway here
131
+ options = {}
177
132
 
178
- def search_payment_methods(search_key, offset = 0, limit = 100, call_context = nil, options = {})
179
- LitlePaymentMethod.search(search_key, offset, limit)
180
- end
133
+ properties = merge_properties(properties, options)
134
+ super(kb_account_id, kb_payment_method_id, properties, context)
135
+ end
181
136
 
182
- private
137
+ def set_default_payment_method(kb_account_id, kb_payment_method_id, properties, context)
138
+ # TODO
139
+ end
183
140
 
184
- def convert_amount_currency_if_required(input_amount, input_currency, kb_payment_id)
141
+ def get_payment_methods(kb_account_id, refresh_from_gateway, properties, context)
142
+ # Pass extra parameters for the gateway here
143
+ options = {}
185
144
 
186
- converted_currency = Killbill::Litle.converted_currency(input_currency)
187
- return [input_amount, input_currency] if converted_currency.nil?
145
+ properties = merge_properties(properties, options)
146
+ super(kb_account_id, refresh_from_gateway, properties, context)
147
+ end
188
148
 
189
- kb_payment = @kb_apis.payment_api.get_payment(kb_payment_id, false, @kb_apis.create_context)
149
+ def search_payment_methods(search_key, offset, limit, properties, context)
150
+ # Pass extra parameters for the gateway here
151
+ options = {}
190
152
 
191
- currency_conversion = @kb_apis.currency_conversion_api.get_currency_conversion(converted_currency, kb_payment.effective_date)
192
- rates = currency_conversion.rates
193
- found = rates.select do |r|
194
- r.currency.to_s.upcase.to_sym == input_currency.to_s.upcase.to_sym
153
+ properties = merge_properties(properties, options)
154
+ super(search_key, offset, limit, properties, context)
195
155
  end
196
156
 
197
- if found.nil? || found.empty?
198
- @logger.warn "Failed to find converted currency #{converted_currency} for input currency #{input_currency}"
199
- return [input_amount, input_currency]
157
+ def reset_payment_methods(kb_account_id, payment_methods, properties, context)
158
+ super
200
159
  end
201
160
 
202
- # conversion rounding ?
203
- conversion_rate = found[0].value
204
- output_amount = input_amount * conversion_rate
205
- return [output_amount.to_i, converted_currency]
206
- end
207
-
208
- def find_value_from_payment_method_props(payment_method_props, key)
209
- prop = (payment_method_props.properties.find { |kv| kv.key == key })
210
- prop.nil? ? nil : prop.value
211
- end
161
+ def build_form_descriptor(kb_account_id, descriptor_fields, properties, context)
162
+ # Pass extra parameters for the gateway here
163
+ options = {}
164
+ properties = merge_properties(properties, options)
212
165
 
213
- def report_group_for_account(kb_account_id)
214
- currency = account_currency(kb_account_id)
215
- report_group_for_currency(currency)
216
- rescue => e
217
- 'Default Report Group'
218
- end
219
-
220
- def account_currency(kb_account_id)
221
- account = @kb_apis.account_user_api.get_account_by_id(kb_account_id, @kb_apis.create_context)
222
- #Killbill::Litle.converted_currency(account.currency)
223
- # Use original currency on the account when creating the payment method
224
- account.currency
225
- end
166
+ # Add your custom static hidden tags here
167
+ options = {
168
+ #:token => config[:litle][:token]
169
+ }
170
+ descriptor_fields = merge_properties(descriptor_fields, options)
226
171
 
227
- def report_group_for_currency(currency)
228
- "Report Group for #{currency.to_s}"
229
- end
230
-
231
- def save_response_and_transaction(litle_response, api_call, kb_payment_id=nil, amount_in_cents=0, currency=nil)
232
- @logger.warn "Unsuccessful #{api_call}: #{litle_response.message}" unless litle_response.success?
233
-
234
- # Save the response to our logs
235
- response = LitleResponse.from_response(api_call, kb_payment_id, litle_response)
236
- response.save!
172
+ super(kb_account_id, descriptor_fields, properties, context)
173
+ end
237
174
 
238
- if response.success and !kb_payment_id.blank? and !response.litle_txn_id.blank?
239
- # Record the transaction
240
- transaction = response.create_litle_transaction!(:amount_in_cents => amount_in_cents,
241
- :currency => currency,
242
- :api_call => api_call,
243
- :kb_payment_id => kb_payment_id,
244
- :litle_txn_id => response.litle_txn_id)
245
- @logger.debug "Recorded transaction: #{transaction.inspect}"
175
+ def process_notification(notification, properties, context)
176
+ # Pass extra parameters for the gateway here
177
+ options = {}
178
+ properties = merge_properties(properties, options)
179
+
180
+ super(notification, properties, context) do |gw_notification, service|
181
+ # Retrieve the payment
182
+ # gw_notification.kb_payment_id =
183
+ #
184
+ # Set the response body
185
+ # gw_notification.entity =
186
+ end
246
187
  end
247
- response
248
188
  end
249
189
  end
250
190
  end