CheckPlus

CheckPlus
CheckPlus
CheckPlus

Increasing the flow of check user transactions through digitization.

  • Discovery Workshop
  • UX Design
  • UI Design
  • Front End
  • MVP
  • My context

    Icalia Labs Custom Dev team 2024 | Digital Product designer/ Design Lead | Figma | Ruby on Rails | MPV design and development

    The Problems

    Checkplus, a payment insurer from northern Mexico, was struggling to grow its service due to a generational issue: they work with physical checks! This made their sales approval process a never-ending nightmare of calls for their sales validation department. If they wanted to expand their client portfolio without their processes collapsing, they needed to go digital. The solution is obvious: a digital platform that would process sales automatically, but the real question to solve was...

    Slow expansion due low process capacity

    Validation time could went from 30 min to 2 hours on bussy days

    How might we transition from traditional process to a digital solution?

    The Methodology

    Discovery
    Design
    Validate
    Implement
    Launch
    1 Week
    1 Day
    4 Months

    Discovery

    Understanding Checkplus

    To initiate the service, we conducted 3 discovery sessions aimed at, (if I may be redundant) discovering, how does your service work? Who are the actors in the validation process? How does the validation process function? What is the best-case scenario and the worst-case scenario? During these sessions, we mapped out 2 transaction flows for those that are paid, established the necessary actions for each participant in the service, and gathered the most significant insights for product ideation from the stakeholders.

    Stakeholder Insights
    Flow Actors
    Checkplus current process

    validating asumptions

    To validate our assumptions and understand real user needs, we conducted research with CheckPlus's existing user base, focusing on two primary user groups. We observed 8 users (4 sellers, 4 buyers) performing actual transactions with paper checks in their work environment, documenting pain points, workarounds, and time spent on each step. with this research we defined our user personas, their user side concerns and a new proposal for the transaction validation method that ends their problems.

    User Personas
    Key findings
    Checkplus new digital flow

    Design

    Replacing the paper check

    During these conversations, we studied different ways to replace the paper check with a digital item. We started with the idea of turning it into a "digital credit with a card" but this created misunderstandings with stakeholders about their service, as they are not a fintech and did not want to be associated with one. This insight cleared up many doubts and options; ultimately, we decided to use a QR code that contained the transaction data as a way to digitize a check.

    QR Instead of checks

    We transform the process of filling a paper check into activating a QR, this with the objective of keeping the “portability” of the check and provide control to the buyer credit line.

    more of this in “MANAGING STAFF”

    From 30 minutes validation

    To Instantly

    We transform the process of filling a paper check into activating a QR, this with the objective of keeping the “portability” of the check and provide control to the buyer credit line.

    more of this in “MANAGING STAFF”

    Design

    Tracking the transactions

    Aa mentioned before, Checkplus had a platform where they display transaction statues for their users this display was only informative and users couldn’t do any action on those transactions, like validate payments or report no payments. I design a a new transaction system where users can not only view their transactions but have control over them with out the intervening of CheckPlus.

    Displaying transactions

    After analyze the service flow we identified all the possible scenarios for a transaction, which was nothing more than 10 possible statues, this was a challenge in terms of information display, i wanted to keep the transaction cards informative but compacted due the amount of transaction a single sealer could have. i identified the most important information from a transaction to be displayed in the preview and categorize them in 4 different types: Expired, Due, Paid and Canceled

    checkplus transaction status

    Categorizing transactions

    I split the transactions into four categories for an easy read and understatement, this division was reinforced with a traffic light systems per transaction status

    Options by status

    The truth is that from the buyer side the only action they can perform is upload payment receipts for the seller to validate. But from the seller side, they can perform different actions based on transaction status.

    Buyer actions:

    • Upload payment receipts
    • View Payment receipts
    • Change payment receipts

    Seller actions:

    • Cancel sales
    • Provide payment extensions.
    • View Receipts
    • Validate or reject payment receipts
    • Report sales

    Design

    Staff members

    A common scenario that came to mind while our discover sessions was that Account owners give paper checks to their employees to perform sales under their names and also the existence of cashiers from the seller side. Over this line it was obvious we have to give both buyers and sellers the feature to have a staff to help them perform purchases and sales. I set 3 state for staff members, "Active", "unactive" and "invited" and 3 main roles, "Owner" the account owner, "Admin" account admin and "Seller or Buyer" the basic role to performance sales or purchases.

    Managing Staff

    An owner or administrator can manage their employees, view their recent activity, and invite or deactivate them as needed. Additionally, administrators and owners on the buying side can assign purchase codes to other members of their staff.

    Implementation

    Design System Translation: Staying Tailwind-Native

    The development team chose Ruby on Rails for its rapid back-end/front-end integration. Rails is powerful, except, it"s not the most front-end friendly framework. This challenge is typically solved with a CSS framework like Tailwind, which is exactly what we did.


    Since the team was already using Tailwind, I took a logic decision: design with Tailwind classes from the start rather than creating a fully custom system that would need translation. In Figma, I documented every component with the actual Tailwind utility classes developers would use, some examples:

    Full version
    Simplified version

    Figma to code

    Either way at the moment of setting up the project the team was struggling to connect Checkplus system with the new platform, for that reason i jumped in to help the front implementation translating all the classes components into custom CSS classes keeping tailwind utilities @apply:

    Component Variants Without JavaScript

    Rails's lack of traditional component variants created a challenge for transaction cards, which needed ~10 different visual states based on status. Writing long chains of if/else statements or switch cases would be messy and hard to maintain.


    My solution: leverage CSS attribute selectors with data attributes.The back-end returns the transaction status, and the front-end styling responds automatically based on that data-status value. No JavaScript loops, no complex conditionals—just clean, declarative CSS.

    How it works:
    The component receives a status from the back-end: using a rails utility:

    code helper


    CSS handles all 10 variants using attribute selectors:

    code helper

    Try it:

    12/10/25
    Without receipt
    Emanuel Del Monte
    $100,000 MXN

    expiration

    21 Days

    psst: You can inspect the code to view the changes in datavalues

    New branch: design/component_fix’

    Once I started reviewing the codebase, I discovered a problem: most of the front-end was built inline. Developers were writing screens line-by-line because Rails doesn't have traditional component architecture like React or Vue.br This approach works initially, but it's neither scalable nor maintainable for future iterations.

    For this reason i translated the repetitive code into custom rail partials, so ui elements as transactions cards, user cards and buttons became modular components that could be called with simple parameters.

    This work not only made the future sprints easier and faster but also improve the code documentation and performance reducing the lines of code use per screen, as showed below where the integration of UI partials reduced the view code from 180 to 100 lines, almost 50%:

    Old code: index.html.erb | 180 lines
    <div class="flex flex-col space-y-4 section-container lg-adjustment lg:relative" data-controller="ventas--ventas"> <%= render 'shared/header', title: 'Ventas' %> <div class="flex gap-2 items-center "> <!-- Botones de navegación --> <div class="grid grid-cols-4 bg-black rounded-full p-1 gap-1 w-full"> <button data-action="click->ventas--ventas#mostrarNuevas click->ventas--ventas#activarBoton" class="nav-button text-center py-2 text-xs rounded-full text-white transition-colors ease-in-out duration-300 bg-brand-500" > Vencidas </button> <button data-action="click->ventas--ventas#mostrarPorVencer click->ventas--ventas#activarBoton" class="nav-button text-center py-2 text-xs rounded-full text-white transition-colors ease-in-out duration-300" > Por vencer </button> <button data-action="click->ventas--ventas#mostrarVencidas click->ventas--ventas#activarBoton" class="nav-button text-center py-2 text-xs rounded-full text-white transition-colors ease-in-out duration-300" > Pagadas </button> <button data-action="click->ventas--ventas#mostrarCanceladas click->ventas--ventas#activarBoton" class="nav-button text-center py-2 text-xs rounded-full text-white transition-colors ease-in-out duration-300" > Canceladas </button> </div> <% if current_user.admin? %> <%= link_to reporte_ventas_path(format: :xlsx), class: 'lg-btn-right btn-download' do %> <span class="hidden lg:block">Descargar</span> <%= image_tag 'xlsx.svg', alt: 'Descargar reporte de ventas', class: 'w-4 h-4' %> <% end %> <% end %> </div> <!-- Ventas Vencidas --> <section id="nuevas" data-ventas--ventas-target="nuevas" class="ventas-seccion space-y-2 mt-2"> <% if @ventas_vencidas.any? %> <div class='text-sm text-neutral-500 flex flex-row justify-between'> <p class='basis-2/3'>Saldo <span class='text-base font-semibold'><%= format_mxn @ventas_vencidas.sum(&:monto) %></span> </p> <p class='basis-1/3 text-end'> Transacciones <span class='text-base font-semibold'><%= @ventas_vencidas.length %></span></p> </div> <% @ventas_vencidas.each do |venta| %> <%= link_to venta_path(venta), class: 'transaction-card mb-2 block' do %> <div class="info-container"> <div class="date-container flex justify-between items-center"> <div class="date flex items-center gap-2"> <span class="material-symbols-outlined text-sm">receipt_long</span> <%= venta.fecha.strftime('%d/%m/%Y') %> </div> <%= vendedor_venta_estatus_badge(venta) %> </div> <div class="name mt-2 text-lg"><%= venta.comprador.empresa.name %></div> <div class="amount mt-2 font-bold"> $<%= number_to_currency(venta.monto, unit: 'MXN', precision: 2, format: "%n %u") %> </div> </div> <%= venta_validity(venta) %> <% end %> <% end %> <% else %> <div class="flex flex-col items-center justify-center mt-28"> <p class="text-lg font-bold mb-8">No tienes ventas vencidas</p> <span class="material-symbols-outlined text-neutral-950 mb-8" style="font-size: 100px">sentiment_satisfied</span> <p class="text-sm text-center px-10"> Cuando alguna transacción <span class="font-bold text-brand-500">supere el plazo de pago establecido</span> aparecerá en esta pantalla dónde podrás realizar los <span class="font-bold text-brand-500">reportes correspondientes</span> </p> </div> <% end %> </section> <!-- Ventas Por Vencer --> <section id="por_vencer" data-ventas--ventas-target="por_vencer" class="ventas-seccion space-y-2 mt-2 hidden"> <% if @ventas_por_vencer.any? %> <div class='text-sm text-neutral-500 flex flex-row justify-between'> <p class='basis-2/3'>Saldo <span class='text-base font-semibold'><%= format_mxn @ventas_por_vencer.sum(&:monto) %></span> </p> <p class='basis-1/3 text-end'> Transacciones <span class='text-base font-semibold'><%= @ventas_por_vencer.length %></span></p> </div> <% @ventas_por_vencer.each do |venta| %> <%= link_to venta_path(venta), class: 'transaction-card mb-2 block' do %> <div class="info-container"> <div class="date-container"> <div class="date"> <span class="material-symbols-outlined" style="font-size:14px">receipt_long</span> <%= venta.fecha.strftime('%d/%m/%Y') %> </div> <%= vendedor_venta_estatus_badge(venta) %> </div> <div class="name"><%= venta.comprador.empresa.name %></div> <div class="amount"> $<%= number_to_currency(venta.monto, unit: 'MXN', precision: 2, format: "%n %u") %> </div> </div> <%= venta_validity(venta) %> <% end %> <% end %> <% else %> <div class="flex flex-col items-center justify-center mt-28"> <p class="text-lg font-bold mb-8">No tienes ventas activas</p> <span class="material-symbols-outlined text-neutral-950 mb-8" style="font-size: 100px">shopping_bag</span> <p class="text-sm text-center px-10"> <span class="font-bold text-brand-500">Invita a tus compradores a comprar con CheckPlus</span> y haz crecer tus ventas de una forma más fácil y segura </p> </div> <% end %> </section> <!-- Ventas Canceladas --> <section id="cancelada" data-ventas--ventas-target="canceladas" class="ventas-seccion space-y-2 mt-2 hidden"> <% if @ventas_canceladas.any? %> <div class='text-sm text-neutral-500 flex flex-row justify-between'> <p class='basis-2/3'>Saldo <span class='text-base font-semibold'><%= format_mxn @ventas_canceladas.sum(&:monto) %></span> </p> <p class='basis-1/3 text-end'> Transacciones <span class='text-base font-semibold'><%= @ventas_canceladas.length %></span></p> </div> <% @ventas_canceladas.each do |venta| %> <%= link_to venta_path(venta), class: 'transaction-card mb-2' do %> <div class="info-container"> <div class="date-container"> <div class="date"> <span class="material-symbols-outlined" style="font-size:14px">receipt_long</span> <%= venta.fecha.strftime('%d/%m/%Y') %> </div> <%= vendedor_venta_estatus_badge(venta) %> </div> <div class="name"><%= venta.comprador.empresa.name %></div> <div class="amount"> $<%= number_to_currency(venta.monto, unit: 'MXN', precision: 2, format: "%n %u") %> </div> </div> <%= venta_validity(venta) %> <% end %> <% end %> <% else %> <div class="flex flex-col items-center justify-center mt-28"> <p class="text-lg font-bold mb-8">No tienes ventas canceladas</p> <span class="material-symbols-outlined text-neutral-950 mb-8" style="font-size: 100px">sentiment_satisfied</span> <p class="text-sm text-center px-10"> Recuerda que puedes <span class="font-bold text-brand-500">cancelar</span> una compra <span class="font-bold text-brand-500">hasta 24 hrs después de la misma</span> en caso de ser necesario </p> </div> <% end %> </section> <!-- Ventas Vencidas --> <section id="vencidas" data-ventas--ventas-target="vencidas" class="ventas-seccion space-y-2 mt-2 hidden "> <% if @ventas_pagadas.any? %> <div class='text-sm text-neutral-500 flex flex-row justify-between'> <p class='basis-2/3'>Saldo <span class='text-base font-semibold'><%= format_mxn @ventas_pagadas.sum(&:monto) %></span> </p> <p class='basis-1/3 text-end'> Transacciones <span class='text-base font-semibold'><%= @ventas_pagadas.length %></span></p> </div> <% @ventas_pagadas.each do |venta| %> <%= link_to venta_path(venta), class: 'transaction-card mb-2' do %> <div class="info-container"> <div class="date-container"> <div class="date"> <span class="material-symbols-outlined" style="font-size:14px">receipt_long</span> <%= venta.fecha.strftime('%d/%m/%Y') %> </div> <%= vendedor_venta_estatus_badge(venta) %> </div> <div class="name"><%= venta.comprador.empresa.name %></div> <div class="amount"> $<%= number_to_currency(venta.monto, unit: 'MXN', precision: 2, format: "%n %u") %> </div> </div> <%= venta_validity(venta) %> <% end %> <% end %> <% else %> <div class="flex flex-col items-center justify-center mt-28"> <p class="text-lg font-bold mb-8">No tienes ventas pagadas</p> <span class="material-symbols-outlined text-neutral-950 mb-8" style="font-size: 100px">priority</span> <p class="text-sm text-center px-10"> <span class="font-bold text-brand-500">Invita a tus compradores a comprar con CheckPlus</span> y haz crecer tus ventas de una forma más fácil y segura </p> </div> <% end %> </section> </div>
    New code: index.html.erb | 100 lines
    <section class="flex flex-col space-y-4 section-container lg-adjustment lg:relative h-auto" data-controller="ventas--ventas"> <%= render 'shared/header', title: 'Compras' %> <div class="flex gap-2 items-center"> <!-- Botones de navegación --> <div class="grid grid-cols-4 bg-black rounded-full p-1 space-x-1 w-full"> <button data-action="click->ventas--ventas#mostrarNuevas click->ventas--ventas#activarBoton" class="nav-button py-2 text-xs rounded-full text-white transition-colors ease-in-out duration-300 bg-brand-500" > Vencidas </button> <button data-action="click->ventas--ventas#mostrarPorVencer click->ventas--ventas#activarBoton" class="nav-button py-2 text-xs rounded-full text-white transition-colors ease-in-out duration-300" > Por vencer </button> <button data-action="click->ventas--ventas#mostrarVencidas click->ventas--ventas#activarBoton" class="nav-button py-2 text-xs rounded-full text-white transition-colors ease-in-out duration-300" > Pagadas </button> <button data-action="click->ventas--ventas#mostrarCanceladas click->ventas--ventas#activarBoton" class="nav-button py-2 text-xs rounded-full text-white transition-colors ease-in-out duration-300" > Canceladas </button> </div> <% if current_user.admin? %> <%= link_to reporte_compras_path(format: :xlsx), class: 'btn-download lg-btn-right' do %> <span class="hidden lg:block">Descargar</span> <%= image_tag 'xlsx.svg', alt: 'Descargar reporte de ventas', class: 'w-4 h-4' %> <% end %> <% end %> </div> <div> <!-- Ventas Vencidas --> <section id="nuevas" data-ventas--ventas-target="nuevas" class="ventas-seccion space-y-2 mt-2"> <% if @compras_vencidas.any? %> <%= render "shared/transaction_balance", transactions: @compras_vencidas %> <% @compras_vencidas.each do |venta| %> <%= render 'shared/compra', compra: venta %> <% end %> <% else %> <%= render 'shared/empty_transactions', message: 'No tienes compras vencidas', icon:'sentiment_satisfied', description: 'Recuerda <span class="font-bold text-brand-500">pagar tus compras antes de las fechas límite</span> para generar un buen historial en <span class="font-bold text-brand-500">CheckPlus</span>' %> <% end %> </section> <!-- Ventas Por Vencer --> <section id="por_vencer" data-ventas--ventas-target="por_vencer" class="ventas-seccion space-y-2 mt-2 hidden"> <% if @compras_por_vencer.any? %> <%= render "shared/transaction_balance", transactions: @compras_por_vencer %> <% @compras_por_vencer.each do |venta| %> <%= render 'shared/compra', compra: venta %> <% end %> <% else %> <%= render 'shared/empty_transactions', message: 'No tienes compras activas', icon:'calendar_clock', description: 'Realiza una compra para empezar a usar tu crédito <span class="font-bold text-brand-500">CheckPlus</span>' %> <% end %> </section> <!-- Ventas Canceladas --> <section id="cancelada" data-ventas--ventas-target="canceladas" class="ventas-seccion space-y-2 mt-2 hidden"> <% if @compras_canceladas.any? %> <%= render "shared/transaction_balance", transactions: @compras_canceladas %> <% @compras_canceladas.each do |venta| %> <%= render 'shared/compra', compra: venta %> <% end %> <% else %> <%= render 'shared/empty_transactions', message: 'No tienes compras canceladas', icon:'sentiment_satisfied', description: 'Recuerda que puedes <span class="font-bold text-brand-500">cancelar</span> una compra <span class="font-bold text-brand-500">hasta 24 hrs después de la misma</span> en caso de ser necesario, poniéndote en contacto con tu vendedor' %> <% end %> </section> <!-- Ventas Pagadas --> <section id="vencidas" data-ventas--ventas-target="vencidas" class="ventas-seccion space-y-2 mt-2 hidden"> <% if @compras_pagadas.any? %> <%= render "shared/transaction_balance", transactions: @compras_pagadas %> <% @compras_pagadas.each do |venta| %> <%= render 'shared/compra', compra: venta %> <% end %> <% else %> <%= render 'shared/empty_transactions', message: 'No tienes compras pagadas', icon:'priority', description: 'Recuerda <span class="font-bold text-brand-500">pagar tus compras antes de las fechas límite</span> para generar un buen historial en <span class="font-bold text-brand-500">CheckPlus</span>' %> <% end %> </section> </div> </section>

    Validation

    Adjusting designs decisions

    Throughout the design, planing and development phases were moments that make me do minimal changes for the good of the overall process

    One platform, two experiences

    Due this was a web platform and not an mobile app, we packed everything in one platform where base on your user you get to one experience or another, i also make sure they look clear different using brand colors to avoid experience mistakes not just for the users but developers know what path they were working on.

    Devices without Camera?

    A key moment in the final stages of development was a conversation we had regarding sub-users and portability, where we noted that some collectors are "static" Our initial proposal was for a highly portable platform that could primarily be accessed on mobile devices; however, we made adaptations to ensure it could also be accessible from a computer that does not have a camera to scan codes, starting with the addition of a numeric code alongside the QR code as another method of validation.

    Launch

    Adopting a new payment method

    After the launch of the platform, we held conversations with 15 former users of the CheckPlus service and 5 users who were just starting with the service to gather their impressions on the new format of the service and measure against our success metrics.

    The platform successfully met our primary technical benchmarks. Transaction processing time dropped from an average of 42 minutes to under 5 minutes, exceeding our goal. The validation department reported a 68% reduction in incoming calls, nearly hitting our 70% target. However, user adoption and task completion revealed a more nuanced picture.

    72%

    of historical users felt enthusiastic about the new platform

    83%

    of new users showed interest in the renewed service

    90%

    Task completation on main task

    QR Creation and purchase completation

    65%

    Task completation on secondary tasks

    Upload receipt, validate and report buyers..

    While we achieved a solid 76% satisfaction score (approaching our 80% target), and core functions like QR activation and basic payment validation performed excellently with 92% first-time completion, the results revealed an important challenge. Despite understanding the benefits of the new service format, some former CheckPlus users—particularly those like our persona Roberto—faced difficulties completing specific secondary tasks such as reporting a sale, requesting an extension, or validating payment receipts. These workflows, which due to time constraints received the least development and testing attention, showed only 68% first-time task completion, falling short of our 90% goal.

    The data told a clear story: users valued the digital transformation and found core functions intuitive, but the complexity of edge-case workflows needed better guidance. This was especially true for traditional users making the transition from familiar paper-based processes to digital-first interactions.

    transition helpers

    Despite having received good validation from the interviewees, we decided to take their recommendations to implement actions on our side that could facilitate a better transition from the old service to the new one. Since we no longer had time for development, our efforts were focused on creating "physical" materials by producing and delivering two digital user manuals, one forThe buyer and the other for The seller (both in spanish).

    Quick guide
    Log-in
    Transactions
    Account report

    Recap and learns

    What would i do different

    There is obviously a significant burden when developing an application solely based on stakeholders as the only reference for how the application should be. This is something very common that I regularly have to deal with due to the nature of MVPs; budgets are not very high and are completely focused on development. Without a doubt, if I were to go through this situation again, I would try to conduct additional validations with users of the platform during the design phase, not just at the end of the MVP.

    Even though my entire focus was on transforming a conventional process into a digital one, I dedicated little time to onboarding within the application. This was later attempted to be addressed with a manual due to development time and costs; however, the idea of having everything within the platform without the need to leave it is the ideal for any application. This has helped me think more carefully about how to communicate the use of a new tool to a new user in future projects.

    What did i learn?

    Due to the development time and the complexity it posed for the developers to adapt to the system they already had, the project was delayed a bit. This prompted me to raise my hand and offer to help with the front-end layout, which allowed me to improve my technical skills with Ruby on Rails, gaining a better understanding of how to use action views and active records in Rails. Additionally, I learned how to enhance the delivery of designs and components specifically tailored for this language, improving my compositions to work better with Rails layouts and partials.