Reputation: 69
I am currently trying to Ping the Payfast API to get basic authentication rate.
I am stuck at getting the signature correct I think.
To generate the signature I am sorting all variables in alphabetical order as seen here and URLencoding:
string signature = HttpUtility.UrlEncode($"merchant-id={merchantID}&passphrase={passphrase}×tamp={timestamp}&version={version}");
I am then generating the MD5 hash string as seen here
using (var md5Hash = MD5.Create())
{
// Byte array representation of source string
var sourceBytes = Encoding.UTF8.GetBytes(signature);
// Generate hash value(Byte Array) for input data
var hashBytes = md5Hash.ComputeHash(sourceBytes);
// Convert hash byte array to string
var hash = BitConverter.ToString(hashBytes).Replace("-", string.Empty);
// Output the MD5 hash
Console.WriteLine(signature + " is: " + hash);
string hashlower = hash.ToLower()
}
I then add the signature to the header
request.AddHeader("merchant-id", merchantID);
request.AddHeader("version", version);
request.AddHeader("signature", hashlower);
request.AddHeader("timestamp", timestamp);
This seems correct when viewing an example in their postman collection https://documenter.getpostman.com/view/10608852/TVCmSQZu
Could anyone pick anything up that I am doing wrong in the signature generation
ull code
string timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH\\:mm\\:ss", CultureInfo.InvariantCulture);
string signature = HttpUtility.UrlEncode($"merchant-id={merchantID}&passphrase={passphrase}×tamp={timestamp}&version={version}");
using (var md5Hash = MD5.Create())
{
// Byte array representation of source string
var sourceBytes = Encoding.UTF8.GetBytes(signature);
// Generate hash value(Byte Array) for input data
var hashBytes = md5Hash.ComputeHash(sourceBytes);
// Convert hash byte array to string
var hash = BitConverter.ToString(hashBytes).Replace("-", string.Empty);
// Output the MD5 hash
Console.WriteLine(signature + " is: " + hash);
string hashlower = hash.ToLower();
var client = new RestClient("https://api.payfast.co.za/ping");
client.Timeout = -1;
var request = new RestRequest(Method.GET);
request.AddHeader("merchant-id", merchantID);
request.AddHeader("version", version);
request.AddHeader("signature", hashlower);
request.AddHeader("timestamp", timestamp);
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);
}
Upvotes: 1
Views: 446
Reputation: 245
Here is a working implementation, where "data" contains the passphrase.
private string GenerateSignature(Dictionary<string, string> data)
{
var orderedData = new SortedList<string, string>(data);
var getString =
string.Join("&", orderedData.Select(item => $"{item.Key}={Uri.EscapeDataString(item.Value)}"));
using var md5 = MD5.Create();
var hashedBytes = md5.ComputeHash(Encoding.ASCII.GetBytes(getString));
var sb = new StringBuilder(hashedBytes.Length * 2);
foreach (byte b in hashedBytes)
{
sb.Append(b.ToString("x2"));
}
return sb.ToString();
}
Upvotes: 0
Reputation: 1
I took the API Documentation and converted line by line matching everything, after copying, and running their Node.js and Python sample code.
Two things came out in my findings.
URL Encoding.
When you URL Encode in c#, eg your date:
2023-02-20T20:00:00 becomes 2023-02-20T20%3a00%3a00
PayFast from Python and Node.js Encode as Uppercase, so:
2023-02-20T20:00:00 becomes 2023-02-20T20%3A00%3A00
Notice the 'A' in the latter instance, ensure your URL encoding is correct.
Use Uri.EscapeDataString([string])
Hash Encoding:
You are assuming that the Encoding is using UTF-8 in c# is the same the Crypto hashing from Node.js and Python, which is in fact ASCII after spending time going through all the variations. Update the following:
var sourceBytes = Encoding.UTF8.GetBytes(signature);
to the following:
var sourceBytes = Encoding.ASCII.GetBytes(signature);
Hex your Hash!
You also need to Hex your hash before you return it
var sb = new StringBuilder(hash.Length * 2);
foreach (byte b in hash)
{
sb.Append(b.ToString("x2")); // x2 is lowercase
}
signature = sb.ToString();
Wrap everything into a nice global method with a SortedList<string,string>
as input, iterate through the values, string them with a StringBuilder
, MD5 Hash it, and return your signature.
(Yes, a SortedList<TKey,TValue>
automatically sorts the list as you enter the values on TKey string, which is a requirement in the API Documentation.)
Upvotes: 0