PPayNow Docs
Menu — PaymentEngine

API Reference

PaymentEngine

The orchestrator. Wraps a PaymentApi with retry, state-machine validation, and event emission.

PaymentEngine (in packages/paynow_core/lib/src/payment_engine.dart) is the central coordinator. It's what PayNowWebSdk delegates to under the hood.

final engine = PaymentEngine(
              api: createPaymentApi(),
              retryPolicy: const RetryPolicy(),
              // optional offlineActionStore for queueing during outages
            );
            

What it does on every call

  1. Wraps the call in _withRetry according to RetryPolicy.
  2. Catches PaymentApiException and decides retry vs rethrow.
  3. On success, runs the returned session through PaymentStateMachine.transition (which validates the move and bumps the version).
  4. Emits a PaymentEvent on the internal EventBus.
  5. Returns the merged session to the caller.

Public surface

Methods (mirror PaymentApi)

  • createInvoice, generateQr, validateAccount, submitAccount, submitOtp, refreshStatus

Each takes the same request DTOs as PaymentApi.

void hydrate(PaymentSession session)

Inject a session without making an API call. Used when a session is initiated server-side (via merchantInitiateMiddleware) and the browser receives the paymentId.

Stream<PaymentEvent> get events

Broadcast stream of every state change. Subscribe in your UI:

engine.events.listen((event) {
              setState(() => _session = event.currentSession);
            });
            

PaymentSession? get currentSession

The most recent session (null before any call).

Future<void> replayQueuedActions()

When configured with an OfflineActionStore, drains queued actions and replays them through the engine. Use after the device comes back online.

PaymentEvent

class PaymentEvent {
              final String paymentId;
              final PaymentSession? previousSession;
              final PaymentSession currentSession;
              final DateTime occurredAt;
            }
            

previousSession is null on the first emit and on hydrate.

Why an engine instead of calling PaymentApi directly

The state machine catches programmer errors (illegal state transitions throw, surfacing flows that wouldn't make sense to the customer). The retry policy keeps transient backend blips from breaking the UX. The event bus decouples the page state from the call site — useful for the status poller, for tests, and for the future Flutter SDK where multiple widgets observe the same session.

Engine in tests

Pass a fake PaymentApi:

class _FakeApi implements PaymentApi {
              PaymentSession nextResponse = ...;
              @override
              Future<PaymentSession> fetchStatus(FetchStatusRequest r) async => nextResponse;
              // ... other methods
            }
            
            final engine = PaymentEngine(api: _FakeApi(), retryPolicy: const RetryPolicy(maxAttempts: 1));
            engine.hydrate(initialSession);
            final newSession = await engine.refreshStatus(FetchStatusRequest(paymentId: '...'));