Developer dashboard

PRA Refund Decline Queue#

Track and retry declined refunds (including PRA — Protective Refund Amount — declines).


Overview#

When a refund is declined by the gateway, AuxCORE adds it to the Refund Decline Queue so it can be retried or resolved later:

  • 📥 Automatically queued - Every declined refund is added (one open entry per refund)
  • 🔄 Retry with backoff - Up to 3 retries, 5 → 10 → 20 minutes apart — but only when something calls process-retries (nothing schedules it today)
  • ✅ Auto-resolution - Marked resolved when the retry's refund call returns successfully (see the caveat under Manual Retry)
  • 🛠️ Manual control - Retry, resolve, or cancel anytime

All endpoints are admin API routes: Authorization: Bearer <JWT> (or an API key in x-api-key).


How It Works#

text
1. Refund Attempted → 2. Declined → 3. Added to Queue (pending)
                                            ↓
4. Resolved ← Retry (process-retries or manual) → retry → … → failed after 3 failed retries

List Queue Entries#

Endpoint#

GET/api/v1/refund-decline-queue

Query Parameters#

Parameter Type Description
status string Filter by status (see below)
merchantId string Filter by merchant
fromDate date Created on/after
toDate date Created on/before
page integer Page number (default 1)
limit integer Items per page (default 50)
Query Parametersbash
curl "https://uat.auxcore.net/api/v1/refund-decline-queue?status=pending" \
  -H "Authorization: Bearer YOUR_TOKEN"

Response:

Query Parametersjson
{
  "success": true,
  "data": {
    "entries": [
      {
        "id": "2c4e6a8b-1d3f-4a5b-9c7d-8e9f0a1b2c3d",
        "tenantId": "your-tenant-id",
        "merchantId": "3f2a9c1e-7b4d-4e8a-9f10-2c5d6e7f8a90",
        "transactionId": "TXN-REF-000789",
        "originalTransactionId": "TXN-000456",
        "amount": 150.75,
        "gatewayType": "NMI",
        "status": "retry",
        "declineCode": "300",
        "declineReason": "Refund declined",
        "gatewayResponse": { "responsetext": "Refund declined" },
        "retryCount": 1,
        "maxRetries": 3,
        "scheduledRetryAt": "2026-01-28T15:15:00Z",
        "lastRetryAt": "2026-01-28T15:05:00Z",
        "lastRetryError": "Refund declined",
        "resolvedAt": null,
        "resolvedBy": null,
        "resolutionNotes": null,
        "createdAt": "2026-01-28T15:00:00Z",
        "updatedAt": "2026-01-28T15:05:00Z"
      }
    ],
    "total": 15,
    "page": 1,
    "limit": 50
  }
}

Entries are ordered newest first. There is no single-entry GET /:id endpoint — filter the list instead.


Queue Entry Statuses#

Status Description
pending Queued, waiting for its first retry
processing A retry is in progress
retry A retry failed; waiting for the next one
resolved Refunded by a retry, or resolved manually
failed All retries used up (retryCount reached maxRetries)
cancelled Manually cancelled

Manual Retry#

Retry one entry now. The refund is re-sent for the original transaction through the same gateway.

Endpoint#

POST/api/v1/refund-decline-queue/:id/retry
Manual Retrybash
curl -X POST https://uat.auxcore.net/api/v1/refund-decline-queue/2c4e6a8b-1d3f-4a5b-9c7d-8e9f0a1b2c3d/retry \
  -H "Authorization: Bearer YOUR_TOKEN"

Response (refund call returned successfully):

Manual Retryjson
{
  "success": true,
  "data": { "success": true, "message": "Refund processed successfully" },
  "message": "Refund retry initiated successfully"
}

A manual retry counts toward maxRetries like an automatic one — it does not reset the counter.

Caveat: the entry is marked resolved whenever the internal refund call returns success: true. That call also returns success: true when it creates a new refund that the gateway declines again, so a resolved entry with resolutionNotes: "Automatic retry successful" does not prove the money was refunded. Check the original transaction's refunds before closing the case with the customer.

Status When
400 VALIDATION_ERROR — entry already resolved, already failed, or max retries reached (a cancelled entry is not blocked)
404 Entry not found
500 INTERNAL_ERROR — the refund call errored (non-2xx or timeout); the entry moves to retry (or failed on the last attempt)

Manual Resolution#

Mark an entry as resolved (e.g., refunded through a different method).

Endpoint#

POST/api/v1/refund-decline-queue/:id/resolve
Manual Resolutionbash
curl -X POST https://uat.auxcore.net/api/v1/refund-decline-queue/2c4e6a8b-1d3f-4a5b-9c7d-8e9f0a1b2c3d/resolve \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "notes": "Refunded via check" }'

notes is required (400 VALIDATION_ERROR otherwise). The response data is the updated entry with status: "resolved", resolvedAt, resolvedBy (your user id) and resolutionNotes, plus "message": "Refund resolved successfully".


Cancel Entry#

Cancel a queue entry (e.g., customer no longer wants the refund).

Endpoint#

POST/api/v1/refund-decline-queue/:id/cancel
Cancel Entrybash
curl -X POST https://uat.auxcore.net/api/v1/refund-decline-queue/2c4e6a8b-1d3f-4a5b-9c7d-8e9f0a1b2c3d/cancel \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Customer request" }'

reason is required (400 VALIDATION_ERROR otherwise). The response data is the updated entry with status: "cancelled"; the reason is stored in resolutionNotes.


Retry Schedule#

Each entry allows 3 retries (maxRetries). The next retry is scheduled at 5 minutes × 2^retryCount, capped at 24 hours:

Retry Scheduled after
1 5 minutes after the decline
2 10 minutes after retry 1 fails
3 20 minutes after retry 2 fails

These are only the scheduledRetryAt times; nothing runs a retry at that time unless process-retries is called (see below). After the third failed retry the entry becomes failed and needs manual action (resolve or cancel).


Process Retries#

Runs every due retry: entries in pending or retry whose scheduledRetryAt has passed and that still have retries left.

Endpoint#

POST/api/v1/refund-decline-queue/process-retries
Process Retriesbash
curl -X POST https://uat.auxcore.net/api/v1/refund-decline-queue/process-retries \
  -H "Authorization: Bearer YOUR_TOKEN"

Response:

Process Retriesjson
{
  "success": true,
  "data": {
    "processed": 5,
    "successful": 2,
    "failed": 3,
    "errors": [
      { "id": "2c4e6a8b-1d3f-4a5b-9c7d-8e9f0a1b2c3d", "error": "Refund declined" }
    ]
  },
  "message": "Processed 5 refunds: 2 successful, 3 failed"
}

Note: No scheduler calls this endpoint automatically today. Scheduled retries only run when process-retries (or a manual retry) is called.


Webhooks and Analytics#

Note: There are no refund_decline_queue.* webhook events and no /refund-decline-queue/stats endpoint. A declined refund raises the standard transaction.refund_failed webhook (see Advanced Webhooks); use the list endpoint with status filters for counts.


Best Practices#

✅ DO:#

  • Monitor the queue daily - Check for failed entries
  • Subscribe to transaction.refund_failed - Get notified of declines
  • Document resolutions - Notes are required and kept on the entry

❌ DON'T:#

  • Don't ignore failed entries - Follow up with customers
  • Don't cancel prematurely - Give retries time to run

Troubleshooting#

Queue Not Processing#

If scheduled retries aren't happening, make sure something calls POST /process-retries (see the note above), then check the entry's scheduledRetryAt and retryCount.

Stuck Entries#

If an entry stays in processing, the retry call did not complete. Use Manual Resolution or Cancel once you have confirmed the refund state with the gateway.


Next Steps#


Need help? Contact support@auxcore.net