{
  "schemaVersion": 1,
  "slug": "credit-card-payoff-calculator",
  "category": "finance",
  "kind": "implementation",
  "independentValidation": false,
  "implementationHash": "3b25d64313612bdaef1b05ac5adbf6a9b002729035ae1f2c3e5f9290de2a7df7",
  "provenance": "Generated from the calculation contract used by the published page. Publication is not external validation.",
  "sourceFiles": [
    "data/calculators/credit-card-payoff-calculator.json",
    "src/engine.js"
  ],
  "runtimeSource": "/sources/engine.txt",
  "runtimeHash": "5c888d8898afbdee36267bb9977829e1e2ac568f6677c8ee87469c139864645c",
  "scope": "The compute function records implemented equations, constants, lookup tables and custom rules. Inputs record defaults and allowed values; outputs record precision and units. Defaults are examples, not recommendations. General background references are not evidence for this specific algorithm.",
  "publishedFormula": {
    "formula": "B_0 = B;  B_{n+1} = B_n × (1 + i) − min(P, B_n × (1 + i));  N = min{n : B_n ≤ 0.01}",
    "vars": [
      [
        "B",
        "Current balance"
      ],
      [
        "APR",
        "Annual percentage rate"
      ],
      [
        "P",
        "Monthly payment"
      ],
      [
        "i",
        "Monthly rate = APR ÷ 1200"
      ],
      [
        "n",
        "Month index, n = 0, 1, 2, …"
      ],
      [
        "N",
        "Months to pay off"
      ]
    ],
    "note": "This simplified fixed-payment model applies APR ÷ 12 to the balance once per month, then applies the payment. It omits daily periodic rates, average daily balances, new purchases, multiple APR buckets, fees and issuer minimum-payment rules. Iteration is capped at 1,200 months; longer payoffs are reported beyond the horizon."
  },
  "externalEvidence": {
    "reviewedAt": "2026-09-28",
    "sources": [
      {
        "name": "CFPB — How credit card interest is calculated",
        "url": "https://www.consumerfinance.gov/ask-cfpb/how-does-my-credit-card-company-calculate-the-amount-of-interest-i-owe-en-51/"
      }
    ]
  },
  "contract": {
    "compute": "const empty={months:null,totalInterest:null,totalPaid:null,minPayment:null};const b=Number(balance),rate=Number(apr),p=Number(payment);if(![b,rate,p].every(Number.isFinite)||b<0||rate<0||p<=0)return{...empty,__error:'inputError'};const i=rate/100/12,minPayment=b*i;let bal=b,months=0,interest=0,paid=0;while(bal>0.01&&months<1200){const int=bal*i;if(p<=int)return{months:'Never pays off',totalInterest:null,totalPaid:null,minPayment};interest+=int;bal+=int;const pay=Math.min(p,bal);paid+=pay;bal-=pay;months++;}if(bal>0.01)return{months:'Over 1200 months',totalInterest:null,totalPaid:null,minPayment};return{months:String(months),totalInterest:interest,totalPaid:paid,minPayment};",
    "inputs": [
      {
        "name": "balance",
        "type": "number",
        "default": 5000,
        "min": 0,
        "max": 10000000,
        "step": 100
      },
      {
        "name": "apr",
        "type": "number",
        "default": 22,
        "min": 0,
        "max": 100,
        "step": 0.01
      },
      {
        "name": "payment",
        "type": "number",
        "default": 200,
        "min": 1,
        "max": 1000000,
        "step": 10
      }
    ],
    "outputs": [
      {
        "name": "months",
        "type": "text",
        "precision": 0
      },
      {
        "name": "totalInterest",
        "precision": 2,
        "unit": "$",
        "optional": true
      },
      {
        "name": "totalPaid",
        "precision": 2,
        "unit": "$",
        "optional": true
      },
      {
        "name": "minPayment",
        "precision": 2,
        "unit": "$"
      }
    ],
    "interpret": null
  }
}
