DPO Pay — payment gateway integration profile
Also known as: 3G Direct Pay
DPO Group
DirectPay Online
API3G
Category: african-aggregator
Regions: East Africa
Southern Africa
West Africa
UAE
Countries: KE
UG
TZ
RW
GH
ZM
NA
BW
ZA
CI
ZW
AE
Currencies: USD
KES
TZS
UGX
ZAR
GHS
ZMW
and per-merchant provisioned
Payment methods: card
mobile_money
bank_transfer

Authentication:
model: company-token
key_types: CompanyToken
sandbox_instant: no
kyc_required_for_live: yes


Payment initiation:
flow: xml-redirect
endpoint: POST https://secure.3gdirectpay.com/API/v6/ (live) | https://secure1.sandbox.directpay.online/API/v6/ (sandbox)
http_method: POST
required_params: CompanyToken
Request=createToken
Transaction.PaymentAmount
Transaction.PaymentCurrency
Transaction.CompanyRef
Services.Service.ServiceType
Services.Service.ServiceDescription
Services.Service.ServiceDate
returns: Result
TransToken
TransRef
amount_unit: decimal-2dp


Status handling:
mechanisms: poll
redirect-return
webhook_verification: None — DPO has no signed webhook. RedirectURL/BackURL are browser redirects and are NOT proof of payment. Poll verifyToken server-side (must verify within 30 min of payment) as the source of truth.
raw_status_values: 000
001
002
004
006
007
801
802
900
901
902
903
904
950
status_map: 801: failed
802: failed
900: pending
901: failed
902: failed
903: failed
904: failed
950: failed
000: paid
001: pending
002: paid
007: pending
006: disputed

Capabilities:
redirect: yes
poll: yes
webhook: no
mobile_push: yes
refund: yes
recurring: yes

Credentials required:
key: companyToken
label: Company Token (GUID)
secret: yes
note: single token sent in every XML body; identifies the merchant account. No API key/secret pair — DPO has none.
key: serviceType
label: Service Type ID
secret: no
note: provisioned service id(s) for the account; sandbox demo uses 3854/5525.
key: environment
label: Environment (sandbox/live)
secret: no
note: selects sandbox vs live host + API base.

Sandbox:
test_credentials: companyToken: 9F416C11-127B-4DE2-AC7F-D5710E4C5E0A
serviceType: 3854 or 5525
note: public demo token/service pre-wired; build before your own KYC clears, swap the real token via config at cutover with zero code change.
notes: Test cards are NOT public — they are issued with a test account; request from DPO support, never hardcode. Mobile-money sandbox returns simulated instructions via GetMobilePaymentOptions + chargeTokenMobile.

Known pain points and mitigations:
problem: XML API in a JSON world
cause: DPO's only API (API3G) is XML request/response with no JSON option
mitigation: Hide XML entirely behind ManishaPay's JSON contract — hand-built buildXml()/parseXml() are the only tag-aware code; the merchant POSTs and receives JSON
severity: high
problem: Company Token onboarding delay, no self-serve portal
cause: KYC-gated; token issued by sales (sales1@dpogroup.com) over days-to-weeks
mitigation: Merchant builds against DPO's public sandbox demo token pre-wired in test mode; swap the real token via config at cutover — zero code change
severity: high
problem: No webhook + mandatory 30-minute verifyToken window
cause: DPO never pushes a signed callback; RedirectURL is a best-effort browser redirect and payment must be verified within 30 min or an alert email fires
mitigation: Run verifyToken server-side on a poll loop and emit a single normalized ManishaPay webhook; never gate fulfilment on the browser redirect
severity: critical
problem: Result-code confusion: 000 'created' vs 'paid', 001 auth-only, 900 pending
cause: createToken returns 000 for token CREATED (not paid) while verifyToken 000 means paid; 001 is authorised-not-captured
mitigation: Interpret 000 by call site; map verifyToken 001 -> pending (never paid) and 900 -> pending; collapse all codes to the 5 canonical statuses
severity: high
problem: Mobile MNO/country string nuance
cause: Mobile-money charge requires exact MNO+country strings (e.g. MTNZM/Zambia, Safaricom STK v2/Kenya)
mitigation: Call GetMobilePaymentOptions first and maintain an MNO->country lookup before chargeTokenMobile
severity: medium
problem: V6 / V7 API version drift
cause: Both /API/v6/ and /API/v7/ are live with subtly different behaviour
mitigation: Pin one tested version (V6) internally and upgrade deliberately after re-testing
severity: medium

SDKs:
node: @kazion/dpopay-sdk (community, unofficial)
php: zepson/dpo-php (community, unofficial)
rest_direct_ok: no

Notes: XML-only 'API3G'. Single CompanyToken (UUID) authenticates every call — no OAuth, no request signing, no signed webhook. Flow: createToken -> redirect payv3.php?ID=<TransToken> -> verifyToken (poll). No native 'refunded' status: refunds go via refundToken (partial OK) and must be tracked in your own ledger. Recurring/mobile charging (chargeTokenCreditCard/chargeTokenMobile/chargeTokenAuth) is per-merchant entitlement. Currencies are provisioned per merchant, not a public list.

Fields not yet cross-checked against official docs: exact per-merchant currency list
full Result-code enumeration
sandbox test cards