PHP Functions to Calculate Interest Payments

I built these to help calculate how much of my monthly mortgage and car payments were actually going towards the principal and how much was interest.

This first function actually does the monthly calculations. There are a number of variables that you can set via the $args array. See the inline comments for more details there.

One that I'll point out is $args['type']. This should be set to either "daily" or "monthly" based on how your interest is calculated. If your loan is compounded daily, you should also set $args['compounded'] to "daily", otherwise you can leave it as FALSE.

function calculate_payment($args) {
	if (!is_array($args)) { return false; }
	$type = $args['type']; // 'daily' or 'monthly'
	if (array_key_exists('compounded',$args)) { $compounded = $args['compounded']; } else { $compounded = false; } // can be 'daily' or false
	$apr = $args['apr'];   // percentage or decimal
	if ($apr > 1) { $apr = $apr / 100; } // (convert percentage to decimal if necessary)
	if (array_key_exists('balance',$args)) { $balance = $args['balance']; } // dollar amount
	else { $balance = $args['principal']; } // dollar amount
	if (array_key_exists('escrow',$args)) { $escrow = $args['escrow']; } // dollar amount
	else { $escrow = 0; }
	$monthly_payment = $args['monthly_payment']; // dollar amount
	$payment_time = $args['payment_time']; // unix timestamp
	if (array_key_exists('start_time',$args)) { $start_time = $args['start_time']; } else { $start_time = strtotime('-1 month',$payment_time); }
	if (array_key_exists('interest_overrides',$args) && is_array($args['interest_overrides'])) { $interest_overrides = $args['interest_overrides']; } else { $interest_overrides = array(); }

	if ($type == 'monthly') {
		$mpr = $apr / 12;
		$monthly_interest = $balance * $mpr;
		$days = date('t',$start_time);
	} elseif ($type == 'daily') {
		$dpr = $apr / 365;
		if ($compounded == 'daily') {
			$current_time = $start_time;
			$monthly_interest = 0;
			while ($current_time < $payment_time) {
				$daily_interest = $balance * $dpr;
				$monthly_interest = $monthly_interest + $daily_interest;
				$current_time = strtotime('+1 day',$current_time);
			}
		} else {
			$days = round(($payment_time - $start_time) / (60*60*24));
			$monthly_interest = $balance * $days * $dpr;
		}
	} else { return false; }

	//check for overrides
	if (array_key_exists($payment_time,$interest_overrides)) {
		$monthly_interest = $interest_overrides[$payment_time];
	}

	$monthly_interest = round($monthly_interest,2);
	$monthly_principal = $monthly_payment - $escrow - $monthly_interest; //principal paid this month
	$balance = $balance - $monthly_principal;
	return array(
	  'payment_date'=>date('Y-m-d',$payment_time),
	  'days'=>$days,
	  'payment'=>$monthly_payment,
	  'escrow'=>$escrow,
	  'principal'=>$monthly_principal,
	  'interest'=>$monthly_interest,
	  'balance'=>$balance,
	);
}

Loans payments often aren't on the first of the month. They wait until the first Monday, skipping weekends and holidays. That affects the amount of interest spent in each cycle (if your interest is calculated daily), so determining your payment dates is important. This function lets you determine your payment dates after you provide some specifics for your loan.

function payment_times($args) {
	if (array_key_exists('holidays',$args)) { $holidays = $args['holidays']; } else { $holidays = array(); }
	$m = 0;
	$times = array();
	$time = $args['loan_origination'];
	if (array_key_exists('payment_times',$args) && is_array($args['payment_times'])) {
		foreach ($args['payment_times'] as $time) {
			$m++;
			$times[$m] = $time;
		}
	}
	while ($m < $args['months']) {
		$m++;
		$time = strtotime('+1 month',$time); //move forward a month
		$time = mktime(12,0,0,date('n',$time),1,date('Y',$time)); //first of the month
		if (date('w',$time) == 6) { $time = strtotime('+2 days',$time); } //Saturday to Monday
		elseif (date('w',$time) == 0) { $time = strtotime('+1 day',$time); } //Sunday to Monday

		if (in_array($time,$holidays)) { $time = strtotime('+1 day',$time); }
		
		$times[] = $time;
		$last_time = $time;
	}
	return $times;
}

Finally, this function displays all the data that you've calculated. It also calls the other two functions as needed, so this is the only one that you'll actually have to call from your code:

function loan_table($loan_info) {
	$date_format = 'M j, Y'; //this can be changed

	$loan_info['payment_times'] = payment_times($loan_info);
	$totals = array('payment'=>0,'principal'=>0,'interest'=>0,'escrow'=>0);
	$table = '<table class="table">';
	$table .= '<thead><tr>';
		$table .= '<th>Payment<br />Date</th>';
		$table .= '<th>Days</th>';
		$table .= '<th>Payment</th>';
		$table .= '<th>Principal</th>';
		$table .= '<th>Interest</th>';
		$table .= '<th>Escrow</th>';
		$table .= '<th>Remaining<br />Balance</th>';
	$table .= '</tr></thead>';

	$table .= '<tr>';
		$table .= '<td>'.date($date_format,$loan_info['loan_origination']).'</td>';
		$table .= '<td>&nbsp;</td>';
		$table .= '<td>&nbsp;</td>';
		$table .= '<td>&nbsp;</td>';
		$table .= '<td>&nbsp;</td>';
		$table .= '<td>&nbsp;</td>';
		$table .= '<td>$'.number_format($loan_info['principal'],2).'</td>';
	$table .= '</tr>';

	$last_time = $loan_info['loan_origination'];
	foreach ($loan_info['payment_times'] as $time) {
		if ($loan_info['balance'] < 0) { break; }
		$args = $loan_info;
		$args['start_time'] = $last_time;
		$args['payment_time'] = $time;
		$payment = calculate_payment($args);
		$table .= '<tr>';
		$table .= '<td>'.date($date_format,$time).'</td>';
		$table .= '<td>'.$payment['days'].'</td>';
		$table .= '<td>$'.number_format($payment['payment'],2).'</td>';
		$table .= '<td>$'.number_format($payment['principal'],2).'</td>';
		$table .= '<td>$'.number_format($payment['interest'],2).'</td>';
		$table .= '<td>$'.number_format($payment['escrow'],2).'</td>';
		$table .= '<td>$'.number_format($payment['balance'],2).'</td>';
		$table .= '</tr>';
		$loan_info['balance'] = round($payment['balance'],2);
		$last_time = $time;
		foreach ($totals as $key => $value) {
			$totals[$key] = $value + $payment[$key];
		}
	}
	$table .= '<tfoot><tr>';
		$table .= '<td colspan="2">Totals</td>';
		$table .= '<td>$'.number_format($totals['payment'],2).'</td>';
		$table .= '<td>$'.number_format($totals['principal'],2).'</td>';
		$table .= '<td>$'.number_format($totals['interest'],2).'</td>';
		$table .= '<td>$'.number_format($totals['escrow'],2).'</td>';
		$table .= '<td>&nbsp;</td>';
	$table .= '</tr></tfoot>';
	$table .= '</table>';
	return $table;
}

Seeing the code in action always helps, so here's an example of how all 3 functions work together. First the code you'd use:

$example = array(
  'type'=>'daily',
  'principal'=>17000.00,
  'apr'=>3.9,
  'monthly_payment'=>300.00,
  'months'=>60,
  'loan_origination'=>mktime(12,0,0,9,17,2012),
  'payment_times'=>array(
	mktime(12,0,0,10,18,2012),
	mktime(12,0,0,12,1,2012)
  ),
  'holidays'=>array(
	mktime(12,0,0,1,1,2013),
  ),
  'interest_overrides'=>array(
  )
);
echo loan_table($example);

And, here's the table that would produce:

Payment
Date
DaysPaymentPrincipalInterestEscrowRemaining
Balance
Sep 17, 2012     $17,000.00
Oct 18, 201231$300.00$243.69$56.31$0.00$16,756.31
Dec 1, 201244$300.00$221.22$78.78$0.00$16,535.09
Jan 2, 201332$300.00$243.46$56.54$0.00$16,291.63
Feb 1, 201330$300.00$247.78$52.22$0.00$16,043.85
Mar 1, 201328$300.00$252.00$48.00$0.00$15,791.85
Apr 1, 201331$300.00$247.69$52.31$0.00$15,544.16
May 1, 201330$300.00$250.17$49.83$0.00$15,293.99
Jun 3, 201333$300.00$246.07$53.93$0.00$15,047.92
Jul 1, 201328$300.00$254.98$45.02$0.00$14,792.94
Aug 1, 201331$300.00$251.00$49.00$0.00$14,541.94
Sep 2, 201332$300.00$250.28$49.72$0.00$14,291.66
Oct 1, 201329$300.00$255.72$44.28$0.00$14,035.94
Nov 1, 201331$300.00$253.51$46.49$0.00$13,782.43
Dec 2, 201331$300.00$254.35$45.65$0.00$13,528.08
Jan 1, 201430$300.00$256.64$43.36$0.00$13,271.44
Feb 3, 201433$300.00$253.20$46.80$0.00$13,018.24
Mar 3, 201428$300.00$261.05$38.95$0.00$12,757.19
Apr 1, 201429$300.00$260.47$39.53$0.00$12,496.72
May 1, 201430$300.00$259.94$40.06$0.00$12,236.78
Jun 2, 201432$300.00$258.16$41.84$0.00$11,978.62
Jul 1, 201429$300.00$262.88$37.12$0.00$11,715.74
Aug 1, 201431$300.00$261.19$38.81$0.00$11,454.55
Sep 1, 201431$300.00$262.06$37.94$0.00$11,192.49
Oct 1, 201430$300.00$264.12$35.88$0.00$10,928.37
Nov 3, 201433$300.00$261.47$38.53$0.00$10,666.90
Dec 1, 201428$300.00$268.09$31.91$0.00$10,398.81
Jan 1, 201531$300.00$265.56$34.44$0.00$10,133.25
Feb 2, 201532$300.00$265.35$34.65$0.00$9,867.90
Mar 2, 201528$300.00$270.48$29.52$0.00$9,597.42
Apr 1, 201530$300.00$269.24$30.76$0.00$9,328.18
May 1, 201530$300.00$270.10$29.90$0.00$9,058.08
Jun 1, 201531$300.00$270.00$30.00$0.00$8,788.08
Jul 1, 201530$300.00$271.83$28.17$0.00$8,516.25
Aug 3, 201533$300.00$269.97$30.03$0.00$8,246.28
Sep 1, 201529$300.00$274.45$25.55$0.00$7,971.83
Oct 1, 201530$300.00$274.45$25.55$0.00$7,697.38
Nov 2, 201532$300.00$273.68$26.32$0.00$7,423.70
Dec 1, 201529$300.00$277.00$23.00$0.00$7,146.70
Jan 1, 201631$300.00$276.33$23.67$0.00$6,870.37
Feb 1, 201631$300.00$277.24$22.76$0.00$6,593.13
Mar 1, 201629$300.00$279.57$20.43$0.00$6,313.56
Apr 1, 201631$300.00$279.09$20.91$0.00$6,034.47
May 2, 201631$300.00$280.01$19.99$0.00$5,754.46
Jun 1, 201630$300.00$281.55$18.45$0.00$5,472.91
Jul 1, 201630$300.00$282.46$17.54$0.00$5,190.45
Aug 1, 201631$300.00$282.81$17.19$0.00$4,907.64
Sep 1, 201631$300.00$283.74$16.26$0.00$4,623.90
Oct 3, 201632$300.00$284.19$15.81$0.00$4,339.71
Nov 1, 201629$300.00$286.55$13.45$0.00$4,053.16
Dec 1, 201630$300.00$287.01$12.99$0.00$3,766.15
Jan 2, 201732$300.00$287.12$12.88$0.00$3,479.03
Feb 1, 201730$300.00$288.85$11.15$0.00$3,190.18
Mar 1, 201728$300.00$290.46$9.54$0.00$2,899.72
Apr 3, 201733$300.00$289.78$10.22$0.00$2,609.94
May 1, 201728$300.00$292.19$7.81$0.00$2,317.75
Jun 1, 201731$300.00$292.32$7.68$0.00$2,025.43
Jul 3, 201732$300.00$293.07$6.93$0.00$1,732.36
Aug 1, 201729$300.00$294.63$5.37$0.00$1,437.73
Sep 1, 201731$300.00$295.24$4.76$0.00$1,142.49
Oct 2, 201731$300.00$296.22$3.78$0.00$846.27
Totals$18,000.00$16,153.73$1,846.27$0.00 

This is just a fictional example that includes a balloon payment at the end. You'd ovbiously want to plug in your own numbers instead.


Comments

Loading…

This post was published on May 10th, 2014 by Robert James Reese in PHP. Before using any of the code or other content in this post, you must read and agree to our terms of use.