SaFi Bank Space : Postings library - Define / Produce / Listen transaction

Definition of the Transaction

List of all transactions are defined in common/tm-client/transactionsDefinitions.yml.

Each transaction definition consist of multiple types of parameters which are categorised to:

Field

Type

Required

Description

name

String

true

Name of your transaction (the generated Kotlin class for this transaction will have this class name, e.g., TopUpPaynamics)

transactionType

String

true

Is one of the core transaction types

staticTypeVariables

Map<variableName, stringValue>

true

Static variables values which are pre-defined for each core transaction type

staticTransactionVariables

Map<variableName, stringValue>

false

Static variables values specific for the given transaction. These variables are sent to TM in instruction details map.

runtimeTransactionVariables

Map<variableName, variableType>

(supported variable types: String, UUID, BigDecimal)

false

Runtime variables specific for the given transaction. You have to pass these as parameters when creating instance of the given transaction. These variables are also sent to TM in instruction details map.

Example:

transacations:
  - name: TopUpPaynamics
    transactionType: InboundHardSettlement
    staticTypeVariables:
      internalAccountId: PaynamicsSuspense
      accountType: MainAccount
      bankTransactionCode: TRX-TOP-123
    staticTransactionVariables:
      transactionType: BILL_PAYMENT
    runtimeTransactionVariables:
      someVariable1: String
      someVariable2: BigDecimal

Based on this definition a Kotlin class is generated in package:
package ph.safibank.tm.model.transaction

(you can see example of creating its instance few lines below on this page)

Basic setup for produce/listen

Setup application properties in micro service for listening to executed transactions

tm-client:
  posting:
    client: <PostingsAPIClientId>
    listener-level: <client|global>
  consumer:
    group-id: ${TM_KAFKA_CONSUMER_GROUP}
  bootstrap:
    servers: ${TM_KAFKA_URL}

In above configuration it is specified which client is used for generating postings and which listener level should be used:

  • client - listens to transactions published by <PostingsAPIClientId>

  • global - listens to all transactions which were created in TM (also Smart Contract transactions - interest, taxes, etc.)

Produce new transaction

There are producers generated per each client defined in kafkaClientDefinitions.yml

Package: package ph.safibank.tm.producer

In package above there are generated clients classes which are Kafka producers used for send messages to TM. There is no need to use them directly and they are enabled by application properties. For usage you just need to inject PostingCoreProducer bean in your service. Producer is enabled by setting application properties:

1. Setup application properties

2. Produce transaction and send to TM

@Inject
lateinit var coreProducer: PostingCoreProducer

val transaction = TopUpPaynamics(
    someVariable1 = "SomeVariable1",
    someVariable2 = BigDecimal("123.45"),
    transactionParams = InboundHardSettlementRuntimeParams(
        accountId = UUID(1, 2),
        amount = BigDecimal("22.33"),
        denomination = "PHP",
        clientTransactionId = "ABC-123-XYZ"
    )
)

coreProducer.process("<requestId>", "<clientBatchId>", listOf(transaction))

Listen to executed transactions

Transactions are sent to TM from micro services through specific channels which are defined as PostingsAPIClients.

Responses from TM Kafka are propagated to specific Kafka topic (per client) or to global topic which receives all transactions that are created in TM.

For most of cases micro service should listen only to client specific transactions, which were sent to TM with client specific producer.

When there is a case in micro service, that it needs to listen to transactions that are not only generated by it’s client or there is need to listen to transactions generated by smart contracts, then it should use global listener which receives all transactions.

Postings API clients

The Postings API implements an asynchronous request/response API over Kafka. Requests are published to one of two topics:

  • vault.core.postings.requests.v1 for requests with a strict service-level agreement (SLA)

  • vault.core.postings.requests.low_priority.v1 for requests with a generous SLA

To use the Postings API, a PostingsAPIClient resource is required. It serves two purposes:

Routing of responses

The PostingsAPIClient resource is used to route responses back to a client of the Postings API. Every payment integration that wants to instruct the movement of funds in Vault must create at least one PostingsAPIClient resource.

1. Setup application properties

2. Handle listener for some specific transaction

There are generated handlers per each transaction defined in yaml definition list.

Package: package ph.safibank.tm.listener.handler

Generated handlers are abstract classes which will receive specific transactions and requires to implement fun process(batchMetadata: BatchMetadata, instruction: T) method.

This process method contains

  • BatchMetadata which contains common meta data from Posting Instruction Batch:

    • id : transaction id

    • requestId : A unique ID generated by the client (payment processor) that is used for idempotency. The client must ensure a unique request_id is passed within their namespace (determined by client_id). Multiple requests with the same <client_id - request_id> will receive the same response.

    • clientId : Uniquely identifies a client of the Posting API. Used to publish responses to the specified Kafka response topic. Together with request_id it forms a unique key used for idempotency.
      In case of Smart Contract generated transaction value is: CoreContracts

    • clientBatchId : This must be set by the client, and is used as a correlation ID across different posting instruction batches. The suggested use is to set the same client_batch_id across batches that contain posting instructions for the same financial transaction. The posting service provides indexing on this ID, allowing for efficient queries to return all posting instructions that belong to a client transaction.

    • status : The status of the processing of the posting instruction batch.
      POSTING_INSTRUCTION_BATCH_STATUS_ACCEPTED:
      The PostingInstructionBatch has been accepted and Postings have been committed to the ledger.
      POSTING_INSTRUCTION_BATCH_STATUS_REJECTED:
      The PostingInstructionBatch has been rejected and no Postings have been committed to the ledger.

  • instruction: T which is per handler specified and all parameters are known.

Handler class names

Handlers classes are generated in format <TransactionName>Handler in package: package ph.safibank.tm.listener.handler.

Parameter instruction has correct type of transactions with related data in model.

Example for TopUpPaynamics handler

import jakarta.inject.Singleton
import ph.safibank.tm.listener.handler.TopUpPaynamicsHandler
import ph.safibank.tm.model.BatchMetadata
import ph.safibank.tm.model.PostingBatchStatus
import ph.safibank.tm.model.transaction.TopUpPaynamics

@Singleton
class MyOwnTopUpPaynamicsHandler: TopUpPaynamicsHandler() {
    override fun process(batchMetadata: BatchMetadata, instruction: TopUpPaynamics) {
        // check status
        if (batchMetadata.status == PostingBatchStatus.ACCEPTED){
            // do process when transaction was accepted
            // Mark as paid -> completed etc
        } else{
            // do process when transaction was rejected
            // notify user, mark as unpaid, retry ?
        }
    }
}