Module: Payu::Hash

Defined in:
lib/payu/hash.rb

Class Method Summary collapse

Class Method Details

.api(key:, command:, var1:, salt:) ⇒ Object

Hash for server-to-server API commands: sha512(key|command|var1|salt)



49
50
51
# File 'lib/payu/hash.rb', line 49

def self.api(key:, command:, var1:, salt:)
  Digest::SHA512.hexdigest("#{key}|#{command}|#{var1}|#{salt}")
end

.request(key:, txnid:, amount:, productinfo:, firstname:, email:, udf1: "", udf2: "", udf3: "", udf4: "", udf5: "", salt:) ⇒ Object

Request hash — confirmed formula from docs.payu.in (May 2026):

sha512(key|txnid|amount|productinfo|firstname|email|udf1|udf2|udf3|udf4|udf5||||||SALT)

17 pipe-delimited values: 6 mandatory + 5 UDF + 5 empty reserved slots + salt



8
9
10
11
12
13
14
15
16
17
# File 'lib/payu/hash.rb', line 8

def self.request(key:, txnid:, amount:, productinfo:, firstname:, email:,
                 udf1: "", udf2: "", udf3: "", udf4: "", udf5: "", salt:)
  parts = [
    key, txnid, amount, productinfo, firstname, email,
    udf1.to_s, udf2.to_s, udf3.to_s, udf4.to_s, udf5.to_s,
    "", "", "", "", "",  # reserved udf6-udf10 slots
    salt
  ]
  Digest::SHA512.hexdigest(parts.join("|"))
end

.response(params:, salt:) ⇒ Object

Response hash — field order is REVERSED vs request (confirmed from docs.payu.in):

sha512(SALT|status||||||udf5|udf4|udf3|udf2|udf1|email|firstname|productinfo|amount|txnid|key)

Variant: when PayU adds additional_charges (platform/convenience fee), it is prepended:

sha512(additional_charges|SALT|status|...|key)


24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/payu/hash.rb', line 24

def self.response(params:, salt:)
  p = params
  core = [
    salt,
    p[:status].to_s,
    "", "", "", "", "",  # reserved slots (reversed)
    p[:udf5].to_s, p[:udf4].to_s, p[:udf3].to_s, p[:udf2].to_s, p[:udf1].to_s,
    p[:email].to_s, p[:firstname].to_s, p[:productinfo].to_s,
    p[:amount].to_s, p[:txnid].to_s, p[:key].to_s
  ]
  additional = p[:additional_charges].to_s
  parts = additional.empty? ? core : [additional] + core
  Digest::SHA512.hexdigest(parts.join("|"))
end

.valid_response?(params:, salt:) ⇒ Boolean

Constant-time comparison to prevent timing attacks

Returns:

  • (Boolean)


40
41
42
43
44
45
46
# File 'lib/payu/hash.rb', line 40

def self.valid_response?(params:, salt:)
  expected = response(params: params, salt: salt)
  received = params[:hash].to_s
  return false if expected.bytesize != received.bytesize

  expected.bytes.zip(received.bytes).reduce(0) { |acc, (a, b)| acc | (a ^ b) }.zero?
end