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:
  • PAYMENT REQUEST :
  • Payment Request
  • For Deep Link Integration
  • TRANSACTION REFERENCE/UTR SUBMIT :
  • Capture Transaction Reference
  • CALLBACK
  • STATUS POLLING :
  • P2P Accounts Wallet Balance API Overview
  • COMPLAINT
  • RECONCILIATION
  • WALLET TRANSACTION SUMMARY
  • WALLET DATA ENDPOINT
  1. API Integration
  2. India

Payin P2P Seamless UPI Integration

UPI is a set of APIs developed by NPCI to enable instant online payments. It simplifies immediate payments via mobile devices. Payments can be initiated by both the sender (payer) and the receiver (payee) and can be completed using virtual payment addresses, Aadhaar integration, mobile numbers, and more. The payer’s smartphone can securely capture credentials for these transactions.

Merchant Onboarding: The merchant must provide the following information for onboarding in both the UAT and production environments:

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

  2. Merchant Callback URL to post the final transaction status from our system.

Once the merchant provides the required technical details, we will complete the necessary back-office configurations and provide a Merchant ID (MID/PID).

Let's see how it works:

  1. The merchant will send payment collection requests through our API. Along with this, the merchant must provide the order ID, PID, amount, UPI ID, name, email, and phone details.

  2. Payment Request: Upon receiving the request in the correct format, we will share the UPI payment string, which is required for QR code generation or intent creation.

  3. The merchant should collect the "Transaction ID" or "Bank Reference" from the customer and submit it to us by invoking the collection_utr API endpoint.

  4. Callback: Once the customer makes the payment, the callback data will be sent to the provided callback URL.

  5. Status Polling: You can confirm or check the payment status at any time by calling the polling_api and updating your system accordingly.

PAYMENT REQUEST :

Before proceeding, ensure you have reviewed the basic workflow. This section explains how to send the payment request.

Note: All requests must come from whitelisted IPs. Please confirm that your IP is whitelisted.

Payment Request

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

Merchant makes a payment request.

Headers

Name
Value

Content-Type

application/json

Body

Name
Type
Description
Mandatory

pid

string

provided MID/PID

Yes

order_id

string

unique order id

Yes

amount

string

requested amount

Yes

upi_id

string

customer's upi id

No

name

string

customer's name

Yes

email

string

customer's email

Yes

phone

string

customer's phone

Yes

{
    "pid":"0951272386617",
    "order_id":"jdherfecuh0",
    "amount":"87",
    "upi_id":"f@gmail.com",
    "name":"john",
    "email":"john@gmail.com",
    "phone":"8974554630"
}

Sample Response Body

{
    "ref_code": "18855d71b83ce84c13b0f924b4efbb40a4b0bbd4cda7f9747906eb49dc8b3fcb",
    "qr_code": "upi://pay?pa=mahekpg@gmail.com&pn=5fk2p&am=87&tn=5fk2p",
    "status": "success",
    "redirect_url": ""
}

For Deep Link Integration

If intent works with this flow.

To integrate UPI (Unified Payments Interface) payments via deep links, you can use the UPI deep link ( qr_code link from response ) schemes provided by various payment apps like Google Pay, PhonePe, Paytm, etc. UPI allows for direct bank-to-bank transfers and is supported by multiple payment apps in India.

UPI Deep Link Format

The general format for UPI deep links is as follows:

upi://pay?pa=<merchant_vpa>&pn=<merchant_name>&mc=<merchant_code>&tid=<transaction_id>&tn=<transaction_note>&am=&cu=<currency_code>

Where:

  • pa: The merchant's UPI ID (VPA, e.g., merchant@upi).

  • pn: The merchant's name.

  • mc: The merchant code (optional).

  • tid: The unique transaction ID (optional, for tracking).

  • tn: Transaction note (optional, for description).

  • am: The amount to be paid (optional, can be left out if you want the user to enter the amount).

  • cu: Currency code, typically "INR" for Indian Rupees (optional).

Example for Google Pay and PhonePe (UPI Payment Deep Links)

  1. Google Pay Deep Link (UPI)

To initiate a payment request via UPI through Google Pay:

upi://pay?pa=merchant@upi&pn=MerchantName&mc=123456&tid=TXN12345&tn=Payment for goods&am=100.00&cu=INR

  • Replace merchant@upi with your merchant UPI ID.

  • Replace MerchantName with your business or merchant name.

  • Replace TXN12345 with a unique transaction ID.

  • Optionally, set the tn (transaction note) and am (amount).

  • The cu parameter is usually set to INR for Indian Rupees.

  1. PhonePe Deep Link (UPI)

PhonePe also supports UPI-based deep linking in a similar manner:

phonepe://pay?pa=merchant@upi&pn=MerchantName&tid=TXN12345&tn=Payment for goods&am=100.00&cu=INR

  1. Paytm Deep Link (UPI)

Paytm uses a similar format to other apps:

paytmmp://upi/pay?pa=merchant@upi&pn=MerchantName&tid=TXN12345&tn=Payment for goods&am=100.00&cu=INR

  1. General (UPI)

General UPI/BHIM uses a given format to all UPI apps:

upi://pay?pa=merchant@upi&pn=MerchantName&tid=TXN12345&tn=Payment for goods&am=100.00&cu=INR

How to Trigger the Deep Link

To trigger the UPI payment deep link, you can use the following methods depending on the platform you're working with:

  1. Android

Use an Intent to open the UPI payment app (like Google Pay, PhonePe, etc.):

Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("upi://pay?pa=merchant@upi&pn=MerchantName&mc=123456&tid=TXN12345&tn=Payment for goods&am=100.00&cu=INR"));
startActivity(intent);
  1. iOS

Use UIApplication to open the UPI link:

if let url = URL(string: "upi://pay?pa=merchant@upi&pn=MerchantName&mc=123456&tid=TXN12345&tn=Payment for goods&am=100.00&cu=INR") {
    UIApplication.shared.open(url)
}

Handling the Payment Result

  1. Google Pay/PhonePe/Paytm will redirect back to your app if the user completes the payment. You can handle the result using a custom URL scheme or callback URL to process the payment status.

  2. You need to set up an intent or listener in your app to capture the payment success/failure and take appropriate actions (e.g., updating the transaction status).

TRANSACTION REFERENCE/UTR SUBMIT :

The merchant needs to design a page where the customer can submit the "Bank Reference" or "Transaction ID" for the payment.

Capture Transaction Reference

POST https://<domain>/api/collection_utr.php

Merchant has to capture a transaction reference from the customer and submit it to us to verify the payment for transaction approval.

Headers

Name
Value

Content-Type

application/json

Body

Name
Type
Description
Mandatory

ref_code

string

given ref_code of transaction

Yes

pid

string

merchant PID/MID

Yes

utr

string

"Bank Reference"/"Transaction Id"

Yes

amount

int

amount in integer

Yes

Sample Request Body

{
"ref_code":"8d94cbdb89aeda7b900a73be951aafdd645b1e16fcf5d9fc1a09ddc6e6d19380",
"pid":"232323232",
"utr":"gfgfh434",
"amount":"12"
}

Response

{ 
    "success": "UTR Saved for the Transaction"
}
{ 
    "error": "Invalid PID" 
}

CALLBACK

We invoke your callback URL with callback data whenever there is a status change against the transaction.

Valid Transaction status are:

  1. Approved

  2. Declined

  3. Late Approved

  4. Pending

  5. User Timed Out

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

  1. Pending=>Approved

  2. Pending=>Declined

  3. Pending=>User Timed Out

  4. User Timed Out=>Late Approved

The callback landing page must be set up on your server at a secret path, but it should be publicly accessible from our whitelisted IP. (Ensure that it is only accessible from our server IP.)

In the POST body, the following properties will be provided in JSON format:

Name
Type
Description

order_id

string

Your order id shared

requested_amount

int

requested amount

received_amount

int

received amount

bank_ref

string

transaction reference/bank reference/UTR if available

ref_code

string

unique code for the transaction

status

string

status of payment at this time

post_hash

string

signature post hash for security verification

Note:

please consider received_amount for final transaction processing

Follow the steps to verify the integrity of received data:

  1. base64_decode post_hash:

  • Capture JSON data from the POST body.

  • JSON decode the data to an array or object.

  • Extract the post_hash from the decoded data.

  • For encrypted post_hash base64_decode the post_hash.

$data = file_get_contents("php://input");
$array = json_decode($data,  true);
$encrypted_hash=base64_decode($array['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 the local hash using the MD5 128-bit hashing algorithm. Generate the hash locally.

$local_hash = md5($order_id.$received_amount.$status.$secret_key);

Decrypt function for python given at the end of this document.

  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 (You should Acknowledge back to the payment gateway that you saved the status of payment, otherwise we will retry Callback)

$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: The payment has been approved by our system.

  • Late Approved: The payment has been approved by our system after manual reconciliation.

  • Declined: The payment has been declined by our system.

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

  • User Timed Out: The user did not complete the payment within the session period.

STATUS POLLING :

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

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

Headers

Name
Value

Content-Type

application/json

Body

Name
Type
Description
Mandatory

pid

string

Merchant ID/PID

Yes

ref_code

string

unique ref_code which is generated in payment request

Yes

post_hash

string

post hash for signature verification

Yes

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 encrypted_hash for transport over the network.

//Compute the payment hash locally

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

Send a POST request containing pid, ref_code, and post_hash as a JSON body to url_of_polling_api, and you will receive a response after validating the data.

<?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

ref_code

string

unique ref_code which is generated in payment request

post_hash

string

post hash for signature verification

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

Decrypt Functions

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);
}
def decrypt(ivHashCiphertext, password):
    # Segregating IV,Hash,Cipher
    iv = ivHashCiphertext[:16]
    hash_val = ivHashCiphertext[16:48]
    ciphertext = ivHashCiphertext[48:]
    key = hashlib.sha256(password.encode()).digest()

    if not hmac.compare_digest(
        hash_hmac("SHA256",ciphertext + iv,key,True), hash_val
    ):
        return None

    backend = default_backend()
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=backend)
    decryptor = cipher.decryptor()
    decrypted_text = decryptor.update(ciphertext) + decryptor.finalize()

    return decrypted_text.decode()
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. 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  }

In python you need to import the following packages:

import base64
import os
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
import hashlib
import hmac

P2P Accounts Wallet Balance API Overview

This API provides detailed information about an operator's payment gateway (PG) accounts, including balance details and account details. It allows you to query and retrieve information related to UPI, IMPS, IMPS with UPI, wallet and payout accounts linked to the operator. For more details on how to use this API, refer to the link below.

P2P Accounts Wallet Balance

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 payment 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

WALLET TRANSACTION SUMMARY

This API endpoint retrieves a summary of transactions for wallets associated with an operator, filtered by account number, date type, and date. It requires authentication via an API key and returns transaction counts and amounts grouped by status.

Wallet Transaction Summary

WALLET DATA ENDPOINT

This API endpoint retrieves cumulative wallet data for an operator, including both active and inactive wallet accounts across multiple wallet types. The endpoint requires authentication via an API key passed in the request headers.

Wallet Data Endpoint

PreviousIndiaNextPayin P2P Non-Seamless UPI Integration

Last updated 8 days ago