WebAPI integration

Technical questions relating to the iVeri WebService integration

WebAPI integration

by Stephen Fri Mar 25, 2016 11:08 pm
I'm trying to interact with the WebAPI layer through my PHP application running using the GuzzleHttp client, but I'm finding myself very confused as the WebAPI documentation isn't very clear. I've a couple of questions.

1) The docs show examples of Java and C# generating the base64 encoded SHA256 string, but the code expects the password and URL string to be defined - What URL is this supposed to be? The API endpoint or the full domain URL including the protocol?

I've rewritten the Java/C# code into PHP as follows:

Code: Select all
    private function generateToken($password, $date, $url) {           
        $md5_password = md5($password, true);
           
        $token_bytes = $this->gateway . $url.$date.$md5_password;
        $token_hash = hash("sha256", $token_bytes, true);
       
        return base64_encode($token_hash);
    }


The above returns an exact match as the Java and C# code depending on what values have been passed to 'password', 'date' and 'url'.

So once the above question is answered, the above code should do fine.

2) The next unclear bit of information is the authorisation header. The documentation specifies basic auth as the HTTP authentication method, but requests for usergroup, username, timestamp and token to be sent using basic auth? Correct me if I'm wrong, but basic auth generally expects only a username and password which GuzzleHttp actually enforces - an error is thrown if an attempt to throw in additional params is executed.

If somebody with some PHP experience with this authentication process could assist, that'd be great or if someone could elaborate on the documentation a little as it's not very detailed.
Posts
11
Joined
Tue Mar 22, 2016 4:03 pm

Re: WebAPI integration

by samora Wed Mar 30, 2016 7:53 am
The URL would be https://portal.nedsecure.co.za/api/merchant/authenticate
Posts
54
Joined
Thu Sep 17, 2015 3:29 pm

Re: WebAPI integration

by Stephen Wed Mar 30, 2016 11:26 am
Thanks. Since the token needs to be re-generated per request, I'm assuming the URL changes per different endpoint being hit. I'm still at a loss of understanding when it comes to submitting the token via basic HTTP auth -- We'll be having a telephonic call with some tech guys later today - hopefully - and I'll post the conclusion of the discussion here for any future developers who may run into this issue.

Can you tell me how the server matches the token server-side? Given that the token makes use of the client timestamp which includes milliseconds (This is a PITA in PHP, but managed to get it done), but I'm curious as to how the token matches given the delay between the client-server and the submitted timestamp. Does the server match against the timestamp passed in the AUTH HEADER? This is really just for curiosity sake.
Posts
11
Joined
Tue Mar 22, 2016 4:03 pm

Re: WebAPI integration

by Stephen Wed Mar 30, 2016 2:45 pm
For any PHP developers who run into issues with authorisation in future - let me save you some time. I've managed to get this working correctly using guzzle and setting the authorization header manually instead of using the standard expected username and password params.

First, we need to declare some credentials somewhere - Following OOP standards:

Code: Select all
private $backoffice_username = 'dummy_username';
private $backoffice_usergroup = 'dummy_usergroup';
private $backoffice_password = 'dummy_password';

private $backoffice_gateway = 'https://gateway.iveri.co.za/api/';


Note: The gateway is not what was mentioned earlier. The above works as expected.

Then we need a function to generate the timestamp in the format '"yyyyMMddHHmmssfff' which is would look something like 20160330143402422 - Year, month, day, hour, minute, seconds and milliseconds.

Now, if we didn't require milliseconds, this would be as easy as:
Code: Select all
return date('YmdHis');


Unfortunately, PHP doesn't have an easy way to append the milliseconds to this date so we need to add a bit of additional code:

Code: Select all
private function getTimestamp() {
   $microtime = microtime(true);
   $microseconds = sprintf("%06d",($t - floor($t)) * 1000000);
   $date = new DateTime( date('YmdHis'.$microseconds, $microtime) );

   return $date->format("YmdHisu");
}


Next up, the token generation as mentioned earlier - exception this time I've wrapped the entire thing in a function called 'generateHeader'.

Code: Select all
    private function generateHeader($url) {
        $date = $this->getTimestamp();

        $password = md5($this->backoffice_password, true);

        $token_bytes = $this->gateway . $url . $date . $password;
        $token_hash = hash("sha256", $token_bytes, true);
        $token_base64 = base64_encode($token_hash);

        $auth_header = 'Basic '
                . 'usergroup="' . $this->backoffice_usergroup . '", '
                . 'username="' . $this->backoffice_username . '", '
                . 'timestamp="' . $date . '", '
                . 'token="' . $token_base64 . '"';

        return $auth_header;
    }


Finally, using GuzzleHttp we can submit the request:

Code: Select all
 public function sendAuthorizationRequest() {
        $auth_header = $this->generateHeader('merchant/authenticate');

        $httpRequest = new Request('GET', 'merchant/authenticate', [
            'Authorization' => $auth_header
        ]);

        $httpResponse = $this->client->send($httpRequest);

        if ($httpResponse->getStatusCode() != 200) {
            throw new Exception(
            "Received unexpected HTTP code from Iveri gateway. Received HTTP '{$httpResponse->getStatusCode()}'");
        }

        return $httpResponse->getBody();
    }


And we'll receive the timestamp of the server. Success.

Of course I've skipped a few pre-configuration steps to take into consideration such as the certificate verification and what not, but that's all described within the documentation and depends on what HTTP client you're using. With Guzzle, you really just need to specify the verification location so that the HTTP client can match against the servers certificate.
Posts
11
Joined
Tue Mar 22, 2016 4:03 pm

Re: WebAPI integration

by samora Wed Mar 30, 2016 3:07 pm
Thanks Stephen, not much of a PHP Developer myself but this will definitely help :D
Posts
54
Joined
Thu Sep 17, 2015 3:29 pm

Re: WebAPI integration

by Stephen Wed Mar 30, 2016 3:16 pm
samora wrote:Thanks Stephen, not much of a PHP Developer myself but this will definitely help :D

No problem, thanks for the pointers. When I've got the full API wrapper integrated, I'll be building it as a Composer package and submitting it alongside my other packages here: https://packagist.org/search/?q=stephenlake so that any future developers can pull this in and avoid reinventing the wheel.
Posts
11
Joined
Tue Mar 22, 2016 4:03 pm

Re: WebAPI integration

by fabian Mon Apr 25, 2016 5:57 pm
Hi Stephen,

I am building something similar in Ruby at the moment and I was/am having the exact same problems and questions. So thank you for pointing most of the stuff out in the first place!

I use the gem RestClient (similar to Guzzle) and rewrote the token algorithm. I tried to establish a successful connection to the merchant/authenticate endpoint.

The token is the same as in the Java-Implementation. I tried to do it like you did, but I only get 401 Unauthorized.

This is my test implementation:

Code: Select all
require 'date'
require 'digest'
require 'rest-client'
require 'awesome_print'

username = 'yyy'
usergroup = 'xxx'
password = 'ccc'

def generate_token url, timestamp, password
  password_digest = Digest::MD5.digest(password)
  Digest::SHA256.base64digest("#{url}#{timestamp}#{password_digest}")
end

url = 'https://gateway.iveri.co.za/api/merchant/authenticate'
timestamp = DateTime.now.strftime("%Y%m%d%H%M%S%3N")

token = generate_token url, timestamp, password
auth = 'Basic usergroup="'+usergroup+'", username="'+username+'", timestamp="'+timestamp+'", token="'+token+'"'

@resource = RestClient::Resource.new( url )
@response = @resource.get( :Authorization => auth )
ap @response.body


Did you do something special to get the http authorization to work? Am I missing something?

I would much appreciate your help in this matter.

Cheers,

Fabian

Stephen wrote:For any PHP developers who run into issues with authorisation in future - let me save you some time. I've managed to get this working correctly using guzzle and setting the authorization header manually instead of using the standard expected username and password params.

First, we need to declare some credentials somewhere - Following OOP standards:

Code: Select all
private $backoffice_username = 'dummy_username';
private $backoffice_usergroup = 'dummy_usergroup';
private $backoffice_password = 'dummy_password';

private $backoffice_gateway = 'https://gateway.iveri.co.za/api/';


Note: The gateway is not what was mentioned earlier. The above works as expected.

Then we need a function to generate the timestamp in the format '"yyyyMMddHHmmssfff' which is would look something like 20160330143402422 - Year, month, day, hour, minute, seconds and milliseconds.

Now, if we didn't require milliseconds, this would be as easy as:
Code: Select all
return date('YmdHis');


Unfortunately, PHP doesn't have an easy way to append the milliseconds to this date so we need to add a bit of additional code:

Code: Select all
private function getTimestamp() {
   $microtime = microtime(true);
   $microseconds = sprintf("%06d",($t - floor($t)) * 1000000);
   $date = new DateTime( date('YmdHis'.$microseconds, $microtime) );

   return $date->format("YmdHisu");
}


Next up, the token generation as mentioned earlier - exception this time I've wrapped the entire thing in a function called 'generateHeader'.

Code: Select all
    private function generateHeader($url) {
        $date = $this->getTimestamp();

        $password = md5($this->backoffice_password, true);

        $token_bytes = $this->gateway . $url . $date . $password;
        $token_hash = hash("sha256", $token_bytes, true);
        $token_base64 = base64_encode($token_hash);

        $auth_header = 'Basic '
                . 'usergroup="' . $this->backoffice_usergroup . '", '
                . 'username="' . $this->backoffice_username . '", '
                . 'timestamp="' . $date . '", '
                . 'token="' . $token_base64 . '"';

        return $auth_header;
    }


Finally, using GuzzleHttp we can submit the request:

Code: Select all
 public function sendAuthorizationRequest() {
        $auth_header = $this->generateHeader('merchant/authenticate');

        $httpRequest = new Request('GET', 'merchant/authenticate', [
            'Authorization' => $auth_header
        ]);

        $httpResponse = $this->client->send($httpRequest);

        if ($httpResponse->getStatusCode() != 200) {
            throw new Exception(
            "Received unexpected HTTP code from Iveri gateway. Received HTTP '{$httpResponse->getStatusCode()}'");
        }

        return $httpResponse->getBody();
    }


And we'll receive the timestamp of the server. Success.

Of course I've skipped a few pre-configuration steps to take into consideration such as the certificate verification and what not, but that's all described within the documentation and depends on what HTTP client you're using. With Guzzle, you really just need to specify the verification location so that the HTTP client can match against the servers certificate.
Posts
1
Joined
Mon Apr 25, 2016 3:48 pm

Re: WebAPI integration

by Stephen Mon May 23, 2016 11:12 pm
Hi Fabian, sorry for the delay -- been well busy getting this integration complete :P

I'm not too familiar with Ruby, but your code looks decent and if you're getting the expected token I assume the issue isn't there. Are there supposed to be hashes (#) within the digest string though? Excuse my ignorance in Ruby.

There were quite a number of things that were tweaked on both our end and IVeri's before we actually started submitting test transactions. Iveri support had some extremely interesting feedback when I emailed them, none of which worked, but perhaps it works out for you.

They told me to switch to the same API URL that was mentioned here:


Which pretty much broke everything and still does not work. while using 'https://gateway.iveri.co.za/api/merchant/authenticate' is working as expected. I emailed them about this about a month ago, but still awaiting a response :!:

Secondly they advised us to renew our Certificate ID, which didn't do anything either. Instead they had to "switch our certificate chain" after creating our first certificate ID (in backoffice) which somehow fixed all problems we were having beyond the 401 errors (again, without switching URL's - when we use the URL they mentioned, the 401 errors return).

Enough with the rambling though, in short, I think these are the most important things to look out for:
- Ensure each parameter in the auth header is correct and there isn't some ridiculously small typo hiding somewhere
- Ensure you've HTTP certificate verification enabled (this was a hassle to setup on my local machine). The SSL certificate link given in the documentation doesn't exist, and instead found that you need to download the file form backoffice after your cerificate ID has been 'activated' for lack of a better word. You may also have to go through a bit of a process converting the CRT to a PEM file as well, but google/stackoverflow comes in handy there.
- Be sure that your timestamp includes milliseconds
- Verify your backoffice usergroup, username and password creds
- If all else fails, email support and ensure that your application/certificate ID's are valid and setup correctly - it took us 5 weeks to get someone to notice that ours weren't "activated".

Sorry I can't be of much more help, I'm actually still having some other unrelated issues that I'm trying to resolve without going in circles, but I hope your issue get resolved soon.
Posts
11
Joined
Tue Mar 22, 2016 4:03 pm

Sort By

Jump To