Remote Deposit Adapter Extension Tutorial

The RemoteDeposit adapter type is used for the RemoteDeposit workflow inside of digital banking. Remote Deposit Capture (RDC) is a service that allows individuals and businesses to deposit checks into their bank accounts without needing to visit a bank branch or ATM.

By default, Remote Deposit is not enabled in a sandbox stack. You can enable this with the q2 sandbox rdc command:

$ q2 sandbox rdc

Which operation?

    1) Enable
    2) Disable

Please make a selection and press Return: 1

Next, we create an Adapter extension just as any other:

  1. Create an extension using q2 create_extension ExampleRDC command:

    $ q2 create_extension
    
    New Extension Name: ConvertToDollars
    What type of extension are you creating?
    
        1) Online (default)
        2) SSO (Third Party Integration)
        3) Ardent (API)
        4) Q2Console (Backoffice)
        5) Central (Legacy Backoffice)
        6) Adapter <--------
        7) Audit Action
        8) Custom Health Check
        9) Message Bus
        10) Caliper API Custom Endpoint
        11) Base Extension
    
    Please make a selection and press Return [1]: 6
    
    Select adapter type to generate
    
        1) Account Details
        2) Authentication Token
        3) Check Image
        4) Domestic Wire
        5) Deposit Item Image
        6) FX Rate
        7) Instant Payments
        8) International Wire
        9) Remote Deposit <--------
        10) Statement Image
    
    Please make a selection and press Return: 2
    
  2. Run q2 install ExampleRDC to register the extension.

What did we get? Our extension.py has the usual boilerplate for a request handler, and a handler class definition with three methods:

async def get_rdc_transaction_list(self, remote_deposit_capture_request: RemoteDepositRequest) -> list[RDCTransactionResponse]:
async def get_rdc_transaction_detail(self, transaction_detail_request: RDCTransactionHistoryDetailRequest) -> RDCTransactionHistoryDetailResponse:
async def capture_rdc(self, remote_deposit_capture_request: RDCCaptureRequest) -> RDCSubmitResponse:
async def get_validation(self, remote_deposit_validation_request: RDCValidateRequest) -> RDCValidation:

That’s it? From the perspective of an extension developer, yes. By the time this code has executed, the Caliper SDK has:

  • Intercepted the request from Online

  • Checked the database for its configured destination

  • Redirected the request to this Caliper SDK extension

  • Translated the XML payload containing the request into an easily used object RemoteDepositRequest()

Remote Deposit Capture is a mobile only workflow for Q2. This workflow is a native iOS and Android workflow and is not available on the web. For development purposes, we have a basic web UI to test the workflow. It is possible for you to build your own Remote Deposit Capture workflow on the web and have it call the adapter you build here. More on that later.

We will build an adapter for the standard workflow. To test this workflow, you will need to download and configure the mobile app on a device. Since iOS simulator doesnt have a camera access, iOS testing needs to be done on a physical device. Android you are able to use the camera in the simulator, so testing this work flow is possible in the Android simulator.

You can download the mobile app to your device following the instructions here:

Mobile Development

Once you login, click the Transactions menu and then Deposit Check. You will be able to fully validate the workflow from here.

Let’s take each API one at a time.

Validate RDC

The first call the adapter will receive is the Transaction List call. This call is expected to give back history but since we are starting fresh, there is no history. get_rdc_transaction_list should just return an empty list for now.

The second call will be to the get_validations function. This function is the opportunity to return a dictionary of validation parameters that will be used by the client to validate the inputs before they are submitted to the server:

{
    "DailyAmtRemain": null,
    "DailyCountRemain": null,
    "WeeklyAmtRemain": null,
    "WeeklyCountRemain": null,
    "MonthlyAmtRemain": null,
    "MonthlyCountRemain": null,
    "DailyAmt": null,
    "DailyCount": null,
    "WeeklyAmt": null,
    "WeeklyCount": null,
    "MonthlyAmt": null,
    "ItemAmt": 13,
    "MonthlyCount": null,
    "CalDailyAmt": 10001.00,
    "CalDailyAmtRemain": 10001.00,
    "CalDailyTransaction": 5,
    "CalDailyTransactionRemain": 5,
}

There is room for 3 error messages; Amount Limit, Count Limit and Item Limit. The way the native Q2 UI handles multiple messages is that it will show the messages in this priority (applies to Amount and Count):

  1. Calendar Daily Remaining

  2. Daily Remaining

  3. Weekly Remaining

  4. Monthly Remaining

  5. Calendar Daily

  6. Daily

  7. Weekly

  8. Monthly

The get_validations function is expectd to return a RDCValidation() object. Simply instantiate an instance of this class, set the validation properties you want and return it.

async def get_validations(
    self, remote_deposit_capture_request: RemoteDepositRequest
) -> RDCValidation:
    validations = RDCValidation()
    validations.weekly_amount = 7.00
    validations.weekly_count = 8
    validations.item_amount = 13
    return validations

Would produce a UI like this:

Warning

These values must be the appropriate types for the parameters (integers and decimals)

../../../_images/validations_example.png

The other optional functionality in this get_validations is to mark certain accounts as invalid for remote deposit capture. Maybe you want to remove certain accounts. You can do this by modifying the account list that is passed in and setting the is_valid property on the remote_deposit_capture_request.accounts objects:

remote_deposit_capture_request.accounts[0].is_valid = False

Capture RDC

Now its time to capture our first RDC transaction. The UI will send the collected values to the capture_rdc function. Your capture_rdc function is passed a RemoteDepositRequest() object. This object has accounts, selected_account, amount, back_image, front_image and (optionally, depending on UI config) check_number.

The capture_rdc function needs to return a RDCCaptureResponse() object. This object has host_transaction_id, end_user_messsage and an optional check_number. The check_number can be set if the RDC system parsed one out of the submitted image, if not, check_number can be left unset.

async def capture_rdc(
    self, remote_deposit_capture_request: RDCCaptureRequest
) -> RDCCaptureResponse:  # pragma: no cover
    return RDCCaptureResponse(host_transaction_id="123456")
../../../_images/desktop_success.png

Note

end_user_message is only used in the case of an error, success shows a generic message. Also, error modals do not show on the desktop test workflow, only in the actual mobile app.

async def capture_rdc(
    self, remote_deposit_capture_request: RDCCaptureRequest
) -> RDCCaptureResponse:  # pragma: no cover
    return RDCCaptureResponse(host_transaction_id="123456", success=False, end_user_message="Sorry! that didnt work")

Transaction List

Now that we have a successful transaction in the database (Q2_RemoteDepositHistory table if you are interested in looking) we can work on the Transaction APIs.

The first API that is called is the get_rdc_transaction_list. This API is responsible for decodating the transactions in the Q2 database with the latest status.

If the RDC system only supports history for a certain period of time, that is fine, only return back the list of transactions that the RDC system still has records for. Other history items in the Q2 DB will be ignored.

The key to the returned transaction list is that the host_transaction_id must match what was returned at capture time. You are also responsible for setting the approproate RemoteDepositStatus(). This can be one of Accepted, Submitted, or Rejected.

async def get_rdc_transaction_list(
    self, transaction_list_request: RemoteDepositRequest
) -> list[RDCTransactionResponse]:
    """Using the information provided in `transaction_list_request` param, return a list of Transaction objects"""
    self.logger.info(transaction_list_request)
    tran_1 = RDCTransactionResponse(
        "123456", "description1", "2024-08-26T18:14:32.717", "100.00",
        RemoteDepositStatus.Accepted,
        "x7954-12"
    )
    return [tran_1]

Transaction Details

Note

Q2’s mobile app history screen does not utilize the Transaction Details API.

Q2’s UI shows RDC history under Transactions -> Activity Center -> Deposited Checks.

../../../_images/activity_center_list.png ../../../_images/activity_center_item.png

With the transaction list populated with history, the next api to complete is get_rdc_transaction_detail. This API will be called when the end user clicks a transaction in the history list. This API is expected to provide back the details (including the front and back images). The images are not stored in the Q2 DB so the images will be retrieved as needed from the API. We provide a MockImage() object for you to try and see what image data looks like.

async def get_rdc_transaction_detail(
        self, transaction_detail_request: RDCTransactionHistoryDetailRequest
) -> RDCTransactionHistoryDetailResponse:
    self.logger.info(transaction_detail_request)
    return RDCTransactionHistoryDetailResponse(
        transaction_id=1,
        transaction_date="2024-06-17T09:38:29.267-05:00",
        transaction_amount="100.00",
        transaction_description="YES!",
        transaction_status=RemoteDepositStatus.Accepted,
        host_ref_number="416947816",
        account_number="x7954-12",
        front_image=RDCImage(MockImage.get_front(), ImageType.PNG),
        back_image=RDCImage(MockImage.get_back(), ImageType.PNG)
    )