question

rakeshkumar125 avatar image
rakeshkumar125 asked exelanzgirija commented

Please help me encrypt card details in card token api

I am trying to integrate pay API using PHP but stuck in generate card tokens. It always generates an error in encrypt details.
Below is my code.

API Name : ECOMMERCE SERVICE API
Enviroment : Sandbox

<?php
/*phpseclib1.0.2*/
include('phpseclib/Crypt/RSA.php');
include('phpseclib/Math/BigInteger.php');

/* ========== GET API KEY USING TOKEN ===========*/

$authToken = 'xxxxxxx-cdde-xxxxx-xxxx-xxxxxxxxx'; /* which is generate from clover backend system*/

$curl = curl_init();
curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://apisandbox.dev.clover.com/pakms/apikey',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'GET',
  CURLOPT_HTTPHEADER => array(
    'Accept: application/json',
    'Authorization: Bearer '.$authToken
  ),
));

$response = curl_exec($curl);
curl_close($curl);
$apikeyArr = json_decode($response);
$apiKey = $apikeyArr->apiAccessKey;

echo 'API KEY <br/> ';
echo $apiKey;

/* ========== GET PAY KEY FOR ENCRYPT CARD DETAILS ===========*/

$curl = curl_init();
curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://sandbox.dev.clover.com/v2/merchant/B0T4DYVTYBTZ1/pay/key',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'GET',
  CURLOPT_HTTPHEADER => array(
    'Accept: application/json',
    'Authorization: Bearer '.$authToken,
    'Content-Type: application/json'
  ),
));

$response = curl_exec($curl);

curl_close($curl);
$pay_key_data = json_decode($response);

echo '<br/> Key Data Response <br/> <pre>';
print_r($pay_key_data);
echo '<pre>';

    $rsa = new Crypt_RSA();
    $prefix = $pay_key_data->prefix;
    $modulus = $pay_key_data->modulus;
    $exponent = $pay_key_data->exponent;
 
    /* ========= PREPARING CARD DETAILS TO PASS FOR TOKEN ============= */

    $user_cc_no = '6011366668';
    $user_cc_mo = '12';
    $user_cc_yr = '2021';
    $user_cc_cvv = '123';
    $user_zip = '23402';
    $stingToEnc = $prefix.$user_cc_no; 

    $modulus = new Math_BigInteger($modulus);
    $exponent = new Math_BigInteger($exponent);
    $rsa->loadKey(array('n' => $modulus, 'e' => $exponent));
    $rsa->setPublicKey();
    $mypublickey = $rsa->getPublicKey();
    $stingToEncPublickey = $stingToEnc; 
    $ciphertext =   $rsa->encrypt($stingToEncPublickey);
    $stingBase64Encpted = base64_encode($ciphertext);
$orderData = array();

    $orderData['cardEncrypted'] = $stingBase64Encpted;
    $orderData['first6'] = substr($user_cc_no, 0, 6);
    $orderData['last4'] = substr($user_cc_no,-4);
$orderData['exp_month'] = $user_cc_mo;
    $orderData['exp_year'] = $user_cc_yr;
    $orderData['cvv'] = $user_cc_cvv;
    $orderData['brand'] = "DISCOVER";
    $cardData['card'] = $orderData;
    $jsonCardData = json_encode($cardData);
    
    echo '<br/> Card data that Passed <br/>';
    echo '<pre>';
    print_r($cardData);
    echo '<pre>';
    /* =============== NOW CALL API FOR TOKEN =================*/

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://token-sandbox.dev.clover.com/v1/tokens',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_POSTFIELDS =>$jsonCardData,
  CURLOPT_HTTPHEADER => array(
    'Accept: application/json',
    'apikey: '.$apiKey,
    'Content-Type: application/json'
  ),
));

$response = curl_exec($curl);

curl_close($curl);
$responseCardTokenDetails = json_decode($response);

echo '<br/> Final Result <br/>';
echo '<pre>';
print_r($responseCardTokenDetails);
echo '</pre>';

?>

Now there is the below result from card token API. The above two working fine but error from last API.

Array
(
    [card] => Array
        (
            [cardEncrypted] => qmLHcbgABk0abTNxm/tugSbR7OheVxlepRyciQDQBLw4WsGIr9UU6TjqxMcNuRxD/dD/e8p8c/G93B97Y1WygLpwmmN0H43E6QNlsNlQ/B/5AE3fpexTseOxLtB0/0wdmPbr1wA4vnxPfBMzNR7yPJ0Vk0iTsUc6ywkel+zn84wCBHjpAqMEtzy0WpdBe4VNVt0FxXZH7JTh7a/BxiGuvBiKPMvdGQChBvMBqEvPO8IQTjGlwjxoVKyKhuj7gT+AFA2SJ2NEIFigrbskkQQduGICxC5GdmT/YQWRPNlxgPvf8mckDNLfANBD5SeiuErmDT8DsdWu2kBhuKhGrf7FVQ==
            [first6] => 601136
            [last4] => 6668
            [exp_month] => 12
            [exp_year] => 2021
            [cvv] => 123
            [brand] => DISCOVER
        )

)

 Final Result 
stdClass Object
(
    [message] => 400 Bad Request
    [error] => stdClass Object
        (
            [type] => invalid_request_error
            [code] => invalid_request
            [message] => Please provide either raw pan or encrypted pan.
        )

)

One more thing in card details data if remove cardEncrypted field then errors still the same.
Does anyone have an idea bout the above issue?

Thanks,
Rakesh Kumar

Payments
4 comments
10 |2000

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

David Marginian avatar image David Marginian ♦♦ commented ·

See our API reference - https://docs.clover.com/reference/createtoken.

I am not very familiar with PHP but your request looks wrong. You aren't passing the expected properties (e.g. encrypted_pan).

0 Likes 0 ·
rakeshkumar125 avatar image rakeshkumar125 David Marginian ♦♦ commented ·

Okay, now I have passed your suggested properties you can see in the below request.

{
   "card":{
      "encrypted_pan":"CfTUMOHgvt88V+pz3XocDS3/QQuWwHuS/9TdhUvvPcQIaf3uxpj6x8Y4s7lwzq3r4aG0DuL8HluICz5VGfIvLcnKmGJ41wWQKFbfT8Qjtlbmq6sOrDt+5haWjC6RKb4zCW2uhpYmbvAUgF1ka3X0ymxKYrbxvyOSv8zfu9zJ4K4nlBBSJsjCMhdMDDVlqsBK1paPcLgA/AydIR8MvBWNopozIB4hoy0wjNrdje7ArLxOv0cXjcDQTrsYgpJgs9CHv2/18RzLFVUOAM+yEXAbnKcS1OnM0CKN1NA4eLsXaA1uYmsazc/IbgM2vZzfnYp0z85Z7Ga0OjhkFxYvyL/zdw==",
      "first6":"601136",
      "last4":"6668",
      "exp_month":"12",
      "exp_year":"2021",
      "cvv":"123",
      "brand":"DISCOVER"
   }
}

Now Error Become changed. But still not working.

{
    "message": "500 Internal Server Error",
    "error": {
        "type": "api_error",
        "code": "processing_error",
        "message": "An error occurred while processing the token request."
    }
}
0 Likes 0 ·
David Marginian avatar image David Marginian ♦♦ rakeshkumar125 commented ·

It appears you aren't encrypting the card properly. Take a look at our documentation and make sure you are doing everything correctly - https://docs.clover.com/docs/ecommerce-generating-a-card-token#encrypting-card-data.

0 Likes 0 ·
exelanzgirija avatar image exelanzgirija commented ·

Please help me. I have written the code in PHP. But i am getting

"message": "Please provide either raw pan or encrypted pan."

while generating card token.


<?php

include 'vendor/autoload.php';

use phpseclib3\Crypt\PublicKeyLoader;

use phpseclib3\Crypt\RSA;

use phpseclib3\Math\BigInteger;

use phpseclib3\Net\SSH2;

//card number

$card_number = "4242424242424242";

//Get Public Encryption Key from CDN ...

$PUBLIC_ENCRYPTION_KEYS_URL = "https://checkout.clover.com/assets/keys.json";

$TA_PUBLIC_KEYS_JSON_ARRAY = file_get_contents($PUBLIC_ENCRYPTION_KEYS_URL);

$TA_PUBLIC_KEYS = json_decode($TA_PUBLIC_KEYS_JSON_ARRAY,true);

//Parse the Base64 public key string (returned by the CDN). Obtain the modulus and exponent.

$pa_key = bin2hex( base64_decode( $TA_PUBLIC_KEYS['TA_PUBLIC_KEY_DEV'] ) );

$modulus = substr( $pa_key, 0, 512 );

$exponent = substr( $pa_key, -5 );

//Generate an RSA public key using the modulus and exponent values.

$rsa_public_key = PublicKeyLoader::load(

array(

'e' => new BigInteger( $exponent, 16 ),

'n' => new BigInteger( $modulus, 16 ),

)

);

//print_r($rsa_public_key); exit;

//Prepend the prefix value to the card number.

//Using the public key, encrypt the combined prefix and card number.

openssl_public_encrypt( '00000000'.$card_number, $encrypted, $rsa_public_key, OPENSSL_PKCS1_OAEP_PADDING );

$pan = base64_encode( $encrypted );

print_r($pan);

?>


0 Likes 0 ·
martin7 avatar image
martin7 answered exelanzgirija commented

@rakeshkumar125 Did you solve this? I am having the same issue and no idea what I am doing wrong with the encryption.

5 comments
10 |2000

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

David Marginian avatar image David Marginian ♦♦ commented ·

Have you seen our documentation - https://docs.clover.com/docs/ecommerce-generating-a-card-token#encrypting-card-data ? I recently went through these instructions as well as the Java example and everything was working fine.

0 Likes 0 ·
martin7 avatar image martin7 David Marginian ♦♦ commented ·

Yes, I am following it but doing in php. I've already spend two days trying to figure what is wrong with the encryption. I am using https://phpseclib.com/docs/rsa#raw-rsa-public-keys for creating the RSA key.

I am fetching the modulus, exponent and prefix from
https://sandbox.dev.clover.com/v2/merchant/MERCHANT_ID/pay/key

This is the code for generating RSA Key

function generate_rsa_key( $modulus, $exponent ) {
  $key = PublicKeyLoader::load(
    array(
       'e' => new BigInteger( $exponent ),
       'n' => new BigInteger( $modulus ),
    )
  );

  return $key;
}

$rsa = $this->generate_rsa_key( $modulus_exponent_data['modulus'], $modulus_exponent_data['exponent'] );

For encrypting is this

$pan = base64_encode( $rsa->encrypt( $modulus_exponent_data['prefix'] . $card_number ) );

I send the request to https://token-sandbox.dev.clover.com/v1/tokens

This is the error I get. I am pretty sure this is because of wrong card encryption.

[message] => 500 Internal Server Error
    [error] => Array
        (
            [type] => api_error
            [code] => processing_error
            [message] => An error occurred while processing the token request.
        )


This is full body request

{
"card": {
"encrypted_pan": "oGloPuHTlyjWtEg7hDZ/IXLYm497xXw/qDimAX/29E4g5FI6eOYxM8khe3sJ/V24hyoolgSb1J/3T+QkJX0OZnQhoHKRXjF+nMyvumT63wjLJoNvulrWxNTtiPN08kD9Phsyp50NpdmWzIBerVRwYvNT6pien9DzjTl6dCbXTN0beA2rsL5gcALz9+4CF9QHQa8Nov3XUJn9+zA+7gd15b9vFbGtDCX1yjeMMlgBpDeKJ3zg3rF68cjS98IrGCW8CdSUicO7LRd0y4dozb2jxFU1YTgcNiiV4jjkh+QMC7N4D0U5wuRjGV3z9h/Km7mKot82fNgrJUj60s2/Usrl5Q==",
"first6": "601136",
"last4": "6668",
"exp_month": "09",
"exp_year": "23",
"cvv": "123",
"brand": "Discover"
}
}
0 Likes 0 ·
David Marginian avatar image David Marginian ♦♦ martin7 commented ·

You aren't following the instructions as they tell you to pull the encryption keys from the CDN, not pay/key. If you want to use the data returned from pay/key you will need to adjust your tokenize call to pass card.transarmor_key_id where transarmor_key_id is the "id" returned in the pay/key call:

"card": {
   ...
   "transarmor_key_id": id from the pay/key response here 
   ...
}


0 Likes 0 ·
Show more comments
exelanzgirija avatar image exelanzgirija David Marginian ♦♦ commented ·

Please help me. I have written the code in PHP. But i am getting

"message": "Please provide either raw pan or encrypted pan."

while generating card token.


<?php

include 'vendor/autoload.php';

use phpseclib3\Crypt\PublicKeyLoader;

use phpseclib3\Crypt\RSA;

use phpseclib3\Math\BigInteger;

use phpseclib3\Net\SSH2;


//card number

$card_number = "4242424242424242";


//Get Public Encryption Key from CDN ...

$PUBLIC_ENCRYPTION_KEYS_URL = "https://checkout.clover.com/assets/keys.json";

$TA_PUBLIC_KEYS_JSON_ARRAY = file_get_contents($PUBLIC_ENCRYPTION_KEYS_URL);

$TA_PUBLIC_KEYS = json_decode($TA_PUBLIC_KEYS_JSON_ARRAY,true);

//Parse the Base64 public key string (returned by the CDN). Obtain the modulus and exponent.

$pa_key = bin2hex( base64_decode( $TA_PUBLIC_KEYS['TA_PUBLIC_KEY_DEV'] ) );

$modulus = substr( $pa_key, 0, 512 );

$exponent = substr( $pa_key, -5 );


//Generate an RSA public key using the modulus and exponent values.

$rsa_public_key = PublicKeyLoader::load(

array(

'e' => new BigInteger( $exponent, 16 ),

'n' => new BigInteger( $modulus, 16 ),

)

);

//print_r($rsa_public_key); exit;

//Prepend the prefix value to the card number.

//Using the public key, encrypt the combined prefix and card number.

openssl_public_encrypt( '00000000'.$card_number, $encrypted, $rsa_public_key, OPENSSL_PKCS1_OAEP_PADDING );

$pan = base64_encode( $encrypted );

print_r($pan);

?>

0 Likes 0 ·
martin7 avatar image
martin7 answered David Marginian Deactivated edited

I also tried with keys from CDN.. Same error

1 comment
10 |2000

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

David Marginian avatar image David Marginian ♦♦ commented ·

With keys from CDN you have to parse out the modulus, so your code would need to change. From looking at the logs, it certainly appears to be an decryption error. I am not familiar with PHP, but a bit of Googling and I found this, https://github.com/phpseclib/phpseclib/issues/1290, may want to take a look at some of the comments as it looks like their code is a bit different. I am not sure what encryption algorithm PHP is using but it must be RSA/None/OAEPWithSHA1AndMGF1Padding.

0 Likes 0 ·
martin7 avatar image
martin7 answered martin7 commented

Yea, I checked that, but I am using the latest version of the library and that code shown there is not used anymore.

When using keys from CDN, I have the following:

$modulus  = substr( $pa_key, 0, 256 );
$exponent = substr( $pa_key, 256, 256 );

If I read the Java code correctly modulus is the first 256 characters of the decoded TA_PUBLIC_KEY_DEV and exponent is the second 256 characters starting from 256 position. The 3rd param here is the length of the sub string.

function generate_rsa_key( $modulus, $exponent ) {
   $key = PublicKeyLoader::load(
     array(
       'e' => new BigInteger( $exponent, 256 ),
       'n' => new BigInteger( $modulus, 256 ),
     )
  );

  return $key;
}

What should the prefix for the card be when keys are from CDN? 8 zeros like in the Java example?


10 |2000

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

martin7 avatar image
martin7 answered

Any chance you can send me generated PAN for this test card 6011 3610 0000 6668 so I can check if it will pass?

10 |2000

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

swood1345 avatar image
swood1345 answered swood1345 published

The following works in a laravel 5 app. There's nothing in the documentation really for transarmor_key_id.

https://docs.clover.com/docs/ecommerce-generating-a-card-token#tokenize-encrypted-card-data is the only mention and it doesn't say that transarmor_key_id is optional or where it comes from.

use phpseclib3\Crypt\PublicKeyLoader;

use phpseclib3\Math\BigInteger;

/**

* Uses exponent and modulo from Clover's public key to create a new RSA key to encrypt card data

* https://docs.clover.com/docs/ecommerce-generating-a-card-token

* https://phpseclib.com/docs/rsa

*/

public function GetEncryptedPan(string $cc_number): string

{

$PREFIX_ID = "00000000";

try {

/**

* Step 1: Parse the Base64 public key string (returned by the CDN). Obtain the modulus and exponent.

*/

$base64PubKeyStr = env('TA_PUBLIC_KEY');

$parsed = base64_decode($base64PubKeyStr);

$modulus = new BigInteger(substr($parsed, 0, 256), 256);

$exponent = new BigInteger(substr($parsed, 256, 512), 256);


/** Step 2: Generate an RSA public key using the modulus and exponent values. */


// Generate public key

$key = PublicKeyLoader::load([

'e' => $exponent,

'n' => $modulus

]);

// echo $key;


/** Step 3: Prepend the prefix value to the card number. */

/** Step 4: Using the public key, encrypt the combined prefix and card number. */

$prefixed = $PREFIX_ID . $cc_number;

$encrypted_data = "";


openssl_public_encrypt($prefixed, $encrypted_data, $key, OPENSSL_PKCS1_OAEP_PADDING);


/** Step 5: Base64 encode the resulting encrypted data into a string. This string is the encrypted_pan value in the */

return base64_encode($encrypted_data);

} catch (Exception $x) {

LogHelper::create('CLOVER_ENCRYPTION_ERROR', ['msg' => $x->getMessage(), 'trace' => $x->getTrace()]);

throw $x;

}

}


$encrypted_pan = $this -> GetEncryptedPan ( $card_number );
$cardObjForToken = [ 'card' => [ 'encrypted_pan' => $encrypted_pan , // 'transarmor_key_id' => '', // 'number' => $card_number, 'exp_month' => $month_select , 'exp_year' => $year_select , 'cvv' => $cvv , 'brand' => $card_brand , 'first6' => $first6 , 'last4' => $last4 , ]
];
10 |2000

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

jamemadison1 avatar image
jamemadison1 answered

Did you solve it? phrazle

10 |2000

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Write an Answer

Hint: Notify or tag a user in this post by typing @username.

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Welcome to the
Clover Developer Community