I am creating a checksum for my payment gateway. The checksum should be in this format:
key|txnid|amount|productinfo|firstname|email|||||||||||salt
I need to leave 11 pipes between the email and the salt.
In my controller I created a method:
require 'digest/sha2'
def getChecksum(key, txnid, amount, productinfo, firstname, email, phone, surl, furl)
String str = key + '|' + txnid + '|' + amount + '|' + productinfo + '|' + firstname + '|' + email + '|' + phone + '|' + surl + '|' + furl+ '|'+ '|'+ '|'+ '|'+ '|'+ '|'+ '|'+ '|' + 'salt'
Digest::SHA512.hexdigest( str )
end
I called this method in my controller:
hash: getChecksum(
key: 'key_value',
txnid: '4',
amount: '2000',
productinfo: 'first sales',
firstname: 'John',
email: 'johndepp#gmail.com',
phone: '123456789',
surl: 'http://someurl.io',
furl: 'http://someurl.io')
But now I am getting a "wrong number of arguments" error:
wrong number of arguments (1 for 9)
def getChecksum( key, txnid,
amount, productinfo, firstname, email, phone, surl, furl)
I think all the parameters are considered as a single one. Can someone tell me what I am doing wrong here?
You are sending a single hash as the parameter, instead you need to send the values only (without keys). Also no need to declare the type String str, you should review your Ruby fundamentals, I'm guessing you come from another language.
# You are calling this
getChecksum(key: 'key_value', txnid: '4', ...)
# which syntactically equals this, a single hash as a parameter
getChecksum({key: 'key_value', txnid: '4', ...})
# Instead, send the values alone
getChecksum('key_value', '4', ...)
Let's look at how you're writing your code and see if we can make it more Ruby-like and hopefully more production-worthy.
You wrote this:
def getChecksum(key, txnid, amount, productinfo, firstname, email, phone, surl, furl)
String str = key + '|' + txnid + '|' + amount + '|' + productinfo + '|' + firstname + '|' + email + '|' + phone + '|' + surl + '|' + furl+ '|'+ '|'+ '|'+ '|'+ '|'+ '|'+ '|'+ '|' + 'salt'
Digest::SHA512.hexdigest( str )
end
First, ick.
Methods and variables in Ruby are snake_case, not camelCase. ItsAReadabilityThing.
We don't define variables using String str =.... Ruby can figure out what type of variable it is 99.99% of the time, so str =... is sufficient.
Think about how you're using fixed-strings and values. '|' is your separator, so define it as such:
SEPARATOR = '|'
And you need eleven of them as a divider, so define that:
DIVIDER = SEPARATOR * 11
I'd write the code more like this:
require 'digest/sha2'
SEPARATOR = '|'
DIVIDER = SEPARATOR * 11 # => "|||||||||||"
def checksum_1(key, txnid, amount, productinfo, firstname, email, phone, surl, furl)
str = [key, txnid, amount, productinfo, firstname, email, phone, surl, furl].join(SEPARATOR) + DIVIDER + 'salt'
Digest::SHA512.hexdigest(str)
end
But even that can be improved upon.
When we have more than three parameters it's time to refactor and clean up the parameter list. There are many ways to do it, but using a hash is the most common:
def checksum_2(params)
str = params.values_at(
:key, :txnid, :amount, :productinfo, :firstname, :email, :phone, :surl, :furl
).join(SEPARATOR) + DIVIDER + 'salt'
Digest::SHA512.hexdigest( str )
end
Using values_at is a nice way to extract the values of the keys in a hash. They come out in an array in the same order of the keys.
The problem now is that a parameter could be missing and Ruby won't catch it. Named parameters are the new-hotness for dealing with that, but for older Rubies, or to get an idea how it might be done in other languages, we can look at the keys received, then their associated values, and if things don't look good raise an exception rather than return a bogus value.
To make things easier I'm going to use Active Support's blank? method, found in the Core Extensions:
require 'active_support/core_ext/object/blank'
def checksum_3(params)
required_values = [:key, :txnid, :amount, :productinfo, :firstname, :email, :phone, :surl, :furl]
missing_keys = required_values - params.keys
fail ArgumentError, "Missing keys: #{ missing_keys.map{ |k| ":#{ k }" }.join(', ') }" unless missing_keys.empty?
missing_values = required_values.select{ |k| params[k].blank? }
fail ArgumentError, "Missing values for: #{ missing_values.map{ |k| ":#{ k }" }.join(', ') }" unless missing_values.empty?
str = params.values_at(*required_values).join(SEPARATOR) + DIVIDER + 'salt'
Digest::SHA512.hexdigest(str)
end
Here's how the calls to the various versions of the method look, along with the associated checksum that's returned:
c1 = checksum_1('key_value', '4', '2000', 'first sales', 'John', 'johndepp#gmail.com', '123456789', 'http://someurl.io', 'http://someurl.io')
c2 = checksum_2(
key: 'key_value',
txnid: '4',
amount: '2000',
productinfo: 'first sales',
firstname: 'John',
email: 'johndepp#gmail.com',
phone: '123456789',
surl: 'http://someurl.io',
furl: 'http://someurl.io'
)
c3 = checksum_3(
key: 'key_value',
txnid: '4',
amount: '2000',
productinfo: 'first sales',
firstname: 'John',
email: 'johndepp#gmail.com',
phone: '123456789',
surl: 'http://someurl.io',
furl: 'http://someurl.io'
)
c1 # => "fcf4e21c6e711808a6984824f09552dccc5c4378b55720c88b3abd4a1904931b8d883beaef51edec705a9d8b0ccd3ba898a0d4c05f75bd41fa3b90f0df7b5c79"
c2 # => "fcf4e21c6e711808a6984824f09552dccc5c4378b55720c88b3abd4a1904931b8d883beaef51edec705a9d8b0ccd3ba898a0d4c05f75bd41fa3b90f0df7b5c79"
c3 # => "fcf4e21c6e711808a6984824f09552dccc5c4378b55720c88b3abd4a1904931b8d883beaef51edec705a9d8b0ccd3ba898a0d4c05f75bd41fa3b90f0df7b5c79"
While we still have to pass in the same amount of information, using the hash allows us to see what each parameter is being assigned to, resulting in self-documenting code, which is a very important thing for long-term maintenance. Also, the hash doesn't require a specific order of the parameters, making it easier to write the code calling it; I usually know I need certain things but can't remember what order they're in.
That's all my take on how to write this type of code. See "Ruby 2 Keyword Arguments" for a similar write-up on the same topic.
Here's how you would define the method with keyword arguments:
def getChecksum(key:, txnid:, amount:, productinfo:, firstname:, email:, phone:, surl:, furl:)
str = key + '|' + txnid + '|' + amount + '|' + productinfo + '|' + firstname + '|' + email + '|' + phone + '|' + surl + '|' + furl+ '|'+ '|'+ '|'+ '|'+ '|'+ '|'+ '|'+ '|' + 'salt'
Digest::SHA512.hexdigest( str )
end
Then you can call it with a hash like you are currently.
I think this looks a bit cleaner, though:
def get_checksum(key:, txnid:, amount:, productinfo:, firstname:, email:, phone:, surl:, furl:)
str = "#{key}|#{txnid}|#{amount}|#{productinfo}|#{firstname}|#{email}|#{phone}|#{surl}|#{furl}||||||||salt"
Digest::SHA512.hexdigest(str)
end
Or perhaps:
def get_checksum(key:, txnid:, amount:, productinfo:, firstname:, email:, phone:, surl:, furl:)
str = [ key, txnid, amount, productinfo, firstname, email, phone, surl, furl ].join("|")
Digest::SHA512.hexdigest("#{str}||||||||salt")
end
You can learn more about keyword arguments, including how to give keywords default values to make them optional, here: https://robots.thoughtbot.com/ruby-2-keyword-arguments
I'm trying to calculate a hash for a payment form but I get the error:
no implicit conversion of Fixnum into String
so I interpret this that I'm trying to do math calculation on what is text. But how should I correct my code?
The instructions from the payment merchant are:
hash = SHA1(salt + "|" + description + "|" + amount + "|" + currency +
"|" + transaction_type)
So in my controller I have:
def checkout
#organization = Organization.new(organizationnew_params)
if #organization.save
#organization.members.each do |single_member|
single_member.send_activation_email
end
#amount = 100.00
#currency = "EUR"
#description = #organization.id
#transaction_description = "My description"
#transaction_type = "S"
### LINE BELOW HAS THE HASH, WHICH REFERS TO THE PRIVATE METHOD BELOW ###
#hash = hash(#description, #amount, #currency, #transaction_type)
render 'checkout'
else
render 'new_premium'
end
end
private
def hash(description, amount, currency, transaction_type)
#hash = SHA1(SALT + "|" + description + "|" + amount + "|" + currency + "|" + transaction_type)
end
In an initializer I have defined SALT (as well as my merchant_id which is used in the form that is posted to the merchant in the checkout view).
Writing this will be better.
hash = SHA1([salt, description, amount.to_s, currency,transaction_type].join("|"))
You interpreted it wrongly, it's the other way round. You're trying to use a number as a string.
Try to explicitely convert the number in to a string, like so:
#hash = SHA1(SALT + "|" + description + "|" + amount.to_s + "|" + currency + "|" + transaction_type)
As I don't know what all the variables' types are, you might have to add another to_s to a non-string.
The error indicates that you are adding a number to a string, I think the problem is that "amount" is a number which you are adding to a string. you just have to change that:
#hash = SHA1(SALT + "|" + description + "|" + amount.to_s + "|" + currency + "|" + transaction_type)
the :to_s method will convert amount into string. (if there are other numbers just do the same.)
Or you can do this with string interpolation:
#hash = SHA1("#{SALT}|#{description}|#{amount}|#{currency}|#{transaction_type}")
I want to convert a string to a byte array before I can sign it with a private key but I have not done it before. My code is as below could anybody give me a clue on how I can do it. Thank you.
require 'openssl'
require 'base64'
require 'digest/sha1'
#date = Date.today.strftime("%m/%d/%Y")
text_to_sign = "#{#order.phone_no}" + "#{#order.name}" + "#{#order.pay_type}" + "#{#order.pay_type}" + "1" + "MABIRA" + "81W30DI846" + "#{#date}" + "PULL" + "1" + "#{#cart.total_price}" + "#{#order.phone_no}" + ""
password = 'secret'
#converting the string to byte array
byte[] buff => new byte[1024]
text = buff.text_to_sign
private_key = OpenSSL::PKey::RSA.new(File.read('Private.key'), password)
ciphertext = private_key.private_encrypt(text)
ciphertext.encoding
signed_text = Base64.encode64(ciphertext).gsub("\n", '')
puts signed_text
signed_text
Ruby has the bytes method and you can convert that to an array with to_a
http://www.ruby-doc.org/core-1.9.3/ARGF.html#method-i-bytes
string = 'some string'
byte_array = string.bytes.to_a
# [115, 111, 109, 101, 32, 115, 116, 114, 105, 110, 103]