Skip to main content

Accept payments

The Velocity payments API takes an opinionated approach that forces the integrating client to package payment activity around orders. This may seem unnecesarily tedious for simple transactions, however, you will find benefits to the approach once you start making complex payments such as multi-currency and split payments. In addition, further benefits are evident when tracking activity on the admin dashbaord as well as when incorporating other payment modes such as POS devices and MoMos.

Payment flow

Payment flow

Create Sales Order

A sales order represents the intention to make a payment for a list of items. It is possible to create a detailed list of products that you sell in the admin dashboard and then use the respective item codes in this payload, see API Reference for more on fetching items.

For ad-hoc payments not mapped to specific products, create a generic item in the admin portal and use the respective item code in the payload.

Request

curl --request POST --location 'https://api.velocityafrica.net/sales-orders' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: ...' \
--data '{
"currencyCodeString": "USD",
"orderDate": "2026-05-06",
"dueDate": "2026-05-13",
"notes": "Thank you for your business",
"authorized": true,
"items": [
{
"itemCode": "{{itemCode}}",
"qty": {{qty}},
"unitPrice": {{unitPrice}},
"amount": {{amount}}
}
],
"charges": [
{
"amount": {{charge}}
}
]
}'
note

You can use the charges collection to add line items that will be added to the order total.

Response

{
"state": "salesOrder",
"status": "manual",
"body": {
"paidAmount": 0.0,
"changeAmount": 0.0,
"outstandingAmount": 5.0,
"trace": "187fcd44-1007-433f-9097-6f3786e8e63a",
"authorized": true,
"name": "SORD-00038",
"grandTotal": 5.0,
"status": "UNPAID"
},
"workflowId": "616",
"externalId": "187fcd44-1007-433f-9097-6f3786e8e63a"
}

Fields

FieldTypeDescription
itemCodeStringItem code (can be fetched once and cached)
qtyIntegerNumber of items (of of type itemCode)
unitPriceFloatPrice of a single item
amountFloatqty × unitPrice total
chargeFloatAdditional amounts (tax, surcharges, etc.)

Initiate payment

Payment is always made against a sales order. Payments can be authorized via WEB (redirect to website) or REMOTE (USSD push). Part payments are allowed.

Request

curl --request POST --location 'https://api.velocityafrica.net/transactions' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: ...' \
--data '{
"amount": {{amount}},
"paymentProcessorLabel": "{{paymentProcessorLabel}}",
"debitPhone": "{{debitPhone}}",
"debitRegion": "ZW",
"debitCurrency": "{{debitCurrency}}",
"debitRef": "{{debitRef}}",
"type": "REQUEST",
"authType": "{{authType}}",
"salesOrderId": "{{salesOrderId}}"
}'

Response

{
"state": "gatewayPayment",
"status": "manual",
"body": {
"id": "7fa6c3d1-cf54-491c-ac1f-84be77a97e5a",
"trace": "e49f39bb-14da-48e0-bd19-1665761b43ba",
"amount": 5,
"paymentStatus": "SUCCESS",
"pollStatus": "PENDING"
},
"workflowId": "617"
}
tip

There are quite a few status fields that track different event types. The relevant one to know if a payment initiation was successful is the body.paymentStatus field.

info

The only exception to this is with VMC (Visa/Mastercard) payments. The paymentStatus field returns as PENDING until a customer successfully completes a checkout on the redirected page. This effectively means that a PENDING response is fine for VMC. You can proceed to polling to verify the transaction outcome.

Fields

FieldTypeDescription
amountStringOutstanding amount from the order (can be partial)
paymentProcessorLabelEnumECOCASH, VMC, CASH, WALLET
debitCurrencyEnumUSD, ZWG
debitPhoneStringPhone number of customer to be charged (for ECOCASH & WALLET this is the account to be charged)
debitRefStringUnique reference from client system
authTypeEnumWEB (VMC only), REMOTE
salesOrderIdStringSales order ID from previous step
info

On paymentProcessorLabels, VMC is for Visa/Mastercard transactions. WALLET is for closed loop wallet programmes

Poll Payment

Use the transaction trace UUID from the previous response to poll for the final payment result. You can repeat the payment → poll cycle as many times as necessary to bring the outstanding amount to zero.

Request

curl --request PUT --location 'https://api.velocityafrica.net/transactions/poll/{{transactionTrace}}'

Response

{
"state": "done",
"status": "finished",
"body": {
"id": "7fa6c3d1-cf54-491c-ac1f-84be77a97e5a",
"trace": "e49f39bb-14da-48e0-bd19-1665761b43ba",
"amount": 5.0,
"paymentStatus": "SUCCESS",
"pollStatus": "SUCCESS"
},
"workflowId": "617"
}
tip

Use the body.pollStatus field to determine whether the transaction succeeded.

Update Sales Order

Once the outstanding amount reaches zero, update the sales order to ensure all necessary accounting entries are completed. Upon success, you will have completed a single sale lifecycle.

Request

curl --location --request PUT 'https://api.velocityafrica.net/sales-orders/update-workflow/{{salesOrderTrace}}'

Response

{
"state": "done",
"status": "finished",
"body": {
"salesOrder": {
"id": "625b63f6-0acb-40d7-b971-5a970a6346df",
"paidAmount": 5.0,
"outstandingAmount": 0.0,
"status": "PAID",
"name": "SORD-00038"
},
"invoice": {
"id": "0bd9c7c1-e5f5-478d-8048-7d4a427aed74",
"name": "SINV-00017",
"status": "COMPLETE"
}
}
}
note

Use the body.salesOrder.status field to confirm the order has been fully paid. A PAID status indicates successful completion.