Authenticating with OPAY API

Authenticating with OPAY API

Who and What's OPAY?

Opay is a financial institution, that provides different solutions and services to handle and make payment seamless in Nigeria.

Motive for writing

This article addresses the problem I faced during the authentication and integration of OPAY API for temporary virtual account and how I was able to solve it.

Before we delve deeper, it is important to note that Opay has two different APIs for what appears to be handling the same services, but according to them, in a different way. This article will focus only on the Opay Checkout API, and you can find the documentation on their docs page.

Challenges and some discrepancies

Opay API integration requires a specific authentication strategy depending on the service(s) being utilized. Some services require a public key for authentication, while others require a hashed value of the payload signed by the secret key. After reviewing the documentation, I determined that the authentication strategy I need is a hashed payload signed by my secret key.

Note: Some parts of the documentation stated that you need to sign your hash payload using both your merchant ID and private key, while another part stated the payload should be signed using private/secret key only. See the pictures below.

However, the best way to go and get authenticated is to sign the payload only with your private key.

The next stage of the doc is where Opay provides you with a sample code to authenticate on their platform, and this is available in 3 different languages.

class BankTransferController
{
    private $secretkey;
    private $merchantId;
    private $url;

    public function __construct() {
        $this->merchantId = '256622011275597';
        $this->secretkey = 'OPAYPRV1641***********926';
        $this->url = 'https://testapi.opaycheckout.com/api/v1/international/payment/create';
    }

    public function test(){
        $data =     [
          'amount'=>
              [
              'currency'=>'NGN',
              'total'=>400
              ],
          'callbackUrl'=>'https://testapi.opaycheckout.com/api/v1/international/print',
          'country'=>'NG',
          'customerName'=>'customerName',
          'payMethod'=>'BankTransfer',
          'product'=>
              [
              'description'=>'dd',
              'name'=>'name'
              ],
          'reference'=>"12345a",
          'userPhone'=>'+1234567879'
          ];

        $data2 = (string) json_encode($data,JSON_UNESCAPED_SLASHES);
        $auth = $this->auth($data2);
        $header = ['Content-Type:application/json', 'Authorization:Bearer '. $auth, 'MerchantId:'.$this->merchantId];
        $response = $this->http_post($this->url, $header, json_encode($data));
        $result = $response?$response:null;
        return $result;
    }

    private function http_post ($url, $header, $data) {
        if (!function_exists('curl_init')) {
            throw new Exception('php not found curl', 500);
        }
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_TIMEOUT, 60);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        $response = curl_exec($ch);
        $httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error=curl_error($ch);
        curl_close($ch);
        if (200 != $httpStatusCode) {
            print_r("invalid httpstatus:{$httpStatusCode} ,response:$response,detail_error:" . $error, $httpStatusCode);
        }
        return $response;
    }
    public function auth ( $data ) {
        $secretKey = $this->secretkey;
        $auth = hash_hmac('sha512', $data, $secretKey);
        return $auth;
    }

}

Upon reviewing the code, you would notice two different things, Opay passed a serialized payload to the hashing method signed only by the private/secret key, and secondly, Opay passed the serialized payload again to the request body for authentication, this failed to authenticate a request for 2days upon integration, despite supplying correct credentials and by following the docs and sample code. See the image below.

Resolutions

After hours of struggle, I came to the following resolution.

  • Do not attempt to sign your payload with both MerchantID and Private Key as stated in some parts of the documentation, but only with your Private key.

  • Serialize your payload with json_encode, if you are a php dev, and pass the serialized payload to a variable, this variable should then be passed to your hashing function/method.

  • Do not pass serialized payload to the body of your request as stated in the docs and the sample code, rather pass the deserialized payload to the body, this is done in PHP using json_decode.

  • and voila we are authenticated and given our temporary virtual account.

Sample code for authentication

Please feel free to point out any typos, and I would gladly make the necessary modifications. Thanks.