Module: Payu::Hash
- Defined in:
- lib/payu/hash.rb
Class Method Summary collapse
-
.api(key:, command:, var1:, salt:) ⇒ Object
Hash for server-to-server API commands: sha512(key|command|var1|salt).
-
.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.
-
.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).
-
.valid_response?(params:, salt:) ⇒ Boolean
Constant-time comparison to prevent timing attacks.
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
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 |