COD Collections
8 API calls in this section.
List COD Collections
/delivery/cod-collectionsList physical cash collections with pagination, search, sorting, and column filters.
Drivers always see only their own collections; any supplied driver_id is ignored for driver callers. Dispatcher and tenant_admin roles see all tenant collections and may filter by driver_id. Authorization for this scope is role-only and does not use permissions. Other roles receive HTTP 403.
amount is the total cash held by the driver. allocations.cod / cod_amount is the goods/COD portion and allocations.delivery_fee / delivery_fee_amount is the delivery-fee portion. collection_stage is pickup or dropoff for new records.
Cash record rules
Sender pays delivery fee in cash: create a pickup record with
{cod: 0, delivery_fee}.Receiver pays COD while sender paid the fee: create a separate dropoff record with
{cod, delivery_fee: 0}.Receiver pays both COD and delivery fee in cash: create one dropoff record with
{cod, delivery_fee}.
Frontend integration contract
Display amount as the amount held/remitted. Display cod_amount and delivery_fee_amount as first-class numeric columns and keep allocations for the original breakdown object. Use collection_stage and allocations for the collection point and breakdown. Status values remain collected, remitted, and settled_by_wallet.
Search example
?search=chirag matches rows where driver name, customer name, email, or address contains 'chirag'
Sort example
?sort_by=amount&sort_order=desc
Column Filters (column_filters as JSON)
Per-column filter object. Takes priority over direct query params for the same field when direct params are absent.
| Key | Type | Behaviour |
|---|---|---|
customer_name | string | ILIKE match |
driver_name | string | ILIKE match |
delivery_address | string | ILIKE match |
external_reference_id | string | ILIKE match |
status | string | Exact match (collected | remitted | settled_by_wallet) |
delivery_code | number or {min,max} | Exact or range |
amount | number or {min,max} | Exact cash amount or range |
cod_amount | number or {min,max} | Exact COD allocation or range |
delivery_fee_amount | number or {min,max} | Exact delivery-fee allocation or range |
collected_at | {from,to} | Date range (day boundaries) |
remitted_at | {from,to} | Date range (day boundaries) |
settled_at | {from,to} | Date range (day boundaries) |
Example
?column_filters={"customer_name":"john","cod_amount":{"min":50},"delivery_fee_amount":{"max":20},"collected_at":{"from":"2024-01-01","to":"2024-01-31"}}
Summary fields
total_collected— sum ofcollectedstatus amountstotal_remitted— sum ofremittedstatus amountstotal_settled_by_wallet— sum ofsettled_by_walletstatus amountstotal_pending— same astotal_collected(unremitted)total_cod_collections— total count (ignores status filter)
Each row also returns settlement_payment_id (UUID or null). Rows settled after receipt support point to one saved batch; use this ID to reopen the receipt endpoint. Historical settled rows remain null until first print. Show Print Receipt for remitted/settled_by_wallet rows; POST /delivery/cod-collections/:id/receipt creates a receipt for that collection on demand. Do not infer historical batches from dates.
Query parameters
| Parameter | Type | Description |
|---|---|---|
page | string | Page number (default: 1) Example: |
limit | string | Items per page (default: 20, max: 100) Example: |
search | string | Case-insensitive partial match across delivery code, customer name, driver name, driver email, delivery address |
sort_by | string | Sort column: Example: |
sort_order | string | Sort direction: asc | desc (default: desc) Example: |
status | string | Filter by status: collected | remitted | |
driver_id | string | Filter by driver ID (Dispatcher and |
delivery_id | string | Filter by delivery ID |
delivery_code | string | Filter by exact delivery code |
amount_min | string | Filter amount >= value |
amount_max | string | Filter amount <= value |
cod_amount_min | string | Filter |
cod_amount_max | string | Filter |
delivery_fee_amount_min | string | Filter |
delivery_fee_amount_max | string | Filter |
collected_at_from | string | Filter |
collected_at_to | string | Filter |
remitted_at_from | string | Filter |
remitted_at_to | string | Filter |
settled_at_from | string | Filter |
settled_at_to | string | Filter |
column_filters | string | JSON object for per-column filtering. Supported keys: |
tenant_idOptional | string | Tenant ID (optional if in auth token) |
language | string | Response language Example: |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/delivery/cod-collections?page=1&limit=20&search=&sort_by=collected_at&sort_order=desc&status=&driver_id=&delivery_id=&delivery_code=&amount_min=&amount_max=&cod_amount_min=&cod_amount_max=&delivery_fee_amount_min=&delivery_fee_amount_max=&collected_at_from=&collected_at_to=&remitted_at_from=&remitted_at_to=&settled_at_from=&settled_at_to=&column_filters=&tenant_id=&language=en" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"Response examples
1{2 "success": true,3 "message": "COD collections retrieved successfully",4 "cod": {5 "collections": [6 {7 "id": "uuid",8 "delivery_id": "uuid",9 "driver_id": "uuid",10 "amount": 60,11 "allocations": {12 "cod": 50,13 "delivery_fee": 1014 },15 "cod_amount": 50,16 "delivery_fee_amount": 10,17 "status": "collected",18 "collected_at": "2023-01-01T10:00:00Z",19 "remitted_at": null,20 "settled_at": null,21 "delivery_code": 1001,22 "delivery_details": {23 "delivery_number": "DEL-001",24 "customer_name": "John Doe",25 "delivery_address": "456 Oak Ave, City"26 },27 "driver_info": {28 "full_name": "Driver Name",29 "email": "[email protected]",30 "avatar": "https://avatar.url"31 },32 "settlement_payment_id": null33 }34 ],35 "pagination": {36 "page": 1,37 "limit": 20,38 "total": 1539 },40 "summary": {41 "total_collected": 500,42 "total_remitted": 300,43 "total_pending": 200,44 "total_cod_collections": 2545 }46 }47}Get COD Summary
/delivery/cod-summaryGet driver's COD summary for wallet dashboard display. Shows unremitted amounts and pending collection counts.
Drivers always see only their own summary; any supplied
driver_idis ignored for driver callersDispatcher and
tenant_adminroles can fetch a specific driver's summary only when that driver belongs to their tenantOther roles receive HTTP 403
Query parameters
| Parameter | Type | Description |
|---|---|---|
driver_idOptional | string | Same-tenant driver ID to fetch summary for (Dispatcher and |
tenant_idOptional | string | Tenant ID (optional if in auth token) Tenant ID (defaults to authenticated user's tenant) |
languageOptional | string | Preferred language for response messages Example: |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/delivery/cod-summary?driver_id=&tenant_id=&language=en" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"Response examples
1{2 "success": true,3 "summary": {4 "total_unremitted": 200,5 "pending_collections_count": 5,6 "oldest_unremitted_date": "2023-01-01T10:00:00Z"7 }8}Bulk Remit COD Collections
/delivery/cod-collections/remitMark physical cash collections as remitted after a dispatcher receives cash from the driver.
Atomic business rules
COD deliveries credit the delivery customer/merchant wallet at delivery completion and mark the
COD_COLLECTIONpaymentSUCCESSimmediately.Cash remittance only marks each collection
remittedand records who received the driver cash. It does not credit the customer/merchant wallet.The delivery-fee allocation is not moved to a wallet in cash-remittance mode.
If any wallet/payment/collection update fails, the whole remittance fails.
Frontend integration contract
For selected rows, submit only scope=selected and cod_collection_ids; do not send driver_id. For remit-all, submit scope=all and driver_id when the actor is dispatcher/admin. Show total_cash_remitted, total_cod_remitted, and total_delivery_fee_remitted separately. Handle HTTP 400 when a selection is mixed-driver, invalid, no longer pending, or missing the required driver for remit-all.
Persistent settlement receipt
The settlement and one DELIVERY / COD_SETTLEMENT payment are saved atomically. The payment snapshots the driver, actor/receiver, timestamp, reference, exact settled collections, per-order COD/delivery-fee breakdown, and totals. The PDF is generated asynchronously; PDF failure never repeats or reverses settlement. A successful response adds settlement_payment_id, receipt_status (pending, in_process, success, or failure), and currency. The returned date is the persisted settlement date. Cash currency uses the tenant currency setting; wallet currency is the actual driver wallet currency.
Send one stable Idempotency-Key (up to 200 characters) per submission and reuse it for network retries. Its receipt identity is durable. Replaying the same request returns the original batch even for scope=all; reusing the same key with changed input returns HTTP 409 and code settlement_idempotency_conflict. A new intended settlement requires a new key.
Receipt integration
Open GET /delivery/cod-settlements/:settlement_payment_id/receipt. HTTP 202 means pending/in_process; poll briefly or reopen later. HTTP 200 supplies a fresh signed url. HTTP 409 with settlement_receipt_failed means settlement succeeded but PDF failed; retry only POST /delivery/cod-settlements/:settlement_payment_id/receipt, never resubmit settlement to print. For history rows with settlement_payment_id=null, first print uses POST /delivery/cod-collections/:id/receipt to save a receipt for that collection only.
Query parameters
| Parameter | Type | Description |
|---|---|---|
tenant_idOptional | string | Tenant ID (optional if in auth token) |
language | string | Preferred language for response messages Example: |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
Content-Type | string | Example: |
Body parameters
| Parameter | Type | Description |
|---|---|---|
scope | string |
|
cod_collection_ids | array<string> | required when |
remitted_to_user_idOptional | string | optional receiver/dispatcher user ID; defaults to the current user. |
notes | string | — |
reference | string | — |
driver_id | string | required only for dispatcher/admin |
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/cod-collections/remit?tenant_id=&language=en" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3 --header "Content-Type: application/json" \4 --data-raw '{5 "scope": "selected",6 "cod_collection_ids": [7 "uuid1",8 "uuid2",9 "uuid3"10 ],11 "remitted_to_user_id": "dispatcher-uuid",12 "notes": "Weekly remittance batch",13 "reference": "REM-REF-001"14}'Response examples
1{2 "success": true,3 "data": {4 "scope": "selected",5 "remitted_collections": [6 {7 "cod_collection_id": "uuid1",8 "amount": 60,9 "allocations": {10 "cod": 50,11 "delivery_fee": 1012 },13 "delivery_code": 1001,14 "delivery_number": "DEL-001"15 }16 ],17 "total_remitted": 60,18 "total_cash_remitted": 60,19 "total_cod_remitted": 50,20 "total_delivery_fee_remitted": 10,21 "remitted_count": 1,22 "failed_count": 0,23 "remitted_at": "2023-01-01T12:00:00Z",24 "settlement_payment_id": "91000000-0000-4000-8000-000000000001",25 "receipt_status": "pending",26 "currency": "IQD"27 }28}Settle COD by Wallet
/delivery/cod-collections/settle-by-walletSettle cash collections using the driver's wallet.
Atomic business rules
Debit the driver wallet by total collection
amount.COD deliveries already credit customer/merchant wallets at delivery completion. Settlement does not credit the customer/merchant wallet.
Credit the tenant wallet with
allocations.delivery_fee.Mark collections
settled_by_wallet.
Frontend integration contract
For selected rows, submit only scope=selected and cod_collection_ids; do not send driver_id. For settle-all, submit scope=all and driver_id when the actor is dispatcher/admin. Show total_cash_settled, total_cod_settled, and total_delivery_fee_settled separately. Handle HTTP 400 for insufficient balance, mixed-driver selections, invalid collections, missing driver for settle-all, or amount mismatch.
Persistent settlement receipt
The settlement and one DELIVERY / COD_SETTLEMENT payment are saved atomically. The payment snapshots the driver, actor/receiver, timestamp, reference, exact settled collections, per-order COD/delivery-fee breakdown, and totals. The PDF is generated asynchronously; PDF failure never repeats or reverses settlement. A successful response adds settlement_payment_id, receipt_status (pending, in_process, success, or failure), and currency. The returned date is the persisted settlement date. Cash currency uses the tenant currency setting; wallet currency is the actual driver wallet currency.
Send one stable Idempotency-Key (up to 200 characters) per submission and reuse it for network retries. Its receipt identity is durable. Replaying the same request returns the original batch even for scope=all; reusing the same key with changed input returns HTTP 409 and code settlement_idempotency_conflict. A new intended settlement requires a new key.
Receipt integration
Open GET /delivery/cod-settlements/:settlement_payment_id/receipt. HTTP 202 means pending/in_process; poll briefly or reopen later. HTTP 200 supplies a fresh signed url. HTTP 409 with settlement_receipt_failed means settlement succeeded but PDF failed; retry only POST /delivery/cod-settlements/:settlement_payment_id/receipt, never resubmit settlement to print. For history rows with settlement_payment_id=null, first print uses POST /delivery/cod-collections/:id/receipt to save a receipt for that collection only.
Query parameters
| Parameter | Type | Description |
|---|---|---|
tenant_idOptional | string | Tenant ID (optional if in auth token) |
language | string | Preferred language for response messages Example: |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
Content-Type | string | Example: |
Body parameters
| Parameter | Type | Description |
|---|---|---|
scope | string |
|
notes | string | — |
reference | string | — |
driver_id | string | required only for dispatcher/admin |
cod_collection_ids | string | required when |
settlement_amountOptional | string | optional guard amount; when supplied it must match the selected/all cash total. |
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/cod-collections/settle-by-wallet?tenant_id=&language=en" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3 --header "Content-Type: application/json" \4 --data-raw '{5 "scope": "all",6 "notes": "Settled all collected CODs using wallet balance",7 "reference": "INV-2024-001",8 "driver_id": "driver-uuid"9}'Response examples
1{2 "success": true,3 "message": "COD collections settled successfully",4 "settlement": {5 "scope": "all",6 "settled_collections": [7 {8 "cod_collection_id": "uuid1",9 "amount": 60,10 "allocations": {11 "cod": 50,12 "delivery_fee": 1013 },14 "delivery_code": 1001,15 "delivery_number": "DEL-001"16 }17 ],18 "total_settled": 60,19 "total_cash_settled": 60,20 "total_cod_settled": 50,21 "total_delivery_fee_settled": 10,22 "settled_at": "2023-01-01T12:00:00Z",23 "settlement_payment_id": "91000000-0000-4000-8000-000000000001",24 "receipt_status": "pending",25 "currency": "USD"26 }27}Remit Single COD Collection
/delivery/cod-collections/:codId/remitRemit a single COD collection. Requires admin permissions.
Requires manage:operations:tenant permission or system admin
Path parameters
| Parameter | Type | Description |
|---|---|---|
codIdRequired | string | ID of the COD collection to remit Example: |
Query parameters
| Parameter | Type | Description |
|---|---|---|
tenant_idOptional | string | Tenant ID (optional if in auth token) Tenant ID (defaults to authenticated user's tenant) |
languageOptional | string | Preferred language for response messages Example: |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
Content-Type | string | Example: |
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/cod-collections/${CODID}/remit?tenant_id=&language=en" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3 --header "Content-Type: application/json" \4 --data-raw '{}'Response examples
1{2 "success": true,3 "message": "COD collection remitted successfully",4 "data": {5 "id": "uuid",6 "delivery_id": "uuid",7 "driver_id": "uuid",8 "amount": "50.00",9 "delivery_code": 1001,10 "collected_at": "2023-01-01T10:00:00Z",11 "remitted_at": "2023-01-01T12:00:00Z",12 "status": "remitted"13 }14}Get COD Settlement Receipt
/delivery/cod-settlements/:payment_id/receiptAccess the saved settlement batch receipt. Requires authentication. Dispatcher/tenant_admin roles or manage:operations:tenant may access their tenant's settlements; drivers may access only their own. Other roles receive 403; unknown/out-of-tenant/out-of-driver scope returns 404. payment_id is the settlement_payment_id returned by either settlement action or a COD collection row.
GET reads status and signs an existing PDF. POST returns the saved PDF when available, reuses an active job, or retries missing/failed PDF generation; it never changes cash, wallet balances, collection status, batch identity, or immutable receipt amounts. No request body is required.
Responses
200:
{success:true,data:{settlement_payment_id,receipt_status:"success",url,expires_in:3600}}202:
{success:true,data:{settlement_payment_id,receipt_status:"pending"|"in_process"}}409:
{success:false,code:"settlement_receipt_failed",message,data:{settlement_payment_id,receipt_status:"failure"}}. Use POST to retry.400: invalid payment UUID.
403: insufficient permissions.
404: payment missing or outside caller scope.
500: unexpected server or storage error.
Display localized message; use code for branching. Signed URLs expire, so reopen this endpoint for a new URL. Bound polling and offer reopen/retry when generation takes longer. Both endpoints send Cache-Control:no-store.
Printed receipt contract
one database-assigned human-readable number (for example CS-1000) is stored in metadata.receipt_snapshot.receipt_number and remains unchanged on retries/reprints. The payment UUID remains the API/storage identifier. The shared cash/wallet template includes the issuer, driver and phone, UTC settlement date, cash receiver or wallet processor, currency, exact COD/fee/order totals, distinct order and collection counts, and optional entered reference, notes and external order references. Internal payment/wallet UUIDs remain in the stored payload rather than the printed layout. No wallet or settlement API response changes are required.
Frontend Print action
call POST once on click to open a saved PDF or retry a failed receipt automatically. Use GET for subsequent status polling; never POST from a polling loop. Initial successful-settlement confirmation may use GET because the backend already queues generation. Repeated/concurrent requests reuse the same payment and active PDF job.
Path parameters
| Parameter | Type | Description |
|---|---|---|
payment_idRequired | string | Example: |
Query parameters
| Parameter | Type | Description |
|---|---|---|
tenant_id | string | — |
language | string | Example: |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/delivery/cod-settlements/${PAYMENT_ID}/receipt?tenant_id=&language=en" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"Response examples
1{2 "success": true,3 "data": {4 "settlement_payment_id": "91000000-0000-4000-8000-000000000001",5 "receipt_status": "pending"6 }7}Retry COD Settlement Receipt
/delivery/cod-settlements/:payment_id/receiptAccess the saved settlement batch receipt. Requires authentication. Dispatcher/tenant_admin roles or manage:operations:tenant may access their tenant's settlements; drivers may access only their own. Other roles receive 403; unknown/out-of-tenant/out-of-driver scope returns 404. payment_id is the settlement_payment_id returned by either settlement action or a COD collection row.
GET reads status and signs an existing PDF. POST returns the saved PDF when available, reuses an active job, or retries missing/failed PDF generation; it never changes cash, wallet balances, collection status, batch identity, or immutable receipt amounts. No request body is required.
Responses
200:
{success:true,data:{settlement_payment_id,receipt_status:"success",url,expires_in:3600}}202:
{success:true,data:{settlement_payment_id,receipt_status:"pending"|"in_process"}}409:
{success:false,code:"settlement_receipt_failed",message,data:{settlement_payment_id,receipt_status:"failure"}}. Use POST to retry.400: invalid payment UUID.
403: insufficient permissions.
404: payment missing or outside caller scope.
500: unexpected server or storage error.
Display localized message; use code for branching. Signed URLs expire, so reopen this endpoint for a new URL. Bound polling and offer reopen/retry when generation takes longer. Both endpoints send Cache-Control:no-store.
Frontend Print action
call POST once on click to open a saved PDF or retry a failed receipt automatically. Use GET for subsequent status polling; never POST from a polling loop. Initial successful-settlement confirmation may use GET because the backend already queues generation. Repeated/concurrent requests reuse the same payment and active PDF job.
Path parameters
| Parameter | Type | Description |
|---|---|---|
payment_idRequired | string | Example: |
Query parameters
| Parameter | Type | Description |
|---|---|---|
tenant_id | string | — |
language | string | Example: |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/cod-settlements/${PAYMENT_ID}/receipt?tenant_id=&language=en" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"Response examples
1{2 "success": true,3 "data": {4 "settlement_payment_id": "91000000-0000-4000-8000-000000000001",5 "receipt_status": "pending"6 }7}Print COD Collection Settlement Receipt
/delivery/cod-collections/:collection_id/receiptPrint a settled collection receipt on demand. POST requires authentication and no body. Dispatcher/tenant_admin or manage:operations:tenant users may access their tenant records; drivers only their own. Other roles receive 403.
If settlement_payment_id already exists, reuse that saved settlement (including its full original batch). Otherwise, lock the historical collection and create one COD_SETTLEMENT receipt payment for this collection only, link it, and use the existing receipt number, HTML template, PDF queue and storage. Concurrent/repeated clicks cannot create duplicate receipts. No bulk historical backfill occurs.
Historical receipts preserve recorded collection amount, COD/fee allocations, method and original remitted_at. The requester is recorded separately from the original receiver/processor. Current profile names are used because historical names were not captured. The original settlement batch is not reconstructed. Receipt creation never changes settlement status/date, order payments, wallet balances or wallet transactions.
Response matches the settlement receipt endpoint
200 supplies data.settlement_payment_id, receipt_status=success, url and expires_in=3600; 202 supplies payment ID and pending/in_process; 409 supplies settlement_receipt_failed and failure status. A saved PDF is reused, an active job is reused, and missing/failed generation is queued. Cache-Control:no-store.
Frontend contract
show the existing print icon and Print Receipt menu action for remitted or settled_by_wallet collections. When no payment ID exists, POST this endpoint once on click. Retain returned settlement_payment_id and poll GET /delivery/cod-settlements/:payment_id/receipt with bounded retries. Later prints may use the payment endpoint POST; even stale rows that still call this collection endpoint reuse the saved receipt. Never resubmit cash/wallet settlement to generate a receipt.
Currency uses the unique matching original driver wallet debit for wallet receipts, or one unambiguous currency from the original successful order payments for cash receipts. It never falls back to current tenant settings. Missing or ambiguous original currency returns 409 with settlement_receipt_history_incomplete and a localized explanation.
Errors
400 invalid UUID or invalid_cod_collections (unsettled or incomplete historical data); 403 insufficient_permissions; 404 cod_collection_not_found for missing/out-of-tenant/out-of-driver rows; 500 unexpected server/storage error. Show localized message, branch on code.
Path parameters
| Parameter | Type | Description |
|---|---|---|
collection_idRequired | string | Example: |
Query parameters
| Parameter | Type | Description |
|---|---|---|
tenant_id | string | — |
language | string | Example: |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/cod-collections/${COLLECTION_ID}/receipt?tenant_id=&language=en" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"Response examples
1{2 "success": true,3 "data": {4 "settlement_payment_id": "91000000-0000-4000-8000-000000000001",5 "receipt_status": "pending"6 }7}