Payatom
  • PayAtom
    • India
      • Payin
      • Payout
    • Bangladesh
      • Payin
      • Payout
  • API Integration
    • India
      • Payin P2P Seamless UPI Integration
      • Payin P2P Non-Seamless UPI Integration
      • Payin P2P Non-Seamless IMPS Integration
      • Payout P2P Seamless IMPS Integration
      • Payin P2C Seamless UPI Integration
      • Payin P2C Non-Seamless UPI Integration
      • Payout P2C Seamless UPI Integration
    • Bangladesh
      • Payin P2P Seamless Wallet Integration
      • Payin P2P Non-Seamless Wallet Integration
      • Payout P2P Seamless wallet Integration
      • Payin P2C Seamless Wallet Integration
      • Payin P2C Non-Seamless Wallet Integration
      • Payout P2C Seamless wallet Integration
    • Pakistan
      • Pakistan H2H Integration
    • P2P Accounts Wallet Balance
    • Complaint
    • Payin Reconciliation
    • Payout Reconciliation
    • Wallet Transaction Summary
    • Wallet Data Endpoint
Powered by GitBook
On this page
  • Let's see how it works:
  • PAYOUT REQUEST :
  • Payout Request
  • CALLBACK
  • STATUS POLLING :
  • COMPLAINT
  • RECONCILIATION
  1. API Integration
  2. India

Payout P2C Seamless UPI Integration

For onboarding in both the UAT and production environments, the merchant needs to provide the following information:

Technical Information Required:

  1. IP Address: (For dynamic IPs, please provide a range of IP addresses.)

  2. Callback URL/WebHook URL: This is used to notify your server of any changes in transaction status from our back office.

Once the merchant has provided all the required technical details, we will complete the necessary configuration in our back office and provide the Merchant ID (MID/PID) along with login credentials.

Let's see how it works:

  1. The merchant will send a payout request through our API. Along with that, the merchant has to send the customer's UPI id, account_holder, the amount, payment_mode as upi.

  2. Once we receive the request in the correct format, we will share a payment reference string (ref_code) that is necessary for any future references or actions related to the transaction.

  3. After the payout is processed, whether successful or failed, we will send callback data to the provided callback URL (WebHook URL) with the details of the transaction.

  4. To confirm the status of the payout, the merchant can use the polling API to check the current status and update their system accordingly with the payment outcome. (Status Polling)

PAYOUT REQUEST :

Before proceeding with this section of the document, the developer must review the basic workflow. This page explains how to submit a payment request.

Note: All requests must originate from whitelisted IP addresses. (Please ensure that your IP is whitelisted before making a request.)

Payout Request

POST https://<domain>/payout/api/request.php

Merchant makes a payout request.

Required Fields :

  • payment_mode: Set to "upi"

  • pid: Provided MID

  • order_id: Unique order ID (must be 10 to 13 alphanumeric characters)

  • amount: Integer value representing the amount

  • vpa: Receiver's UPI VPA

  • account_holder: Receiver's account name

Headers

Name
Value

Content-Type

application/json

Body

Name
Type
Description
Mandatory

payment_mode

string

upi

Yes

pid

string

provided MID/PID

Yes

order_id

string

unique order id(10 to 13 alphanumeric string)

Yes

amount

string

amount

Yes

upi_id

string

Receiver's UPI VPA

Yes

account_holder

string

Receiver's account name

Yes

{
    "payment_mode":"upi",
    "pid":"2323232323",
    "order_id":"6876mhnytg",
    "amount":"8128",
    "upi_id":"3223mdsds@upi",
    "account_holder":"sanu",
    "email":"sanu@gmail.com",
    "phone":"8754850254"
}

Sample Response Body

{
    "ref_code":"7701004b34f1db47cbf7edf3eb376703158df1e88b4c1c4a8b2b04d9ebe500d07683f0f5615eccc0c038cb621823f0aadb6df1adc44904e0ba1044c0cbcbeb4e",
    "status":"Pending",
    "message":"Request Saved successfully"
}

CALLBACK

We trigger your callback URL whenever there is a change in the transaction status.

Valid Transaction status are:

  1. Approved

  2. Declined

  3. Pending

  4. Processing

  5. Failed

The most famous transaction changes are (but not limited):

  1. Pending => Processing

  2. Pending => Approved

  3. Pending => Declined/Failed

  4. Approved => Declined/Failed

The callback landing page must be hosted on your server at a secret path, but it should still be publicly accessible from our whitelisted IPs. (Please ensure you accept data only from our IPs by implementing proper IP whitelisting.)

In the POST body, you will receive the following properties in JSON format:

Name
Type
Description

order_id

string

Your order id shared

requested_amount

int

requested amount

processed_amount

int

received amount

bank_ref

string

bank reference/UTR details if available

sender_pg

string

sender account name id if available

ref_code

string

unique code for the transaction

status

string

status of payment at this time

post_hash

string

post hash for security verification

payment_type

string

payment type

request_time

string

payment request time

action_time

string

payment made time

upi_vpa

string

receiver vpa

account_no

string

receiver account number

account_holder

string

receiver name

ifsc

string

receiver IFSC code

bank_name

string

receiver bank name if available

bank_address

string

receiver bank address if available

transaction_info

array

status change log of transaction

Follow the steps to verify the integrity of received data:

  1. base64_decode post_hash:

Capture json data available in post body, then json decode and then pick post hash, and then base64 decode

$data = file_get_contents("php://input");
$json = json_decode($data, true);
$encrypted_hash=base64_decode($json['post_hash']);
  1. Decrypt hash

Once you decrypt $encrypted_hash, you will get get plain remote_hash.

$remote_hash=decrypt($encrypted_hash);
function decrypt($ivHashCiphertext, $password) 
{    
    $method = "AES-256-CBC";    
    $iv = substr($ivHashCiphertext, 0, 16);    
    $hash = substr($ivHashCiphertext, 16, 32);    
    $ciphertext = substr($ivHashCiphertext, 48);    
    $key = hash('sha256', $password, true);    
    if (!hash_equals(hash_hmac('sha256', $ciphertext . $iv, $key, true),$hash)) 
    return null;    
    return openssl_decrypt($ciphertext, $method, $key,    		    
    OPENSSL_RAW_DATA, $iv);                               
}
$remote_hash=decrypt($encrypted_hash,$secret_key);
const crypto = require('crypto');

function decrypt(ivHashCiphertext, password) {
    // If ivHashCiphertext is a string, assume it's base64 encoded
    if (typeof ivHashCiphertext === 'string') {
        ivHashCiphertext = Buffer.from(ivHashCiphertext, 'base64');
    }

    const method = 'aes-256-cbc';

    // Extract the initialization vector (first 16 bytes)
    const iv = ivHashCiphertext.slice(0, 16);

    // Extract the hash (next 32 bytes)
    const hash = ivHashCiphertext.slice(16, 48);

    // Extract the ciphertext (remaining bytes)
    const ciphertext = ivHashCiphertext.slice(48);

    // Generate the key using SHA-256 hash of the password
    const key = crypto.createHash('sha256').update(password, 'utf8').digest();

    // Compute HMAC-SHA256 of (ciphertext || iv) using the key
    const hmac = crypto.createHmac('sha256', key)
        .update(Buffer.concat([ciphertext, iv]))
        .digest();

    // Compare the computed HMAC with the extracted hash
    if (!crypto.timingSafeEqual(hmac, hash)) {
        return null; // Hashes do not match; return null
    }

    try {
        // Decrypt the ciphertext using AES-256-CBC
        const decipher = crypto.createDecipheriv(method, key, iv);
        const plaintext = Buffer.concat([
            decipher.update(ciphertext),
            decipher.final()
        ]);
        return plaintext.toString('utf8'); // Return the decrypted text as a UTF-8 string
    } catch (err) {
        // Decryption failed; return null
        return null;
    }
}
  1. Compute hash (use md5 128 bit hashing algorithm to generate hash)

//Compute the pament hash locally

$local_hash = md5($order_id.$amount_processed.$status.$secret_key);
  1. Verify hash (Compare hash given at request and local hash)

if ($remote_hash == $local_hash)
{  
    // consider received amount to update  
    // Mark the transaction as success & process the order  
    // You can write code process the order here  
    // Update your db with payment success  
    $hash_status = "Hash Matched";    
}  
else  
{  // Verification failed       
    $hash_status = "Hash Mismatch";  
}
  1. Acknowledge the payment gateway by confirming that you have saved the payment status. Failure to acknowledge may result in multiple acknowledgment requests.

$data['hash_status']=$hash_status; // 'Hash Matched' or 'Hash Mismatch'
$data['acknowledge']=$acknowledge; // 'yes' or 'no'
header('Content-Type: application/json; charset=utf-8');
echo json_encode($data); // output as a json file

Definition of Payment Status:

  • Approved: Payment has been successfully approved by our system.

  • Failed: Payment failed on the bank's side.

  • Processing: The bank is currently processing the payment.

  • Declined: Payment has been declined by our system.

  • Pending: The user session is active, and the payment is awaiting completion.

STATUS POLLING :

POST https://<domain>/payout/api/status_polling.php

This API is used to poll the status of a specific transaction.

Headers

Name
Value

Content-Type

application/json

Body

Name
Type
Description

secret_key

string

given secret key;

url_of_polling_api

string

you will get it

pid

string

Merchant ID/PID

ref_code

string

unique ref_code which is generated in payment request response

post_hash

string

we will explain how to generate this

Steps to generate post_hash :

  1. Create a hash using md5 algorithm by appending values of ref_code, pid, secret_key

$local_hash = md5($ref_code . $pid  . $row['secret_key']);

NodeJS Example:

const local_hash = crypto.createHash('md5').update(ref_code + pid + secret_key).digest('hex');
const encodedStr=encrypt(local_hash, secret_key).toString('base64');
  1. Encrypt hash (You need to encrypt the hash using the secret key)

$encrypted_hash=encrypt($local_hash,  $row['secret_key']); 
function encrypt($plaintext, $password) 
{    
    $method = "AES-256-CBC";    
    $key = hash('sha256', $password, true);    
    $iv = openssl_random_pseudo_bytes(16);    
    $ciphertext = openssl_encrypt($plaintext, $method, $key,       
    OPENSSL_RAW_DATA, $iv);    
    $hash = hash_hmac('sha256', $ciphertext . $iv, $key, true);    
    return $iv . $hash . $ciphertext;
}
encrypted_hash=encrypt(local_hash,  row['secret_key']); 
def encrypt(plaintext, password):
    key = hashlib.sha256(password.encode()).digest()
    iv = os.urandom(16)
    cipher = AES.new(key, AES.MODE_CBC, iv)
    ciphertext = cipher.encrypt(pad(plaintext.encode(), AES.block_size))
    hash_value = hash_hmac("SHA256",ciphertext + iv,key,True)
    return iv + hash_value + ciphertext
 
 
def hash_hmac(algorithm, data, key, raw_output=False):
    if isinstance(data, str):
        data = data.encode('utf-8')
    if isinstance(key, str):
        key = key.encode('utf-8')

    hmac_hash = hmac.new(key, data, getattr(hashlib, algorithm.lower()))
    if raw_output:
        return hmac_hash.digest()
    else:
        return hmac_hash.hexdigest()
function encrypt(plaintext, password) {
    const method = 'aes-256-cbc';

    // Generate key using SHA-256 hash of the password
    const key = crypto.createHash('sha256').update(password).digest();

    // Generate a random IV (initialization vector)
    const iv = crypto.randomBytes(16);

    // Create AES cipher
    const cipher = crypto.createCipheriv(method, key, iv);

    // Encrypt plaintext
    let ciphertext = cipher.update(plaintext, 'utf8');
    ciphertext = Buffer.concat([ciphertext, cipher.final()]);

    // Generate HMAC hash of (ciphertext + iv)
    const hmac = crypto.createHmac('sha256', key);
    hmac.update(Buffer.concat([ciphertext, iv]));
    const hash = hmac.digest();

    // Concatenate iv + hash + ciphertext
    const encrypted = Buffer.concat([iv, hash, ciphertext]);

    // Return the encrypted data (as Buffer)
    return encrypted;
}
  1. base64_encode on encrypted hash for safe delivery

//Compute the payment hash locally

$encoded_hash=base64_encode($encrypted_hash);
  1. Send a post request with given URL

//Send a POST request with the following parameters in the JSON body: pid, ref_code, and post_hash to url_of_polling_api. You will receive a response after the data is validated.

<?php
//A very simple PHP example that sends a HTTP POST to a remote site
$data['pid']=pid;
$data['ref_code']=ref_code;
$data['post_hash']=post_hash;
$ch = curl_init();
$url=you will get api url in the call
curl_setopt($ch, CURLOPT_URL,$url);
curl_setopt($ch, CURLOPT_POST, 1); // In real life you should use something like:
curl_setopt($ch, CURLOPT_POSTFIELDS,json_encode($data,true));
// Receive server response …
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$server_output = curl_exec($ch);
curl_close($ch);
if($server_output)
{
    // You can follow step 5 to process response
}
?>
  1. Process Response (You will get a JSON response)

Name
Type
Description

order_id

string

Merchant ID/PID

requested_amount

int

requested amount

processed_amount

int

processed amount

bank_ref

string

bank reference details if available

sender_pg

string

sender account name id if available

ref_code

string

unique code for the transaction

status

string

status of payment at this time

post_hash

string

post hash for security verification

payment_type

string

payment type

request_time

string

payment request time

action_time

string

payment made time

upi_vpa

string

receiver vpa

account_no

string

receiver account number

account_holder

string

receiver name

ifsc

string

receiver ifsc code

bank_name

string

receiver bank name

Status API Response process

$data = file_get_contents("php://input");              
$row1=json_decode($data, true);                
echo $row1['order_id'];                
echo $row1['upi_id'];               
echo $row1['amount'];                
echo $row1['webhook_acknowledged'];            
echo $row1['status'];                
echo $row1['post_hash']; //  decode post hash
$encrypted_hash=base64_decode($row1['post_hash']);  // decrypt encrypted hash
$remote_hash = decrypt($encrypted_hash,$row['secret_key']);
$local_hash = md5($order_id . $data['amount'].
$data['status'].$row['secret_key']);   // generate local hash
  1. Verify Response

#PHP Example if $local_hash equal to $remote_hash then the data is verified:

if($remote_hash==$local_hash)
{ // validated status }
else {  // invalid status  }

COMPLAINT

We have a dedicated Complaint Section where merchants can manage transaction-related complaints. Through this section, merchants can submit complaints with all necessary details and optional evidence. Upon submission, a unique complaint reference ID is generated, allowing merchants to track the complaint’s status and receive real-time updates via the status-check API. This ensures a smooth, secure, and efficient process for resolving any transaction issues.

Complaint

RECONCILIATION

This API endpoint allows authorized users to retrieve approved payout transactions based on a specific pid (Partner ID) and date. The API performs authentication using a token and signature verification to ensure secure communication.

Reconciliation

PreviousPayin P2C Non-Seamless UPI IntegrationNextBangladesh

Last updated 6 months ago