> For the complete documentation index, see [llms.txt](https://doc.payatom.in/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://doc.payatom.in/api-integration/v1/india/payin-p2c-seamless-integration.md).

# Payin P2C Seamless 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:

1. IP Address (For dynamic IPs, please provide the range of IP addresses).&#x20;
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).

### <mark style="color:orange;">Let's see how it works:</mark>&#x20;

1. Merchant sends a payment collection request through our API, they must include the `order_id`, `pid`, `amount`, `upi_id`, `name`, `email`, and `phone` details.
2. **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.
3. **CALLBACK :** After the customer completes the payment, you will receive callback data at the provided callback URL.
4. **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.

## <mark style="color:orange;">PAYMENT REQUEST :</mark>

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

<mark style="color:green;">`POST`</mark> <mark style="color:blue;">`https://<domain>/api/request.php`</mark>

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`   | int    | 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       |
|            |        |                   |           |

{% tabs %}
{% tab title="Sample Request body" %}

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

{% endtab %}
{% endtabs %}

**Sample Response Body**

{% tabs %}
{% tab title="200" %}

```json
{
    "ref_code": "18855d71b83ce84c13b0f924b4efbb40a4b0bbd4cda7f9747906eb49dc8b3fcb",
    "qr_code": "upi://pay?pa=mahekpg@testupi&pn=5fk2p&am=87&tn=5fk2p",
    "status": "success",
    "redirect_url": ""
    "amount": 87,
    "receiverVPA": "mahekpg@testupi"
}
```

{% endtab %}
{% endtabs %}

## <mark style="color:orange;">For Deep Link Integration</mark>

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.

#### <mark style="color:purple;">UPI Deep Link Format</mark>

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).

#### <mark style="color:purple;">Example for Google Pay and PhonePe (UPI Payment Deep Links)</mark>

1. *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://`pay `with` gpay://upi/pay

2. *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`

Replace`upi://`pay `with` phonepe://pay

3. *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`

Replace`upi://`pay `with` paytmmp`://`

3. *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`

#### <mark style="color:purple;">How to Trigger the Deep Link</mark>

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.):

{% tabs %}
{% tab title="Java" %}

```java
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);
```

{% endtab %}
{% endtabs %}

2. *iOS*

Use UIApplication to open the UPI link:

{% tabs %}
{% tab title="Swift" %}

```swift
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)
}
```

{% endtab %}
{% endtabs %}

#### <mark style="color:purple;">Handling the Payment Result</mark>

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).

## <mark style="color:orange;">CALLBACK</mark>

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
6. Cancelled
7. Failed
8. Amount Mismatch

#### The most famous transaction changes are (but not limited):&#x20;

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:

<table><thead><tr><th>Name</th><th width="186">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>order_id</code></td><td>string</td><td>Your order id shared</td></tr><tr><td><code>requested_amount</code></td><td>string</td><td>requested amount</td></tr><tr><td><code>received_amount</code></td><td>string</td><td>received amount</td></tr><tr><td><code>bank_ref</code></td><td>string</td><td>transaction reference/bank reference/UTR if available</td></tr><tr><td><code>ref_code</code></td><td>string</td><td>unique code for the transaction</td></tr><tr><td><code>status</code></td><td>string</td><td>status of payment at this time</td></tr><tr><td><code>post_hash</code></td><td>string</td><td>signature post hash for security verification</td></tr></tbody></table>

Note:

please consider `received_amount`  for final transaction processing

Follow the steps to verify the integrity of received data:&#x20;

1. **Capture and Decode the Payload**

*Capture the raw JSON data from the POST body and decode it.*

```php
$data = file_get_contents("php://input");
$array = json_decode($data,  true);
```

2. **Extract and Decode the `post_hash`**

*The `post_hash` field in the JSON payload is a Base64-encoded string. You must Base64-decode it to get the raw binary data needed for decryption.*

```php
// Get the Base64 string from the array
$post_hash_base64 = $array['post_hash'];
 
// Decode it to get the raw binary ciphertext
$ivHashCiphertext = base64_decode($post_hash_base64);
```

3. **Decrypt the Binary Data to get the Remote Hash**

*Pass the raw binary data (not the Base64 string) to the `decrypt` function along with your secret key.*

```php
//$secret_key is your provided SECRET KEY
$remote_hash = decrypt($ivHashCiphertext, $secret_key);
```

*The `decrypt` function for your language is provided in the reference section below.*

{% tabs %}
{% tab title="PHP" %}

```php
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);                               
}
```

{% endtab %}

{% tab title="Node JS" %}

```javascript
const crypto = require('crypto');

/**
 * @param {Buffer} ivHashCiphertext - The raw binary data (Buffer) after Base64-decoding.
 * @param {string} password - Your secret key.
 * @returns {string|null} The decrypted plaintext or null on failure.
 */
function decrypt(ivHashCiphertext, password) {
    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;
    }
}
```

{% endtab %}

{% tab title="Python" %}

```python
import hashlib
import hmac
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding

def decrypt(ivHashCiphertext, password):
    """
    Decrypts the iv/hash/ciphertext blob.
    
    :param ivHashCiphertext: The raw binary data (after base64-decoding).
    :param password: Your secret key string.
    :return: The decrypted plaintext string, or None on failure.
    """
    try:
        # 1. Extract the components
        iv = ivHashCiphertext[:16]
        hash_val = ivHashCiphertext[16:48]
        ciphertext = ivHashCiphertext[48:]

        # 2. Generate the key
        key = hashlib.sha256(password.encode('utf-8')).digest()

        # 3. Verify the HMAC
        computed_hash = hmac.new(key, ciphertext + iv, hashlib.sha256).digest()

        if not hmac.compare_digest(computed_hash, hash_val):
            return None

        # 4. Decrypt the ciphertext
        backend = default_backend()
        cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=backend)
        decryptor = cipher.decryptor()
        
        # Decrypt and unpad
        # Using PKCS7 unpadder explicitly is more robust
        decrypted_padded_text = decryptor.update(ciphertext) + decryptor.finalize()
        unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
        decrypted_text = unpadder.update(decrypted_padded_text) + unpadder.finalize()

        return decrypted_text.decode('utf-8')

    except Exception as e:
        # Catch any error (e.g., bad padding, bad key) and return None
        # print(f"Decryption failed: {e}") # Optional: for debugging
        return None
```

{% endtab %}
{% endtabs %}

4. **Compute the Local Hash**&#x20;

*Compute the local hash using the MD5 128-bit hashing algorithm. Use the `order_id`, `received_amount`, and `status` received in the callback array for computing the local hash.*

```php
// Get the values from the same callback $array
$order_id = $array['order_id'];
$received_amount = $array['received_amount'];
$status = $array['status'];
 
$local_hash = md5($order_id . $received_amount . $status . $secret_key);
```

5. **Verify Hash**

*Compare the decrypted `$remote_hash` from the request and the computed `$local_hash`.*

```php
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";  
}
```

6. **Acknowledge the Payment Gateway**

To confirm you have received the callback and to prevent our gateway from sending retries, you must do two things:

* Respond with an **HTTP 200 OK** status code.
* Respond with a JSON body containing the key `"acknowledge"` set to the string `"yes"`.

Our system will check for both the `200` status and the `{"acknowledge": "yes"}` in the response body. If either is missing, we will assume the callback failed and will attempt to send it again.

```php
// --- This is the required acknowledgment ---

// 1. Set the HTTP 200 OK status code
http_response_code(200);

// 2. Prepare the required JSON response body
$response_data = [
    'acknowledge' => 'yes',
    'hash_status' => $hash_status // You can include this for your own logs
];

// 3. Send the response
header('Content-Type: application/json; charset=utf-8');
echo json_encode($response_data);
exit;
```

## <mark style="color:orange;">STATUS POLLING :</mark>

<mark style="color:green;">`POST`</mark> <mark style="color:blue;">`https://<domain>/api/status_polling.php`</mark>

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

**Headers**

| Name         | Value              |
| ------------ | ------------------ |
| Content-Type | `application/json` |

**Body**

<table><thead><tr><th>Name</th><th width="143">Type</th><th width="242">Description</th><th>Mandatory</th></tr></thead><tbody><tr><td><code>pid</code></td><td>string</td><td>Provided Merchant ID/PID</td><td>Yes</td></tr><tr><td><code>ref_code</code></td><td>string</td><td>unique ref_code which is generated in payment request</td><td>Yes</td></tr><tr><td><code>post_hash</code></td><td>string</td><td>The Base64-encoded encrypted hash. (See steps below).</td><td>Yes</td></tr></tbody></table>

**Steps to generate post\_hash :**&#x20;

1. **Generate the Request** `post_hash`&#x20;

1.1 **Create Plaintext Hash**: Concatenate the `ref_code`, `pid`, and your `secret_key`, then create an MD5 hash.

{% tabs %}
{% tab title="PHP" %}

```php
$ref_code = "YOUR_REF_CODE";
$pid = "YOUR_PID";
$secret_key = "YOUR_SECRET_KEY";

$local_hash = md5($ref_code . $pid . $secret_key);
```

{% endtab %}

{% tab title="Node JS" %}

```javascript
const crypto = require('crypto');

const ref_code = "YOUR_REF_CODE";
const pid = "YOUR_PID";
const secret_key = "YOUR_SECRET_KEY";

const local_hash = crypto
  .createHash('md5')
  .update(ref_code + pid + secret_key)
  .digest('hex');

```

{% endtab %}

{% tab title="Python" %}

```python
import hashlib

ref_code = "YOUR_REF_CODE"
pid = "YOUR_PID"
secret_key = "YOUR_SECRET_KEY"

local_hash = hashlib.md5((ref_code + pid + secret_key).encode('utf-8')).hexdigest()
```

{% endtab %}
{% endtabs %}

1.2 **Encrypt the Hash:** Encrypt the `$local_hash` using the `encrypt` function shown below.

```php
$encrypted_hash = encrypt($local_hash, $secret_key);
```

{% tabs %}
{% tab title="PHP" %}

```php
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;
}
```

{% endtab %}

{% tab title="Python" %}

```python
import hashlib
import hmac
import os
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding as crypto_padding

def encrypt(plaintext, password):
    key = hashlib.sha256(password.encode('utf-8')).digest()
    iv = os.urandom(16)
    
    cipher = AES.new(key, AES.MODE_CBC, iv)
    plaintext_bytes = plaintext.encode('utf-8')
    padded_plaintext = pad(plaintext_bytes, AES.block_size)
    ciphertext = cipher.encrypt(padded_plaintext)
    
    hash_value = hmac.new(key, ciphertext + iv, hashlib.sha256).digest()
    
    return iv + hash_value + ciphertext
```

{% endtab %}

{% tab title="Node JS" %}

```javascript
const crypto = require('crypto');

function encrypt(plaintext, password) {
    const method = 'aes-256-cbc';
    const key = crypto.createHash('sha256').update(password, 'utf8').digest();
    const iv = crypto.randomBytes(16);

    const cipher = crypto.createCipheriv(method, key, iv);
    let ciphertext = cipher.update(plaintext, 'utf8');
    ciphertext = Buffer.concat([ciphertext, cipher.final()]);

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

    // Return the raw binary Buffer
    return Buffer.concat([iv, hash, ciphertext]);
}
```

{% endtab %}
{% endtabs %}

1.3 **Base64 Encode:** Base64-encode the raw binary output from the `encrypt` function. This final string is your `post_hash`.

```php
$post_hash = base64_encode($encrypted_hash);
```

2. &#x20;**Send the POST Request**

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

{% code title="PHP (cURL) Example:" %}

```php
<?php
// Your transaction data
$pid = "YOUR_PID";
$ref_code = "YOUR_REF_CODE";
$secret_key = "YOUR_SECRET_KEY";

// --- Step 1: Generate Hash ---
$local_hash = md5($ref_code . $pid . $secret_key);
$encrypted_hash = encrypt($local_hash, $secret_key);
$post_hash = base64_encode($encrypted_hash);

// --- Step 2: Send Request ---
$api_url = "https://<domain>/api/status_polling.php";

$data = [
    'pid' => $pid,
    'ref_code' => $ref_code,
    'post_hash' => $post_hash
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $api_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);

$server_output = curl_exec($ch);
curl_close($ch);

?>

```

{% endcode %}

3. **Processing the API Response**

The API will respond with a JSON object. If the request is successful and the `ref_code` is found, it will return the transaction details. If it fails (e.g., bad hash, `ref_code` not found), it will return an `error` message.

#### <sub>Success Response Parameters</sub>

A successful response will contain the following parameters as JSON body:

| `order_id`             | String | Your  order ID.                                                                     |
| ---------------------- | ------ | ----------------------------------------------------------------------------------- |
| `ref_code`             | String | The unique `ref_code` for this transaction.                                         |
| `upi_id`               | String | The UPI ID the payment was made to.                                                 |
| `requested_amount`     | Number | The amount originally requested for the transaction.                                |
| `received_amount`      | Number | The final amount confirmed as received.                                             |
| `bank_ref`             | String | The bank's UTR / reference number, if available.                                    |
| `sender_upi`           | String | The customer's UPI ID from which the payment was received.                          |
| `webhook_acknowledged` | String | `"1"` if our server has sent the callback, `"0"` otherwise.                         |
| `status`               | String | The current status of the transaction (e.g., `Approved`, `Pending, Declined, etc`). |
| `post_hash`            | String | A Base64-encoded encrypted hash to verify the integrity of this response.           |

#### <sub>Error Response</sub>

An error response will contain an `error` key.

```json
{
    "error": "error message"
}
```

4. **Verify the Response** `post_hash`

Before trusting any data from the response, you must verify its `post_hash` to ensure the data is from us and has not been tampered with. This verification logic is identical to the logic used for verifying a callback.

You do not need to send an "Acknowledge" response for a status poll.

**4.1 Compute the Local Hash**

From the JSON response, get the `order_id`, `received_amount`, and `status`. Concatenate them with your `secret_key` and create an MD5 hash.

{% tabs %}
{% tab title="PHP" %}

```php
// $response_data is the decoded JSON response from /api/status_polling.php
$order_id = $response_data['order_id'];
$received_amount = $response_data['received_amount'];
$status = $response_data['status'];
 
$local_hash = md5($order_id . $received_amount . $status . $secret_key);
```

{% endtab %}

{% tab title="Node JS" %}

```javascript
// responseData is the parsed JSON response
const order_id = responseData.order_id;
const received_amount = responseData.received_amount;
const status = responseData.status;
 
const local_hash = crypto
  .createHash('md5')
  .update(order_id.toString() + received_amount.toString() + status.toString() + secret_key)
  .digest('hex');
```

{% endtab %}

{% tab title="Python" %}

```python
# response_data is the parsed JSON response (a dict)
order_id = response_data['order_id']
# Ensure values are strings for concatenation
received_amount = str(response_data['received_amount']) 
status = response_data['status']
 
local_hash = hashlib.md5((order_id + received_amount + status + secret_key).encode('utf-8')).hexdigest()
```

{% endtab %}
{% endtabs %}

**4.2 Decrypt the Remote Hash**

Get the `post_hash` string from the JSON response. Base64-decode it, then pass the raw binary data to the `decrypt` function.

{% tabs %}
{% tab title="PHP" %}

```php
// Get the Base64 string from the response
$post_hash_base64 = $response_data['post_hash'];
 
// Decode it to get the raw binary ciphertext
$ivHashCiphertext = base64_decode($post_hash_base64);
 
// Decrypt using the function from the reference section below
$remote_hash = decrypt($ivHashCiphertext, $secret_key);
```

{% endtab %}

{% tab title="Node JS" %}

```javascript
const postHashBase64 = responseData.post_hash;
 
// Decode it to get the raw binary buffer
const ivHashCiphertext = Buffer.from(postHashBase64, 'base64');
 
// Decrypt using the function from the reference section below
const remote_hash = decrypt(ivHashCiphertext, secret_key);
```

{% endtab %}

{% tab title="Python" %}

```python
import base64

post_hash_base64 = response_data['post_hash']
 
# Decode it to get the raw binary data
ivHashCiphertext = base64.b64decode(post_hash_base64)
 
# Decrypt using the function from the reference section below
remote_hash = decrypt(ivHashCiphertext, secret_key)
```

{% endtab %}
{% endtabs %}

*The `decrypt` function for your language is provided in the reference section below.*

{% tabs %}
{% tab title="PHP" %}

```php
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);                               
}
```

{% endtab %}

{% tab title="Node JS" %}

```javascript
const crypto = require('crypto');

/**
 * @param {Buffer} ivHashCiphertext - The raw binary data (Buffer) after Base64-decoding.
 * @param {string} password - Your secret key.
 * @returns {string|null} The decrypted plaintext or null on failure.
 */
function decrypt(ivHashCiphertext, password) {
    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;
    }
}
```

{% endtab %}

{% tab title="Python" %}

```python
import hashlib
import hmac
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding

def decrypt(ivHashCiphertext, password):
    """
    Decrypts the iv/hash/ciphertext blob.
    
    :param ivHashCiphertext: The raw binary data (after base64-decoding).
    :param password: Your secret key string.
    :return: The decrypted plaintext string, or None on failure.
    """
    try:
        # 1. Extract the components
        iv = ivHashCiphertext[:16]
        hash_val = ivHashCiphertext[16:48]
        ciphertext = ivHashCiphertext[48:]

        # 2. Generate the key
        key = hashlib.sha256(password.encode('utf-8')).digest()

        # 3. Verify the HMAC
        computed_hash = hmac.new(key, ciphertext + iv, hashlib.sha256).digest()

        if not hmac.compare_digest(computed_hash, hash_val):
            return None

        # 4. Decrypt the ciphertext
        backend = default_backend()
        cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=backend)
        decryptor = cipher.decryptor()
        
        # Decrypt and unpad
        # Using PKCS7 unpadder explicitly is more robust
        decrypted_padded_text = decryptor.update(ciphertext) + decryptor.finalize()
        unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
        decrypted_text = unpadder.update(decrypted_padded_text) + unpadder.finalize()

        return decrypted_text.decode('utf-8')

    except Exception as e:
        # Catch any error (e.g., bad padding, bad key) and return None
        # print(f"Decryption failed: {e}") # Optional: for debugging
        return None
```

{% endtab %}
{% endtabs %}

**4.3 Compare the Hashes**

Securely compare the `local_hash` you just computed with the `remote_hash` you decrypted. If they match, you can trust the data.

{% tabs %}
{% tab title="PHP" %}

```php
if ($remote_hash !== null && hash_equals($local_hash, $remote_hash)) {
    // --- SUCCESS: Data is verified ---
    // You can now trust the data and update your database.
    // echo "Status: " . $response_data['status'];
} else {
    // --- FAILURE: Hash mismatch! ---
    // Do NOT trust this data.
}
```

{% endtab %}

{% tab title="Node JS" %}

```javascript
let isVerified = false;
if (remote_hash) {
    // Use crypto.timingSafeEqual for secure comparison
    try {
        const localHashBuffer = Buffer.from(local_hash, 'utf8');
        const remoteHashBuffer = Buffer.from(remote_hash, 'utf8');

        if (localHashBuffer.length === remoteHashBuffer.length) {
            isVerified = crypto.timingSafeEqual(localHashBuffer, remoteHashBuffer);
        }
    } catch (e) {
        // Error during buffer creation (e.g., bad hash)
        isVerified = false;
    }
}

if (isVerified) {
    // --- SUCCESS: Data is verified ---
    // console.log("Status:", responseData.status);
} else {
    // --- FAILURE: Hash mismatch! ---
}
```

{% endtab %}

{% tab title="Python" %}

```python
import hmac

isVerified = False
if remote_hash:
    # Use hmac.compare_digest for secure comparison
    isVerified = hmac.compare_digest(local_hash, remote_hash)

if isVerified:
    # --- SUCCESS: Data is verified ---
    # print(f"Status: {response_data['status']}")
    pass
else:
    # --- FAILURE: Hash mismatch! ---
    pass
```

{% endtab %}
{% endtabs %}

## <mark style="color:orange;">TRANSACTION STATUS :</mark>&#x20;

1. **Amount Mismatch** : We received money but customer paid different money than the requested money
2. **Approved** : We received money same value as requested&#x20;
3. **Late Approved** : We recieved money but it happened late while doing reconcilation from bank side
4. **Declined** : The transaction declined due to security reasons
5. **Failed** : The payment failed from bank side
6. **Cancelled** : This status for NonSeamess when customer cancel the payment from the screen
7. **User Timed Out:** The user did not complete the payment within the session period.

## <mark style="color:orange;">COMPLAINT</mark>

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](/api-integration/v1/complaint.md)

## <mark style="color:orange;">RECONCILIATION</mark>

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](/api-integration/v1/payin-reconciliation.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://doc.payatom.in/api-integration/v1/india/payin-p2c-seamless-integration.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
