<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Troy Johnston</title>
    <description>The latest articles on DEV Community by Troy Johnston (@stackeasy).</description>
    <link>https://dev.to/stackeasy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3802814%2F63c41ab8-96be-47e0-bd3b-573aaa0f46a5.png</url>
      <title>DEV Community: Troy Johnston</title>
      <link>https://dev.to/stackeasy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/stackeasy"/>
    <language>en</language>
    <item>
      <title>The JavaScript Logic Behind Tracking 14 Credit Card APR Deadlines (Without Losing Your Mind)</title>
      <dc:creator>Troy Johnston</dc:creator>
      <pubDate>Fri, 17 Apr 2026 20:17:10 +0000</pubDate>
      <link>https://dev.to/stackeasy/the-javascript-logic-behind-tracking-14-credit-card-apr-deadlines-without-losing-your-mind-3l6h</link>
      <guid>https://dev.to/stackeasy/the-javascript-logic-behind-tracking-14-credit-card-apr-deadlines-without-losing-your-mind-3l6h</guid>
      <description>&lt;p&gt;I run 14 credit cards simultaneously.&lt;/p&gt;

&lt;p&gt;Not because I'm reckless — because I understand how to use 0% APR introductory periods as free short-term financing. Done right, it's a legitimate way to smooth cash flow, fund purchases, or carry a balance for 12-21 months without paying a cent in interest.&lt;/p&gt;

&lt;p&gt;Done wrong — miss one deadline — and you're looking at retroactive interest charges that can wipe out months of careful management.&lt;/p&gt;

&lt;p&gt;This is the problem I set out to solve with code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Problem: Date Math Is Harder Than It Looks
&lt;/h2&gt;

&lt;p&gt;Every 0% APR card has a deadline. But the deadline isn't just "the date on the agreement." It's a function of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When the card was approved (not always when you first used it)&lt;/li&gt;
&lt;li&gt;The promotional period length (12, 15, 18, or 21 months)&lt;/li&gt;
&lt;li&gt;The exact billing cycle the promotion ends on&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's the naive approach most people take:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Don't do this&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deadline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;approvalDate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setMonth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMonth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;promoMonths&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem? This doesn't account for billing cycles. Your card's promotional period ends on a &lt;em&gt;statement date&lt;/em&gt;, not the anniversary of approval. If your billing cycle closes on the 15th and your promo ends "after 12 months," you might actually have until the 15th of the 13th month — or you might lose the promo two weeks earlier than you thought.&lt;/p&gt;

&lt;h2&gt;
  
  
  A More Reliable Model
&lt;/h2&gt;

&lt;p&gt;Here's the logic I built to handle this properly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getAprDeadline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;approvalDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;promoMonths&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cycleClosingDay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;approval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;approvalDate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Calculate raw deadline&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rawDeadline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;approval&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;rawDeadline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setMonth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawDeadline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMonth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;promoMonths&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Find the billing cycle close BEFORE the raw deadline&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deadlineMonth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rawDeadline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMonth&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deadlineYear&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rawDeadline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFullYear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cycleClose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deadlineYear&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deadlineMonth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cycleClosingDay&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// If cycle closing day is after raw deadline, use the previous cycle&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cycleClose&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;rawDeadline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cycleClose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setMonth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cycleClose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMonth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cycleClose&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Usage&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deadline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getAprDeadline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2023-11-15&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toDateString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// The actual safe payoff date&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you the last billing cycle close &lt;em&gt;before&lt;/em&gt; the promotion expires — which is the date your balance needs to hit zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Alert Thresholds
&lt;/h2&gt;

&lt;p&gt;Knowing the deadline is step one. The second piece is tiered alerts. I set three:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getAlertStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;daysLeft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;deadline&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;daysLeft&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CRITICAL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;daysLeft&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;daysLeft&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WARNING&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;daysLeft&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;daysLeft&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HEADS_UP&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;daysLeft&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OK&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;daysLeft&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here's where it gets useful at scale — looping over all cards and sorting by urgency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cards&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Chase Freedom Flex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;approvalDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2023-06-01&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;promoMonths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;cycleClosingDay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Citi Simplicity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;approvalDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2023-09-10&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;promoMonths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;cycleClosingDay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Wells Fargo Reflect&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;approvalDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2024-01-05&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;promoMonths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;cycleClosingDay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// ... 11 more&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dashboard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cards&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getAprDeadline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;approvalDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;promoMonths&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cycleClosingDay&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getAlertStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deadline&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;dashboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;daysLeft&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;daysLeft&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; days left (deadline: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toDateString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[CRITICAL] Chase Freedom Flex: 23 days left (deadline: Mon Apr 28 2026)
[WARNING] Citi Simplicity: 47 days left (deadline: Fri May 22 2026)
[OK] Wells Fargo Reflect: 112 days left (deadline: Mon Jul 27 2026)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Minimum Payment Problem
&lt;/h2&gt;

&lt;p&gt;Here's a trap I see developers fall into when building this kind of tracker: they focus on the deadline, not the minimum payment.&lt;/p&gt;

&lt;p&gt;Even with a 0% APR card, you &lt;em&gt;must&lt;/em&gt; make minimum payments every month. Miss one, and many issuers will immediately terminate your promotional rate. So the dashboard needs to track two separate things per card:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;APR deadline&lt;/strong&gt; — when the full balance must be paid&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next minimum payment due&lt;/strong&gt; — monthly, non-negotiable&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I added a second layer to the data model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Chase Freedom Flex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;approvalDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2023-06-01&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;promoMonths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;cycleClosingDay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;currentBalance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;minimumPaymentDue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-04-28&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;minimumPaymentAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a function that calculates the monthly paydown needed to hit zero before deadline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getRequiredMonthlyPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;monthsLeft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFullYear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFullYear&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt; 
    &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMonth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMonth&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;monthsLeft&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Pay it all now&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;monthsLeft&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Result: how much to pay each month to clear the balance in time&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getRequiredMonthlyPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// e.g., "$200/month"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What I Learned (And What I Eventually Used)
&lt;/h2&gt;

&lt;p&gt;After building this out, I realized maintaining the logic for 14 cards manually — with JSON files, cron jobs for alerts, and hand-updated billing cycles — was its own part-time job.&lt;/p&gt;

&lt;p&gt;I eventually found &lt;a href="https://stackeasy.ai" rel="noopener noreferrer"&gt;StackEasy&lt;/a&gt;, which handles this whole layer for you: tracks APR windows, sends deadline alerts, and shows the monthly paydown math without requiring you to maintain the underlying code.&lt;/p&gt;

&lt;p&gt;But the exercise of building it myself first was worth it. I understood the edge cases: what happens when a promotional rate ends mid-cycle, how grace periods affect payoff timing, why the "months remaining" counter on most apps is an oversimplification.&lt;/p&gt;

&lt;p&gt;If you're building something similar, the key insight is: &lt;strong&gt;the deadline isn't a date — it's a billing cycle.&lt;/strong&gt; Model it that way from the start and you'll avoid a lot of pain.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Full Pattern
&lt;/h2&gt;

&lt;p&gt;For anyone who wants to build their own version, here's the complete minimal tracker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AprTracker&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cards&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cards&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cards&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;getDeadline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;approval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;approvalDate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;approval&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setMonth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMonth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;promoMonths&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cycle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFullYear&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMonth&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cycleClosingDay&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cycle&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;cycle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setMonth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cycle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMonth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cycle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;getDaysLeft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;deadline&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;86400000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;getMonthlyPaydown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;months&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFullYear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFullYear&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;
      &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMonth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMonth&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;months&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;months&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cards&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deadline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDeadline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;daysLeft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDaysLeft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;monthly&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMonthlyPaydown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;daysLeft&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;monthly&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;daysLeft&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;daysLeft&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tracker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AprTracker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cards&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tracker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Drop this in a cron job with a Slack webhook and you have a functional APR deadline system.&lt;/p&gt;




&lt;p&gt;Happy to share the full implementation with cron scheduling and Slack alerts if there's interest — let me know in the comments.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>fintech</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How I Built a Credit Card Management Dashboard (And Why Spreadsheets Were Not Cutting It)</title>
      <dc:creator>Troy Johnston</dc:creator>
      <pubDate>Tue, 03 Mar 2026 02:56:36 +0000</pubDate>
      <link>https://dev.to/stackeasy/how-i-built-a-credit-card-management-dashboard-and-why-spreadsheets-were-not-cutting-it-il1</link>
      <guid>https://dev.to/stackeasy/how-i-built-a-credit-card-management-dashboard-and-why-spreadsheets-were-not-cutting-it-il1</guid>
      <description>&lt;p&gt;I had 17 credit cards open. Each one had a purpose. 0% APR on a balance transfer expiring in four months. A business card earning 3x on advertising. A personal card with rotating 5% categories. Another business line I used specifically for inventory purchases.&lt;/p&gt;

&lt;p&gt;I tracked all of this in a Google Sheet. Columns for card name, issuer, credit limit, current balance, APR, promo rate, promo expiration date, annual fee date, reward structure, and notes. Color-coded rows for urgency. Conditional formatting that was supposed to flag upcoming deadlines.&lt;/p&gt;

&lt;p&gt;It worked for about two years. Then it stopped working.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Breaking Point
&lt;/h2&gt;

&lt;p&gt;I missed a 0% APR expiration on a card carrying a $12,000 balance. The promotional rate ended on a Tuesday. I did not check the spreadsheet that week. The standard APR kicked in. By the time I noticed, I owed interest I never needed to pay.&lt;/p&gt;

&lt;p&gt;The spreadsheet had the date in it. The conditional formatting was set up correctly. The problem was not the data. The problem was that a spreadsheet does not tap you on the shoulder and say "hey, you have 7 days before this rate expires and you still have $12,000 on this card."&lt;/p&gt;

&lt;p&gt;That was the moment I decided to build something better.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Actually Needed
&lt;/h2&gt;

&lt;p&gt;Before writing any code, I wrote down the things that had gone wrong over two years of spreadsheet-based card management:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No proactive alerts.&lt;/strong&gt; The spreadsheet shows data. It does not push notifications. I had to remember to check it, and I did not always remember.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Utilization was manual math.&lt;/strong&gt; Calculating total credit utilization across 17 cards meant updating balances, summing them, dividing by total limits. Every time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No "which card should I use" logic.&lt;/strong&gt; I knew the reward structures in my head, mostly. But I regularly used the wrong card at the wrong merchant because I was not thinking about it in the moment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Annual fee decisions were reactive.&lt;/strong&gt; I would notice the fee on my statement instead of evaluating whether to keep the card before the fee hit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application tracking was a mess.&lt;/strong&gt; Am I under Chase's 5/24? When was my last application with Amex? I had to count manually.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Technical Approach
&lt;/h2&gt;

&lt;p&gt;I am not going to pretend this needed bleeding-edge technology. The core problem is data management and timely notifications. Here is what mattered:&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Model
&lt;/h3&gt;

&lt;p&gt;Every credit card is an object with these core properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- Card name / issuer
- Credit limit
- Current balance (manual or API-synced)
- Standard APR
- Promotional rate (if any)
- Promotional rate expiration date
- Annual fee amount
- Annual fee date
- Reward structure (categories + multipliers)
- Application date
- Last statement date
- Payment due date
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tricky part is that some of these properties are time-dependent. A card's effective APR changes when the promo expires. Reward categories rotate quarterly. Annual fees create decision points that need to be surfaced before the charge hits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deadline Engine
&lt;/h3&gt;

&lt;p&gt;The most important feature. Every card generates a set of deadlines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Promo rate expiration (alert at 90, 60, 30, 7 days)&lt;/li&gt;
&lt;li&gt;Annual fee renewal (alert at 60 and 30 days before)&lt;/li&gt;
&lt;li&gt;Payment due date (alert at 7 and 3 days before)&lt;/li&gt;
&lt;li&gt;Rotating category activation windows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each deadline has a severity level and an associated action. "Your Citi card's 0% APR expires in 30 days. Current balance: $8,400. Action: pay off or transfer." That context is what a spreadsheet cannot provide.&lt;/p&gt;

&lt;h3&gt;
  
  
  Utilization Calculator
&lt;/h3&gt;

&lt;p&gt;Real-time (or near-real-time) calculation of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Total utilization across all cards&lt;/li&gt;
&lt;li&gt;Per-card utilization&lt;/li&gt;
&lt;li&gt;Utilization impact if you move a balance from Card A to Card B&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This sounds simple, but it becomes genuinely useful when you are deciding whether to put a $3,000 purchase on your Chase card (pushing that card to 45% utilization) or your Amex (which stays at 12%). The per-card utilization matters for your credit score, not just the total.&lt;/p&gt;

&lt;h3&gt;
  
  
  Card Selector
&lt;/h3&gt;

&lt;p&gt;Given a spending category (groceries, gas, dining, online, etc.), which card in your portfolio earns the best return? This requires knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Base reward rate per card&lt;/li&gt;
&lt;li&gt;Bonus categories per card (including rotating quarterly bonuses)&lt;/li&gt;
&lt;li&gt;Whether quarterly bonuses have been activated&lt;/li&gt;
&lt;li&gt;Any spending caps on bonus categories&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is essentially a lookup function, but one that changes quarterly and needs to account for caps and activation status.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned Building This
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Financial data is messy
&lt;/h3&gt;

&lt;p&gt;Credit card terms are not standardized. One issuer calls it "promotional APR." Another calls it "introductory rate." The expiration logic varies. Some promos end on a specific date. Others end "18 months after account opening," which means you need to calculate the date from the application date.&lt;/p&gt;

&lt;h3&gt;
  
  
  People do not update manually
&lt;/h3&gt;

&lt;p&gt;I built the first version with manual balance entry. Nobody used it consistently. The dashboard was only useful when the data was current, and manual data entry creates a chicken-and-egg problem. I had to find ways to minimize manual input or make it frictionless.&lt;/p&gt;

&lt;h3&gt;
  
  
  Notifications are the product
&lt;/h3&gt;

&lt;p&gt;The dashboard is nice. The charts are helpful. But the thing that actually prevents expensive mistakes is the notification that arrives three days before a promo rate expires. Everything else is secondary to that alert engine.&lt;/p&gt;

&lt;h3&gt;
  
  
  YMYL content requires extreme care
&lt;/h3&gt;

&lt;p&gt;Building in the financial space means your content is classified as "Your Money, Your Life" by search engines. Google holds YMYL content to a higher standard. Every number needs to be accurate. Every claim needs to be defensible. You cannot publish sloppy financial advice and expect to rank.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current State
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.stackeasy.ai?utm_source=devto&amp;amp;utm_medium=cross-publish&amp;amp;utm_campaign=builder-story" rel="noopener noreferrer"&gt;StackEasy&lt;/a&gt; is live and people are using it. The core features that matter most:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Portfolio dashboard&lt;/strong&gt; showing all cards, balances, limits, and utilization at a glance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deadline tracker&lt;/strong&gt; with proactive alerts for promo expirations, annual fees, and payment dates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Card selector&lt;/strong&gt; that recommends the best card for each spending category&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application log&lt;/strong&gt; that tracks your velocity across issuers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The biggest validation was when users told me they caught a promo expiration they would have missed. That is the whole point. Not the charts, not the UI polish. Did you avoid losing money you did not need to lose?&lt;/p&gt;

&lt;h2&gt;
  
  
  For Other Builders
&lt;/h2&gt;

&lt;p&gt;If you are thinking about building in fintech, here are the non-obvious lessons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start with your own pain.&lt;/strong&gt; I built StackEasy because I needed it. That meant I understood the edge cases before writing a single line of code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Management tools are undervalued.&lt;/strong&gt; Everyone builds tools to help people find or buy financial products. Almost nobody builds tools to help people manage what they already have.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple beats clever.&lt;/strong&gt; A deadline alert that fires on time is worth more than a machine learning model that predicts spending patterns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trust is everything.&lt;/strong&gt; People are entering their financial data. Earn that trust through transparency, security, and never being clever with their information.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;If you are managing multiple credit cards and a spreadsheet is starting to crack, check out &lt;a href="https://www.stackeasy.ai?utm_source=devto&amp;amp;utm_medium=cross-publish&amp;amp;utm_campaign=builder-story" rel="noopener noreferrer"&gt;StackEasy&lt;/a&gt;. Happy to answer questions about the build in the comments.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>startup</category>
      <category>fintech</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Using Automation to Track 0% APR Deadlines Across 20 Credit Cards</title>
      <dc:creator>Troy Johnston</dc:creator>
      <pubDate>Tue, 03 Mar 2026 02:40:05 +0000</pubDate>
      <link>https://dev.to/stackeasy/using-automation-to-track-0-apr-deadlines-across-20-credit-cards-5dhg</link>
      <guid>https://dev.to/stackeasy/using-automation-to-track-0-apr-deadlines-across-20-credit-cards-5dhg</guid>
      <description>&lt;p&gt;Here is a number that should bother you: the average credit card APR in 2026 is north of 20%. If you are carrying a balance on a promotional 0% APR offer and that promotion expires without you noticing, your effective interest rate jumps from zero to twenty-something percent overnight.&lt;/p&gt;

&lt;p&gt;On a $10,000 balance, that is roughly $170 per month in interest you were not paying yesterday.&lt;/p&gt;

&lt;p&gt;This is the single most expensive failure point in credit card management. And it happens to smart people constantly because tracking promotional rate expirations manually is a system designed to fail.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;I manage a portfolio of credit cards for both personal and business use. At any given time, I might have 4-6 cards with active 0% APR promotions. Some are balance transfers. Some are purchase promotions. Each one has a different expiration date.&lt;/p&gt;

&lt;p&gt;Here is what a typical month looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Card A (Chase)     | 0% APR Purchase    | Expires: June 15, 2026    | Balance: $4,200
Card B (Citi)      | 0% Balance Transfer| Expires: October 3, 2026  | Balance: $8,500
Card C (Amex)      | 0% APR Purchase    | Expires: March 28, 2026   | Balance: $2,100
Card D (Discover)  | 0% Balance Transfer| Expires: August 22, 2026  | Balance: $6,000
Card E (US Bank)   | 0% APR Purchase    | Expires: April 10, 2026   | Balance: $3,300
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five cards. Five different expiration dates. Total exposure: $24,100 in balances that will start accruing interest at 20%+ if I miss any of these dates.&lt;/p&gt;

&lt;p&gt;The traditional approach: put these dates in a calendar. Set reminders. Check a spreadsheet. Hope you do not miss one during a busy week.&lt;/p&gt;

&lt;p&gt;The problem: you will miss one during a busy week. I know because I did.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Calendar Reminders Are Not Enough
&lt;/h2&gt;

&lt;p&gt;A calendar reminder says "Card C promo expires March 28." That is data without context. Here is what you actually need to know when a promo is about to expire:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Current balance on the card&lt;/strong&gt; - Is it $200 or $12,000? The urgency is completely different.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Can you pay it off before expiration?&lt;/strong&gt; - If you have 30 days and the balance is $8,000, do you have $8,000 available?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Can you transfer the balance?&lt;/strong&gt; - Do you have another card with a 0% balance transfer offer available?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What is the fallback APR?&lt;/strong&gt; - Some cards go to 15%. Some go to 25%. The cost of missing this deadline varies by card.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What are your other upcoming expirations?&lt;/strong&gt; - If three promos expire within 60 days of each other, you need a plan, not a reminder.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A smart notification system provides all of this in the alert itself. A calendar event provides none of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Data Model
&lt;/h2&gt;

&lt;p&gt;Every promo period needs these fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;cardId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chase-freedom-flex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;promoType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;purchase_apr&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// or "balance_transfer_apr"&lt;/span&gt;
  &lt;span class="nx"&gt;promoRate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                  &lt;span class="c1"&gt;// the promotional APR (usually 0)&lt;/span&gt;
  &lt;span class="nx"&gt;standardRate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;22.49&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;// what it reverts to&lt;/span&gt;
  &lt;span class="nx"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2025-06-15&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;endDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-06-15&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;currentBalance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// needs regular updates&lt;/span&gt;
  &lt;span class="nx"&gt;minimumPayment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;isActive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From this data, you can derive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Days remaining:&lt;/strong&gt; Simple date math&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monthly interest if promo lapses:&lt;/strong&gt; &lt;code&gt;currentBalance * (standardRate / 12)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payoff amount per month:&lt;/strong&gt; &lt;code&gt;currentBalance / monthsRemaining&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Urgency tier:&lt;/strong&gt; Based on days remaining and balance&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Notification Logic
&lt;/h2&gt;

&lt;p&gt;Not all deadlines are created equal. A card with a $200 balance expiring next month does not need the same urgency as a card with $10,000 expiring in two weeks. The notification system needs tiers:&lt;/p&gt;

&lt;h3&gt;
  
  
  Tier 1: Informational (90-60 days out)
&lt;/h3&gt;

&lt;p&gt;A gentle heads up. "Your Chase card's 0% APR expires in 78 days. Current balance: $4,200. At this point, paying $1,400/month would eliminate the balance before the promo ends."&lt;/p&gt;

&lt;p&gt;No panic. Just awareness and a path forward.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tier 2: Planning (60-30 days out)
&lt;/h3&gt;

&lt;p&gt;More urgent. Include options. "Your Citi card's 0% balance transfer rate expires in 45 days. Current balance: $8,500. Options: (1) Pay $4,250/month to clear it, (2) Transfer to a new 0% card, (3) Accept the standard rate of 21.99%."&lt;/p&gt;

&lt;p&gt;This is where most people need to make a decision.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tier 3: Action Required (30-7 days out)
&lt;/h3&gt;

&lt;p&gt;This is the "do something now" alert. "Your Amex 0% APR expires in 12 days. Balance: $2,100. Monthly interest at standard rate: $38.50. Pay off now to avoid charges."&lt;/p&gt;

&lt;h3&gt;
  
  
  Tier 4: Critical (Under 7 days)
&lt;/h3&gt;

&lt;p&gt;Daily alerts. "TOMORROW: Your Discover 0% balance transfer rate expires. Current balance: $6,000. Standard APR: 23.99%. This will cost approximately $120/month in interest if not addressed."&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cascade Problem
&lt;/h2&gt;

&lt;p&gt;Here is what makes this genuinely complex. Promo expirations do not happen in isolation. If three cards lose their promotional rates within the same quarter, you might face $15,000+ in balances suddenly accruing interest. The system needs to model these cascades.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;March 28:  Card C expires -&amp;gt; $2,100 starts accruing at 22%
April 10:  Card E expires -&amp;gt; $3,300 starts accruing at 20%
June 15:   Card A expires -&amp;gt; $4,200 starts accruing at 24%

Total cascade exposure (Q2): $9,600
Combined monthly interest if all three lapse: ~$165/month
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the system sees this pattern 90 days out, it should surface it as a strategic alert, not three separate notifications. "You have $9,600 in promotional balances expiring between March and June. Here is a prioritized payoff plan."&lt;/p&gt;

&lt;h2&gt;
  
  
  Utilization Side Effects
&lt;/h2&gt;

&lt;p&gt;Paying off balances changes your credit utilization. If you aggressively pay down $9,600 in balances before promos expire, your total utilization drops. That is good for your credit score. But if you transfer balances to consolidate, your per-card utilization on the receiving card spikes. That could hurt your score.&lt;/p&gt;

&lt;p&gt;The automation needs to model these tradeoffs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Option A: Pay off Card C ($2,100) and Card E ($3,300) directly.
  - Total utilization drops from 28% to 19%
  - Cash flow impact: $5,400 over 6 weeks

Option B: Transfer Card C balance to Card F (new 0% BT offer).
  - Card F utilization goes from 0% to 21%
  - No immediate cash flow impact
  - Extends the 0% window by 15 months
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both options are valid. The right choice depends on the person's cash flow, credit score goals, and whether they have a new 0% offer available. The system presents the math. The person makes the call.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;This is all baked into &lt;a href="https://www.stackeasy.ai?utm_source=devto&amp;amp;utm_medium=cross-publish&amp;amp;utm_campaign=apr-tracking" rel="noopener noreferrer"&gt;StackEasy&lt;/a&gt;. The tiered notification system, the cascade detection, the utilization modeling. It is the feature I am most proud of because it directly prevents the most expensive mistake credit stackers make.&lt;/p&gt;

&lt;p&gt;The dashboard shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All active promo periods on a timeline&lt;/li&gt;
&lt;li&gt;Days remaining with color-coded urgency&lt;/li&gt;
&lt;li&gt;Projected interest cost if the promo lapses&lt;/li&gt;
&lt;li&gt;Available balance transfer options from your existing portfolio&lt;/li&gt;
&lt;li&gt;Utilization impact of different payoff strategies&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lessons for Other Developers
&lt;/h2&gt;

&lt;p&gt;If you are building anything that involves time-sensitive financial events:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Context in notifications beats raw data every time.&lt;/strong&gt; "Card expires March 28" is useless. "Card with $8,500 balance expires in 12 days, costing $156/month if missed" drives action.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Model interactions between events.&lt;/strong&gt; Financial decisions cascade. A single-event view misses the bigger picture.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Make the math visible.&lt;/strong&gt; People trust systems that show their work. "Here is the interest calculation" builds more confidence than "Alert: take action now."&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Default to conservative.&lt;/strong&gt; In financial tools, a false positive (alerting when unnecessary) is always better than a false negative (missing a real deadline).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Date math is deceptively complex.&lt;/strong&gt; "18 months from account opening" sounds simple until you account for weekends, statement cycles, and issuer-specific grace periods.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;If you are managing multiple credit cards with promotional rates and want to stop worrying about missed deadlines, &lt;a href="https://www.stackeasy.ai?utm_source=devto&amp;amp;utm_medium=cross-publish&amp;amp;utm_campaign=apr-tracking" rel="noopener noreferrer"&gt;StackEasy&lt;/a&gt; was built for exactly this. Happy to discuss the implementation details in the comments.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>fintech</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
