module Killbill::PaypalExpress class PaymentPlugin < Killbill::Plugin::Payment def start_plugin Killbill::PaypalExpress.initialize! @logger, @conf_dir @gateway = Killbill::PaypalExpress.gateway @ip = Utils.ip super @logger.info "Killbill::PaypalExpress::PaymentPlugin started" end # return DB connections to the Pool if required def after_request ActiveRecord::Base.connection.close end def process_payment(kb_account_id, kb_payment_id, kb_payment_method_id, amount_in_cents, currency, call_context, options = {}) # If the payment was already made, just return the status paypal_express_transaction = PaypalExpressTransaction.from_kb_payment_id(kb_payment_id.to_s) rescue nil return paypal_express_transaction.paypal_express_response.to_payment_response unless paypal_express_transaction.nil? options[:currency] ||= currency.respond_to?(:enum) ? currency.enum : currency.to_s options[:payment_type] ||= 'Any' options[:invoice_id] ||= kb_payment_id.to_s options[:description] ||= "Kill Bill payment for #{kb_payment_id}" options[:ip] ||= @ip if options[:reference_id].blank? payment_method = PaypalExpressPaymentMethod.from_kb_payment_method_id(kb_payment_method_id.to_s) options[:reference_id] = payment_method.paypal_express_baid end # Go to Paypal (DoReferenceTransaction call) paypal_response = @gateway.reference_transaction amount_in_cents, options response = save_response_and_transaction paypal_response, :charge, kb_payment_id, amount_in_cents response.to_payment_response end def get_payment_info(kb_account_id, kb_payment_id, tenant_context, options = {}) paypal_express_transaction = PaypalExpressTransaction.from_kb_payment_id(kb_payment_id.to_s) begin transaction_id = paypal_express_transaction.paypal_express_txn_id response = @gateway.transaction_details transaction_id PaypalExpressResponse.from_response(:transaction_details, kb_payment_id.to_s, response).to_payment_response rescue => e @logger.warn("Exception while retrieving Paypal Express transaction detail for payment #{kb_payment_id.to_s}, defaulting to cached response: #{e}") paypal_express_transaction.paypal_express_response.to_payment_response end end def process_refund(kb_account_id, kb_payment_id, amount_in_cents, currency, call_context, options = {}) paypal_express_transaction = PaypalExpressTransaction.find_candidate_transaction_for_refund(kb_payment_id.to_s, amount_in_cents) options[:currency] ||= currency.respond_to?(:enum) ? currency.enum : currency.to_s options[:refund_type] ||= paypal_express_transaction.amount_in_cents != amount_in_cents ? 'Partial' : 'Full' identification = paypal_express_transaction.paypal_express_txn_id # Go to Paypal paypal_response = @gateway.refund amount_in_cents, identification, options response = save_response_and_transaction paypal_response, :refund, kb_payment_id, amount_in_cents response.to_refund_response end def get_refund_info(kb_account_id, kb_payment_id, tenant_context, options = {}) paypal_express_transaction = PaypalExpressTransaction.refund_from_kb_payment_id(kb_payment_id.to_s) begin transaction_id = paypal_express_transaction.paypal_express_txn_id response = @gateway.transaction_details transaction_id PaypalExpressResponse.from_response(:transaction_details, kb_payment_id.to_s, response).to_refund_response rescue => e @logger.warn("Exception while retrieving Paypal Express transaction detail for payment #{kb_payment_id.to_s}, defaulting to cached response: #{e}") paypal_express_transaction.paypal_express_response.to_refund_response end end def add_payment_method(kb_account_id, kb_payment_method_id, payment_method_props, set_default, call_context, options = {}) token = (payment_method_props.properties.find { |kv| kv.key == 'token' }).value return false if token.nil? # The payment method should have been created during the setup step (see private api) payment_method = PaypalExpressPaymentMethod.from_kb_account_id_and_token(kb_account_id.to_s, token) # Go to Paypal to get the Payer id (GetExpressCheckoutDetails call) paypal_express_details_response = @gateway.details_for token response = save_response_and_transaction paypal_express_details_response, :details_for return false unless response.success? payer_id = response.payer_id unless payer_id.nil? # Go to Paypal to create the BAID for recurring payments (CreateBillingAgreement call) paypal_express_baid_response = @gateway.store token response = save_response_and_transaction paypal_express_baid_response, :create_billing_agreement return false unless response.success? payment_method.kb_payment_method_id = kb_payment_method_id.to_s payment_method.paypal_express_payer_id = payer_id payment_method.paypal_express_baid = response.billing_agreement_id payment_method.save! logger.info "Created BAID #{payment_method.paypal_express_baid} for payment method #{kb_payment_method_id.to_s} (account #{kb_account_id.to_s})" true else logger.warn "Unable to retrieve Payer id details for token #{token} (account #{kb_account_id.to_s})" false end end def delete_payment_method(kb_account_id, kb_payment_method_id, call_context, options = {}) PaypalExpressPaymentMethod.mark_as_deleted! kb_payment_method_id.to_s end def get_payment_method_detail(kb_account_id, kb_payment_method_id, tenant_context, options = {}) PaypalExpressPaymentMethod.from_kb_payment_method_id(kb_payment_method_id.to_s).to_payment_method_response end def set_default_payment_method(kb_account_id, kb_payment_method_id, call_context, options = {}) # No-op end def get_payment_methods(kb_account_id, refresh_from_gateway, call_context, options = {}) PaypalExpressPaymentMethod.from_kb_account_id(kb_account_id.to_s).collect { |pm| pm.to_payment_method_info_response } end def reset_payment_methods(kb_account_id, payment_methods) return if payment_methods.nil? paypal_pms = PaypalExpressPaymentMethod.from_kb_account_id(kb_account_id.to_s) payment_methods.delete_if do |payment_method_info_plugin| should_be_deleted = false paypal_pms.each do |paypal_pm| # Do paypal_pm and payment_method_info_plugin represent the same PayPal payment method? if paypal_pm.external_payment_method_id == payment_method_info_plugin.external_payment_method_id # Do we already have a kb_payment_method_id? if paypal_pm.kb_payment_method_id == payment_method_info_plugin.payment_method_id.to_s should_be_deleted = true break elsif paypal_pm.kb_payment_method_id.nil? # We didn't have the kb_payment_method_id - update it paypal_pm.kb_payment_method_id = payment_method_info_plugin.payment_method_id.to_s should_be_deleted = paypal_pm.save break # Otherwise the same BAID points to 2 different kb_payment_method_id. This should never happen, # but we cowardly will insert a second row below end end end should_be_deleted end # The remaining elements in payment_methods are not in our table (this should never happen?!) payment_methods.each do |payment_method_info_plugin| PaypalExpressPaymentMethod.create :kb_account_id => payment_method_info_plugin.account_id.to_s, :kb_payment_method_id => payment_method_info_plugin.payment_method_id.to_s, :paypal_express_baid => payment_method_info_plugin.external_payment_method_id, :paypal_express_token => 'unknown (created by reset)' end end private def save_response_and_transaction(paypal_express_response, api_call, kb_payment_id=nil, amount_in_cents=0) @logger.warn "Unsuccessful #{api_call}: #{paypal_express_response.message}" unless paypal_express_response.success? # Save the response to our logs response = PaypalExpressResponse.from_response(api_call, kb_payment_id.to_s, paypal_express_response) response.save! if response.success and !kb_payment_id.blank? and !response.authorization.blank? # Record the transaction transaction = response.create_paypal_express_transaction!(:amount_in_cents => amount_in_cents, :api_call => api_call, :kb_payment_id => kb_payment_id.to_s, :paypal_express_txn_id => response.authorization) @logger.debug "Recorded transaction: #{transaction.inspect}" end response end end end