신용카드 관리 대시보드 구축기 (스프레드시트가 충분하지 않았던 이유)
Source: Dev.to
How I built a credit card management dashboard and why spreadsheets were not cutting it
TL;DR
I needed a way to keep track of multiple credit cards, their balances, due dates, and rewards without the mess of spreadsheets. I built a small dashboard using React, Node.js, and MongoDB that pulls data from the banks’ APIs, visualizes it, and sends me reminders.
The problem
- Multiple cards – I have 5 different credit cards from three issuers.
- Different reward structures – Some give cash‑back, others give points or travel miles.
- Due dates don’t line up – Missing a payment even by a day hurts my credit score.
- Spreadsheets are fragile – Every time I add a new card I have to copy‑paste formulas, and I keep forgetting to update the “last refreshed” column.
Why spreadsheets didn’t work
| Issue | Spreadsheet | Dashboard |
|---|---|---|
| Data freshness | Manual import → outdated | API pulls → real‑time |
| Error‑prone formulas | Easy to break when structure changes | Centralized logic in code |
| Scalability | Adding a new card = new sheet + new formulas | Add a new document in MongoDB |
| Notifications | No built‑in reminder system | Cron job + email/SMS alerts |
| Visualization | Limited charts, need add‑ins | D3 / Chart.js for custom graphs |
Solution overview
- Backend – Node.js + Express server that authenticates with each bank’s API (OAuth2).
- Database – MongoDB stores card metadata, transactions, and reward rules.
- Frontend – React app with Material‑UI components, Chart.js for balance trends, and a table view for upcoming due dates.
- Scheduler –
node‑cronruns nightly to fetch the latest transactions and send email reminders via SendGrid.
Architecture diagram
+----------------+ +----------------+ +-------------------+
| Frontend | ---> | Backend API | ---> | MongoDB Atlas |
| (React) | | (Node/Express) | | (Cards, Txns,…) |
+----------------+ +----------------+ +-------------------+
^ ^ ^
| | |
| HTTPS (JWT) | REST (JSON) |
| | |
| +----------------+ |
+----------------| Scheduler (cron) |------------+
+----------------+
Step‑by‑step implementation
1. Set up the project
# Backend
mkdir credit-dashboard-backend && cd $_
npm init -y
npm install express mongoose axios dotenv node-cron
# Frontend
npx create-react-app credit-dashboard-frontend
cd credit-dashboard-frontend
npm install @mui/material @emotion/react @emotion/styled chart.js react-chartjs-2 axios
2. Connect to bank APIs
Most banks expose a RESTful endpoint for transaction data.
// backend/services/bankService.js
import axios from 'axios';
import dotenv from 'dotenv';
dotenv.config();
export const fetchTransactions = async (bank, token) => {
const url = `${process.env[`${bank}_API_URL`]}/transactions`;
const { data } = await axios.get(url, {
headers: { Authorization: `Bearer ${token}` },
});
return data;
};
Note: Each bank has its own OAuth flow; I stored the refresh token in MongoDB and refreshed it automatically.
3. Store data in MongoDB
// backend/models/Card.js
import mongoose from 'mongoose';
const CardSchema = new mongoose.Schema({
name: String,
issuer: String,
lastFour: String,
rewardType: { type: String, enum: ['cash', 'points', 'miles'] },
rewardRate: Number,
dueDate: Number, // day of month
transactions: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Transaction' }],
});
export default mongoose.model('Card', CardSchema);
4. Build the React UI
// frontend/src/components/BalanceChart.tsx
import { Line } from 'react-chartjs-2';
import { useEffect, useState } from 'react';
import axios from 'axios';
export const BalanceChart = () => {
const [chartData, setChartData] = useState<any>(null);
useEffect(() => {
axios.get('/api/cards/balance-history').then(res => {
const { dates, balances } = res.data;
setChartData({
labels: dates,
datasets: [{ label: 'Total Balance', data: balances, borderColor: '#3f51b5' }],
});
});
}, []);
return chartData ? <Line data={chartData} /> : <p>Loading…</p>;
};
5. Add reminders
// backend/scheduler.js
import cron from 'node-cron';
import { Card } from './models/Card';
import sendgrid from '@sendgrid/mail';
sendgrid.setApiKey(process.env.SENDGRID_API_KEY);
cron.schedule('0 9 * * *', async () => {
const today = new Date().getDate();
const dueCards = await Card.find({ dueDate: today });
dueCards.forEach(card => {
const msg = {
to: process.env.USER_EMAIL,
from: 'no-reply@creditdash.com',
subject: `Payment due for ${card.name}`,
text: `Your ${card.name} payment is due today. Balance: $${card.balance}.`,
};
sendgrid.send(msg);
});
});
Lessons learned
- API limits matter – Some banks only allow 100 calls per hour. I added exponential back‑off and caching.
- Security first – All secrets are stored in
.envand never committed. I also enabled HTTPS on the server. - Keep the UI simple – Users (myself) only need to see total balance, upcoming due dates, and reward progress. Over‑engineering the UI caused unnecessary bugs.
Future improvements
- Machine‑learning recommendation engine – Suggest which card to use for a given purchase to maximize rewards.
- Mobile app – Wrap the React app with Capacitor for iOS/Android push notifications.
- Multi‑user support – Right now the dashboard is single‑user; adding auth (JWT + Passport) would let families share a view.
Conclusion
Spreadsheets are great for one‑off calculations, but when you need real‑time data, automated reminders, and clear visualizations, a lightweight web dashboard wins hands‑down. Building it with React, Node, and MongoDB took a weekend, and now I never miss a payment again.
Happy coding!
Source: …
상황
저는 17장의 신용카드를 보유하고 있었으며, 각각은 특정한 목적을 가지고 있었습니다:
| 카드 | 목적 |
|---|---|
| 0 % APR 잔액이체 | 4개월 소개 기간 |
| 비즈니스 카드 | 광고 지출 시 3배 포인트 |
| 개인 카드 | 회전 5 % 카테고리 |
| 비즈니스 재고 카드 | 재고 구매 |
| … | … |
모든 정보를 Google Sheet에 다음과 같은 열로 기록했습니다:
- 카드 이름 / 발급사
- 신용 한도
- 현재 잔액
- APR / 프로모션 금리
- 프로모션 종료일
- 연회비 부과일
- 보상 구조
- 비고
행은 긴급도에 따라 색상으로 구분했으며, 조건부 서식을 이용해 다가오는 마감일을 표시했습니다.
이 시스템은 약 2년 동안 잘 작동했지만, 결국 멈추고 말았습니다.
한계점
$12,000 잔액이 있는 카드의 0 % APR 종료를 놓쳤습니다.
프로모션 금리가 화요일에 종료됐지만, 그 주에 시트를 확인하지 않아 표준 APR이 적용되었고, 피할 수 있었던 이자를 지불하게 되었습니다.
스프레드시트에는 날짜가 있었고 조건부 서식도 정상 작동했습니다.
문제는? 스프레드시트는 “이 금리가 만료되기 7일 남았고, 아직 $12,000이 남아 있습니다”와 같은 알림을 푸시하지 못한다는 점이었습니다.
그 순간, 더 나은 무언가를 만들자는 결심이 생겼습니다.
무엇이 잘못됐는가 (스프레드시트 기반 카드 관리 2년)
| 문제 | 실패 원인 |
|---|---|
| 사전 알림 없음 | 시트는 데이터를 표시할 뿐, 나에게 알림을 주지 않았습니다. |
| 수동 사용량 계산 | 17개의 카드 전체 사용량을 계산하려면 매번 잔액을 업데이트하고, 합산하고, 나누어야 했습니다. |
| ‘어떤 카드를 사용해야 할까’ 로직 부재 | 보상 구조를 머릿속에 알고 있었지만, 종종 잘못된 가맹점에서 잘못된 카드를 사용했습니다. |
| 반응형 연회비 결정 | 연회비가 청구되기 전이 아니라 명세서에서만 비용을 확인했습니다. |
| 신청 추적 혼란 | Chase의 5/24 규칙을 지키기 위해 신청 건수를 수동으로 셀 수밖에 없었습니다. |
| 수동 잔액 입력 | 사용자(저 포함)가 데이터를 최신 상태로 유지하지 않아 대시보드가 깨졌습니다. |
핵심 데이터 모델 – 모든 카드는 객체입니다
{
"name": "string",
"issuer": "string",
"creditLimit": 0,
"currentBalance": 0,
"standardAPR": 0.0,
"promoRate": {
"rate": 0.0,
"expiration": "YYYY‑MM‑DD"
},
"annualFee": {
"amount": 0,
"date": "YYYY‑MM‑DD"
},
"rewardStructure": {
"baseRate": 0.0,
"categories": {
"groceries": 0.0,
"gas": 0.0,
"...": 0.0
},
"rotating": {
"quarter": "Q1‑2024",
"categories": ["dining", "travel"],
"multiplier": 5,
"cap": 1500
}
},
"applicationDate": "YYYY‑MM‑DD",
"lastStatementDate": "YYYY‑MM‑DD",
"paymentDueDate": "YYYY‑MM‑DD"
}
핵심 포인트: 여러 속성이 시간‑종속적입니다 (프로모션 만료, 회전 카테고리, 연회비 날짜).
Deadline Engine – 실시간 알림 시스템
각 카드는 심각도 수준과 권장 조치가 포함된 마감일 집합을 생성합니다:
| 마감일 | 알림 시점 | 예시 메시지 |
|---|---|---|
| Promo‑rate expiration | 90 d, 60 d, 30 d, 7 d before | “Your Citi 0 % APR expires in 30 days. Balance: $8,400. Action: Pay off or transfer.” |
| Annual‑fee renewal | 60 d, 30 d before | “Annual fee of $95 on your Amex hits in 30 days. Evaluate keep vs. cancel.” |
| Payment due date | 7 d, 3 d before | “Payment of $1,200 due on 2024‑04‑15. Avoid late fees.” |
| Rotating‑category activation | Start of each quarter | “Q2 bonus: 5 % on groceries (cap $1,500).” |
Alert engine은 실제로 비용이 많이 드는 실수를 방지하는 기능입니다.
활용도 계산 (실시간)
- 총 활용도 = Σ (currentBalance) ÷ Σ (creditLimit)
- 카드별 활용도 = currentBalance ÷ creditLimit
- 영향 시뮬레이션 – “카드 A(45 % 활용)에서 $3,000을 카드 B(12 % 활용)로 옮기면 각 활용도는 어떻게 될까요?”
이 수치는 신용점수 모델이 전체가 아니라 카드별 활용도를 중요하게 여기기 때문에 중요합니다.
Card Selector – “Which Card Gives Me the Best Return?”
주어진 지출 카테고리(예: 식료품, 주유, 외식, 온라인)에서 선택자는 다음을 평가합니다:
- 카드별 기본 보상율.
- 현재 보너스 카테고리(회전하는 분기별 보너스 포함).
- 활성화 상태 – 분기별 보너스가 켜져 있는가?
- 지출 한도 – 구매가 보너스 한도를 초과하는가?
그 결과는 lookup function이며, 매 분기마다 업데이트되고 한도 및 활성화 상태를 고려합니다.
프로모션 금리 로직 – 다양한 용어 처리
| 용어 | 만료 로직 |
|---|---|
| “Promotional APR” | 고정 캘린더 날짜 (예: 2024‑07‑01). |
| “Introductory rate” | 고정 캘린더 날짜 또는 “계좌 개설 후 X개월”. |
| “0 % for 18 months” | 만료 계산 = applicationDate + 18 개월. |
시스템은 이러한 변형을 단일 만료 날짜로 정규화하여 알림 스케줄링에 사용합니다.
수동 데이터 입력 감소
문제: 수동 입력은 닭‑달걀 루프를 만들어요 – 대시보드는 데이터가 최신일 때만 유용하지만, 사용자는 최신 상태를 유지하는 것을 꺼려합니다.
탐색된 해결책:
- API 동기화 발행사와 (가능한 경우).
- 은행 수준 집계 (예: Plaid) 잔액을 위해.
- 빠른 추가 위젯 (모바일 친화적) 잔액 업데이트를 위해.
- 스마트 기본값 (예: 마지막 명세서에서 결제 기한 자동 채우기).
목표는 데이터 입력을 마찰 없이 만드는 것입니다.
알림 엔진 – 핵심 가치
All the charts and dashboards are nice, but the 진정한 가치 is the 프로모션 요금이 만료되기 3일 전에 도착하는 알림. Everything else is secondary to that alert engine.
YMYL (Your Money, Your Life) 고려사항
금융 콘텐츠는 검색 엔진에 의해 YMYL로 분류됩니다. 이는 다음을 의미합니다:
- 모든 숫자는 정확해야 합니다.
- 모든 주장은 방어 가능해야 합니다.
- 조심성 없는 조언 금지 – 사용자는 실제 금전적 결정을 위해 이 정보를 신뢰합니다.
따라서 핀테크를 구축할 때는 철저한 데이터 검증과 명확한 출처 표기가 필수입니다.
StackEasy – 사용자가 실제로 사용하는 것
| 기능 | 왜 중요한가 |
|---|---|
| 포트폴리오 대시보드 | 한눈에 모든 카드, 잔액, 한도 및 사용량을 보여줍니다. |
| 마감일 추적기 | 프로모션 만료, 연회비 및 결제 날짜에 대한 사전 알림을 제공합니다. |
| 카드 선택기 | 각 지출 카테고리별 최적의 카드를 추천합니다. |
| 신청 로그 | 발급사별 신청 속도를 추적합니다 (예: 5/24 이하 유지). |
가장 큰 검증은 사용자가 놓쳤을 수도 있는 프로모션 만료를 잡았다고 말했을 때였습니다. 그것이 바로 핵심입니다.
Non‑Obvious Lessons for Fintech Builders
- Start with your own pain. I built StackEasy because I needed it.
- Focus on the single‑most‑valuable feature. In my case: the deadline‑alert engine.
- Make data entry frictionless or automate it; otherwise the product dies.
- Normalize terminology (promo APR, intro rate, etc.) early to simplify logic.
- Treat every number as a legal claim – YMYL standards are unforgiving.
- Iterate on alerts first, then layer dashboards and analytics.
TL;DR
- 스프레드시트는 데이터를 저장할 수 있지만 시기적절한 알림을 푸시할 수 없습니다.
- 시간에 따라 변하는 필드를 가진 카드 객체 모델은 자동화를 가능하게 합니다.
- 마감 알림(프로모션 종료, 연회비, 결제 기한)은 핵심 가치입니다.
- 실시간 활용도와 카드‑셀렉터는 신용 점수와 보상을 최적화하는 데 도움을 줍니다.
- 수동 입력을 줄이고 YMYL 기준을 준수하는 것이 핀테크 성공에 필수적입니다.
신용카드 관리 도구를 구축한다면, 알림 문제를 먼저 해결하세요 – 나머지는 따라올 것입니다.
주요 요점
- 먼저 엣지 케이스를 이해하세요 – 코드를 한 줄이라도 작성하기 전에 엣지 케이스를 식별했는지 확인하세요.
- 관리 도구는 과소평가됩니다 – 모두가 사람들에게 금융 상품을 찾거나 구매하도록 돕는 도구를 만들지만, 이미 가지고 있는 것을 관리하도록 돕는 도구는 거의 없습니다.
- 단순함이 영리함을 이깁니다 – 제때 작동하는 마감 알림은 지출 패턴을 예측하는 머신러닝 모델보다 더 가치가 있습니다.
- 신뢰가 전부입니다 – 사람들이 자신의 금융 데이터를 입력하고 있습니다. 투명성, 보안, 그리고 정보를 가지고 교묘하게 행동하지 않음으로써 그 신뢰를 얻으세요.
여러 개의 신용카드를 동시에 관리하고 스프레드시트가 엉망이 되기 시작했다면 StackEasy를 확인해 보세요. 댓글에서 빌드에 관한 질문에 기꺼이 답변하겠습니다.