Shopify/offsite_payments

View on GitHub
lib/offsite_payments/integrations/payu_in.rb

Summary

Maintainability
B
4 hrs
Test Coverage
module OffsitePayments #:nodoc:
  module Integrations #:nodoc:
    module PayuIn
      mattr_accessor :test_url
      mattr_accessor :production_url

      self.test_url = 'https://test.payu.in/_payment.php'
      self.production_url = 'https://secure.payu.in/_payment.php'

      def self.service_url
        OffsitePayments.mode == :production ? self.production_url : self.test_url
      end

      def self.notification(post, options = {})
        Notification.new(post, options)
      end

      def self.return(post, options = {})
        Return.new(post, options)
      end

      def self.checksum(merchant_id, secret_key, payload_items )
        Digest::SHA512.hexdigest([merchant_id, *payload_items, secret_key].join("|"))
      end

      class Helper < OffsitePayments::Helper

        CHECKSUM_FIELDS = [ 'txnid', 'amount', 'productinfo', 'firstname', 'email', 'udf1', 'udf2', 'udf3', 'udf4',
                            'udf5', 'udf6', 'udf7', 'udf8', 'udf9', 'udf10']

        mapping :amount, 'amount'
        mapping :account, 'key'
        mapping :order, 'txnid'
        mapping :description, 'productinfo'

        mapping :customer, :first_name => 'firstname',
          :last_name  => 'lastname',
          :email => 'email',
          :phone => 'phone'

        mapping :billing_address, :city => 'city',
          :address1 => 'address1',
          :address2 => 'address2',
          :state => 'state',
          :zip => 'zipcode',
          :country => 'country'

        # Which tab you want to be open default on PayU
        # CC (CreditCard) or NB (NetBanking)
        mapping :mode, 'pg'

        mapping :notify_url, 'notify_url'
        mapping :return_url, ['surl', 'furl']
        mapping :cancel_return_url, 'curl'
        mapping :checksum, 'hash'

        mapping :user_defined, { :var1 => 'udf1',
          :var2 => 'udf2',
          :var3 => 'udf3',
          :var4 => 'udf4',
          :var5 => 'udf5',
          :var6 => 'udf6',
          :var7 => 'udf7',
          :var8 => 'udf8',
          :var9 => 'udf9',
          :var10 => 'udf10'
        }

        def initialize(order, account, options = {})
          super
          @options = options
          self.pg = 'CC'
          add_field('udf5', application_id)
        end

        def form_fields
          sanitize_fields
          @fields.merge(mappings[:checksum] => generate_checksum)
        end

        def generate_checksum
          checksum_payload_items = CHECKSUM_FIELDS.map { |field| @fields[field] }

          PayuIn.checksum(@fields["key"], @options[:credential2], checksum_payload_items )
        end

        def sanitize_fields
          @fields['phone'] = @fields['phone'].gsub(/[^0-9]/, '') if @fields['phone']
          ['address1', 'address2', 'city', 'state', 'country', 'productinfo', 'email'].each do |field|
            @fields[field] = @fields[field].gsub(/[^a-zA-Z0-9\-_@\/\s.]/, '') if @fields[field]
          end
        end

      end

      class Notification < OffsitePayments::Notification
        def initialize(post, options = {})
          super(post, options)
          @merchant_id = options[:credential1]
          @secret_key = options[:credential2]
        end

        def complete?
          status == "Completed"
        end

        def status
          case transaction_status.downcase
            when 'success' then 'Completed'
            else 'Failed'
          end
        end

        def invoice_ok?( order_id )
          order_id.to_s == invoice.to_s
        end

        # Order amount should be equal to gross - discount
        def amount_ok?( order_amount, order_discount = BigDecimal( '0.0' ) )
          parsed_discount = discount.nil? ? 0.to_d : discount.to_d
          BigDecimal( original_gross ) == order_amount && parsed_discount == order_discount
        end

        # Status of transaction return from the PayU. List of possible values:
        # <tt>SUCCESS</tt>::
        # <tt>PENDING</tt>::
        # <tt>FAILURE</tt>::
        def transaction_status
          params['status']
        end

        # ID of this transaction (PayU.in number)
        def transaction_id
          params['mihpayid']
        end

        # Mode of Payment
        #
        # 'CC' for credit-card
        # 'NB' for net-banking
        # 'CD' for cheque or DD
        # 'CO' for Cash Pickup
        def type
          params['mode']
        end

        # What currency have we been dealing with
        def currency
          'INR'
        end

        def item_id
          params['txnid']
        end

        # This is the invoice which you passed to PayU.in
        def invoice
          params['txnid']
        end

        # Merchant Id provided by the PayU.in
        def account
          params['key']
        end

        # original amount send by merchant
        def original_gross
          params['amount']
        end

        def gross
          parse_and_round_gross_amount(params['amount'])
        end

        # This is discount given to user - based on promotion set by merchants.
        def discount
          params['discount']
        end

        # Description offer for what PayU given the offer to user - based on promotion set by merchants.
        def offer_description
          params['offer']
        end

        # Information about the product as send by merchant
        def product_info
          params['productinfo']
        end

        # Email of the customer
        def customer_email
          params['email']
        end

        # Phone of the customer
        def customer_phone
          params['phone']
        end

        # Firstname of the customer
        def customer_first_name
          params['firstname']
        end

        # Lastname of the customer
        def customer_last_name
          params['lastname']
        end

        # Full address of the customer
        def customer_address
          { :address1 => params['address1'], :address2 => params['address2'],
            :city => params['city'], :state => params['state'],
            :country => params['country'], :zipcode => params['zipcode'] }
        end

        def user_defined
          @user_defined ||= 10.times.map { |i| params["udf#{i + 1}"] }
        end

        def checksum
          params['hash']
        end

        def message
          @message || params['error']
        end

        def acknowledge(authcode = nil)
          checksum_ok?
        end

        def checksum_ok?
          checksum_fields = [transaction_status, *user_defined.reverse, customer_email, customer_first_name, product_info, original_gross, invoice]

          unless Digest::SHA512.hexdigest([@secret_key, *checksum_fields, @merchant_id].join("|")) == checksum
            @message = 'Return checksum not matching the data provided'
            return false
          end
          true
        end

        private
        def parse_and_round_gross_amount(amount)
          rounded_amount = (amount.to_f * 100.0).round
          sprintf("%.2f", rounded_amount / 100.00)
        end
      end

      class Return < OffsitePayments::Return
        def initialize(query_string, options = {})
          super
          @notification = Notification.new(query_string, options)
        end

        def transaction_id
          @notification.transaction_id
        end

        def status( order_id, order_amount )
          if @notification.invoice_ok?( order_id ) && @notification.amount_ok?( BigDecimal(order_amount) )
            @notification.status
          else
            'Mismatch'
          end
        end

        def success?
          status( @params['txnid'], @params['amount'] ) == 'Completed'
        end

        def message
          @notification.message
        end
      end
    end
  end
end