module Killbill #:nodoc: module PayuLatam #:nodoc: class PaymentPlugin < ::Killbill::Plugin::ActiveMerchant::PaymentPlugin def initialize gateway_builder = Proc.new do |config| ::OffsitePayments.mode = config[:test] ? :test : :production ::ActiveMerchant::Billing::PayULatamGateway.new :api_login => config[:api_login], :api_key => config[:api_key], :country_account_id => config[:country_account_id], :merchant_id => config[:merchant_id] end super(gateway_builder, :payu_latam, ::Killbill::PayuLatam::PayuLatamPaymentMethod, ::Killbill::PayuLatam::PayuLatamTransaction, ::Killbill::PayuLatam::PayuLatamResponse) end def on_event(event) # Require to deal with per tenant configuration invalidation super(event) # # Custom event logic could be added below... # end def authorize_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context) # Pass extra parameters for the gateway here options = {} add_required_options(properties, options) properties = merge_properties(properties, options) super(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context) end def capture_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context) # Pass extra parameters for the gateway here options = {} add_required_options(properties, options) properties = merge_properties(properties, options) super(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context) end def purchase_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context) # Direct payment? if find_value_from_properties(properties, 'from_hpp') != 'true' options = {} add_required_options(properties, options) properties = merge_properties(properties, options) super(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context) else # Nope, HPP payu_order_id = find_value_from_properties(properties, 'payu_order_id') payu_transaction_id = find_value_from_properties(properties, 'payu_transaction_id') payment_processor_account_id = find_value_from_properties(properties, 'payment_processor_account_id') # Record a response row, to keep track of the created vouchers in PayU response = @response_model.create(:api_call => :build_form_descriptor, :kb_account_id => kb_account_id, :kb_payment_id => kb_payment_id, :kb_payment_transaction_id => kb_payment_transaction_id, :transaction_type => :PURCHASE, :authorization => [payu_order_id, payu_transaction_id].join(';'), :payment_processor_account_id => payment_processor_account_id, :kb_tenant_id => context.tenant_id, :success => true, :created_at => Time.now.utc, :updated_at => Time.now.utc) # Get the payment status from PayU (we are required to fetch the status from PayU within 7 minutes of the URL creation) get_payment_transaction_info_from_payu(kb_payment_id, kb_payment_transaction_id, amount, currency, properties_to_hash(properties), context.tenant_id, response) end end def void_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, properties, context) # Pass extra parameters for the gateway here options = {} add_required_options(properties, options) properties = merge_properties(properties, options) super(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, properties, context) end def credit_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context) # Pass extra parameters for the gateway here options = {} add_required_options(properties, options) properties = merge_properties(properties, options) super(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context) end def refund_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context) # Pass extra parameters for the gateway here options = {} add_required_options(properties, options) properties = merge_properties(properties, options) super(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context) end def get_payment_info(kb_account_id, kb_payment_id, properties, context) t_info_plugins = super(kb_account_id, kb_payment_id, properties, context) t_info_plugins.reject! { |t_info_plugin| t_info_plugin.status == :UNDEFINED } t_info_plugins = get_payment_info_from_payu(kb_payment_id, properties_to_hash(properties), context) if t_info_plugins.empty? t_info_plugins end def search_payments(search_key, offset, limit, properties, context) # Pass extra parameters for the gateway here options = {} properties = merge_properties(properties, options) super(search_key, offset, limit, properties, context) end def add_payment_method(kb_account_id, kb_payment_method_id, payment_method_props, set_default, properties, context) # Pass extra parameters for the gateway here options = { :payer_user_id => kb_account_id } add_required_options(properties, options) properties = merge_properties(properties, options) super(kb_account_id, kb_payment_method_id, payment_method_props, set_default, properties, context) end def delete_payment_method(kb_account_id, kb_payment_method_id, properties, context) # Pass extra parameters for the gateway here options = {} properties = merge_properties(properties, options) super(kb_account_id, kb_payment_method_id, properties, context) end def get_payment_method_detail(kb_account_id, kb_payment_method_id, properties, context) # Pass extra parameters for the gateway here options = {} properties = merge_properties(properties, options) super(kb_account_id, kb_payment_method_id, properties, context) end def set_default_payment_method(kb_account_id, kb_payment_method_id, properties, context) # TODO end def get_payment_methods(kb_account_id, refresh_from_gateway, properties, context) # Pass extra parameters for the gateway here options = {} properties = merge_properties(properties, options) super(kb_account_id, refresh_from_gateway, properties, context) end def search_payment_methods(search_key, offset, limit, properties, context) # Pass extra parameters for the gateway here options = {} properties = merge_properties(properties, options) super(search_key, offset, limit, properties, context) end def reset_payment_methods(kb_account_id, payment_methods, properties, context) super end def build_form_descriptor(kb_account_id, descriptor_fields, properties, context) payu_hpp_credentials = payu_hpp_credentials(properties_to_hash(properties)) # Prepare the PayU request # Available fields: # * :expiration_date # * :customer (:name, :email, :phone, :dni_number) # * :billing_address (:address1, :city, :state, :country) form_fields = properties_to_hash(descriptor_fields) payu_reference_code = form_fields[:externalKey] || SecureRandom.uuid options = { # Required fields :order_id => payu_reference_code, :description => form_fields['description'] || 'Kill Bill payment', :payment_method => form_fields['paymentMethod'] || 'BALOTO', }.merge(payu_hpp_credentials) descriptor_fields = merge_properties(descriptor_fields, options) # Create the voucher descriptor = super(kb_account_id, descriptor_fields, properties, context) payu_response = properties_to_hash(descriptor.form_fields) descriptor.form_url = payu_response[:url] # Create the payment in Kill Bill to start the polling kb_account = @kb_apis.account_user_api.get_account_by_id(kb_account_id, context) kb_payment_method_id = (@kb_apis.payment_api.get_account_payment_methods(kb_account_id, false, [], context).find { |pm| pm.plugin_name == 'killbill-payu-latam' }).id kb_payment_id = nil payment_external_key = payu_response[:order_id] payment_transaction_external_key = payu_reference_code # See purchase call above properties = hash_to_properties(:from_hpp => true, :payu_order_id => payu_response[:order_id], :payu_transaction_id => payu_response[:transaction_id]) @kb_apis.payment_api.create_purchase(kb_account, kb_payment_method_id, kb_payment_id, form_fields[:amount], form_fields[:currency] || kb_account.currency, payment_external_key, payment_transaction_external_key, properties, context) descriptor end def process_notification(notification, properties, context) # Pass extra parameters for the gateway here options = {} properties = merge_properties(properties, options) super(notification, properties, context) do |gw_notification, service| # Retrieve the payment # gw_notification.kb_payment_id = # # Set the response body # gw_notification.entity = end end private def get_payment_info_from_payu(kb_payment_id, options, context) kb_payment = @kb_apis.payment_api.get_payment(kb_payment_id, false, [], context) t_info_plugins = [] kb_payment.transactions.each do |transaction| t_info_plugins << get_payment_transaction_info_from_payu(kb_payment_id, transaction.id, transaction.amount, transaction.currency, options, context.tenant_id) end t_info_plugins.compact end def get_payment_transaction_info_from_payu(kb_payment_id, kb_payment_transaction_id, amount, currency, options, kb_tenant_id, response=nil) # Guaranteed to have a unique mapping KB transaction <=> PayU HPP creation response = @response_model.where("transaction_type = 'PURCHASE' AND kb_payment_id = '#{kb_payment_id}' AND kb_payment_transaction_id = '#{kb_payment_transaction_id}' AND kb_tenant_id = '#{kb_tenant_id}'").order(:created_at)[0] if response.nil? raise "Unable to retrieve response for kb_payment_id=#{kb_payment_id}, kb_payment_transaction_id=#{kb_payment_transaction_id}, kb_tenant_id=#{kb_tenant_id}" if response.nil? return nil if response.authorization.nil? # Retrieve the transaction from PayU order_id, transaction_id = response.authorization.split(';') payu_status = (get_payu_status(transaction_id, options) || {})[:status] transaction = nil if payu_status == 'APPROVED' transaction = response.create_payu_latam_transaction(:kb_account_id => response.kb_account_id, :kb_tenant_id => response.kb_tenant_id, :amount_in_cents => amount, :currency => currency, :api_call => :purchase, :kb_payment_id => kb_payment_id, :kb_payment_transaction_id => kb_payment_transaction_id, :transaction_type => response.transaction_type, :payment_processor_account_id => response.payment_processor_account_id, :txn_id => response.txn_id, :payu_latam_response_id => response.id) end t_info_plugin = response.to_transaction_info_plugin(transaction) t_info_plugin.status = payu_status_to_plugin_status(payu_status) t_info_plugin end def payu_status_to_plugin_status(payu_status) if payu_status == 'APPROVED' :PROCESSED elsif payu_status == 'DECLINED' || payu_status == 'ERROR' || payu_status == 'EXPIRED' :ERROR elsif payu_status == 'PENDING' :PENDING else :UNDEFINED end end def get_payu_status(transaction_id, options) options = payu_hpp_credentials(options) helper = get_active_merchant_module.const_get('Helper').new(nil, options.delete(:account_id), options) helper.transaction_status(transaction_id) end def payu_hpp_credentials(options) payment_processor_account_id = options[:payment_processor_account_id] || :default gateway = lookup_gateway(payment_processor_account_id) { :credential2 => gateway.options[:api_login], :credential3 => gateway.options[:api_key], :credential4 => gateway.options[:country_account_id], :account_id => gateway.options[:merchant_id] } end def get_active_merchant_module ::OffsitePayments::Integrations::PayULatam end def add_required_options(properties, options) language = find_value_from_properties(properties, 'language') || 'en' options[:language] ||= language end end end end