From ea4a54f5c6f0d1bf9a144b31a1cfd2865e75af41 Mon Sep 17 00:00:00 2001
From: SerinaNya <34389622+SerinaNya@users.noreply.github.com>
Date: Mon, 28 Sep 2026 14:26:22 +0800
Subject: [PATCH] fix(channels): sanitize card data and format suffixes
---
app/bookkeeping/bookkeeping-view.tsx | 14 +++++------
app/channels/channel-dialog.tsx | 9 +++++++
app/channels/channels-view.tsx | 16 +++++++-----
lib/actions/channel.ts | 37 +++++++++++++++++++---------
4 files changed, 51 insertions(+), 25 deletions(-)
diff --git a/app/bookkeeping/bookkeeping-view.tsx b/app/bookkeeping/bookkeeping-view.tsx
index 82b3c04..b87b326 100644
--- a/app/bookkeeping/bookkeeping-view.tsx
+++ b/app/bookkeeping/bookkeeping-view.tsx
@@ -71,14 +71,12 @@ const TXN_SCENES = [
function getChannelIdentifier(channel: ChannelWithAccountNames): string {
if (channel.channelType === "PAYMENT_CARD") {
- let suffix = channel.cardNumberSuffix?.trim()
- if (
- !suffix &&
- channel.cardNumberFull &&
- channel.cardNumberFull.length >= 4
- ) {
- suffix = channel.cardNumberFull.slice(-4)
- }
+ const digits = (
+ channel.cardNumberSuffix ||
+ channel.cardNumberFull ||
+ ""
+ ).replace(/\D/g, "")
+ const suffix = digits.length >= 4 ? digits.slice(-4) : digits
return suffix ? `•••• ${suffix}` : ""
}
if (channel.channelType === "E_WALLET") {
diff --git a/app/channels/channel-dialog.tsx b/app/channels/channel-dialog.tsx
index 6917368..2272701 100644
--- a/app/channels/channel-dialog.tsx
+++ b/app/channels/channel-dialog.tsx
@@ -275,11 +275,20 @@ export function ChannelDialog({
cardBrandRequest.current += 1
setCardBrand((value as CardBrand | null) || "")
}}
+ itemToStringLabel={(brand: CardBrand) =>
+ brand ? CARD_BRAND_LABELS[brand] || brand : ""
+ }
+ itemToStringValue={(brand: CardBrand) =>
+ brand
+ ? `${brand} ${CARD_BRAND_LABELS[brand] || ""}`
+ : ""
+ }
autoHighlight
>
diff --git a/app/channels/channels-view.tsx b/app/channels/channels-view.tsx
index 4e22e65..06ff887 100644
--- a/app/channels/channels-view.tsx
+++ b/app/channels/channels-view.tsx
@@ -118,6 +118,15 @@ function formatCardNumber(cardNumber: string) {
.trim()
}
+function getCardSuffix(channel: ChannelWithAccountNames) {
+ const digits = (
+ channel.cardNumberSuffix ||
+ channel.cardNumberFull ||
+ ""
+ ).replace(/\D/g, "")
+ return digits.length >= 4 ? digits.slice(-4) : digits
+}
+
export function ChannelsView({
initialChannels,
initialAccounts,
@@ -402,12 +411,7 @@ export function ChannelsView({
{visibleCardIds.has(channel.id) &&
channel.cardNumberFull
? formatCardNumber(channel.cardNumberFull)
- : `•••• ${
- channel.cardNumberSuffix ||
- formatCardNumber(
- channel.cardNumberFull || ""
- ).slice(-4)
- }`}
+ : `•••• ${getCardSuffix(channel)}`}
{channel.cardNumberFull && (
<>
diff --git a/lib/actions/channel.ts b/lib/actions/channel.ts
index 5d23661..941c104 100644
--- a/lib/actions/channel.ts
+++ b/lib/actions/channel.ts
@@ -133,10 +133,16 @@ export async function createChannelAction(
return { success: false, error: "关联的部分账户不存在或已被删除" }
}
- // 自动提取或补全卡号后4位
- let suffix = val.cardNumberSuffix?.trim() || null
- if (!suffix && val.cardNumberFull && val.cardNumberFull.length >= 4) {
- suffix = val.cardNumberFull.slice(-4)
+ // 自动提取并清洗纯数字卡号及后4位
+ const cleanFull = val.cardNumberFull
+ ? val.cardNumberFull.replace(/\D/g, "")
+ : ""
+ const cleanSuffix = val.cardNumberSuffix
+ ? val.cardNumberSuffix.replace(/\D/g, "")
+ : ""
+ let suffix = cleanSuffix.slice(-4) || null
+ if (!suffix && cleanFull.length >= 4) {
+ suffix = cleanFull.slice(-4)
}
const [newChannel] = await db
@@ -151,8 +157,9 @@ export async function createChannelAction(
region: val.region ? val.region.toUpperCase().trim() : null,
issuerName: val.issuerName?.trim() || null,
cardType: val.cardType || null,
- cardNumberFull: val.cardNumberFull?.trim() || null,
- cardNumberSuffix: suffix,
+ cardNumberFull:
+ val.channelType === "PAYMENT_CARD" ? cleanFull || null : null,
+ cardNumberSuffix: val.channelType === "PAYMENT_CARD" ? suffix : null,
cardBrand: val.cardBrand?.trim() || null,
platform: val.platform?.trim() || null,
platformAccountId: val.platformAccountId?.trim() || null,
@@ -205,9 +212,16 @@ export async function updateChannelAction(
return { success: false, error: "关联的部分账户不存在或已被删除" }
}
- let suffix = val.cardNumberSuffix?.trim() || null
- if (!suffix && val.cardNumberFull && val.cardNumberFull.length >= 4) {
- suffix = val.cardNumberFull.slice(-4)
+ // 自动提取并清洗纯数字卡号及后4位
+ const cleanFull = val.cardNumberFull
+ ? val.cardNumberFull.replace(/\D/g, "")
+ : ""
+ const cleanSuffix = val.cardNumberSuffix
+ ? val.cardNumberSuffix.replace(/\D/g, "")
+ : ""
+ let suffix = cleanSuffix.slice(-4) || null
+ if (!suffix && cleanFull.length >= 4) {
+ suffix = cleanFull.slice(-4)
}
const [updated] = await db
@@ -220,8 +234,9 @@ export async function updateChannelAction(
region: val.region ? val.region.toUpperCase().trim() : null,
issuerName: val.issuerName?.trim() || null,
cardType: val.cardType || null,
- cardNumberFull: val.cardNumberFull?.trim() || null,
- cardNumberSuffix: suffix,
+ cardNumberFull:
+ val.channelType === "PAYMENT_CARD" ? cleanFull || null : null,
+ cardNumberSuffix: val.channelType === "PAYMENT_CARD" ? suffix : null,
cardBrand: val.cardBrand?.trim() || null,
platform: val.platform?.trim() || null,
platformAccountId: val.platformAccountId?.trim() || null,