Payin P2C 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 On-boarding: The merchant must provide the following information for on-boarding in both the UAT and production environments:
IP Address (For dynamic IPs, please provide the range of IP addresses).
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:
Merchant sends a payment collection request through our API, they must include the
order_id
,pid
,amount
,upi_id
,name
,email
, andphone
details.PAYMENT REQUEST : Upon receiving a request in the correct format, we will provide a UPI payment string, which is necessary for generating a QR code or creating a payment intent.
CALLBACK : After the customer completes the payment, you will receive callback data at the provided callback URL.
STATUS POLLING : To confirm or check the payment status, you can use the
polling_api
at any time to update your system about the payment.
PAYMENT REQUEST :
Before proceeding with this section, please ensure you have reviewed the Basic Workflow of the system. This page outlines how to send a payment request effectively.
Important Note: All requests must originate from whitelisted IP addresses. Please verify that your IP is properly whitelisted before initiating any requests.
Payment Request
POST
https://<domain>/api/request.php
Merchant makes a payment request.
Headers
Content-Type
application/json
Body
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
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)
Google Pay Deep Link (UPI)
To initiate a payment request via UPI through Google Pay:
gpay://upi/pay?pa=merchant@upi&pn=MerchantName&mc=123456&tid=TXN12345&tn=Payment for goods&am=100.00&cu=INR
Replace
upi://
paywith
gpay://upi/pay
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
Replaceupi://
pay with
phonepe://pay
Paytm Deep Link (UPI)
Paytm uses a similar format to other apps:
paytmmp://pay?pa=merchant@upi&pn=MerchantName&tid=TXN12345&tn=Payment for goods&am=100.00&cu=INR
Replaceupi://
pay with
paytmmp://
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:
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);
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
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.
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).
CALLBACK
We invoke your callback URL with callback data whenever there is a status change against the transaction.
Valid Transaction status are:
Approved
Declined
Late Approved
Pending
User Timed Out
The most famous transaction changes are (but not limited):
Pending=>Approved
Pending=>Declined
Pending=>User Timed Out
User Timed Out=>Late Approved
The callback landing page has to be set on your server at some secret path but it should be publicly available from our white-listed IP. ( make it accessible only from our server IP )
In the POST body, you will get the following properties in JSON:
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:
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 thepost_hash
.
$data = file_get_contents("php://input");
$array = json_decode($data, true);
$encrypted_hash=base64_decode($array['post_hash']);
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);
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.
Verify hash (Compare hash given at requestand 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";
}
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: Payment is Approved by our system
Late Approved: Payment is Approved by our system after manual reconciliation
Declined: Payment is declined by our system
Pending: User session in active waiting to finish payment
User Timed Out: User didn’t finished payment within the session period
STATUS POLLING :
POST
https://<domain>/api/status_polling.php
This API is for polling the status for a particular transaction.
Headers
Content-Type
application/json
Body
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 :
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');
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;
}
base64_encode encrypted_hash for transport over the network.
//Compute the payment hash locally
$encoded_hash=base64_encode($encrypted_hash);
Send a post request to the given URL
//Send a post request that contains pid,ref_code and post_hash(as jSON post body) to url_of_polling_api and you will get 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}
?>
Process Response (You will get a JSON response)
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);
}
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
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.
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.
Last updated