Skip to content

Flutter — error handling

The drop-in widget surfaces errors through a single onError callback. Direct service calls return a PdirectHttpResponse<T> wrapper you check explicitly. Both paths funnel into the same PdirectPayApiResponseCode enum.

PdirectPayCheckout(
configs: configs,
paymentBody: paymentBody,
onResponse: (r) {
// success path: r.paymentStatus == PdirectPaymentStatus.approved
},
onError: (PdirectPayOnError e) {
debugPrint('${e.errorCode?.name} ${e.message}');
switch (e.errorCode) {
case PdirectPayApiResponseCode.lok003: // auth failed
_logoutAndRedirect();
break;
case PdirectPayApiResponseCode.lok005: // insufficient funds
_showSimpleError(e.message);
break;
case PdirectPayApiResponseCode.lok008: // timeout
_showRetryPrompt(e.message);
break;
default:
_showSimpleError(e.message);
}
},
)

PdirectPayOnError fields:

FieldNotes
messageLocalised, human-readable. Don’t surface to end users; show your own copy.
titleLocalised display title.
errorCodePdirectPayApiResponseCode?LOK000LOK009.
identifierOptional gateway-side identifier.
customerReferenceEchoed from the request.
systemReferenceGateway transaction id, if any.
amount, currencyEchoed from the request.
successRedirectUrl, failRedirectUrlEchoed.
timestampDefaults to DateTime.now() when unset.

PdirectHttpClient returns PdirectHttpResponse<T> — never throws on HTTP errors. Check isSuccess and inspect error for the underlying DioException.

final result = await PdirectHttpClient.instance.post<Map<String, dynamic>>(
'/payments/collect',
data: paymentBody.toJson(),
);
if (!result.isSuccess) {
final code = result.statusCode;
final body = result.data; // gateway error envelope
final dioError = result.error; // raw exception, if any
// body['error_code'] is the underlying four-digit code.
}

When you call PdirectPaymentService methods, you get the same wrapper.

The widget’s PdirectPayApiResponseCode (LOK000LOK009) collapses the gateway’s four-digit ranges into nine display buckets. If you need the exact gateway code, drop down to PdirectHttpClient and parse the response body’s error_code field.

PdirectPayApiResponseCodeTypical gateway codes
lok000 Success(no error)
lok001 General error1401, 1403
lok002 Invalid params11011110
lok003 Auth failed10011005
lok004 Method unavailable1205, 1206
lok005 Insufficient funds1201, 1202
lok006 Limit exceeded1207, 1301 (rate limit)
lok007 Network error(client-side classification)
lok008 Timeout1503
lok009 Service unavailable1402, 1404

See the full Error catalogue for every gateway code.

Session-binding mismatch (lok002, gateway 1101 / 1102 / 1103)

Section titled “Session-binding mismatch (lok002, gateway 1101 / 1102 / 1103)”

The body’s amount, currency, or customer_reference doesn’t match the values your backend declared at session-mint. Auth-v3 makes those three fields optional in PdirectPaymentBody — if you leave them unset, the SDK omits them on the wire and the gateway reads the bound values straight from the session, sidestepping this class of failure entirely.

Fix: drop the three fields from PdirectPaymentBody (or mint a fresh session whose bound values match what you intend to send). Don’t try to “correct” the body on retry — the gateway will reject.

Session expired or consumed (lok003, gateway 1001)

Section titled “Session expired or consumed (lok003, gateway 1001)”

The session’s TTL ran out, or /payments/submit already succeeded once on this session.

Fix: mint a new session. Sessions are single-use.

Device fingerprint mismatch (lok003, gateway 1001)

Section titled “Device fingerprint mismatch (lok003, gateway 1001)”

The session was first used from a different device than this one.

Fix: mint a new session from this device. This usually only shows up in development if you re-use a token across emulators.

Five OTP attempts in five minutes. The customer has miskeyed too many times or your client is auto-retrying.

Fix: show a “wait and try again” UX, honour retry_after.

The customer doesn’t have enough money in their wallet / mobile money account / card limit.

Fix: non-recoverable from the gateway side. Surface to the customer.

The SDK never logs request bodies. Card data, PINs, OTP codes, and session tokens are filtered out of every log path. Your logger callback (set on PdirectPayConfig.logger) only ever sees safe strings.

PdirectPay.init(PdirectPayConfig(
environment: PdirectPayEnvironment.production,
logger: (msg) {
// safe to forward to Sentry, Datadog, etc.
Sentry.addBreadcrumb(Breadcrumb(message: msg, category: 'pdirect'));
},
));