From 443622cd3c5aeef89140b8866d38048577c6164a Mon Sep 17 00:00:00 2001 From: richjohnrambo Date: Fri, 16 Oct 2026 10:14:41 +0800 Subject: [PATCH] fix: preserve legacy payment account display --- internal/finance/service.go | 47 ++++++++++++++++--------------- internal/finance/service_test.go | 22 +++++++++++++++ internal/store/withdrawals.go | 6 +++- web/src/mobile/views/FormView.vue | 2 ++ 4 files changed, 54 insertions(+), 23 deletions(-) diff --git a/internal/finance/service.go b/internal/finance/service.go index e1ffd0b..fce6592 100644 --- a/internal/finance/service.go +++ b/internal/finance/service.go @@ -104,6 +104,7 @@ type PaymentAccountDetail struct { AlipayQRURL string `json:"alipay_qr_url"` VerifyStatus string `json:"verify_status"` IsDefault bool `json:"is_default"` + Masked bool `json:"masked"` } type QuoteInput struct { @@ -297,7 +298,7 @@ func (s *Service) PaymentAccounts(ctx context.Context, tenantID, memberID int64) } func (s *Service) PaymentAccountDetails(ctx context.Context, tenantID, memberID int64) ([]PaymentAccountDetail, error) { - if !s.ready || s.repository == nil { + if s.repository == nil { return nil, ErrUnavailable } rows, err := s.repository.PaymentAccountDetails(ctx, tenantID, memberID) @@ -306,33 +307,35 @@ func (s *Service) PaymentAccountDetails(ctx context.Context, tenantID, memberID } items := make([]PaymentAccountDetail, 0, len(rows)) for _, row := range rows { - holderName, err := s.decrypt(row.HolderCipher) - if err != nil { - return nil, store.ErrPaymentAccountInvalid - } - accountNo, err := s.decrypt(row.AccountNoCipher) - if err != nil { - return nil, store.ErrPaymentAccountInvalid - } - identityNo := "" - if row.IdentityNoCipher != "" { - identityNo, err = s.decrypt(row.IdentityNoCipher) - if err != nil { - return nil, store.ErrPaymentAccountInvalid - } - } - digitalRMBCode := "" - if row.DigitalRMBCodeCipher != "" { - digitalRMBCode, err = s.decrypt(row.DigitalRMBCodeCipher) - if err != nil { - return nil, store.ErrPaymentAccountInvalid + holderName, accountNo := row.HolderMask, row.AccountNoMask + identityNo, digitalRMBCode := row.IdentityNoMask, row.DigitalRMBCodeMask + masked := true + if s.ready { + decryptedHolder, holderErr := s.decrypt(row.HolderCipher) + decryptedAccount, accountErr := s.decrypt(row.AccountNoCipher) + if holderErr == nil && accountErr == nil { + holderName, accountNo, masked = decryptedHolder, decryptedAccount, false + if row.IdentityNoCipher != "" { + if value, decryptErr := s.decrypt(row.IdentityNoCipher); decryptErr == nil { + identityNo = value + } else { + masked = true + } + } + if row.DigitalRMBCodeCipher != "" { + if value, decryptErr := s.decrypt(row.DigitalRMBCodeCipher); decryptErr == nil { + digitalRMBCode = value + } else { + masked = true + } + } } } items = append(items, PaymentAccountDetail{ ID: row.ID, BankName: row.BankName, HolderName: holderName, IdentityNo: identityNo, AccountNo: accountNo, DigitalRMBCode: digitalRMBCode, WechatQRURL: row.WechatQRURL, AlipayQRURL: row.AlipayQRURL, - VerifyStatus: verifyStatusName(row.VerifyStatus), IsDefault: row.IsDefault == 1, + VerifyStatus: verifyStatusName(row.VerifyStatus), IsDefault: row.IsDefault == 1, Masked: masked, }) } return items, nil diff --git a/internal/finance/service_test.go b/internal/finance/service_test.go index 0098cf4..0d8b29e 100644 --- a/internal/finance/service_test.go +++ b/internal/finance/service_test.go @@ -168,6 +168,28 @@ func TestPaymentAccountDetailsReturnsOriginalFields(t *testing.T) { } } +func TestPaymentAccountDetailsFallsBackToMasksWhenHistoricalKeyIsUnavailable(t *testing.T) { + repository := &fakeRepository{details: []store.PaymentAccount{{ + ID: 8, BankName: "中国银行", HolderCipher: "legacy-holder", HolderMask: "张*", + IdentityNoCipher: "legacy-id", IdentityNoMask: "1101**********123X", + AccountNoCipher: "legacy-account", AccountNoMask: "6222********1234", + DigitalRMBCodeCipher: "legacy-rmb", DigitalRMBCodeMask: "12****78", + WechatQRURL: "/wechat.png", AlipayQRURL: "/alipay.png", VerifyStatus: 2, + }}} + service := New(repository, &redis.Client{}, config.AuthConfig{ + FinanceMasterKey: "different-finance-master-key-with-32-characters", + }) + items, err := service.PaymentAccountDetails(context.Background(), 1, 2) + if err != nil { + t.Fatal(err) + } + if len(items) != 1 || !items[0].Masked || items[0].HolderName != "张*" || + items[0].IdentityNo != "1101**********123X" || items[0].AccountNo != "6222********1234" || + items[0].DigitalRMBCode != "12****78" { + t.Fatalf("unexpected masked fallback: %#v", items) + } +} + func TestCreatePaymentAccountDoesNotStoreEmptyDigitalRMBCode(t *testing.T) { repository := &paymentAccountRepository{} service := New(repository, &redis.Client{}, config.AuthConfig{ diff --git a/internal/store/withdrawals.go b/internal/store/withdrawals.go index 5854e0c..b91124d 100644 --- a/internal/store/withdrawals.go +++ b/internal/store/withdrawals.go @@ -156,14 +156,18 @@ func (s *Store) PaymentAccounts(ctx context.Context, tenantID, memberID int64) ( return items, err } -// PaymentAccountDetails returns encrypted fields only for the authenticated account owner. +// PaymentAccountDetails returns encrypted fields and their masked fallbacks for the authenticated owner. func (s *Store) PaymentAccountDetails(ctx context.Context, tenantID, memberID int64) ([]PaymentAccount, error) { items := make([]PaymentAccount, 0) err := s.db.SelectContext(ctx, &items, `SELECT a.id,a.account_type,a.bank_code,a.bank_name, COALESCE(a.holder_cipher,'') AS holder_cipher, + COALESCE(a.holder_mask,'') AS holder_mask, COALESCE(a.identity_no_cipher,'') AS identity_no_cipher, + COALESCE(a.identity_no_mask,'') AS identity_no_mask, COALESCE(a.account_no_cipher,'') AS account_no_cipher, + COALESCE(a.account_no_mask,'') AS account_no_mask, COALESCE(a.digital_rmb_code_cipher,'') AS digital_rmb_code_cipher, + COALESCE(a.digital_rmb_code_mask,'') AS digital_rmb_code_mask, COALESCE(a.wechat_qr_attachment_id,0) AS wechat_qr_attachment_id, COALESCE(a.alipay_qr_attachment_id,0) AS alipay_qr_attachment_id, COALESCE(wechat.file_url,'') AS wechat_qr_url, diff --git a/web/src/mobile/views/FormView.vue b/web/src/mobile/views/FormView.vue index 61e3338..1d16c0c 100644 --- a/web/src/mobile/views/FormView.vue +++ b/web/src/mobile/views/FormView.vue @@ -54,6 +54,7 @@

已绑定的取出信息