Pesepay — payment gateway integration profile
Also known as: PesePay
Pesepay Payments Engine
Category: zim-aggregator
Regions: Zimbabwe
Countries: ZW
Currencies: USD
ZWL
Payment methods: ecocash
onemoney
innbucks
zimswitch
card

Authentication:
model: aes-encrypted
key_types: integrationKey
encryptionKey
sandbox_instant: no
kyc_required_for_live: yes


Payment initiation:
flow: redirect
endpoint: POST https://api.pesepay.com/api/payments-engine/v1/payments/initiate
http_method: POST
required_params: amountDetails.amount
amountDetails.currencyCode
reasonForPayment
resultUrl
returnUrl
returns: referenceNumber
pollUrl
redirectUrl
redirectRequired
amount_unit: decimal-2dp


Status handling:
mechanisms: webhook
poll
redirect-return
webhook_verification: resultUrl callback is only a 'go poll now' trigger — the body is NOT trusted. Re-verify server-side via GET /v1/payments/check-payment?referenceNumber= and decrypt the { payload } response.
raw_status_values: INITIATED
PENDING
PROCESSING
PARTIALLY_PAID
AUTHORIZATION_REQUIRED
SUCCESS
FAILED
DECLINED
CANCELLED
ERROR
INSUFFICIENT_FUNDS
TERMINATED
TIME_OUT
SERVICE_UNAVAILABLE
CLOSED
CLOSED_PERIOD_ELAPSED
REVERSED
status_map: SUCCESS: paid
INITIATED: pending
PENDING: pending
PROCESSING: pending
PARTIALLY_PAID: pending
AUTHORIZATION_REQUIRED: pending
FAILED: failed
DECLINED: failed
CANCELLED: failed
ERROR: failed
INSUFFICIENT_FUNDS: failed
TERMINATED: failed
TIME_OUT: failed
SERVICE_UNAVAILABLE: failed
CLOSED: failed
CLOSED_PERIOD_ELAPSED: failed
REVERSED: refunded

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

Credentials required:
key: integrationKey
label: Integration Key
secret: yes
note: Sent as an HTTP header on every call. NOTE: SDKs disagree on the header name ('authorization' vs 'key'); the value is the raw key, NOT a Bearer token.
key: encryptionKey
label: Encryption Key
secret: yes
note: Never sent over the wire — it is the AES key. MUST be 16/24/32 chars, and its first 16 chars ARE the IV.

Sandbox:
test_credentials: note: Sandbox is documented but no official test MSISDNs were found — obtain them from Pesepay support. Sandbox host is UNVERIFIED; ManishaPay falls back to the live base until confirmed.
notes: Use the ManishaPay simulator for local end-to-end testing until Pesepay provides sandbox MSISDNs/cards.

Known pain points and mitigations:
problem: All requests/responses fail to decrypt or are rejected outright
cause: Wrong AES parameters — devs use a random IV or prepend the IV, instead of using the first 16 chars of the Encryption Key as a fixed IV
mitigation: IV = key.subarray(0,16); AES-CBC + PKCS7 + base64; wrap/unwrap the { payload } envelope on BOTH directions. Covered by an encrypt→decrypt round-trip unit test with a fixed vector.
severity: critical
problem: createCipheriv throws 'Invalid key length'
cause: Encryption Key copied with trailing spaces or truncated, so it is not 16/24/32 bytes
mitigation: Validate the key length up-front (assertKeyLength) and choose the AES variant (128/192/256) from the byte length.
severity: high
problem: 401 Unauthorized even with a correct Integration Key
cause: The header NAME differs across community SDKs — some expect 'key:', others 'authorization:'
mitigation: Pin the header name in one const (AUTH_HEADER_NAME) so it can be flipped in a single place; send the raw key (not 'Bearer ').
severity: high
problem: Payment method code errors / breakage after Pesepay changes
cause: Method codes (PZW201 EcoCash, PZW204 Visa, PZW211 mobile, PZW212 InnBucks) are currency-scoped and change
mitigation: Never hardcode codes — resolve them live via GET /v1/payment-methods/for-currency?currencyCode= and match by rail name; cache per currency.
severity: high
problem: Order marked paid when it is not
cause: Trusting the resultUrl callback body, or treating PARTIALLY_PAID/liquidationStatus as completion
mitigation: Only transactionStatus === 'SUCCESS' is paid; PARTIALLY_PAID stays pending; ignore liquidationStatus (settlement). Re-poll after the callback with a timeout/backoff.
severity: critical
problem: Card payments hang (Visa) / Mastercard unsupported
cause: Card rail on Pesepay is unreliable in Zimbabwe
mitigation: Route card elsewhere; use Pesepay primarily for EcoCash/mobile money.
severity: medium
problem: Wrong endpoint version for the flow
cause: Redirect uses /v1/payments/initiate but seamless mobile-push uses /v2/payments/make-payment
mitigation: Encode the version per flow — redirect => v1, seamless => v2.
severity: medium

SDKs:
node: pesepay-js (community, unofficial)
php: codevirtus/pesepay (community)
rest_direct_ok: yes

Notes: Zimbabwean aggregator with faster onboarding than PayNow (registration ~5min, verification ~30-60min). Encryption is the defining integration hazard: AES-256-CBC with a NON-random IV = first 16 chars of the Encryption Key, PKCS7, base64, wrapped as { payload } on request AND response. No public refund or recurring API (portal/manual only). EcoCash is the reliable rail.

Fields not yet cross-checked against official docs: sandbox host
auth header name (authorization vs key)
exact payment method code list
sandbox test MSISDNs