<commit_before>#encoding: utf-8
module Spree
  CheckoutController.class_eval do
    cattr_accessor :alipay_skip_payment_methods
    self.alipay_skip_payment_methods = [:alipay_notify, :alipay_done]#, :tenpay_notify, :tenpay_done
    before_filter :alipay_checkout_hook, :only => [:update]
    #invoid WARNING: Can't verify CSRF token authenticity
    skip_before_filter :verify_authenticity_token, :only => self.alipay_skip_payment_methods
    # these two filters is from spree_auth_devise
    skip_before_filter :check_registration, :check_authorization, :only=> self.alipay_skip_payment_methods

    def alipay_done
      payment_return = OffsitePayments::Integrations::Alipay::Return.new(request.query_string)
      #TODO check payment_return.success
      alipay_retrieve_order(payment_return.order)
#      Rails.logger.info "payment_return=#{payment_return.inspect}"
      if @order.present?
        @order.payments.where(:state => ['processing', 'pending', 'checkout']).first.complete!
        @order.state='complete'
        @order.finalize!
        session[:order_id] = nil
        redirect_to completion_route
      else
        #Strange, Failed trying to complete pending payment!
        redirect_to edit_order_checkout_url(@order, :state => "payment")
      end
    end

    def alipay_notify
      notification = OffsitePayments::Integrations::Alipay::Notification.new(request.raw_post)
      alipay_retrieve_order(notification.out_trade_no)
      if @order.present? and notification.acknowledge() and valid_alipay_notification?(notification,@order.payments.first.payment_method.preferred_partner)
        if notification.complete?
          @order.payment.first.complete!
        else
          @order.payment.first.failure!
        end
        render text: "success" 
      else
        render text: "fail" 
      end

    end

    #https://github.com/flyerhzm/donatecn
    #demo for activemerchant_patch_for_china
    #since alipay_full_service_url is working, it is only for debug for now.
    def alipay_checkout_payment
      payment_method =  PaymentMethod.find(params[:payment_method_id])
      #Rails.logger.debug "@payment_method=#{@payment_method.inspect}"       
      Rails.logger.debug "alipay_full_service_url: "+alipay_full_service_url(@order, payment_method)
      # notice that load_order would call before_payment, if 'http==put' and 'order.state == payment', the payments will be deleted. 
      # so we have to create payment again
      @order.payments.create(:amount => @order.total, :payment_method_id => payment_method.id)
      #redirect_to_alipay_gateway(:subject => "donatecn", :body => "donatecn", :amount => @donate.amount, :out_trade_no => "123", :notify_url => pay_fu.alipay_transactions_notify_url)
    end

    private
    
    def load_order_with_lock_with_alipay_return      
      if request.referer=~/alipay.com/
        payment_return = OffsitePayments::Integrations::Alipay::Return.new(request.query_string)
        @current_order = alipay_retrieve_order(payment_return.order)                  
      end      
      load_order_with_lock_without_alipay_return
    end
    
    #because of PR below, load_order is renamed to load_order_with_lock
    #https://github.com/spree/spree/commit/45eabed81e444af3ff1cf49891f64c85fdd8d546
    alias_method_chain :load_order_with_lock, :alipay_return
     
    def alipay_checkout_hook
      #logger.debug "----before alipay_checkout_hook"    
      #all_filters = self.class._process_action_callbacks
      #all_filters = all_filters.select{|f| f.kind == :before}
      #logger.debug "all before filers:"+all_filters.map(&:filter).inspect 
      #TODO support step confirmation 
      #Rails.logger.debug "--->alipay_checkout_hooking?"
      return unless @order.next_step_complete?
      return unless params[:order][:payments_attributes].present?
      #Rails.logger.info "--->before update_attributes"
      #Rails.logger.info "paramsss #{params.inspect}"
      #if @order.update_attributes(object_params) #it would create payments
      #Rails.logger.debug "payment params returned: #{alipay_payment_params}"
      if @order.update_attributes(alipay_payment_params) #it would create payments
        if params[:order][:coupon_code] and !params[:order][:coupon_code].blank? and @order.coupon_code.present?
          fire_event('spree.checkout.coupon_code_added', :coupon_code => @order.coupon_code)
        end
      end
      if alipay_pay_by_billing_integration?
      #Rails.logger.debug "--->before alipay_handle_billing_integration"
        alipay_handle_billing_integration
      end
    end

    def alipay_retrieve_order(order_number)
      @order = Spree::Order.find_by_number(order_number)
      if @order
        #@order.payment.try(:payment_method).try(:provider) #configures ActiveMerchant
      end
      @order
    end

    def valid_alipay_notification?(notification, account)
      url = "https://mapi.alipay.com/gateway.do?service=notify_verify"
      result = HTTParty.get(url, query: {partner: account, notify_id: notification.notify_id}).body
      result == 'true'
    end


    def alipay_full_service_url( order, alipay)
      #Rails.logger.debug "alipay gateway is configured to be #{alipay.inspect}"
      raise ArgumentError, 'require Spree::BillingIntegration::Alipay' unless alipay.is_a? Spree::BillingIntegration::Alipay
      #url = OffsitePayments::Integrations::Alipay.service_url+'?'
      helper = OffsitePayments::Integrations::Alipay::Helper.new(order.number, alipay.preferred_partner, key: alipay.preferred_sign)
      #Rails.logger.debug "helper is #{helper.inspect}"
      using_direct_pay_service = alipay.preferred_using_direct_pay_service

      if using_direct_pay_service
        helper.total_fee order.total
        helper.service OffsitePayments::Integrations::Alipay::Helper::CREATE_DIRECT_PAY_BY_USER
      else
        helper.price order.item_total
        helper.quantity 1
        helper.logistics :type=> 'EXPRESS', :fee=>order.adjustment_total, :payment=>'BUYER_PAY' 
        helper.service OffsitePayments::Integrations::Alipay::Helper::TRADE_CREATE_BY_BUYER
      end
      helper.seller :email => alipay.preferred_email
      #url_for is controller instance method, so we have to keep this method in controller instead of model
      #Rails.logger.debug "helper is #{helper.inspect}"
      helper.notify_url url_for(:only_path => false, :action => 'alipay_notify')
      helper.return_url url_for(:only_path => false, :action => 'alipay_done')
      helper.body order.products.collect(&:name).to_s #String(400) 
      helper.charset "utf-8"
      helper.payment_type 1
      helper.subject "订单编号:#{order.number}"
      Rails.logger.debug "order--- #{order.inspect}"
      Rails.logger.debug "signing--- #{helper.inspect}"
      helper.sign
      url = URI.parse(OffsitePayments::Integrations::Alipay.service_url)
      #Rails.logger.debug "query from url #{url.query}"
      #Rails.logger.debug "query from url parsed #{Rack::Utils.parse_nested_query(url.query).inspect}"
      #Rails.logger.debug "helper fields #{helper.form_fields.to_query}"
      url.query = ( Rack::Utils.parse_nested_query(url.query).merge(helper.form_fields) ).to_query
      #Rails.logger.debug "full_service_url to be encoded is #{url.to_s}"
      url.to_s
    end

    def alipay_pay_by_billing_integration?
     
      #Rails.logger.debug "current orderrrr: #{@order.inspect}"
      if @order.next_step_complete?
        #Rails.logger.debug "pending paymentssss: #{@order.pending_payments.inspect}"
        if @order.pending_payments.first.payment_method.kind_of? BillingIntegration 
          return true
        end
      end
      return false
    end
    
    # handle all supported billing_integration
    def alipay_handle_billing_integration      
      payment_method = @order.pending_payments.first.payment_method
      if payment_method.kind_of?(BillingIntegration::Alipay)
        # set_alipay_constant_if_needed 
        # OffsitePayments::Integrations::Alipay::KEY
        # OffsitePayments::Integrations::Alipay::ACCOUNT
        # gem activemerchant_patch_for_china is using it.
        # should not set when payment_method is updated, after restart server, it would be nil
        # TODO fork the activemerchant_patch_for_china, change constant to class variable
        #alipay_helper_klass = OffsitePayments::Integrations::Alipay::Helper
        alipay_helper_klass = OffsitePayments.integration(:alipay)::Helper
        #alipay_helper_klass.send(:remove_const, :KEY) if alipay_helper_klass.const_defined?(:KEY)
        #alipay_helper_klass.const_set(:KEY, payment_method.preferred_sign)

        #redirect_to(alipay_checkout_payment_order_checkout_url(@order, :payment_method_id => payment_method.id))
        redirect_to alipay_full_service_url(@order, payment_method)
      end
    end
    
    #patch spree_auth_devise/checkout_controller_decorator
    def alipay_skip_state_validation?
      %w(registration update_registration).include?(params[:state])
    end

    def alipay_payment_params
      params.require(:order).permit(:authenticity_token, {:payments_attributes => [ :payment_method_id]} , :coupon_code)
    end
  end
end
<commit_msg>Refactored Alipay code to AlipayDecorator. This was intended to utilize the new ':prepend' (instead of :include) feature but it turned out that didn't work. It causes 'Stack level too deep' errors when the prepended method from the module calls 'super'<commit_after>#encoding: utf-8
module Spree
  module AlipayDecorator

    # triggered when user is redirected back to our website after completing payment on the 3rd party website
    def alipay_done
      #@payment_method = Spree::BillingIntegration::Alipay.new
      #payment_return = OffsitePayments::Integrations::Alipay::Return.new(request.query_string, @payment_method.preferred_sign)
      #unless payment_return.acknowledge
      #alipay_retrieve_order(payment_return.order)
      if @return.is_payment_complete?
        #TODO: The following logic need to be revised
        #FIXME: what if the amount paid is not enough for the whole order?
        @order.payments.where(:state => ['processing', 'pending', 'checkout']).first.complete!
        @order.state='complete'
        @order.finalize!
        session[:order_id] = nil
        redirect_to completion_route
      else
        #TODO: added this to tx log too
        redirect_to edit_order_checkout_url(@order, :state => "payment")
      end
    end
    
    # triggered when Alipay sends a notification request unbeknownst to the user, from Alipay server to our server
    def alipay_notify
      if valid_alipay_notification?(@notification,@order.payments.first.payment_method.preferred_partner)
        if @notification.is_payment_complete?
          @order.payment.first.complete!
        else
          @order.payment.first.failure!
        end
        render text: "success" 
      else
        render text: "fail" 
      end
    rescue
      render text: "fail"
    end
    
    def alipay_checkout_hook
      #TODO support step confirmation 
      return unless @order.next_step_complete?
      return unless params[:order][:payments_attributes].present?
      if @order.update_attributes(alipay_payment_params) #it would create payments
        if params[:order][:coupon_code] and !params[:order][:coupon_code].blank? and @order.coupon_code.present?
          fire_event('spree.checkout.coupon_code_added', :coupon_code => @order.coupon_code)
        end
      end
      if alipay_pay_by_billing_integration?
      #Rails.logger.debug "--->before alipay_handle_billing_integration"
        alipay_handle_billing_integration
      end
    end

    #This overrides the method in CheckoutController
    def wrong_load_order_with_lock

      Rails.logger.debug "#{__LINE__}:#{request.path_parameters[:action]}"
      
      #alipay_load_order
      super unless @order.present? 
    rescue RuntimeError => e
      Rails.logger.error("alipay_load_order failed since: #{e.message}")
      render text: 'fail'
    end

    # This is done in the default checkout_controller filter chain
    def alipay_load_order
      raise "'out_trade_no' requird to load the order" unless params.key?('out_trade_no')
      order_number = parse_alipay_out_trade_no(params['out_trade_no'])[:payment_order_number]
      @order  = Order.find_by_number(order_number) #if request.referer=~/alipay.com/
      unless @order.present?
        raise RuntimeError, "Could not find order #{order_number}"
      end
    end
    
  private

    def ensure_valid_alipay_request
      #@payment_method = Spree::BillingIntegration::Alipay.new()
      @payment_method = Spree::PaymentMethod.find_by(type: Spree::BillingIntegration::Alipay)
      Rails.logger.debug "key is #{@payment_method.inspect}"
      Rails.logger.debug "key is #{@payment_method.preferred_sign}"
      case request.path_parameters[:action]
      when 'alipay_notify'
        begin
          @notification = OffsitePayments::Integrations::Alipay.notification(request.raw_post, key: @payment_method.preferred_sign)
          @notification.acknowledge
        rescue RuntimeError => e
          Rails.logger.warn(e)
          render text: 'failure'
        end
      when 'alipay_done'
        begin
        @return = OffsitePayments::Integrations::Alipay.return(request.query_string, key: @payment_method.preferred_sign) 
        #@return.acknowledge
        rescue RuntimeError => e
          Rails.logger.warn(e)
          flash[:error] = Spree.t('illegal_sign_in_request')
          redirect_to spree.root_path
        end
      else
        raise RuntimeError, "Configuration error. It shouldn't get here"
      end
    end

    def parse_alipay_out_trade_no(out_trade_no)
      case 
      when 18 == out_trade_no.length
        return { payment_order_number: out_trade_no[0..-9], payment_identifier: out_trade_no[-8,8] }
      when 10 == out_trade_no.length
        return { payment_order_number: out_trade_no, payment_identifier: ''}
      else  
        Rails.logger.warn "Suspecious out_trade_no #{out_trade_no}"
        return { payment_order_number: out_trade_no, payment_identifier: ''}
      end
    end
    
    def valid_alipay_notification?(notification, account)
      url = "https://mapi.alipay.com/gateway.do?service=notify_verify"
      result = HTTParty.get(url, query: {partner: account, notify_id: notification.notify_id}).body
      result == 'true'
    end
    
    # TODO: we the code only supports "create_direct_pay_by_user" as of now (2014-11-14)
    def alipay_full_service_url( order, alipay)
      #Rails.logger.debug "alipay gateway is configured to be #{alipay.inspect}"
      raise ArgumentError, 'require Spree::BillingIntegration::Alipay' unless alipay.is_a? Spree::BillingIntegration::Alipay
      #url = OffsitePayments::Integrations::Alipay.service_url+'?'
      helper = OffsitePayments::Integrations::Alipay::Helper.new(order.number, alipay.preferred_partner, key: alipay.preferred_sign)
      #Rails.logger.debug "helper is #{helper.inspect}"

      if alipay.preferred_using_direct_pay_service
        helper.total_fee order.total
        helper.service OffsitePayments::Integrations::Alipay::Helper::CREATE_DIRECT_PAY_BY_USER
      else
        helper.price order.item_total
        helper.quantity 1
        helper.logistics :type=> 'EXPRESS', :fee=>order.adjustment_total, :payment=>'BUYER_PAY' 
        helper.service OffsitePayments::Integrations::Alipay::Helper::TRADE_CREATE_BY_BUYER
      end
      helper.seller :email => alipay.preferred_email
      #url_for is controller instance method, so we have to keep this method in controller instead of model
      #Rails.logger.debug "helper is #{helper.inspect}"
      helper.notify_url url_for(:only_path => false, :action => 'alipay_notify')
      helper.return_url url_for(:only_path => false, :action => 'alipay_done')
      helper.body order.products.collect(&:name).to_s #String(400) 
      helper.charset "utf-8"
      helper.payment_type 1
      helper.subject "订单编号:#{order.number}"
      Rails.logger.debug "order--- #{order.inspect}"
      Rails.logger.debug "signing--- #{helper.inspect}"
      helper.sign
      url = URI.parse(OffsitePayments::Integrations::Alipay.service_url)
      #Rails.logger.debug "query from url #{url.query}"
      #Rails.logger.debug "query from url parsed #{Rack::Utils.parse_nested_query(url.query).inspect}"
      #Rails.logger.debug "helper fields #{helper.form_fields.to_query}"
      url.query = ( Rack::Utils.parse_nested_query(url.query).merge(helper.form_fields) ).to_query
      #Rails.logger.debug "full_service_url to be encoded is #{url.to_s}"
      url.to_s
    end

    def alipay_pay_by_billing_integration?
      #Rails.logger.debug "current orderrrr: #{@order.inspect}"
      if @order.next_step_complete?
        #Rails.logger.debug "pending paymentssss: #{@order.pending_payments.inspect}"
        if @order.pending_payments.first.payment_method.kind_of? BillingIntegration 
          return true
        end
      end
      return false
    end
    
    # handle all supported billing_integration
    def alipay_handle_billing_integration      
      payment_method = @order.pending_payments.first.payment_method
      if payment_method.kind_of?(BillingIntegration::Alipay)
        redirect_to alipay_full_service_url(@order, payment_method)
      end
    end
    
    #patch spree_auth_devise/checkout_controller_decorator
    def alipay_skip_state_validation?
      %w(registration update_registration).include?(params[:state])
    end

    def alipay_payment_params
      params.require(:order).permit(:authenticity_token, {:payments_attributes => [ :payment_method_id]} , :coupon_code)
    end
  end

  CheckoutController.send(:prepend, AlipayDecorator)
  CheckoutController.class_eval do
    SKIP_FILTERS_FOR = [:alipay_notify, :alipay_done]
    before_action :alipay_checkout_hook, :only => [:update]
    skip_before_action :verify_authenticity_token, only: SKIP_FILTERS_FOR 
    # these two actions is from spree_auth_devise
    skip_before_action :check_registration, :check_authorization, only: SKIP_FILTERS_FOR 
    prepend_before_action :ensure_valid_alipay_request, :alipay_load_order, only: SKIP_FILTERS_FOR 
  end

end

{'fraction_non_alphanumeric': 0.09531378459852015, 'fraction_numerical': 0.003069334064127158, 'mean_word_length': 3.652218255991841, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 46, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4262675', 'n_tokens_mistral': 5801, 'n_tokens_neox': 5693, 'n_words': 1100}