Refactoring monthly fees (card.com)

Ashok Modi

About CARD.com

  • Offered debit cards with vanity branding
  • ~5 million cardholders
    • ~3-4 million active
  • 2 plans
    • Single cardholder ($6-10 per month depending on bank)
      • Waived fees if direct deposits
    • Family of cards ($20 per month)

About engineering team

  • 8 people
  • 2 lead, 3 senior, 3 mid
  • Primary monolith codebase
    • Drupal (PHP CMS/Framework)
  • 2 microservices
    • Email validation
    • Image derivative generation

History

  • Company reorg
  • Engineering team given responsibilities of Data Eng team
    • Sep data eng codebases
      • R Scripts
        • Charge customers fees

History (cont'd)

  • Feature request
    • Add new waivers for users that don't get charged
    • "Shouldn't take more than a week"

Review old code

  • 1000+ line R script
    • No separated functions
    • No real error handling
    • Not processing cardholders in parallel
  • Took over 24h to run

Results of review

  • Made more sense to rewrite code
    • Bring into monolith
      • Utilize existing classes around CardHolder
        • Figure out plans
        • Figure out waivers
      • Full test framework
      • Utilize queueing libraries

Requirements

  • Fee Schedules
    • Easy way to have a cardholder get billed on a given date
      • Support different fees for different products/banks
  • Fee waivers
    • Staff
    • Matched deposit amounts
    • Inactive accounts

Nice-to-haves

  • Track history
    • Know full history when a card was charged/credited monthly fees

Overall idea

  • Card acquired
    • Card added to a fee schedule in pending state
      • Have a scheduled job to check new cards with deposits
      • Once added, add card to first-fee-queue
        • Put card on a fee schedule to be charged every 30 days

Overall Idea (cont'd)

  • Recurring fee
  • Check cards that are active and have a past 'next_charge_date'
    • Add them to queue for processing
    • Queue runner processes card and updates 'next_charge_date' to 30 days from current date
    • Create new revision of fee schedule row for card

DB Structure (models)

  • card_fee
    • Amount proposed/waived/charged/collected
  • card_fee_(charge_allocation)
    • Corresponding tables logging and having auditable history
  • card_fee_schedule
    • Schedule of fees for cards
    • Associated revision table for tracking when data is updated

Core classes

  • AbstractFeeInvoice
    • Gets proposed amounts, schedules
  • AbstractFeeWaiver
    • Gets all credits to be applied to proposed fees
  • AbstractFeeAllocation
    • Allocates fees to be collected
  • AbstractFeeDriver
    • Makes API calls to charge customer

Code (Runners)

  • lib/cli
    • Set of cli entrypoints for running various tasks
      • queue-first-fees (every 5 minutes)
        • process-first-fees (background runner)
      • queue-recurring-fees (every 2 hours)
        • process-recurring-fees (background runner)

Queues

  • First Fee queue
    • When a customer was going to be charged the first time
      • Would later get added to the recurring fee queue
  • Recurring Fee queue
    • When a customer with a scheduled date gets charged

Examples

  • Show how config variables were set / tables were defined
  • Show code in lib
    • Show runner code
  • Show some of the tests

Outcome

  • 15%  revenue increase per month
  • All cards slated for processing completed within few hours
    • Slower processing of individual cardholder
    • Running in queue allowed proper parallelizing
    • More accurate
  • Everything validated via testing
  • Actually easier to add new functionality

Questions?

Thank you

Refactoring MMF

By btmash

Refactoring MMF

  • 439