# Unified Transaction‑Confirmation Standard

Scope Applies to all responses produced by the three live endpoints:

• /confirmation/\<transactionRef> → Card (NGN & USD)

• /confirmation/pwt/\<session\_id> → PwT (Pay‑with‑Transfer)

• /confirmation/byCustomerRef/\<ref> → aggregate wrapper that returns a txn\_list composed of the above two.

This document tells integrators exactly which fields to check for fulfilment decisions and

gives sample payloads for every pathway.

1. &#x20;**Canonical Top‑Level Fields**

<table><thead><tr><th width="154.438232421875">Field</th><th width="114.70281982421875">Type</th><th width="155.0399169921875">Always Present?</th><th>Notes</th></tr></thead><tbody><tr><td><mark style="color:green;">status</mark></td><td>string</td><td>             ✓</td><td><p>One of Successful, Pending, Unsuccessful –</p><p>already set by each endpoint.</p></td></tr><tr><td><mark style="color:green;">instrument</mark></td><td>string</td><td>             ✓</td><td><p>Card or PwT (the aggregate endpoint</p><p>forwards child objects untouched; each</p><p>child still contains its own instrument).</p></td></tr><tr><td><mark style="color:green;">code</mark></td><td>string</td><td>             ✓</td><td><p>Gateway‑result code normalised:</p><p>• NGN → response.ResponseCode (e.g. "00").</p><p>• USD → result ("SUCCESS" / "PENDING" /</p><p>"FAILURE").</p><p>• PwT → payment_status ("00" success,</p><p>"AWAITING_PAYMENT",</p><p>"EXPIRED_SESSION", …).</p></td></tr><tr><td><mark style="color:green;">amount</mark></td><td>number</td><td>             ✓</td><td><p>Amount authorised/received (decimal,</p><p>major currency unit).</p></td></tr><tr><td><mark style="color:green;">currency</mark></td><td>string</td><td>             ✓</td><td>ISO‑4217; for PwT this is fetched from the linked PaymentURL.</td></tr><tr><td><mark style="color:green;">message</mark></td><td>string</td><td>             ✓</td><td><p>Human‑readable description –</p><p><em>informational only</em>.</p></td></tr><tr><td><mark style="color:green;">merchant</mark></td><td>object</td><td>             ✓</td><td>Contains at minimum {first_name, last_name, email, phone_number}.</td></tr><tr><td><p><mark style="color:green;">customer_</mark></p><p><mark style="color:green;">transaction_ref</mark></p></td><td>string</td><td>             ⅹ</td><td>Passed through when available (Card).</td></tr><tr><td><mark style="color:green;">internal_transa ction_ref</mark></td><td>string</td><td>             ⅹ</td><td><p>Always returned by the aggregate endpoint;</p><p>single‑instrument endpoints include it in</p><p>logs and snapshots.</p></td></tr><tr><td><mark style="color:green;">error_code</mark></td><td>string</td><td>             ⅹ</td><td>Present only when status == "Unsuccessful".</td></tr><tr><td><mark style="color:green;">error_message</mark> </td><td>string</td><td>             ⅹ</td><td>Present only when status == "Unsuccessful".</td></tr></tbody></table>

Processor‑specific keys (response, status\_description, virtual\_account, …) are preserved for

analytics but not required for order fulfilment.

2. **Decision Matrix (merchant validation rules)**

<table><thead><tr><th width="118.5994873046875">Instrument</th><th width="108.40838623046875">Currency</th><th width="235.4576416015625">Accept (fulfil order) IF ALL true</th><th>Decline/Retry rules</th></tr></thead><tbody><tr><td>Card</td><td>NGN</td><td><p><mark style="color:green;">status == "Successful"</mark></p><p><strong>AND</strong> <mark style="color:green;">code == "00"</mark></p><p><strong>AND</strong> <mark style="color:green;">amount == expected_amount</mark> <strong>AND</strong></p><p><mark style="color:green;">currency == store_currency</mark></p></td><td><p>Any other combo → failure.</p><p>Use <mark style="color:green;">response.ResponseCode</mark> for</p><p>decline reason (e.g. 51</p><p>insufficient funds).</p></td></tr><tr><td>Card</td><td>USD</td><td><p><mark style="color:green;">status == "Successful"</mark></p><p><strong>AND</strong> </p><p><mark style="color:green;">code == "SUCCESS"</mark> <br><strong>AND</strong> <mark style="color:green;">amount == expected_amount</mark><br><strong>AND</strong> <br>currency == store_currency</p></td><td><p><mark style="color:green;">code == "PENDING"</mark> → wait &#x26; re‑poll;</p><p>anything else → failure.</p></td></tr><tr><td>PwT</td><td>—</td><td><p><mark style="color:green;">status == "Successful"</mark></p><p><strong>AND</strong> <mark style="color:green;">code == "00"</mark></p><p><strong>AND</strong> <mark style="color:green;">amount == expected_amount</mark> <strong>AND</strong></p><p><mark style="color:green;">currency == store_currency</mark></p></td><td><p><mark style="color:green;">code == "AWAITING</mark></p><p><mark style="color:green;">PAYMENT"</mark> → keep polling until session</p><p>expiry.<br><mark style="color:green;">code == "EXPIRED</mark></p><p><mark style="color:green;">SESSION"</mark> or <br>any other non‑00 → failure.</p></td></tr><tr><td>Any</td><td>Any</td><td><p>HTTP ≥ 500 or</p><p>missing JSON keys →</p><p>technical error; retry</p><p>API.</p></td><td></td></tr></tbody></table>

**Quick reference – code mapping**

<table><thead><tr><th width="120.9892578125">Instrument</th><th>Condition</th><th>Value</th></tr></thead><tbody><tr><td><p>Card</p><p>(NGN)</p></td><td><mark style="color:green;">response.ResponseCode == "00"</mark></td><td><mark style="color:green;">"00"</mark></td></tr><tr><td><p>Card</p><p>(NGN)</p></td><td>other two‑digit ISO8583 code</td><td>same code ("51","54", …)</td></tr><tr><td><p>Card</p><p>(USD)</p></td><td><mark style="color:green;">result == "SUCCESS"</mark></td><td><mark style="color:green;">"SUCCESS"</mark></td></tr><tr><td><p>Card</p><p>(USD)</p></td><td><mark style="color:green;">result == "PENDING"</mark></td><td><mark style="color:green;">"PENDING"</mark></td></tr><tr><td><p>Card</p><p>(USD)</p></td><td><mark style="color:green;">result == "FAILURE"</mark></td><td><mark style="color:green;">"FAILURE"</mark></td></tr><tr><td>PwT</td><td><mark style="color:green;">payment_status == "Successful"</mark> </td><td><mark style="color:green;">"00"</mark></td></tr><tr><td>PwT</td><td><mark style="color:green;">payment_status == "AWAITING_PAYMENT"</mark> </td><td><mark style="color:green;">"AWAITING_PAYMENT"</mark></td></tr><tr><td>PwT</td><td>session expired without payment</td><td><mark style="color:green;">"EXPIRED_SESSION"</mark></td></tr></tbody></table>

3. JSON Schema (Draft 7)

<pre><code>{
<strong>    "$schema": "http://json-schema.org/draft-07/schema#",
</strong>    "title": "ConfirmationResponse",
    "type": "object",
    "required": ["status","instrument","code","amount","currency","message"],
    "properties": {
        "status": {
            "type": "string",
            "enum": ["Successful","Pending","Unsuccessful"]
            },
<strong>        "instrument": {
</strong>            "type": "string","enum": ["Card","PwT"]
            },
        "code": { "type": "string" },
        "amount": { "type": "number", "minimum": 0 },
        "currency": { "type": "string","pattern": "^[A-Z]{3}$" },
        "message": { "type": "string" },
        "error_code": { "type": "string" },
        "error_message": { "type": "string" },
        "merchant": { "type": "object" }
    },
    "if": { "properties": { "status": { "const": "Unsuccessful" } } },
    "then": {
        "required":["error_code","error_message"]
        }
}
</code></pre>

4. **Worked Example**

**4.1 Card – NGN (Successful)**

```
{
    "status": "Successful",
    "instrument": "Card",
    "code": "00",
    "amount": 1000.0,
    "currency": "NGN",
    "message": "Approved by Financial Institution",
    "merchant": { "first_name": "zishan","last_name": "jawed","email":"dev.zishan@gmail.com","phone_number": "8979938492" },
    "customer_transaction_ref": "AIRTEST004",
    "internal_transaction_ref": "8199d79d-39e5-4bff-bafd-d04fa01d50d5",
    "response": { "ResponseCode": "00", "...", "..."}
}
```

**4.2 Card – USD (Pending)**

```
{
    "status": "Pending",
    "instrument": "Card",
    "code": "PENDING",
    "amount": 1000.0,
    "currency": "USD",
    "message": "Payment Status … is PENDING",
    "merchant": { "first_name": "zishan,"last_name": "jawed","email": "dev.zishan@gmail.com","phone_number": "8979938492" }
}
```

**4.3 PwT (Successful)**

```
{
    "status": "Successful",
    "instrument": "PwT",
    "code": "00",
    "amount": 1000.0,
    "currency": "NGN",
    "message": "Payment completed successfully",
    "merchant": { "first_name": "zishan,"last_name": "jawed","email": "dev.zishan@gmail.com","phone_number": "8979938492" },
    "session_id": "SESSION73F40FD0",
    "transactions": [ { "...": "..." } ]
}
```

**4.4 PwT (Awaiting payment)**

```
{
    "status": "Pending",
    "instrument": "PwT",
    "code": "AWAITING_PAYMENT",
    "amount": 0.0,
    "currency": "NGN",
    "message": "Awaiting payment of 1000.0",
    "session_expires_at": "2025-07-01T09:26:57.22Z"
}
```

**4.5 Any – Server‑side exception**

```
{
"status": "Unsuccessful",
"instrument": "Card",
"code": "X000",
"amount": 0,
"currency": "",
"message": "Internal server error",
"error_code": "X000",
"error_message": "Internal server error
}
```

5. **Merchant‑side Pseudocode**

```
resp = call_confirmation_api()

if resp["status"] == "Successful":
    if resp["code"] in ("00","SUCCESS") and resp["amount"] == order_amount:
        fulfil_order()
    else:
        flag_mismatch()
elif resp["status"] == "Pending":
    if resp["code"] == "AWAITING_PAYMENT":
        schedule_poll()
    else: # USD_PENDING or other temporary states
        schedul_poll_short()
else: # Unsuccessful
    log_failure(resp["code"], resp.get("error_code"))
    show_failure_to_user()
```
