CardPin is a free, open-source payment card reward helper. The vision is simple: when you are about to pay, CardPin should make it easy to choose the card that appears to earn the best reward for that merchant, category, and spend amount.
CardPin is built for transparency and privacy. Reward data lives in this repository, recommendations are calculated in the browser, and users do not need to create an account or send their wallet choices to a server.
CardPin is static-first:
- No backend
- No authentication
- No database
- No server-side recommendation API
- No telemetry or analytics
- Browser-only preferences via
localStorage
Data starts as country-scoped JSON files under data/. The compile script validates schemas and relationships, then writes static bundles to apps/web/public/data/. The Next.js app fetches those JSON bundles and runs the TypeScript recommendation engine locally in the browser.
data/<country>/*.json
-> pnpm compile:data
-> apps/web/public/data/<country>.json
-> static web app
-> local browser recommendation
| Path | Purpose |
|---|---|
apps/web/ |
Next.js frontend configured for static export |
packages/engine/ |
Pure TypeScript recommendation engine |
packages/schemas/ |
Zod schemas for datasets |
data/be/ |
Belgium dataset |
data/de/ |
Germany dataset |
data/nl/ |
Netherlands dataset |
data/us/ |
United States dataset |
scripts/compile-datasets.ts |
Dataset validation and static bundle generation |
- Node.js: Version
20(LTS is recommended. A.nvmrcis provided). - Corepack: Required to manage the package manager version automatically.
This project uses the package manager declared in package.json:
"packageManager": "pnpm@9.0.0"To set up the project locally:
- Ensure you are using Node.js 20 (e.g. run
nvm useif using NVM). - Enable Corepack and activate the configured
pnpmversion:corepack enable corepack prepare pnpm@9.0.0 --activate - Install dependencies:
pnpm install
- Compile the datasets into static frontend JSON bundles:
pnpm compile:data
- Run the development server:
pnpm dev
If your environment or shell does not expose pnpm globally, use the Corepack fallback prefix:
corepack pnpm install
corepack pnpm compile:data
corepack pnpm devRun the following commands to validate the data and build the application:
- Format Datasets:
pnpm format:data(alphabetizes data collections and applies standard spacing) - Validate Datasets:
pnpm validate:data(checks schema rules, duplicates, relational integrity, and formatting) - TypeScript Typecheck:
pnpm typecheck - Lint Codebase:
pnpm lint - Run Unit Tests:
pnpm test - Build Production App:
pnpm build(this compiles the datasets and builds the static Next.js export inapps/web/out)
Fallback forms:
corepack pnpm format:data
corepack pnpm validate:data
corepack pnpm typecheck
corepack pnpm lint
corepack pnpm test
corepack pnpm buildUse these commands when editing datasets:
pnpm validate:data
pnpm compile:datapnpm validate:data checks schemas and relationships without writing compiled bundles. pnpm compile:data performs the same validation and updates the static JSON bundles consumed by the web app.
CardPin currently has datasets for Belgium (data/be/), Germany (data/de/), Netherlands (data/nl/), and United States (data/us/). Each dataset contains:
issuers.jsoncards.jsonmerchants.jsonreward_rules.json
To add an issuer, edit the relevant issuers.json file and use a stable lowercase kebab-case id.
To add a card, edit the relevant cards.json file, reference an existing issuerId, use the correct uppercase country code, and include source proof for card terms where the schema requires it.
To add a merchant, edit the relevant merchants.json file and use existing category names where possible so reward rules continue to match predictably.
To add a reward rule, edit the relevant reward_rules.json file and reference an existing cardId. Do not guess reward rates. Every reward rule must be backed by an official public source.
Reward rules must include source attribution:
{
"source": {
"sourceUrl": "https://www.example-bank.com/card-terms",
"verifiedAt": "2026-06-14",
"verifiedBy": "your-github-handle"
}
}Requirements:
sourceUrlmust point to an official issuer, card product, or terms page.verifiedAtmust be the date you checked the source.verifiedBymust identify the contributor who verified the source.- Reward rates, fees, exclusions, caps, and eligibility rules must be copied from official sources, not inferred from marketing summaries.
Expanded CardPin dataset coverage to include 166 US credit cards, 16 major issuers (Chase, American Express, Citi, Capital One, Bank of America, U.S. Bank, Discover, Wells Fargo, etc.), and 345 welcome bonus & reward rules.
Introduced a node-based CSV import runner (scripts/import-us-csv.ts) to ingest bulk credit card data dumps, map raw card attributes to Zod schemas with unique kebab-case IDs (${issuerId}-${rawName}-${rawCardId}), and validate generated JSON entities against CountryCardsJsonSchema, CountryIssuersJsonSchema, and CountryRewardRulesJsonSchema prior to writing to disk. Run pnpm compile:data as a follow-up step to bundle the dataset for the frontend app.
Universal base reward rules are kept uncategorized (!r.category) so that the recommendation engine (recommendBestCard()) matches them as general fallback rules for all transaction categories when no category-specific bonus rules apply.
Base reward rates accurately distinguish cash earnings (cashback_percentage) from loyalty points (points) and airline miles (miles) for rewards programs (Delta SkyMiles, Chase Ultimate Rewards, Amex Membership Rewards, Hilton Honors, Marriott Bonvoy, etc.).
Refactored CardPinCalculator into specialized, typed custom React hooks (useWalletDataset and useDevTools). Separated dataset loading, wallet synchronization, catalog search, monthly spend calculations, and dev tools panel management from presentation components, ensuring high maintainability, strict type safety, and zero lint warnings.
Eliminated blank and generic "No recommendation" error states during search. When a search query produces no matching bonus rules for the user's owned cards, CardPin dynamically falls back to recommending the best base card (e.g. lowest FX fee card for foreign transactions or standard base reward card) and displays clear fallback rationale.
Alternative cards in the comparison list now display explicit disqualification tags when applicable:
⚠️ Cap Reached: Indicates a card's monthly reward cap has been exhausted.⚠️ Min Spend €X Not Met: Highlights when a purchase amount falls below a rule's minimum threshold.- ℹ️ No Bonus Rule: Indicates standard base reward terms apply.
Added an explicit "
Wallet cards in the wallet manager feature real-time cap exhaustion alerts (⚠️ CAP REACHED / ⚠️ CAP) whenever a user's configured monthly spend reaches the card's reward cap limits.
Verification dates for card terms and reward rules are formatted into human-readable relative time (e.g., "Verified 2 months ago" or "Verified this month") for clearer data freshness context.
To prevent recommending rules on transactions that do not qualify for reward tiers, the recommendation engine dynamically validates purchase amounts against conditions.minSpend defined in dataset rules. If a purchase falls below a rule's minimum threshold, rewards are zeroed out for that rule.
CardPin evaluates un-owned cards matching the user's active country dataset alongside their wallet. When an un-owned card yields higher net rewards than the user's current wallet (or when no cards are selected), CardPin displays a Next Card to Unlock banner in the dashboard showing the calculated extra reward benefit.
Reorganized the primary calculator interface into a single-screen responsive utility. On desktop, the result hero card is positioned at the top-left, transaction parameters entry input strip at the bottom-left, and the wallet manager resides in a sidecar column on the right. On mobile, the vertical flow prioritizes calculation results at the top, followed by transaction inputs, with wallet management stacked cleanly at the bottom.
Refactored the CardPinCalculator page into modular, typed components:
CountryAudienceSelector(compact top bar controls)InputStrip(integrated purchase parameters row with foreign spend toggle)ResultHero(high-density recommendation display with fallback support)WalletManager(sidecar wallet setups with cap alerts and imports/exports)StatusBanner(unified state handlers for empty/pre-search states)
CardPin features a 3-stage progression that balances instant in-store decision-making with narrative transparency:
- Stage 1: Context Setting (
InputStrip): Purchase parameters with quick spend presets (€10, €50, €100, €500), currency selection, and foreign transaction toggle. - Stage 2: Recommendation Rationale (
ResultHero): Instant recommendation display alongside plain-English story rationale explaining reward multipliers, FX fee offsets, or base card fallbacks. - Stage 3: Portfolio Opportunity Story (
WalletManager): Unowned catalog card recommendations highlighted with exact comparative return deltas.
Owned cards use a stable vertical stack with a consistent visible header for every collapsed card and one fully expanded selected card. Selecting a collapsed card moves it to the front cleanly. Selected-card settings (monthly spend input and removal) are integrated directly inside the active card shell, while import, export, and clear actions live in a compact wallet-options menu.
CardPin uses keyboard-visible focus states, semantic selectable cards with proper role="button" accessibility, an accessible native card-catalog dialog, and touch-friendly actions. On small screens, wallet spacing is compressed so the purchase search remains close to the selected cards. Search controls stay disabled until a card is selected and provide a direct route to the catalog.
The interface uses the operating system font stack to avoid a render-blocking third-party font request. Live exchange rates are fetched only after a non-EUR currency is selected. Recommendations wait for a valid selected-currency rate, preventing missing or malformed rates from silently treating foreign spend as EUR.
CardPin is configured as a fully installable PWA. A Service Worker (sw.js) caches the application shell and country datasets (/data/*.json) locally. EUR recommendations work offline; foreign-currency recommendations also work when a valid rate is available in the session cache.
To prevent recommending cards that have reached their monthly reward limits, users can input their Spent this month value inside each card mockup. The recommendation engine dynamically tracks this against the card rule's cap (or conditions.cap) and applies fallback rates or zero points/miles on the portion of the purchase exceeding the limit.
To verify that database reference URLs remain active, run the link scanner utility:
npx tsx scripts/check-links.tsThis utility parallel-scans all reference URLs inside the datasets to identify broken (e.g. 404, 403, or failed) bank terms pages.
To easily merge or update data without manual text editing:
- Run the local dev server:
pnpm dev - Open
http://localhost:3000/?dev=true - Click 🔧 Dev Tools at the bottom-right corner to open the bulk importer modal. You can paste raw JSON arrays or objects and click Merge & Recompile Datasets to write, format, validate, and compile changes to disk automatically.
CardPin is not financial advice. Data is community-sourced and may be inaccurate or outdated. Always verify terms with official issuer sources before making decisions.
See DISCLAIMER.md for the full disclaimer.
See CONTRIBUTING.md for data standards, workflow, and PR expectations.
CardPin is released under the MIT License. See LICENSE.