module Killbill::PaypalExpress class PaypalExpressPaymentMethod < ActiveRecord::Base attr_accessible :kb_account_id, :kb_payment_method_id, :paypal_express_payer_id, :paypal_express_baid, :paypal_express_token alias_attribute :external_payment_method_id, :paypal_express_baid def self.from_kb_account_id(kb_account_id) find_all_by_kb_account_id_and_is_deleted(kb_account_id, false) end def self.from_kb_payment_method_id(kb_payment_method_id) payment_methods = find_all_by_kb_payment_method_id_and_is_deleted(kb_payment_method_id, false) raise "No payment method found for payment method #{kb_payment_method_id}" if payment_methods.empty? raise "Killbill payment method mapping to multiple active PaypalExpress tokens for payment method #{kb_payment_method_id}" if payment_methods.size > 1 payment_methods[0] end # Used to complete the checkout process def self.from_kb_account_id_and_token(kb_account_id, token) payment_methods = find_all_by_kb_account_id_and_paypal_express_token_and_is_deleted(kb_account_id, token, false) raise "No payment method found for account #{kb_account_id}" if payment_methods.empty? raise "Paypal token mapping to multiple active PaypalExpress payment methods #{kb_account_id}" if payment_methods.size > 1 payment_methods[0] end def self.mark_as_deleted!(kb_payment_method_id) payment_method = from_kb_payment_method_id(kb_payment_method_id) payment_method.is_deleted = true payment_method.save! end # VisibleForTesting def self.search_query(search_key, offset = nil, limit = nil) t = self.arel_table tr = PaypalExpressResponse.arel_table # Note 1: Exact match for ids and email, partial match for name # Note 2: Creating a payment method is a two-step process. We first create a placeholder during the SetExpressCheckout call, which # doesn't have a kb_payment_method_id (nor a paypal_express_payer_id). During the CreateBillingAgreement call, both attributes # will be populated, as well as the baid. If the second step is never completed, the payment method placeholder is garbage and # we want to ignore it. query = t.join(tr, Arel::Nodes::OuterJoin).on( tr[:api_call].eq('details_for') .and(tr[:success].eq(true)) .and(tr[:token].eq(t[:paypal_express_token]))) .where( t[:paypal_express_payer_id].eq(search_key) .or(t[:paypal_express_baid].eq(search_key)) .or(t[:paypal_express_token].eq(search_key)) .or(tr[:payer_email].eq(search_key)) .or(tr[:payer_name].matches("%#{search_key}%"))) .where(t[:kb_payment_method_id].not_eq(nil)) .order(t[:id]) if offset.blank? and limit.blank? # true is for count distinct query.project(t[:id].count(true)) else query.skip(offset) unless offset.blank? query.take(limit) unless limit.blank? query.project(t[Arel.star]) # Not chainable query.distinct end query end def self.search(search_key, offset = 0, limit = 100) pagination = Killbill::Plugin::Model::Pagination.new pagination.current_offset = offset pagination.total_nb_records = self.count_by_sql(self.search_query(search_key)) pagination.max_nb_records = self.where('kb_payment_method_id is not NULL').count pagination.next_offset = (!pagination.total_nb_records.nil? && offset + limit >= pagination.total_nb_records) ? nil : offset + limit # Reduce the limit if the specified value is larger than the number of records actual_limit = [pagination.max_nb_records, limit].min pagination.iterator = StreamyResultSet.new(actual_limit) do |offset,limit| self.find_by_sql(self.search_query(search_key, offset, limit)) .map(&:to_payment_method_response) end pagination end def to_payment_method_response properties = [] properties << create_pm_kv_info('payerId', paypal_express_payer_id) properties << create_pm_kv_info('baid', paypal_express_baid) properties << create_pm_kv_info('token', paypal_express_token) # We're pretty much guaranteed to have a (single) entry for details_for, since it was called during add_payment_method details_for = PaypalExpressResponse.find_all_by_api_call_and_token('details_for', paypal_express_token).last unless details_for.nil? properties << create_pm_kv_info('payerName', details_for.payer_name) properties << create_pm_kv_info('payerEmail', details_for.payer_email) properties << create_pm_kv_info('payerCountry', details_for.payer_country) properties << create_pm_kv_info('contactPhone', details_for.contact_phone) properties << create_pm_kv_info('shipToAddressName', details_for.ship_to_address_name) properties << create_pm_kv_info('shipToAddressCompany', details_for.ship_to_address_company) properties << create_pm_kv_info('shipToAddressAddress1', details_for.ship_to_address_address1) properties << create_pm_kv_info('shipToAddressAddress2', details_for.ship_to_address_address2) properties << create_pm_kv_info('shipToAddressCity', details_for.ship_to_address_city) properties << create_pm_kv_info('shipToAddressState', details_for.ship_to_address_state) properties << create_pm_kv_info('shipToAddressCountry', details_for.ship_to_address_country) properties << create_pm_kv_info('shipToAddressZip', details_for.ship_to_address_zip) end pm_plugin = Killbill::Plugin::Model::PaymentMethodPlugin.new pm_plugin.kb_payment_method_id = kb_payment_method_id pm_plugin.external_payment_method_id = external_payment_method_id pm_plugin.is_default_payment_method = is_default pm_plugin.properties = properties pm_plugin.type = 'PayPal' pm_plugin end def to_payment_method_info_response pm_info_plugin = Killbill::Plugin::Model::PaymentMethodInfoPlugin.new pm_info_plugin.account_id = kb_account_id pm_info_plugin.payment_method_id = kb_payment_method_id pm_info_plugin.is_default = is_default pm_info_plugin.external_payment_method_id = external_payment_method_id pm_info_plugin end def is_default # No concept of default payment method in Paypal Express false end private def create_pm_kv_info(key, value) prop = Killbill::Plugin::Model::PaymentMethodKVInfo.new prop.key = key prop.value = value prop end end end