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
Figure {@fig:payment-process} shows the basic procedure the system needs to execute when a client (cashregister) makes a payment:
- Validate payment method and currency (e.g. store specific configuration) - Validation is not implemented as of now.
- Validation against the carts sum
- Usage of external service if necessary (e.g. loyalty service)
- Creation of a receipt
- Fiscalization if required (external service needs to be called before the receipt is handed out. Details follow.)
- Tracking money movement in cashbook
- 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).
- cashregister initiates payment by sending the cartId, amount, currency, paymentMethod, etc. to the payment endpoint.
- payment looks up the cart to be payed to get the sum
- payment possibly carries out some other validation.
- 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)
- cashbook makes the corresponding entries in the cashbook for the store and settlement.
- payment returns the remaining sum to be payed (if any)
- cashregister opens the cashdrawer to allow the cashier to exchange the money.
- cashregister requests the receipt for the transaction in printable xml format.
Failure scenarios
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})
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.
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
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:
- payment sends a
PaymentCompleted
event right before the response of the call is sent to the client - cashbook subscribes to this event (durable subscription)
- 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.