Transaction Origination

This section walks through ZipRights initial user-interface rendering workflow.


Determine: New Transaction or Existing Transaction?


As part of its user-interface bootstrap process - the first determination the ZipRight UI needs to make, is whether it is being launched in the context of a new transaction, or to view details on an existing transaction. ZipRight makes this determination as part of its initial page load - by retrieving the origination context for the user-interaction from the JavaScript API's transaction.getOrigin method.

If the origination context contains a valid transactionId, indicating the UI has been launched in the context of an existing transaction - ZipRight routes to its order details page.

Else - it routes to its new order page:

...
	
	// Function to instantiate application state with origination context
  const setOriginationContext = async () => {
    // Instantiate instance of host application interface
    // and invoke access to origination context
    const proxy = await host;
    const originationData = await proxy.getTransactionOrigin();
    console.log(originationData);

    // Access and instantiate application state with originating context
    const currentTransactionId = originationData.transactionId;

    if (currentTransactionId) {
      // If in context of existing transaction - navigate to details view
      navigate(`/details/${currentTransactionId}`);
    } else {
      // Else - navigate to new order view
      navigate('/order');
    }
  };

...

Case: New Transaction


For a new transaction, ZipRight takes the user through a wizard experience where they are able to view and verify the borrower, co-borrower, subject property and mortgage transaction information.

ZipRight accesses this information via the REST API's GET /partner/v2/origins/:id endpoint, which requires a valid partnerAccessToken and originId, also received from the origination context as illustrated above. The ZipRight UI passes this information to the ZipRight back-end when invoking the back-ends GET /origins/:id?partnerAccessToken={{token}} endpoint, which in turn calls the EPC REST API's origins endpoint to retrieve and marshall this information back up to the user-interface.

📘

Don't confuse the ZipRight back-end's GET /origins/:id?partnerAccessToken={{token}} endpoint with the EPC REST API's GET /partner/v2/origins/:id endpoint. The former is a proxy endpoint for the user-interface to retrieve information accessible via the latter.

If you're wondering why ZipRight jumps a hop to access this information - keep in mind that the EPC /origins endpoint (and in fact all EPC REST API endpoints) should not be accessed directly from code executing in the users browser, for two primary reasons:

  1. This may imply compromising your super-sensitive EPC oAuth API client_id and client_secret - which are required to authenticate against the EPC API and invoke the /origins endpoint

  2. The /origins endpoint may return sensitive information based on your applications entitlements, such as PII borrower information and user credentials for your integration, which you may not need to expose to the user-interface (a borrowers SSN, for example)


User-interface:
...

// Initializor function to update application state with origin data
  const initializeOriginInformation = async () => {
    // Access origin context
    const proxy = await host;
    const originContext = await proxy.getTransactionOrigin();
    const originId = originContext.id;
    const partnerAccessToken = originContext.partnerAccessToken;

    // Initialize application state with origin information
    const origin = OriginService(originId, partnerAccessToken);
    const originLoanData = await origin.getOriginLoanData();
    setInitializationInformation(originLoanData);
  };

...

📘

In the sample above - the transaction.getOrigin function is encapsulated within the host objects getTransactionOrigin method. The host object here is an instance of the HostConnectionService module that encapsulates all interactions with the JavaScript API.

You can see its implementation at .../epc-hello-world/user-interface/src/services/HostConnectionService/HostConnectionService.js


Back-end:

The controller function bound to the back-ends GET /origins/:id operation invokes the back-ends origin utility service to retrieve the information from the EPC REST API:

from services.origins import get_loan_information

"""" Origins retrieve controller """


def retrieve(origin_id, partner_access_token):

    res = get_loan_information(origin_id, partner_access_token)
    if res is False: # This means the credentials are not valid
        res['status'] = "Invalid username or password"
        return res, 401
    else: # if credentials are valid simply pass on the response
        return res, 200
...

"""" Internal function to pull down Origin information """

def _get_origin(origin_id, partner_access_token) -> dict:
    # Ask token_manager for current oAuth token
    access_token = app.token_manager.get_token()

    url = EPC_BASE_URL + EPC_ORIGINS_URL + origin_id
    headers = {
        'Authorization': 'Bearer' + ' ' + access_token,
        'X-Elli-PAT': partner_access_token
    }
    response = get(url=url, headers=headers)

    return response

...

"""" Public function utilized by Origin controller """

def get_loan_information(origin_id, partner_access_token) -> dict:
    response = dict()
    origin_data = _get_origin(origin_id, partner_access_token)
    if origin_data.status_code == 200:
        origin_data_obj = origin_data.json()
        if validate_credentials(origin_data_obj) is True:
            if origin_data_obj.get("loan", None) is not None:
                loan_information = origin_data_obj.get("loan", None)
                return loan_information
            elif origin_data_obj.get('errors', None) is not None:
                response['errors'] = origin_data_obj.get('errors', None)
                return response
            else:
                response['errors'] = "Loan Object not found"
                return response
        else:
            return False
    else:
        response['errors'] = origin_data.status_code
        return response
      
...

If all goes well and there are no exceptions raised in this workflow - the user-interface receives the origin information and is able to render the order form! 🎉


Case: Existing Transaction


If ZipRight determines it is being launched in the context of an existing transaction - it routes to its transaction details view. Upon rendering this view - it queries the back-end for the status of the transaction (along with the EPC resource identifier for the mock PDF report it generates):

User-interface:
...

import StatusService from '../../services/StatusService/StatusService';

...

// Function that asynchronously initializes order information
  const initializeOrderInformation = async (transactionId) => {
    // Get order status information based on available transaction ID
    const statusTracker = StatusService(transactionId);
    const response = await statusTracker.getStatus();

    // Initialize component state with order information
    setOrderInformation(response);
  };

...
...

// Internal function that invokes the integrations back-end API to access
  // transaction processing status
  const _getStatus = async () => {
    // Prepare API URL
    const url = API_URL + STATUS_PATH + transactionId;

    try {
      // Access and serialize the response
      const response = await fetch(url);
      const responseBody = await response.json();

      return responseBody;
    } catch (error) {
      console.error(error);
    };
  };

...

Back-end:

The ZipRight back-end exposes a transaction status request endpoint, so that the ZipRight user-interface can fetch and display the processing status of a transaction that it maintains. For simplicities sake, the ZipRight back-end code currently does not implement any entitlement or authorization checks. The following code snippet shows how ZipRight processes a request for status exposed at its GET /v1/status/:id endpoint:

...

""""" Controller for transaction status and details query """

def retrieve(id):
	
	res = dict()
	if app.request_tracker.get(id, None) != None:
		req_tracker = app.request_tracker.get(id, 'None')
		res['status'] = req_tracker.get('status', 'None')
		res['resource_id'] = req_tracker.get('resource_id', 'None')
	else:
		return 404

	return res, 200

If all goes well and there are no exceptions raised in this workflow - the user-interface receives the transaction status information and is able to render the order details view! 🎉