Skip to content
Payments

EftPeer Operations in pepperQik: Purchases, Reversals and End-of-Day Explained

The EftPeer operation lifecycle from a POS developer's seat: connect and login, purchase outcomes, reversal vs refund, recovery queries and the end-of-day settlement everyone forgets.

6 min read Updated Sep 2, 2026
EftPeer Operations in pepperQik: Purchases, Reversals and End-of-Day Explained

Every payment middleware has a core abstraction. In the pepper world it's the EftPeer — the object that represents "a payment terminal you can talk to". Once you hold an EftPeer, everything a checkout ever needs is an operation on it: open the connection, run a purchase, refund yesterday's mistake, reverse the transaction that just went sideways, and close the day with a settlement. This article walks through the operation lifecycle the way a POS developer actually experiences it, with the failure modes that only show up in production.

Flow: POS request goes through the EftPeer API, the terminal protocol executes it, and a response with receipt data comes back

Key takeaways

  • Think of an EftPeer as a stateful session with a terminal, not a stateless HTTP endpoint.
  • The big five operations: connect/login, purchase, refund, reversal, end-of-day. Master these and 95% of retail flows are covered.
  • A reversal is not a refund — mixing them up costs real money and real reconciliation pain.
  • Design your POS around operation results, including the third result nobody plans for: unknown.

The operation lifecycle

Terminal sessions follow a strict choreography. A typical day at one checkout lane looks like this:

┌ Morning ────────────────────────────────────────────┐
│ connect() → login()          terminal ready         │
├ Trading hours ──────────────────────────────────────┤
│ purchase(24.90) → approved   receipt printed        │
│ purchase(9.50)  → declined   cashier asks for cash  │
│ refund(24.90)   → approved   customer changed mind  │
│ purchase(120.0) → timeout!   → reversal() → cleared │
├ Evening ────────────────────────────────────────────┤
│ endOfDay()      totals match acquirer settlement    │
│ logout() → disconnect()                             │
└─────────────────────────────────────────────────────┘

Three properties of this choreography bite integrators who ignore them:

  1. One operation at a time. The terminal is a physical device with one screen and one cardholder in front of it. Serialize your requests; a queue in the POS beats a busy-error storm.
  2. Operations are long-running. A purchase includes a human fishing a card out of a wallet. Timeouts must be generous (60–120s is normal) and cancellation must be explicit, not a dropped socket.
  3. Login state matters. Most terminals require a login/activation before financial operations and will reject a purchase after a connection loss until you re-login.

Purchase: the happy path (and its shadow)

An illustrative purchase against a pepperQik-style API:

var op = await peer.ExecuteAsync(new PurchaseRequest
{
    Amount = Money.Chf(24.90m),
    Reference = "TICKET-4711"
});

switch (op.Outcome)
{
    case Outcome.Approved:
        await pos.CompleteSale(op.AuthCode, op.ReceiptText);
        break;

    case Outcome.Declined:
        pos.ShowDecline(op.DisplayText);   // never invent your own decline text
        break;

    case Outcome.Aborted:                  // cardholder pressed the red button
        pos.ReturnToBasket();
        break;

    case Outcome.Unknown:                  // timeout, crash, cable pulled
        await RecoverLastTransaction(peer); // ask the terminal what happened
        break;
}

The Unknown branch is where careers are made. When the response never arrives, the money may or may not have moved. The only correct move is a last-transaction / recovery query: ask the terminal for the result of the most recent operation and reconcile your POS state against the truth on the device. Ship this on day one, not after the first support ticket.

Refund vs. reversal — know the difference

ReversalRefund
WhenSame session, typically the last transaction, before settlementAny time after, even days later
What it doesVoids the authorization as if it never happenedCreates a new, opposite transaction
Cardholder seesUsually nothing (hold disappears)A credit on the statement
FeesGenerally noneOften full interchange
Use it forTimeouts, cashier error caught immediatelyReturns, complaints, goodwill

The operational rule: if the drawer hasn't closed yet, reverse; otherwise refund. Wire your POS "cancel last payment" button to reversal and hide the refund flow behind a supervisor role — you'll thank yourself at reconciliation time.

End-of-day: the operation everyone forgets

The end-of-day (balance / settlement) operation closes the terminal's batch, transmits totals to the acquirer and resets counters. Skipping it doesn't break payments — it breaks accounting: settlements drift across days, and matching acquirer payouts to till reports turns into archaeology. Automate it:

// Run automatically when the shift closes — never rely on humans
var balance = await peer.ExecuteAsync(new EndOfDayRequest());

if (!balance.TotalsMatch(pos.CardTotalsForShift()))
    alerting.Raise("EOD mismatch", balance.Diff);  // investigate TONIGHT, not at month-end

Designing the POS side

  • Model operations as a state machine: Idle → InFlight → (Approved | Declined | Aborted | Unknown). Forbid a second InFlight.
  • Persist before you send. Write "payment attempt started" to disk before calling the terminal, so a POS crash can resume recovery.
  • Print what the terminal gives you. Receipt text from the middleware is scheme-compliant; your hand-rolled version probably isn't.
  • Log the operation trail with references, not PANs. Disputes are won with timestamps.

FAQ

Can I run a purchase and a refund concurrently on one terminal?

No — one cardholder, one screen, one operation. Concurrency belongs above the EftPeer, as a queue.

How long should my purchase timeout be?

Longer than the slowest human: 60–120 seconds is typical, plus an explicit cashier-facing cancel that triggers an abort operation rather than just abandoning the request.

What if the reversal itself times out?

Recovery query again. The last-transaction result is the single source of truth; keep querying (with back-off) until you get a definitive answer, and alert a human if the terminal stays unreachable.

Further reading

The authoritative operation reference is the EftPeer operations documentation. For the deployment side, see running pepperQik as a Windows service and in Docker. Building a POS and want these flows implemented properly the first time? Let's talk.

Keep reading

Related articles