{
  "info": {
    "name": "Shukria Payments — MPGS API: Refund",
    "description": "Refund previously captured funds on an MPGS order. Works for every MPGS flow (Hosted Checkout, Hosted Session, Direct). Only captured funds can be refunded: use a PURCHASE-mode payment, or an AUTHORIZE-mode payment after capture. A voided order cannot be refunded.\n\nSet the collection variables before sending:\n- `merchant_api_key`: the merchant's API key (starts with `shkdirect_`).\n- `order_ref`: CloudLayer's order id for the payment (`pg_transactions.gateway_order_ref`, also returned as `reference` by `/pay/status`), e.g. `916`.\n- `refund_amount`: amount per partial refund (up to 3 decimal places).\n- `currency`: the order's currency, e.g. `AED`.\n\n`refund_txn_id`, `refund_txn_id_2` and `refund_txn_id_over` are set automatically by pre-request scripts. Each refund needs a new transaction id; re-sending an id returns the original result instead of refunding again.\n\nRun requests 1 → 2 → 3 → 4 in order on a fresh payment; 5–9 can run in any order and never reach MPGS.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{merchant_api_key}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "base_url",
      "value": "https://mercurypay.ariticapp.com/pn"
    },
    {
      "key": "pg_prefix",
      "value": "/pgpayments"
    },
    {
      "key": "merchant_api_key",
      "value": "YOUR_MERCHANT_API_KEY"
    },
    {
      "key": "order_ref",
      "value": "916"
    },
    {
      "key": "refund_amount",
      "value": "10.00"
    },
    {
      "key": "currency",
      "value": "AED"
    },
    {
      "key": "refund_txn_id",
      "value": ""
    },
    {
      "key": "refund_txn_id_2",
      "value": ""
    },
    {
      "key": "refund_txn_id_over",
      "value": ""
    },
    {
      "key": "refunded_after_first",
      "value": ""
    }
  ],
  "item": [
    {
      "name": "1 · Partial refund",
      "event": [
        {
          "listen": "prerequest",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.collectionVariables.set('refund_txn_id', 'refund-' + pm.variables.get('order_ref') + '-' + Date.now());"
            ]
          }
        },
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "const body = pm.response.json();",
              "const close = (a, b) => Math.abs(Number(a) - Number(b)) < 0.0005;",
              "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
              "pm.test('result is SUCCESS', () => pm.expect(body.result).to.eql('SUCCESS'));",
              "pm.test('merchant present', () => pm.expect(body.merchant).to.be.a('string'));",
              "pm.test('order fields present', () => {",
              "  ['amount', 'creationTime', 'currency', 'id', 'lastUpdatedTime', 'merchantAmount', 'merchantCurrency',",
              "   'totalAuthorizedAmount', 'totalCapturedAmount', 'totalRefundedAmount']",
              "    .forEach(f => pm.expect(body.order).to.have.property(f));",
              "});",
              "pm.test('order.id matches', () => pm.expect(body.order.id).to.eql(pm.variables.get('order_ref')));",
              "pm.test('response.gatewayCode present', () => pm.expect(body.response.gatewayCode).to.be.a('string'));",
              "pm.test('transaction fields present', () => {",
              "  ['amount', 'currency', 'id', 'type', 'acquirer'].forEach(f => pm.expect(body.transaction).to.have.property(f));",
              "});",
              "pm.test('transaction.type is REFUND', () => pm.expect(body.transaction.type).to.eql('REFUND'));",
              "pm.test('transaction.id is the refund id', () => pm.expect(body.transaction.id).to.eql(pm.collectionVariables.get('refund_txn_id')));",
              "pm.test('transaction.amount is the requested amount', () => pm.expect(close(body.transaction.amount, pm.variables.get('refund_amount'))).to.be.true);",
              "if (body.order) pm.collectionVariables.set('refunded_after_first', String(body.order.totalRefundedAmount));"
            ]
          }
        }
      ],
      "request": {
        "method": "PUT",
        "header": [
          {
            "key": "Accept",
            "value": "application/json"
          },
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"apiOperation\": \"REFUND\",\n  \"transaction\": {\n    \"amount\": \"{{refund_amount}}\",\n    \"currency\": \"{{currency}}\"\n  }\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        },
        "url": {
          "raw": "{{base_url}}{{pg_prefix}}/api/mpgs/order/{{order_ref}}/transaction/{{refund_txn_id}}",
          "host": [
            "{{base_url}}{{pg_prefix}}"
          ],
          "path": [
            "api",
            "mpgs",
            "order",
            "{{order_ref}}",
            "transaction",
            "{{refund_txn_id}}"
          ]
        },
        "description": "Refunds `{{refund_amount}}` against the order's captured funds. The pre-request script sets a fresh `refund_txn_id`; it is reused by request 2 to prove a retry does not refund twice.\n\nSuccess returns: merchant, result, order {amount, creationTime, currency, id, lastUpdatedTime, merchantAmount, merchantCurrency, totalAuthorizedAmount, totalCapturedAmount, totalRefundedAmount}, response {gatewayCode}, transaction {acquirer {id}, amount, currency, id, type}.\n\nA decline is still HTTP 200, with `result: FAILURE` and the reason in `response.gatewayCode`."
      },
      "response": []
    },
    {
      "name": "2 · Retry same refund id (idempotent)",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "const body = pm.response.json();",
              "const close = (a, b) => Math.abs(Number(a) - Number(b)) < 0.0005;",
              "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
              "pm.test('same transaction id', () => pm.expect(body.transaction.id).to.eql(pm.collectionVariables.get('refund_txn_id')));",
              "pm.test('totalRefundedAmount unchanged (no double refund)', () =>",
              "  pm.expect(close(body.order.totalRefundedAmount, pm.collectionVariables.get('refunded_after_first'))).to.be.true);"
            ]
          }
        }
      ],
      "request": {
        "method": "PUT",
        "header": [
          {
            "key": "Accept",
            "value": "application/json"
          },
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"apiOperation\": \"REFUND\",\n  \"transaction\": {\n    \"amount\": \"{{refund_amount}}\",\n    \"currency\": \"{{currency}}\"\n  }\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        },
        "url": {
          "raw": "{{base_url}}{{pg_prefix}}/api/mpgs/order/{{order_ref}}/transaction/{{refund_txn_id}}",
          "host": [
            "{{base_url}}{{pg_prefix}}"
          ],
          "path": [
            "api",
            "mpgs",
            "order",
            "{{order_ref}}",
            "transaction",
            "{{refund_txn_id}}"
          ]
        },
        "description": "Re-sends request 1 with the same `refund_txn_id`. MPGS returns the original result; `order.totalRefundedAmount` must not change. Run right after request 1."
      },
      "response": []
    },
    {
      "name": "3 · Second partial refund (new id)",
      "event": [
        {
          "listen": "prerequest",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.collectionVariables.set('refund_txn_id_2', 'refund-' + pm.variables.get('order_ref') + '-' + Date.now());"
            ]
          }
        },
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "const body = pm.response.json();",
              "const close = (a, b) => Math.abs(Number(a) - Number(b)) < 0.0005;",
              "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
              "pm.test('result is SUCCESS', () => pm.expect(body.result).to.eql('SUCCESS'));",
              "pm.test('transaction.id is the second refund id', () => pm.expect(body.transaction.id).to.eql(pm.collectionVariables.get('refund_txn_id_2')));",
              "pm.test('totalRefundedAmount increased by refund_amount', () => {",
              "  const expected = Number(pm.collectionVariables.get('refunded_after_first')) + Number(pm.variables.get('refund_amount'));",
              "  pm.expect(close(body.order.totalRefundedAmount, expected)).to.be.true;",
              "});"
            ]
          }
        }
      ],
      "request": {
        "method": "PUT",
        "header": [
          {
            "key": "Accept",
            "value": "application/json"
          },
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"apiOperation\": \"REFUND\",\n  \"transaction\": {\n    \"amount\": \"{{refund_amount}}\",\n    \"currency\": \"{{currency}}\"\n  }\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        },
        "url": {
          "raw": "{{base_url}}{{pg_prefix}}/api/mpgs/order/{{order_ref}}/transaction/{{refund_txn_id_2}}",
          "host": [
            "{{base_url}}{{pg_prefix}}"
          ],
          "path": [
            "api",
            "mpgs",
            "order",
            "{{order_ref}}",
            "transaction",
            "{{refund_txn_id_2}}"
          ]
        },
        "description": "A second refund on the same order with a new transaction id. `order.totalRefundedAmount` must increase by `{{refund_amount}}`. Run after request 1."
      },
      "response": []
    },
    {
      "name": "4 · Refund more than remains (expect decline)",
      "event": [
        {
          "listen": "prerequest",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.collectionVariables.set('refund_txn_id_over', 'refund-' + pm.variables.get('order_ref') + '-' + Date.now());"
            ]
          }
        },
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "const body = pm.response.json();",
              "pm.test('not a server error', () => pm.expect(pm.response.code).to.be.below(500));",
              "pm.test('refund not successful', () => pm.expect(body.result).to.not.eql('SUCCESS'));"
            ]
          }
        }
      ],
      "request": {
        "method": "PUT",
        "header": [
          {
            "key": "Accept",
            "value": "application/json"
          },
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"apiOperation\": \"REFUND\",\n  \"transaction\": {\n    \"amount\": \"999999.00\",\n    \"currency\": \"{{currency}}\"\n  }\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        },
        "url": {
          "raw": "{{base_url}}{{pg_prefix}}/api/mpgs/order/{{order_ref}}/transaction/{{refund_txn_id_over}}",
          "host": [
            "{{base_url}}{{pg_prefix}}"
          ],
          "path": [
            "api",
            "mpgs",
            "order",
            "{{order_ref}}",
            "transaction",
            "{{refund_txn_id_over}}"
          ]
        },
        "description": "Asks for more than the captured-minus-refunded balance. MPGS rejects it; CloudLayer passes that through as `result: FAILURE` or `result: ERROR` — never a 5xx."
      },
      "response": []
    },
    {
      "name": "5 · Missing amount (expect 400)",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "const body = pm.response.json();",
              "pm.test('HTTP 400', () => pm.response.to.have.status(400));",
              "pm.test('result is ERROR', () => pm.expect(body.result).to.eql('ERROR'));",
              "pm.test('cause is INVALID_REQUEST', () => pm.expect(body.error.cause).to.eql('INVALID_REQUEST'));",
              "pm.test('field is transaction.amount', () => pm.expect(body.error.field).to.eql('transaction.amount'));",
              "pm.test('validationType is MISSING', () => pm.expect(body.error.validationType).to.eql('MISSING'));"
            ]
          }
        }
      ],
      "request": {
        "method": "PUT",
        "header": [
          {
            "key": "Accept",
            "value": "application/json"
          },
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"apiOperation\": \"REFUND\",\n  \"transaction\": {\n    \"currency\": \"{{currency}}\"\n  }\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        },
        "url": {
          "raw": "{{base_url}}{{pg_prefix}}/api/mpgs/order/{{order_ref}}/transaction/refund-validation",
          "host": [
            "{{base_url}}{{pg_prefix}}"
          ],
          "path": [
            "api",
            "mpgs",
            "order",
            "{{order_ref}}",
            "transaction",
            "refund-validation"
          ]
        },
        "description": "Rejected by CloudLayer before MPGS is called."
      },
      "response": []
    },
    {
      "name": "6 · Invalid amount — 4 decimals (expect 400)",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "const body = pm.response.json();",
              "pm.test('HTTP 400', () => pm.response.to.have.status(400));",
              "pm.test('result is ERROR', () => pm.expect(body.result).to.eql('ERROR'));",
              "pm.test('cause is INVALID_REQUEST', () => pm.expect(body.error.cause).to.eql('INVALID_REQUEST'));",
              "pm.test('field is transaction.amount', () => pm.expect(body.error.field).to.eql('transaction.amount'));",
              "pm.test('validationType is INVALID', () => pm.expect(body.error.validationType).to.eql('INVALID'));"
            ]
          }
        }
      ],
      "request": {
        "method": "PUT",
        "header": [
          {
            "key": "Accept",
            "value": "application/json"
          },
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"apiOperation\": \"REFUND\",\n  \"transaction\": {\n    \"amount\": \"12.3456\",\n    \"currency\": \"{{currency}}\"\n  }\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        },
        "url": {
          "raw": "{{base_url}}{{pg_prefix}}/api/mpgs/order/{{order_ref}}/transaction/refund-validation",
          "host": [
            "{{base_url}}{{pg_prefix}}"
          ],
          "path": [
            "api",
            "mpgs",
            "order",
            "{{order_ref}}",
            "transaction",
            "refund-validation"
          ]
        },
        "description": "MPGS allows at most 3 decimal places. Rejected by CloudLayer before MPGS is called."
      },
      "response": []
    },
    {
      "name": "7 · Missing currency (expect 400)",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "const body = pm.response.json();",
              "pm.test('HTTP 400', () => pm.response.to.have.status(400));",
              "pm.test('result is ERROR', () => pm.expect(body.result).to.eql('ERROR'));",
              "pm.test('cause is INVALID_REQUEST', () => pm.expect(body.error.cause).to.eql('INVALID_REQUEST'));",
              "pm.test('field is transaction.currency', () => pm.expect(body.error.field).to.eql('transaction.currency'));",
              "pm.test('validationType is MISSING', () => pm.expect(body.error.validationType).to.eql('MISSING'));"
            ]
          }
        }
      ],
      "request": {
        "method": "PUT",
        "header": [
          {
            "key": "Accept",
            "value": "application/json"
          },
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"apiOperation\": \"REFUND\",\n  \"transaction\": {\n    \"amount\": \"{{refund_amount}}\"\n  }\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        },
        "url": {
          "raw": "{{base_url}}{{pg_prefix}}/api/mpgs/order/{{order_ref}}/transaction/refund-validation",
          "host": [
            "{{base_url}}{{pg_prefix}}"
          ],
          "path": [
            "api",
            "mpgs",
            "order",
            "{{order_ref}}",
            "transaction",
            "refund-validation"
          ]
        },
        "description": "Rejected by CloudLayer before MPGS is called."
      },
      "response": []
    },
    {
      "name": "8 · sourceOfFunds provided (expect 400)",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "const body = pm.response.json();",
              "pm.test('HTTP 400', () => pm.response.to.have.status(400));",
              "pm.test('result is ERROR', () => pm.expect(body.result).to.eql('ERROR'));",
              "pm.test('cause is INVALID_REQUEST', () => pm.expect(body.error.cause).to.eql('INVALID_REQUEST'));",
              "pm.test('field is sourceOfFunds', () => pm.expect(body.error.field).to.eql('sourceOfFunds'));",
              "pm.test('validationType is UNSUPPORTED', () => pm.expect(body.error.validationType).to.eql('UNSUPPORTED'));"
            ]
          }
        }
      ],
      "request": {
        "method": "PUT",
        "header": [
          {
            "key": "Accept",
            "value": "application/json"
          },
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"apiOperation\": \"REFUND\",\n  \"transaction\": {\n    \"amount\": \"{{refund_amount}}\",\n    \"currency\": \"{{currency}}\"\n  },\n  \"sourceOfFunds\": {\n    \"type\": \"CARD\"\n  }\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        },
        "url": {
          "raw": "{{base_url}}{{pg_prefix}}/api/mpgs/order/{{order_ref}}/transaction/refund-validation",
          "host": [
            "{{base_url}}{{pg_prefix}}"
          ],
          "path": [
            "api",
            "mpgs",
            "order",
            "{{order_ref}}",
            "transaction",
            "refund-validation"
          ]
        },
        "description": "Only refunds linked to the order's captured funds are supported; MPGS forbids `sourceOfFunds` on them. Rejected by CloudLayer before MPGS is called."
      },
      "response": []
    },
    {
      "name": "9 · Bad API key (expect 401)",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('HTTP 401', () => pm.response.to.have.status(401));",
              "pm.test('error is unauthorized', () => pm.expect(pm.response.json().error).to.eql('unauthorized'));"
            ]
          }
        }
      ],
      "request": {
        "auth": {
          "type": "bearer",
          "bearer": [
            {
              "key": "token",
              "value": "shkdirect_invalid",
              "type": "string"
            }
          ]
        },
        "method": "PUT",
        "header": [
          {
            "key": "Accept",
            "value": "application/json"
          },
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"apiOperation\": \"REFUND\",\n  \"transaction\": {\n    \"amount\": \"{{refund_amount}}\",\n    \"currency\": \"{{currency}}\"\n  }\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        },
        "url": {
          "raw": "{{base_url}}{{pg_prefix}}/api/mpgs/order/{{order_ref}}/transaction/refund-validation",
          "host": [
            "{{base_url}}{{pg_prefix}}"
          ],
          "path": [
            "api",
            "mpgs",
            "order",
            "{{order_ref}}",
            "transaction",
            "refund-validation"
          ]
        },
        "description": "Rejected by MerchantApiAuthPlug."
      },
      "response": []
    }
  ]
}
