killbill-litle 1.10.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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