Skip to main content
knowledgecenter.avangate.com

Instant Refund Notification (IRN)

Overview

Use Instant Refund/Reverse Notifications (IRN) to automate the process of requesting: 

  • Transaction reversals
  • Full refunds
  • Partial refunds

IRNs let you send refund and reversal requests directly from your own management interface/platform into the Avangate system. 

Once Avangate approves the REVERSE/REFUND procedure, we send you an email and a IPN notification containing the status corresponding to the canceled transaction, REVERSE or REFUND respectively. We display the total canceled amount as a negative value.

Reversal

A reversal involves Avangate unblocking the shopper's balance corresponding to the transaction that we authorized for future capture immediately after payment confirmation, following approval from the financial department. Reversals happen before Avangate captures the authorized amount and also precedes order fulfillment/delivery. Avangate does not charge transaction processing commissions for orders that we reverse. Such orders feature the REVERSED status in the Avangate Control Panel as well as in all notifications regarding the reversal.

REFUND

Avangate can only refund completed transactions associated with finalized orders (fulfilled/delivered). 

Authentication

You're required to validate each request with a HASH (HMAC_MD5 signature). To build the HMAC_MD5 source string, prepend each required value with its own length in bytes.

  • Use 0 for null or empty values without prepending their length. However, when the value is 0 (zero), you do need to prepend its length (1).
  • Note that for UTF-8 characters the length in bytes can be longer that the string length.

IRN HASH example

For the purposes of this example, we assume that the values of the secret key is 123456789!@#$%^&*. Your secret key is available in the Account settings are of your account. 

 

Field name

Length

Field value

MERCHANT

8

MERCCODE

ORDER_REF

8

12345678

ORDER_AMOUNT

5

39.99

ORDER_CURRENCY

3

USD

IRN_DATE

19

2012-12-12 12:12:12

PRODUCTS_IDS

35386 and 35387 in an array

PRODUCTS_QTY

1 and 2 in an array

REGENERATE_CODES

19

1234-5678-9012-3456

LICENSE_HANDLING

6

CANCEL

 

$params = array(
'MERCHANT' => 'MERCCODE',
'ORDER_REF' => 12345678,
'ORDER_AMOUNT' => 39.99,
'ORDER_CURRENCY' => 'USD',
'IRN_DATE' => '2012-12-12 12:12:12',
'PRODUCTS_IDS' => array(35386, 35387),
'PRODUCTS_QTY' => array(1, 2),
'REGENERATE_CODES' => array('1234-5678-9012-3456'),
'LICENSE_HANDLING' => array('CANCEL')
);

Serialized string for hash 

8MERCCODE812345678539.993USD192012-12-12 12:12:125353865353871112191234-5678-9012-34566CANCEL
Hash: e24fe2f3a2fadcd375be2fc9410d48fe

Parameters

Include parameters in the request in the exact order featured below. 

Field

Type/Required

Description

Used in HASH calculation

MERCHANT

String/Required

Merchant ID. This is available in the "Account administration" / "Account settings" section of the Control Panel

YES

ORDER_REF

String/Required

The unique reference number of the order from the Avangate system for which you’re initiating a refund request.

YES

ORDER_AMOUNT

Double/Required

The total value of the order (total costs customers incurred for the order) for which you’re requesting a reverse/refund.

You can refund a different amount and not necessarily the entire value of the order, and you would specify the funds using the AMOUNT parameter.

YES

ORDER_CURRENCY

String/Required

The currency used for the order.

YES

IRN_DATE

String/Required

The date of the refund request in the following format: Y-m-d H:i:s (2008-05-10 17:30:56).

If you changed the time zone for the Avangate API by editing system settings under Account settings, then calculate the IRN_DATE according to your custom configuration. Avangate uses your custom time zone for the IRN_DATE when calculating the HASH, and it's important that you also use the same datetime stamp, also per the custom time zone. 

 The default Avangate API time zone is GMT+02:00 (EET).

YES

ORDER_HASH

String/Required

The HMAC_MD5 signature for the transmitted data.

-

REF_URL 

String/Optional

You have the option of setting a URL where Avangate sends the response to your IRN request using GET. Make sure to host a listener capable of interpreting the response. (http://www.my-site.com/irn.php)

If you do not use this parameter, Avangate displays the answer inline.

NO

PRODUCTS_IDS

 

Array/Optional for full refund/Required for partial refunds

When included, it cannot be empty. Array with the product(s) ID for which you send the reverse/refund request. Number of PRODUCTS_IDS array elements much match those of the PRODUCTS_QTY array.

For total refunds, Avangate validates product identifiers, and they need to belong to the products purchased in the same order. Include identifiers for all products in the order.

For partial refunds, include only the IDs for the products that you want to refund.

YES only if included in the request

PRODUCTS_QTY 

 

Array/Optional/Required when you use PRODUCTS_IDS

When included, it cannot be empty. Array with corresponding quantities for product(s) mentioned in PRODUCTS_IDS. Number of PRODUCTS_QTY array elements much match those of the PRODUCTS_IDS array. Quantities cannot exceed those of items purchased as part of the order you’re refunding.

For example, let's assume Customer A purchased 2 units of Product A at $1000. To refund $500 for 1 unit of product A, the value of PRODUCTS_QTY needs to be 1.

For total refunds, match the value of PRODUCTS_QTY to the total number of units per product purchased with that order.

YES only if included in the request

REGENERATE_CODES

String/Optional

Array with codes you want Avangate to reallocate to the static list associated with the product. Ignore if you use dynamic lists.

For dynamic lists of activation codes/keys, Avangate does not generate an error if you include the parameter. This applies to HASH calculation as well.

YES only if included in the request.

LICENSE_HANDLING

Array/Optional

For regular products: array containing the action that Avangate should carry out regarding the subscriptions/licenses generated with the customer’s purchase.

For bundles set to generate subscriptions/licenses for each bundled product, LICENSE_HANDLING should be an array of an array.

Possible values:

  • CANCEL - cancels the subscription/license immediately. No future actions can be performed for canceled items, including renewals or upgrades.
  • NONE - leaved the subscription/license unchanged.

If LICENSE_HANDLING is empty, the Avangate system interprets the value as NONE.

Avangate correlates impact on subscriptions based on the values you send using LICENSE_HANDLING with the product identifiers you set using PRODUCTS_IDS. For example, let's assume that you're trying to refund

  • Product 1 (regular product): unique ID (1234567), that generated a subscription that you want to cancel with the reference 1A234567B89.
  • Product 2 (bundle product): unique ID (1122334), that generated 2 subscriptions, one you want to cancel, with the reference 9X234567X00 and one you wish to leave untouched 5Z234567Z11.

Then you will need to send:

  • 'PRODUCTS_IDS' => array(1234567, 1122334)
  • 'LICENSE_HANDLING' => array('CANCEL', array(9X234567X00=>CANCEL, 5Z234567Z11=>NONE))

YES only if included in the request

AMOUNT

Double or Array/Required

Double (Float) - a single value for total refunds. 'AMOUNT' => '150.00'- in this case AMOUNT (equal to ORDER_AMOUNT) needs to match the total value of the order.

Or, array with the specific amounts that will be refunded to customers only for partial refunds. When issuing partial refunds, PRODUCTS_IDS is mandatory.

 

The amounts reimbursed to customers must match the products for which the refund/reverse is made.

  • When the amount of money paid back to customers is smaller than the value of the initial order (ORDER_AMOUNT), specify the value of the refund using AMOUNT.
  • If AMOUNT is missing, then the entire value of the order (ORDER_AMOUNT) will be refunded.

For example, Customer A purchased 3 units of Product A (ID=1234567) at $300 and 5 units of Product B (ID=1112223)at $500. You want to issue partial refunds of $150 for Product A and $250 for Product B.

'PRODUCTS_IDS' => array('1234567', '1112223'),

'PRODUCTS_QTY' => array(2,3),

'AMOUNT' => array ('150.00', '250.00'),

For the same example, if you want to refund only $150 for Product A:

'PRODUCTS_IDS' => array('1234567'),

'PRODUCTS_QTY' => array(1),

'AMOUNT' => array ('150.00'),

You can issue a partial refund through IRN even for orders that contain only a single unit of a product. For example, let's assume Customer A purchased 2 units of Product A at $150. You will be able to refund a part of the full $150 for 1 unit of product A (the value of PRODUCTS_QTY needs to be 1). In this case, you also need to use the following format:

 

'PRODUCTS_IDS' => array('1234567'),

'PRODUCTS_QTY' => array(2),

'AMOUNT' => array ('300.00'),

For the same example, if you want to refund only $50 for Product A:

'PRODUCTS_IDS' => array('1234567'),

'PRODUCTS_QTY' => array(1),

'AMOUNT' => array ('150.00'),

YES

Reponse

Avangate validates your request by showing a response in the page that receives the information, under the following format:

<EPAYMENT>ORDER_REF|RESPONSE_CODE|RESPONSE_MSG|IRN_DATE|ORDER_HASH </EPAYMENT>

The parameters in the validation signature Avangate sends:

ORDER_REF

The order reference in the Avangate system, received through IRN

RESPONSE_CODE

The answer code for the order reverse request

RESPONSE_MSG

The answer to the order reverse request

IRN_DATE

The date of the answer to the order reverse request, in the following format: YYYY-MM-DD HH:mm:ss (24 hour format)

(Ex: 2009-01-30 11:33:37)

If you changed the time zone for the Avangate API by editing system settings under Account settings, then the IRN_DATE will be calculated according to your custom configuration. Avangate will use your custom set time zone for the IRN_DATE when calculating the HASH, and it's important that you also use the same datetime stamp, also per the custom time zone. Note: The default Avangate API time zone is GMT+02:00

ORDER_HASH

The HMAC_MD5 data validation signature


If the Avangate response is set to be sent to a specific URL (the REF_URL parameter contains a valid URL), the reply will be sent as follows:

REF_URL = http://www.mysite.com/callback.php

http://www.mysite.com/callback.php?ORDER_REF=value&RESPONSE_CODE=value&RESPONSE_MSG=value&IRN_DATE=value&ORDER_HASH=value
Secret key: 123456789!@#$%^&*
<EPAYMENT>12345678|1|OK|2012-12-12 12:12:12</EPAYMENT>
Serialized string for hash: 812345678112OK192012-12-12 12:12:12
Hash: e8324511d50f0f78a0a20aca28295290

IRN response codes and messages

Response code

Response message

1

OK

2

ORDER_REF missing or format incorrect

3

ORDER_AMOUNT missing or format incorrect

4

ORDER_CURRENCY is missing or format incorrect

5

IRN_DATE is not in the correct format

6

Error cancelling order

7

Order already canceled

8

Unknown error

9

Invalid ORDER_REF

10

Invalid ORDER_AMOUNT

11

Invalid ORDER_CURRENCY

12

PRODUCTS_IDS missing or format incorrect

13

PRODUCTS_QTY missing or format incorrect

14

Invalid PRODUCTS_QTY

15

Invalid REGENERATE_CODES

16

Invalid LICENSE_HANDLING

17

AMOUNT missing or format incorrect

18

Invalid AMOUNT

19

You have already placed a Total refund for this order.

20

You have already placed a refund for this order.

21

You already have a pending refund request.

22

The maximum refundable amount for this order has been exceeded.

23

You cannot place a refund request due to the order's current status.

24

You cannot place a refund request due to the order's payment details.

25

The allowed period to request a new refund for this order has expired.

26

Multiple refunds are not supported by this order's payment type.

27

Refunding not supported for this Cross Vendor Sale order.

28

Order total is negative.

29

You cannot place a refund request due to the order's approval status.

30

Multiple refunds are not supported by this order's terminal.

31

Partial reverse is not supported.

32

Invalid product type. Refunds are available only for the following product types: REGULAR / BUNDLE / MEDIA / DOWNLOAD_INSURANCE, but not for DISCOUNT / SHIPPING.

33

You cannot request a refund because a chargeback dispute was open for the order.

 

Batch refunds/reversals

Batch processing is not supported. You need to send standalone requests for every reverse or total/partial refund.

PHP code samples

Total refund

<?php

function SerializeArray($myarray, $strip_slashes = true, $elementsToProcess = null) {

        $retvalue = "";
        if ($elementsToProcess !== null && is_array($elementsToProcess)) {
            $_myarray = $myarray;
            $myarray = array();

            foreach ($elementsToProcess as $key) {
                if (!isset($_myarray[$key])) {
                    continue;
                }
                $myarray[$key] = $_myarray[$key];
            }
        }
        while (list($key, $val) = each($myarray)) {
            if (is_array($val)) {
                $retvalue .= SerializeArray($val, $strip_slashes);
            } else {
                $size = ($strip_slashes) ? strlen(StripSlashes($val)) : strlen($val);
                $retvalue .= ($strip_slashes) ? $size . StripSlashes($val) : $size . $val;
                
            }
        }

        return $retvalue;
    }

function hmac ($key, $data){
       $b = 64; // byte length for md5
       if (strlen($key) > $b) {
           $key = pack("H*",md5($key));
       }
       $key  = str_pad($key, $b, chr(0x00));
       $ipad = str_pad('', $b, chr(0x36));
       $opad = str_pad('', $b, chr(0x5c));
       $k_ipad = $key ^ $ipad ;
       $k_opad = $key ^ $opad;
       return md5($k_opad  . pack("H*",md5($k_ipad . $data)));
    }
                
       
        $key = "SECRET_KEY";

        $irn = array(
                'MERCHANT'                      => 'MERCHANT_CODE',     
                'ORDER_REF'                     => 12345678,
                'ORDER_AMOUNT'                  => 11.00,
                'ORDER_CURRENCY'                => 'USD',
                'IRN_DATE'                      => date('Y-m-d H:i:s'),
                );

        $process = array('MERCHANT', 'ORDER_REF', 'ORDER_AMOUNT', 'ORDER_CURRENCY', 'IRN_DATE', 'PRODUCTS_IDS', 'PRODUCTS_QTY', 'REGENERATE_CODES', 'LICENSE_HANDLING', 'AMOUNT');
        $string = SerializeArray($irn, true, $process);
                                
        $hash = hmac($key, $string);

        $irn['ORDER_HASH'] = $hash;
        
        $dataels = array();
        
        $data = http_build_query($irn);                      

        $curl = curl_init("https://secure.avangate.com/order/irn.php");
        curl_setopt($curl, CURLOPT_POST, 1);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
        curl_setopt($curl, CURLOPT_SSLVERSION, 0);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($curl, CURLOPT_PROXY, '');
        curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
        $responseString = curl_exec($curl);
        
        var_dump($responseString);
?>

Multiple partial refunds

<?php

function SerializeArray($myarray, $strip_slashes = true, $elementsToProcess = null) {

        $retvalue = "";
        if ($elementsToProcess !== null && is_array($elementsToProcess)) {
            $_myarray = $myarray;
            $myarray = array();

            foreach ($elementsToProcess as $key) {
                if (!isset($_myarray[$key])) {
                    continue;
                }
                $myarray[$key] = $_myarray[$key];
            }
        }
        while (list($key, $val) = each($myarray)) {
            if (is_array($val)) {
                $retvalue .= SerializeArray($val, $strip_slashes);
            } else {
                $size = ($strip_slashes) ? strlen(StripSlashes($val)) : strlen($val);
                $retvalue .= ($strip_slashes) ? $size . StripSlashes($val) : $size . $val;
                
            }
        }

        return $retvalue;
    }

function hmac ($key, $data){
       $b = 64; // byte length for md5
       if (strlen($key) > $b) {
           $key = pack("H*",md5($key));
       }
       $key  = str_pad($key, $b, chr(0x00));
       $ipad = str_pad('', $b, chr(0x36));
       $opad = str_pad('', $b, chr(0x5c));
       $k_ipad = $key ^ $ipad ;
       $k_opad = $key ^ $opad;
       return md5($k_opad  . pack("H*",md5($k_ipad . $data)));
    }
                
       
        $key = "SECRET_KET";

        $irn = array(
                'MERCHANT'                      => 'MERCHANT_CODE',     
                'ORDER_REF'                     => 12345678,
                'ORDER_AMOUNT'                  => 11.00,
                'ORDER_CURRENCY'                => 'USD',
                'IRN_DATE'                      => date('Y-m-d H:i:s'),
                'PRODUCTS_IDS'                  =>array(1119321,2225673),
                'PRODUCTS_QTY'                  =>array(1,1),
                'AMOUNT'                        =>array('1.00','5.00')
                );

        $process = array('MERCHANT', 'ORDER_REF', 'ORDER_AMOUNT', 'ORDER_CURRENCY', 'IRN_DATE', 'PRODUCTS_IDS', 'PRODUCTS_QTY', 'REGENERATE_CODES', 'LICENSE_HANDLING', 'AMOUNT');
        $string = SerializeArray($irn, true, $process);
                                
        $hash = hmac($key, $string);

        $irn['ORDER_HASH'] = $hash;
        
        $dataels = array();
        
        $data = http_build_query($irn);                      

        $curl = curl_init("https://secure.avangate.com/order/irn.php");
        curl_setopt($curl, CURLOPT_POST, 1);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
        curl_setopt($curl, CURLOPT_SSLVERSION, 0);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($curl, CURLOPT_PROXY, '');
        curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
        $responseString = curl_exec($curl);
        
        var_dump($responseString);
?>