Overview

Introduction

This documents describes the conceptual thoughts behind payment processing of the SalesEngine.

When money goes over the counter, the cashier needs to be able to:

  • select payment method
  • enter amount of payment
  • if amount is enough to pay
    • if payed cash: return change if needed
    • get a printed receipt
  • if amount is not enough to pay (i.e. combined payment with giftcard and cash)
    • see the amount left to pay
    • enter new payment information

Preconditions

From previous requests the client already has:

  • The complete cart including sum for display purposes
  • The id of the cart in question

Payment processing

Payment processing

Figure {@fig:payment-process} shows the basic procedure the system needs to execute when a client (cashregister) makes a payment:

  1. Validate payment method and currency (e.g. store specific configuration) - Validation is not implemented as of now.
  2. Validation against the carts sum
  3. Usage of external service if necessary (e.g. loyalty service)
  4. Creation of a receipt
  5. Fiscalization if required (external service needs to be called before the receipt is handed out. Details follow.)
  6. Tracking money movement in cashbook
  7. Deliver receipt to backbackend system (SAP)

Enter payment service

The idea is to create a payment microservice which provides a /payment endpoint for the client (cashregister) to use.

Scenario cash payment (happy path)

Figure {@fig:seq} shows how a cash payment at a store is processed by the system. It consists of the following steps (note that the process is shown synchronous for simplicitys sake).

  1. cashregister initiates payment by sending the cartId, amount, currency, paymentMethod, etc. to the payment endpoint.
  2. payment looks up the cart to be payed to get the sum
  3. payment possibly carries out some other validation.
  4. receipt
    • creates the receipt for the transaction
    • calls another service to perform country specific fiscalization of the receipt
    • hands out the receipt to the backbackend system (SAP)
  5. cashbook makes the corresponding entries in the cashbook for the store and settlement.
  6. payment returns the remaining sum to be payed (if any)
  7. cashregister opens the cashdrawer to allow the cashier to exchange the money.
  8. cashregister requests the receipt for the transaction in printable xml format.
Classical cash payment in store

Failure scenarios

Possible failures

Figure {@fig:seq-fail-one} shows several points where payment processing might fail (the fallacies of distributed computing apply). Items 1 and 2 result in an error for the client. Payment simply cannot be processed.

If payment isn't able to call an external service to process a payment (item 3), this particular payment also results in an error for the client. If the call to the external service results in a legitimate error in business logic (e.g. unknown card), this information can be passed to the client.

Items 4 and 5 are getting interesing, because failures here might make it necessary to trigger rollback of external transaction (e.g. roll back a giftcard transaction).

Item 6 is critical. From cashregister's point of view the call just times out or is aborted. But at this point, the payment service is done processing the payment and might have used some external services (charging giftcards e.g.) Furthermore, entries to the cashbook were made documenting the money movements of the transaction. Even a receipt is created and already on it's way to the ERP system.

The criticality of item 7 is unclear for now. In this case the payment was processed by the system, but cashregister is unable to print the receipt for the customer (which is required by law in several countries).

transaction Id

Item 6 can be handled by introducing a transaction Id (txId), which must be requested before every call. The txId is then submitted alongside the payment information (see figure {@fig:seq-txid})

Usage of transaction Id

Thanks to txID, the client can repeat the call with the same txID and the payment service guarantees that the payment is only recorded (and processed) once. This effectively renders this call idempotent.

Lazy cashbook

5 can be factored out of the payment processing chain by creating cashbook entries lazily. Whenever a preview for settlement is attempted and no cashbook entries exist for the settlement in question, the service can pull the needed payments from payment to create them.

Managing settlements could still fail in case the service is not available, payment however becomes agnostic of cashbook failures.

Lazy cashbook creation

pros

  • No problem with Events not yet delivered (in case of async processing)
  • Failure of creating the cashbook entries doesn't break payment

cons

  • Failure of retrieving the payments means that closing the settlement won't work.

  • No cashbook updates during normal business processing. It is created on the fly when the settlement is attempted to be closed. It might be desirable though to keep a continuously updated cashbook during normal operations. We should probably discuss that with the business.

Lazy receipt creation

Lazy receipt creation

Involved microservices

  • payment
  • receipt
  • cart
  • cashbook
  • fiscalization

Payment

Tasks

  • Provide API to submit payments
  • Must be open to extension for online payments
  • Check if caller is allowed to use payment (method, currency, ...)
  • Manage 3rd party services to validate or process payments
  • Calculate remaining sums to pay in case of partial payments

WARNING:

That's a critical endpoint! Adding payment information might trigger business logic further down the line (like shipping stuff).

receipt

  • Adding country specific fiscalization features to receipt will blow up the service considerably. Its probably a good idea to create a dedicated microservice for fiscalization.

Required data

  • cart(id)
  • sum
  • payments
  • store
  • cashierid /name
  • devicehub
  • stationNo

Cashbook

  • Fetch payments belonging to a settlement
  • Create entry in the cashbook for the respective settlement.

Required data

  • the cart(id)
  • the settlement
  • the store
  • stationNo
  • cashierid / name

Cart

Provide API to retrieve cart information

Fiscalization

TODO

Thoughts on asynchronous processing

We could process several parts of the payment sequence asynchronously, to decouple the call to the payment service from things like adding entries to the cashbook and creating receipts. This would be accomplished by letting the payment service fire a PaymentCompleted event whenever a cart was payed for.

Cashbook entries

Cashbook could listen for PaymentCompleted events, extract the necessary transaction details and make the corresponding cashbook entry:

  1. payment sends a PaymentCompleted event right before the response of the call is sent to the client
  2. cashbook subscribes to this event (durable subscription)
  3. When an event is received, cashbook can make the necessary entries for the money movement.

But then we'll run into the following problems:

  • When counting (or closing) a settlement, there's no way of knowing if there are PaymentEvents not yet processed for this settlement.

  • Counting and closing a settlement relies on all the cashbook entries being there when the settlement is closed. The whole point of the settlement is to track financial transactions for a specific timeframe and account for all "spillage" (missing cash money) with a counter booking. That a closed settlement is changed the next day because a message was stuck in the broker is not going to happen.

  • Changing settlements after they're closed is not acceptable from the business POV.

Receipt creation

Even receipt creation could be processed asynchronously. Posmanager could listen to CartPayedEvents and create the desired receipt accordingly.

Advantages:

  • The payment call can be simplified to only perform the absolute necessary steps for the transaction to work (external validation etc.).

Problems:

  • The receipt is usually needed very quickly after the payment has been processed.

  • From the POV of the cashregister's user, eventual consistency is ok when we're talking seconds.

  • For our ERP system it's paramount that the receipt arrives. If it does so 1 second or 30 minutes after the transaction took place is completely irrelevant. People might start frowning though if it takes longer than an hour.

  • The event that receipt isn't ready for printing when the client requests it is not likely, but the system still needs to be robust in this scenario.

  • It is required by law in several countries that the receipt is cryptographically signed. If that is the case, an external service has to be called to retrieve that signature which will then be added to the receipt. Only now the receipt can be printed and handed out to the customer. In case of failure there are known procedures to follow.