Files
financial/app/lib/card_billing_service.php
T
2026-06-07 00:33:58 +09:00

274 lines
7.5 KiB
PHP

<?php
function normalize_card_kind(array $account): ?string
{
$kind = strtolower(trim((string)($account['card_kind'] ?? '')));
if ($kind === 'credit') {
return 'credit';
}
if ($kind === 'check') {
return 'check';
}
return null;
}
function safe_date_ts(string $date): ?int
{
$ts = strtotime($date);
return $ts === false ? null : $ts;
}
function card_month_day_date(string $billingYearMonth, int $monthOffset, int $day): ?DateTime
{
if (!preg_match('/^\d{4}-\d{2}$/', $billingYearMonth)) {
return null;
}
if ($day < 1 || $day > 31) {
return null;
}
$base = new DateTime($billingYearMonth . '-01');
if ($monthOffset !== 0) {
$base->modify(($monthOffset > 0 ? '+' : '') . $monthOffset . ' month');
}
$lastDay = (int)$base->format('t');
$realDay = min($day, $lastDay);
$base->setDate(
(int)$base->format('Y'),
(int)$base->format('m'),
$realDay
);
return $base;
}
function account_has_statement_period(array $account): bool
{
return isset(
$account['statement_start_month_offset'],
$account['statement_start_day'],
$account['statement_end_month_offset'],
$account['statement_end_day']
)
&& $account['statement_start_month_offset'] !== null
&& $account['statement_start_day'] !== null
&& $account['statement_end_month_offset'] !== null
&& $account['statement_end_day'] !== null
&& (int)$account['statement_start_day'] >= 1
&& (int)$account['statement_start_day'] <= 31
&& (int)$account['statement_end_day'] >= 1
&& (int)$account['statement_end_day'] <= 31;
}
function transaction_date_in_card_statement_month(array $account, string $transactionDate, string $billingYearMonth): bool
{
$txTs = safe_date_ts($transactionDate);
if ($txTs === null) {
return false;
}
if (!account_has_statement_period($account)) {
return false;
}
$start = card_month_day_date(
$billingYearMonth,
(int)$account['statement_start_month_offset'],
(int)$account['statement_start_day']
);
$end = card_month_day_date(
$billingYearMonth,
(int)$account['statement_end_month_offset'],
(int)$account['statement_end_day']
);
if (!$start || !$end) {
return false;
}
$tx = new DateTime(date('Y-m-d', $txTs));
return $tx >= $start && $tx <= $end;
}
function get_card_billing_year_month_by_statement_period(array $account, string $transactionDate): ?string
{
$txTs = safe_date_ts($transactionDate);
if ($txTs === null) {
return null;
}
if (!account_has_statement_period($account)) {
return null;
}
$tx = new DateTime(date('Y-m-d', $txTs));
/*
* 청구월 기준 사용기간 예:
* 2026-05 청구월 = 2026-04-11 ~ 2026-05-10
*
* 거래일 주변의 청구월만 검사하면 됨.
* 전월/당월/익월/익익월까지 여유 있게 검사.
*/
$base = new DateTime($tx->format('Y-m-01'));
for ($i = -1; $i <= 2; $i++) {
$candidate = clone $base;
if ($i !== 0) {
$candidate->modify(($i > 0 ? '+' : '') . $i . ' month');
}
$candidateYm = $candidate->format('Y-m');
if (transaction_date_in_card_statement_month($account, $transactionDate, $candidateYm)) {
return $candidateYm;
}
}
return null;
}
function get_card_billing_year_month(array $account, string $transactionDate): ?string
{
if (($account['account_type'] ?? '') !== 'card') {
return null;
}
$ts = safe_date_ts($transactionDate);
if ($ts === null) {
return null;
}
$cardKind = normalize_card_kind($account);
// 체크카드는 즉시형으로 보고 거래월 그대로
if ($cardKind === 'check') {
return date('Y-m', $ts);
}
// 신용카드인데 신용공여기간 계산을 안 쓰면 거래월 그대로
if (empty($account['use_credit_grace_period'])) {
return date('Y-m', $ts);
}
// 신규 방식: 카드사별 실제 사용기간 설정 우선
$statementYm = get_card_billing_year_month_by_statement_period($account, $transactionDate);
if ($statementYm !== null) {
return $statementYm;
}
// fallback: 기존 billing_day 단순 방식
$billingDay = (int)($account['billing_day'] ?? 0);
if ($billingDay <= 0 || $billingDay > 31) {
return date('Y-m', $ts);
}
$dt = new DateTime(date('Y-m-d', $ts));
$day = (int)$dt->format('d');
if ($day <= $billingDay) {
return $dt->format('Y-m');
}
$dt->modify('first day of next month');
return $dt->format('Y-m');
}
function get_card_payment_date(array $account, string $billingYearMonth): ?string
{
if (($account['account_type'] ?? '') !== 'card') {
return null;
}
if (!preg_match('/^\d{4}-\d{2}$/', $billingYearMonth)) {
return null;
}
$paymentDay = (int)($account['payment_day'] ?? 0);
if ($paymentDay <= 0 || $paymentDay > 31) {
return null;
}
[$year, $month] = explode('-', $billingYearMonth);
$year = (int)$year;
$month = (int)$month;
$firstDay = new DateTime(sprintf('%04d-%02d-01', $year, $month));
$lastDay = (int)$firstDay->format('t');
$day = min($paymentDay, $lastDay);
$firstDay->setDate($year, $month, $day);
return $firstDay->format('Y-m-d');
}
function get_card_statement_period_label(array $account): ?string
{
if (!account_has_statement_period($account)) {
return null;
}
$startOffset = (int)$account['statement_start_month_offset'];
$startDay = (int)$account['statement_start_day'];
$endOffset = (int)$account['statement_end_month_offset'];
$endDay = (int)$account['statement_end_day'];
$monthText = function (int $offset): string {
if ($offset === -2) return '전전월';
if ($offset === -1) return '전월';
if ($offset === 0) return '당월';
if ($offset === 1) return '익월';
if ($offset === 2) return '익익월';
return $offset . '개월';
};
return $monthText($startOffset) . ' ' . $startDay . '일 ~ ' .
$monthText($endOffset) . ' ' . $endDay . '일 사용분';
}
function get_card_billing_label(array $account): string
{
if (($account['account_type'] ?? '') !== 'card') {
return '-';
}
$cardKind = normalize_card_kind($account);
if ($cardKind === 'check') {
return '체크카드 · 즉시출금';
}
if ($cardKind === 'credit') {
$paymentDay = (int)($account['payment_day'] ?? 0);
if (!empty($account['billing_cycle_memo'])) {
return (string)$account['billing_cycle_memo'];
}
$periodLabel = get_card_statement_period_label($account);
if ($periodLabel !== null && $paymentDay > 0) {
return '신용카드 · ' . $periodLabel . ' / 납부일 ' . $paymentDay . '일';
}
if ($periodLabel !== null) {
return '신용카드 · ' . $periodLabel;
}
$billingDay = (int)($account['billing_day'] ?? 0);
if ($billingDay > 0 && $paymentDay > 0) {
return '신용카드 · 결제기준일 ' . $billingDay . '일 / 납부일 ' . $paymentDay . '일';
}
return '신용카드';
}
return '카드';
}