Public | Automated Build

Last pushed: 10 months ago
Short Description
Official build of payments service
Full Description

LIPA NA M-PESA ONLINE CHECKOUT

This microservice implements Safaricom's Lipa na M-pesa Online Checkout
API for Chamarika.

Chamarika is is the best software platform for running an internet Microfinancial Institution. Chamarika-payments is a suite of
APIs that powers commerce for Microfinancial Institutions of all sizes.

Financial institutions and their clients using Chamarika are able to send and receive online payments using Safaricoms M-pesa
mobile payment platform. This includes both paybill and buy goods till numbers offered by Safaricom.This microservice has implemented
the following use cases:

  • Clients-to-Chama (Lipa na Mpesa API & Paybill validation); Clients are able to send or transfer funds from their individual mobile
    wallets to their respective Microfinancial Institution mobile wallet.

  • Chama-to-Chama payments (Business-to-Business API): Microfinancial Institutions are able to send funds among themselves.

  • Chama-to-Clients (Business-to-Clients API): Microfinancial Institutions are able to remitt funds in the mobile wallets of their clients.

ROADMAP

The following are some of the basic feature requirements this microservice.

  1. List chama and client payments and loads new ones in real time.
  2. Chamarika captures customer payment details on the client side.
  3. Chamarika sends the process checkout request to Safaricom M-pesa system via the Service Access Gateway.
  4. Safaricom M-pesa validates and authenticates the process checkout request.
  5. Chamarika receives a process checkout response from Safaricom M-pesa system via the Service Access Gateway.
  6. Chamarika displays a customer message on the client side.
  7. Chamarika sends a transaction confirmation request to Safaricom M-pesa system via the Service Access Gateway.
  8. Safaricoms M-pesa system pushes a USSD menu to the customer and prompts the customer to enter their
    BONGA PIN and any other validation information.
  9. The transaction is processed on Safaricoms M-pesa system.
  10. Safaricoms M-pesa system sends a transaction confirmation response via the Service Access Gateway.
  11. A Callback is executed after completion of the transaction.
  12. Chamas and clients to be able to query transaction status.

CONTRIBUTING

This microservice is built using Isomorphic Javascript using the React library built by the facebook team.

What do we mean by Isomorphic Javascript? This basically means that we are writing one codebase that runs on
both the server and client side.

Overview

This microservice gets data via REST APIs from the following microservices;

hela-c2b-checkout : Transforms the SOAP API requests from https://safaricom.co.ke/mpesa_online/lnmo_checkout_server.php?wsdl
and converts them to REST APIs that can be consumed by other microservices.The REST APIs created by Loopback and are documented via
swagger.

hela-c2b-validation : Transforms the SOAP API requests from http://cps.huawei.com/cpsinterface/c2bpayment/wsdl/CPSInterface_C2BPaymentValidationAndConfirmation?wsdl
and converts them to REST APIs that can be consumed by other microservices.The REST APIs created by Loopback and are documented via
swagger.

hela-cbp-request : Transforms the SOAP API requets from http://api-v1.gen.mm.vodafone.com/mminterface/request?wsdl and converts them
to REST APIs that can be consumed by other microservices.The REST APIs created by Loopback and are documented via swagger.

hela-cbp-response : Transforms the SOAP API from http://api-v1.gen.mm.vodafone.com/mminterface/result?wsdl converts them to REST APIs
that can be consumed by other microservices.The REST APIs created by Loopback and are documented via swagger.

Analysis of the Lipa Na M-pesa Checkout WSDL

This section explains how to read the Lipa Na M-pesa Checkout WSDL document by analyzing the Web Services desciption of WSDL.
During the section , a tree diagram is developed from the content of the WSDL document. The tree illustrates the structure of the WSDL.
Developers and Contributors can get an understanding of the WSDL elements and their relationships.

The root element of the WSDL document is the definitions . So we start the WSDL tree with a definitions node as the root.

1.Service

To analyze the WSDL document ,it is recommended we read from the bottom upwards.At the bottom of the lnmo_checkout_Service's wsdl ,
we find a child element of definitions named service .

   <wsdl:service name="lnmo_checkout_Service">
      <wsdl:port name="lnmo_checkout" binding="tns:LNMO_binding">
         <soap:address location="lnmo_checkout_server.php" />
      </wsdl:port>
   </wsdl:service>

The name of the service is lnmo_checkout_Service

    <wsdl:service name="lnmo_checkout_Service">

It has one port for SOAP 1.1 endpoint named lnmo_checkout.

      <wsdl:port name="lnmo_checkout" binding="tns:LNMO_binding">
         <soap:address location="lnmo_checkout_server.php" />
      </wsdl:port>

Its child element address has a different XML prefix to other elements. The prefix soap is bound to the SOAP
1.1 binding in the Checkout.wsdl

Instead of the SOAP binding , other bindings for JMS or a file transport can be used. The address element has
one attribute named location pointing to an endpoint address of the service.

2.Binding

To move on we have to look at the binding attribute of the port. The value tns:LNMO_binding points to a binding
further up in the document. The lnmo_checkout port is pointing to the _tns:LNMO_binding binding.

A binding provides details about a specific transport.The binding has two types of children.

2.1 soap: binding

First , we look the soap:binding element. The value of the transport attribute is an URI that indicates
that SOAP messages should be sent over HTTP. The value document of the style attribute gives a clue about the
message style together with the use attribute of the soap:body elements.Here we have a Document/Literal message style.
A binding can specify different transport options for each method of a service.

<wsdl:binding name="LNMO_binding" type="tns:LNMO_portType">
      <soap:binding transport="http://schemas.xmlsoap.org/soap/http" />
      <wsdl:operation name="processCheckOut">
         <soap:operation soapAction="" style="document" />
         <wsdl:input>
        <soap:header message="tns:mpesaCheckOutHeader" part="header" use="literal"/>
            <soap:body use="literal" />
         </wsdl:input>
         <wsdl:output>
            <soap:body use="literal" />
         </wsdl:output>
      </wsdl:operation>
      <wsdl:operation name="transactionStatusQuery">
         <soap:operation soapAction="" style="document" />
         <wsdl:input>
            <soap:header message="tns:mpesaCheckOutHeader" part="header" use="literal"/>
            <soap:body use="literal" />
         </wsdl:input>
         <wsdl:output>
            <soap:body use="literal" />
         </wsdl:output>
      </wsdl:operation>
     <wsdl:operation name="confirmTransaction">
         <soap:operation soapAction="" style="document" />
         <wsdl:input>
            <soap:header message="tns:mpesaCheckOutHeader" part="header" use="literal"/>
            <soap:body use="literal" />
         </wsdl:input>
         <wsdl:output>
            <soap:body use="literal" />
         </wsdl:output>
      </wsdl:operation>
      <wsdl:operation name="LNMOResult">
            <soap:operation soapAction="" style="document"/>
            <wsdl:input>
                <soap:body use="literal" />
            </wsdl:input>
            <wsdl:output>
                <soap:body use="literal" />
            </wsdl:output>
        </wsdl:operation>
   </wsdl:binding>

2.2 SOAP 1.1 Binding

Below we will find transport options for different operations.

Inside the wsdl:operation element there is a soap:operation element defining details for the SOAP
protocol and its transport.

The soapAction is a reminiscent from the past. THe Basic Profile of the Web services Interoperability
Organization stipulates that the soapAction should be used with a fixed value of an empty string.

Transport options for processCheckOut Operation

<wsdl:operation name="processCheckOut">
         <soap:operation soapAction="" style="document" />
         <wsdl:input>
        <soap:header message="tns:mpesaCheckOutHeader" part="header" use="literal"/>
            <soap:body use="literal" />
         </wsdl:input>
         <wsdl:output>
            <soap:body use="literal" />
         </wsdl:output>

Transport options for transactionStatusQuery Operation

<wsdl:operation name="transactionStatusQuery">
         <soap:operation soapAction="" style="document" />
         <wsdl:input>
            <soap:header message="tns:mpesaCheckOutHeader" part="header" use="literal"/>
            <soap:body use="literal" />
         </wsdl:input>
         <wsdl:output>
            <soap:body use="literal" />
         </wsdl:output>

Transport options for confirmTransaction Operation

<wsdl:operation name="confirmTransaction">
         <soap:operation soapAction="" style="document" />
         <wsdl:input>
            <soap:header message="tns:mpesaCheckOutHeader" part="header" use="literal"/>
            <soap:body use="literal" />
         </wsdl:input>
         <wsdl:output>
            <soap:body use="literal" />
         </wsdl:output>

Transport options for LNMOResult Operation

<wsdl:operation name="LNMOResult">
            <soap:operation soapAction="" style="document"/>
            <wsdl:input>
                <soap:body use="literal" />
            </wsdl:input>
            <wsdl:output>
                <soap:body use="literal" />
            </wsdl:output>

2.3 binding operation

Because web services set the focus on messages not parameters, information about the transport of these messages
can be found in the wsdl:input and wsdl:output element. A service may specify one of several faults as an alternative
for the outputs.

The soap:body and soap:header elements can describe a message further where the styles in this case is literal.

3. portType

Its time again we move up in the WSDL.Now we follow the value of the type attribute of the binding.It points to a portType
with the same name further up in the document.

Now we have crossed the border from the concrete details about the transport and location of a service to its
pure abstract description of its interface. portType is in WSDL 1.1 similar to the term interface of the web service.In WSDL 2.0 ,
the term portType is substituted with the term interface. An interface can have several operations.An operation coressponds to
a function in procedural programming.

This WSDL of the lnmo_checkout_Service has only one portType named LNMO_portType .All the bindings refer to the portType.

Inside a portType , we find operation elements as in the binding.But this time the input and output describe the
structure of the messages not the transport specific options.

 <wsdl:portType name="LNMO_portType">
      <wsdl:operation name="processCheckOut">
         <wsdl:input message="tns:mpesaCheckOutRequest" />
         <wsdl:output message="tns:mpesaCheckOutResponse" />
      </wsdl:operation>
      <wsdl:operation name="transactionStatusQuery">
         <wsdl:input message="tns:mpesaTransactionRequest" />
         <wsdl:output message="tns:mpesaTransactionResponse" />
      </wsdl:operation>
      <wsdl:operation name="confirmTransaction">
         <wsdl:input message="tns:mpesaConfirmRequest" />
         <wsdl:output message="tns:mpesaConfirmResponse" />
      </wsdl:operation>
      <wsdl:operation name="LNMOResult">
            <wsdl:input message="tns:ResultMessage">
            </wsdl:input>
            <wsdl:output message="tns:ResponseMessage">
            </wsdl:output>
        </wsdl:operation>
   </wsdl:portType>

4. Messages

The message attribute of the input and output refers again up in the WSDL document.The messages has one part element as child.
A WSDL specialist will recognise the value of the attribute name , parameters indicates the wrapper substyle of the document/literal style.

It referes to messages named:

Message 1: tns:mpesaCheckOutRequest

   <wsdl:message name="mpesaCheckOutRequest">
      <wsdl:part name="body" element="tns:processCheckOutRequest" />
   </wsdl:message>

Message 2: tns:mpesaCheckOutResponse

   <wsdl:message name="mpesaCheckOutResponse">
      <wsdl:part name="parameters" element="tns:processCheckOutResponse" />
   </wsdl:message>

Message 3: tns:mpesaTransactionRequest

   <wsdl:message name="mpesaTransactionRequest">
      <wsdl:part name="body" element="tns:transactionStatusRequest" />
   </wsdl:message>

Message 4: tns:mpesaTransactionResponse

   <wsdl:message name="mpesaTransactionResponse">
      <wsdl:part name="parameters" element="tns:transactionStatusResponse" />
   </wsdl:message>

Message 5: tns:mpesaConfirmRequest

   <wsdl:message name="mpesaConfirmRequest">
      <wsdl:part name="parameters" element="tns:transactionConfirmRequest" />
   </wsdl:message>

Message 6: tns:mpesaConfirmResponse

   <wsdl:message name="mpesaConfirmResponse">
      <wsdl:part name="parameters" element="tns:transactionConfirmResponse" />
   </wsdl:message>

Message 7: tns:ResultMessage

   <wsdl:message name="ResultMessage">
        <wsdl:part name="ResultMsg" element="tns:ResultMsg">
        </wsdl:part>
    </wsdl:message>

Message 8: tns:ResponseMessage

      <wsdl:message name="ResponseMessage">
        <wsdl:part name="ResponseMsg" element="tns:ResponseMsg">
        </wsdl:part>
    </wsdl:message>
   <wsdl:message name="mpesaCheckOutHeader">
      <wsdl:part name="header" element="tns:CheckOutHeader" />
   </wsdl:message>

5. Types

The attribute element in the above messages in line 2 further points up the WSDL document.It refers to the following elements
in an XML Schema.

The Types element can have multiple XML schemas as children.

In a schema we can find the definition of complexType and simpleTypes and the declaration of elements.

The XML Schema inside lnmo_checkout_Service is a typical schema used for web service that has only only complexTypes and elements
as toplevel schema components.

   <wsdl:types>
      <s:schema targetNamespace="tns:ns">
         <s:element name="CheckOutHeader">
            <s:complexType>
               <s:sequence>
                  <s:element minOccurs="1" maxOccurs="1" name="MERCHANT_ID" type="s:string" />
                  <s:element minOccurs="1" maxOccurs="1" name="PASSWORD" type="s:string" />
                  <s:element minOccurs="1" maxOccurs="1" name="TIMESTAMP" type="s:string" />
               </s:sequence>
            </s:complexType>
         </s:element>
         <s:element name="processCheckOutRequest">
            <s:complexType>
               <s:sequence>
                  <s:element minOccurs="1" maxOccurs="1" name="MERCHANT_TRANSACTION_ID" type="s:string" />
                  <s:element minOccurs="1" maxOccurs="1" name="REFERENCE_ID" type="s:string" />
                  <s:element minOccurs="1" maxOccurs="1" name="AMOUNT" type="s:double" />
                  <s:element minOccurs="1" maxOccurs="1" name="MSISDN" type="s:string" />
                  <s:element minOccurs="0" maxOccurs="1" name="ENC_PARAMS" type="s:string" />
                  <s:element minOccurs="1" maxOccurs="1" name="CALL_BACK_URL" type="s:string" />
                  <s:element minOccurs="1" maxOccurs="1" name="CALL_BACK_METHOD" type="s:string" />
          <s:element minOccurs="0" maxOccurs="1" name="TIMESTAMP" type="s:string" />
               </s:sequence>
            </s:complexType>
         </s:element>
          <s:element name="transactionStatusRequest">
            <s:complexType>
               <s:sequence>
                  <s:element minOccurs="0" maxOccurs="1" name="TRX_ID" type="s:string" />
                  <s:element minOccurs="0" maxOccurs="1" name="MERCHANT_TRANSACTION_ID" type="s:string" />
               </s:sequence>
            </s:complexType>
         </s:element>

         <s:element name="processCheckOutResponse">
            <s:complexType>
               <s:sequence>
                  <s:element minOccurs="1" name="RETURN_CODE" type="s:string" />
                  <s:element minOccurs="1" name="DESCRIPTION" type="s:string" />
                  <s:element minOccurs="1" name="TRX_ID" type="s:string" />
                  <s:element minOccurs="1" name="ENC_PARAMS" type="s:string" />
                  <s:element minOccurs="1" name="CUST_MSG" type="s:string" />
               </s:sequence>
            </s:complexType>
         </s:element>
        <s:element name="transactionStatusResponse">
            <s:complexType>
               <s:sequence>
        <s:element minOccurs="1" name="MSISDN" type="s:string" />
        <s:element minOccurs="1" name="AMOUNT" type="s:string" />
        <s:element minOccurs="1" name="MPESA_TRX_DATE" type="s:string" />
        <s:element minOccurs="1" name="MPESA_TRX_ID" type="s:string" />
        <s:element minOccurs="1" name="TRX_STATUS" type="s:string" />
        <s:element minOccurs="1" name="RETURN_CODE" type="s:string" />
        <s:element minOccurs="1" name="DESCRIPTION" type="s:string" />
        <s:element minOccurs="1" name="MERCHANT_TRANSACTION_ID" type="s:string" />
        <s:element minOccurs="1" name="ENC_PARAMS" type="s:string" />
        <s:element minOccurs="1" name="TRX_ID" type="s:string" />
               </s:sequence>
            </s:complexType>
         </s:element>
         <s:element name="transactionConfirmRequest">
            <s:complexType>
               <s:sequence>
                  <s:element minOccurs="0" maxOccurs="1" name="TRX_ID" type="s:string" />
                  <s:element minOccurs="0" maxOccurs="1" name="MERCHANT_TRANSACTION_ID" type="s:string" />
               </s:sequence>
            </s:complexType>
         </s:element>
     <s:element name="transactionConfirmResponse">
            <s:complexType>
               <s:sequence>
                  <s:element minOccurs="1" name="RETURN_CODE" type="s:string" />
                  <s:element minOccurs="1" name="DESCRIPTION" type="s:string" />
                  <s:element minOccurs="1" name="MERCHANT_TRANSACTION_ID" type="s:string" />
          <s:element minOccurs="1" name="TRX_ID" type="s:string" />
               </s:sequence>
            </s:complexType>
         </s:element>
        <s:element name="ResultMsg">
            <s:complexType>
               <s:sequence>
        <s:element minOccurs="1" name="MSISDN" type="s:string" />
        <s:element minOccurs="1" name="AMOUNT" type="s:string" />
        <s:element minOccurs="1" name="MPESA_TRX_DATE" type="s:string" />
        <s:element minOccurs="1" name="MPESA_TRX_ID" type="s:string" />
        <s:element minOccurs="1" name="TRX_STATUS" type="s:string" />
        <s:element minOccurs="1" name="RETURN_CODE" type="s:string" />
        <s:element minOccurs="1" name="DESCRIPTION" type="s:string" />
        <s:element minOccurs="1" name="MERCHANT_TRANSACTION_ID" type="s:string" />
        <s:element minOccurs="1" name="ENC_PARAMS" type="s:string" />
        <s:element minOccurs="1" name="TRX_ID" type="s:string" />
               </s:sequence>
            </s:complexType>
         </s:element>
       <s:element name="ResponseMsg" type="s:string" />
      </s:schema>
   </wsdl:types>

In document/literal style , all the parts point to elements.Therefore a part can always reference an element.
For example the CheckOutHeader

      <wsdl:part name="header" element="tns:CheckOutHeader" />

A complexType with a sequence as content

A sequence can consist of several elements that describe the order of elements that describe the order of elements in a SOAP message.
For example, the CheckOutHeader

         <s:element name="CheckOutHeader">
            <s:complexType>
               <s:sequence>
                  <s:element minOccurs="1" maxOccurs="1" name="MERCHANT_ID" type="s:string" />
                  <s:element minOccurs="1" maxOccurs="1" name="PASSWORD" type="s:string" />
                  <s:element minOccurs="1" maxOccurs="1" name="TIMESTAMP" type="s:string" />
               </s:sequence>
            </s:complexType>
         </s:element>

GETTING STARTED

This application shows payments and loads new ones in real time.

Application Requirement

  • Merchant captures customer payment details

    • CheckOutHeader // Element
      mpesaCheckOutHeader // Message_name

        "MERCHANT_ID"
        "PASSWORD"
        "TIMESTAMP"
      
    • processCheckOutRequest // Operation // Element

      Merchant invokes SAG processCheckOutRequest interface

      Input: mpesaCheckOutRequest // Message_name
      Output: mpesaCheckOutResponse // Message_name

        "MERCHANT_TRANSACTION_ID"
        "REFERENCE_ID"
        "AMOUNT"
        "MSISDN"
        "ENC_PARAMS"
        "CALL_BACK_URL"
        "CALL_BACK_METHOD"
        "TIMESTAMP"
      
SAG validates the request

* processCheckOutResponse // Element
        "RETURN_CODE"
        "DESCRIPTION"
        "TRX_ID"
        "ENC_PARAMS"
        "CUST_MSG"

        SAG invokes processCheckOutResponse

Merchant displays customer message

* confirmTransaction

Input: mpesaConfirmRequest
Output: mpesaConfirmResponse

* transactionConfirmRequest // Element
        Merchant invokes transactionConfirmRequest

        "TRX_ID"
        "MERCHANT_TRANSACTION_ID"

System pushes a USSD menu to the customer and prompt the customer to enter 

their BONGA PIN and any other validation information.

The transaction is processed on M-PESA

* transactionConfirmResponse // Element
        SAG invokes transactionConfirmResponse

        "RETURN_CODE"
        "DESCRIPTION"
        "MERCHANT_TRANSACTION_ID"
        "TRX_ID"



* Payment Notification (Merchant Callback)/ResultMsg // Element
        Callback is executed after completion of the transaction.

        "MSISDN" 
        "AMOUNT" 
        "MPESA_TRX_DATE" 
        "MPESA_TRX_ID" 
        "TRX_STATUS" 
        "RETURN_CODE" 
        "DESCRIPTION" 
        "MERCHANT_TRANSACTION_ID" 
        "ENC_PARAMS" 
        "TRX_ID" 

# Transaction Status Query // Operation
This helps to get the status of the transaction

Input: mpesaTransactionRequest
Output: mpesaTransactionResponse

* transactionStatusRequest // Element
        "TRX_ID"
        "MERCHANT_TRANSACTION_ID"

* transactionStatusResponse // Element
        "MSISDN"
        "AMOUNT"
        "MPESA_TRX_DATE"
        "MPESA_TRX_ID"
        "TRX_STATUS"
        "RETURN_CODE"
        "DESCRIPTION"
        "MERCHANT_TRANSACTION_ID"
        "ENC_PARAMS"
        "TRX_ID"

COMPONENTS

  1. Main App // Main App
  • The page should render server side initially, and the client side should take it from there
  1. mpesaCheckOutHeader
  2. processCheckOutRequest // sends mpesa processing requests to SAG
  • It should save process checkout requests and emitt to the SAG.
  • On save , an event should be emitted to the server side that updates the views.
  • When processCheckOutRequest connection sends a new process checkout request event , we need
    a method to take the data , save it in our database , and emit an event to the SAG with the process checkout request data.
  1. processCheckOutResponse // receives mpesa processing responses from SAG
  • It should listen to the SAG and save new process checkout responses as they come in.
  • On save , an event should be emitted to the client side that will update the views.
  • When processCheckOutResponse connection sends a new process checkout response event, we
    need a method to take that data , save it to our database, and emit an event to the client
    side with the process checkout response data.(pcrHandler.js) We start by requiring our Model ,
    and when our SAG emitts an event , we grab the data we want to save , save it , and emit our socket
    event to the client with the process checkout response we just saved.
  1. transactionConfirmRequest // Sends transaction confirmation requests to SAG
  • It should save transaction confirmation requests and emitt to the SAG.
  • On save , an event should be emitted to the server side that updates the views.
  • When transactionConfirmRequest connection sends a new process checkout request event , we need
    a method to take the data , save it in our database , and emit an event to the SAG with the transaction
    confirmation request data.
  1. transactionConfirmResponse // Receives transaction confirmation responses from SAG
  • It should listen to the SAG and save new transaction confirmation responses as they come in.
  • On save , an event should be emitted to the client side that will update the views.
  • When transactionConfirmResponse connection sends a new transaction confirmation response event, we
    need a method to take that data , save it to our database, and emit an event to the client
    side with the transaction confirmation response data.(tcrHandler.js) We start by requiring our Model ,
    and when our SAG emitts an event , we grab the data we want to save , save it , and emit our socket
    event to the client with the transaction confirmation response we just saved.
  1. Payment Notification (Merchant Callback)/ResultMsg SAG calls back merchant URL via POST or GET or XML Result Message

  2. transactionStatusRequest // Sends transaction status request to SAG

  • It should save transaction status requests and emitt to the SAG.
  • On save , an event should be emitted to the server side that updates the views.
  • When transactionStatusRequest connection sends a new transaction status request event , we need
    a method to take the data , save it in our database , and emit an event to the SAG with the transaction
    status request data.
  1. transactionStatusResponse // Receives transaction status response from SAG
  • It should listen to the SAG and save new transaction status responses as they come in.
  • On save , an event should be emitted to the client side that will update the views.
  • When transactionStatusResponse connection sends a new transaction status response event, we
    need a method to take that data , save it to our database, and emit an event to the client
    side with the transaction status response data. (tsrHandler.js) We start by requiring our Model ,
    and when our SAG emitts an event , we grab the data we want to save , save it , and emit our socket
    event to the client with the transaction status response we just saved.
  1. paymentDetailsForm // Form

  2. displayPaymentDetails // Message to display

  1. transactionStatusForm // Form

  2. displayTransactionStatus Message to display

  3. chamaPaymentList // List of payments made to Chama.

  4. memberPaymentList // List of payments made by member.

  5. chamaPaymentLoader // Loads 10 blocks of payments at a time.

  6. memberPaymentLoader // Loads 10 blocks of payments at a time.

  7. paymentNotificationBar

  • New unread payments should have a notification bar that prompts user to view them.

PROPERTIES
EVENTS
STATE
PROPERTY VALIDATION

Docker Pull Command
Owner
chamaconektkenya
Source Repository

Comments (0)