313 lines
8.2 KiB
Go
313 lines
8.2 KiB
Go
package paymentaccount
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
|
|
"hfb_sys/backend/internal/model"
|
|
"hfb_sys/backend/pkg/crypto"
|
|
|
|
"gorm.io/datatypes"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// 删除加密密钥常量
|
|
|
|
type Repository struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
func NewRepository(db *gorm.DB) *Repository {
|
|
return &Repository{db: db}
|
|
}
|
|
|
|
func (r *Repository) List(userID uint64, page, pageSize int) (*PaginatedResult, error) {
|
|
var total int64
|
|
if err := r.db.Model(&model.UserPaymentAccount{}).
|
|
Where("user_id = ? AND status = ?", userID, "active").
|
|
Count(&total).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
offset := (page - 1) * pageSize
|
|
var accounts []model.UserPaymentAccount
|
|
if err := r.db.Where("user_id = ? AND status = ?", userID, "active").
|
|
Order("is_default DESC, created_at DESC").
|
|
Offset(offset).Limit(pageSize).
|
|
Find(&accounts).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
items := make([]PaymentAccountDTO, 0, len(accounts))
|
|
for _, acc := range accounts {
|
|
dto, err := r.toDTO(acc)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
items = append(items, *dto)
|
|
}
|
|
|
|
return &PaginatedResult{
|
|
Items: items,
|
|
Total: total,
|
|
Page: page,
|
|
PageSize: pageSize,
|
|
}, nil
|
|
}
|
|
|
|
func (r *Repository) FindByID(userID, id uint64) (*PaymentAccountDTO, error) {
|
|
var account model.UserPaymentAccount
|
|
if err := r.db.Where("id = ? AND user_id = ? AND status = ?", id, userID, "active").
|
|
First(&account).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, ErrAccountNotFound
|
|
}
|
|
return nil, err
|
|
}
|
|
return r.toDTO(account)
|
|
}
|
|
|
|
func (r *Repository) Create(userID uint64, req CreatePaymentAccountRequest) (*PaymentAccountDTO, error) {
|
|
// 加密账号
|
|
encryptedNo, err := crypto.Encrypt(req.AccountNo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 处理凭证URLs
|
|
var certURLs datatypes.JSON
|
|
if len(req.CertificateURLs) > 0 {
|
|
certURLs, _ = json.Marshal(req.CertificateURLs)
|
|
}
|
|
|
|
// 如果是第一个账号,自动设为默认
|
|
isDefault := false
|
|
var count int64
|
|
r.db.Model(&model.UserPaymentAccount{}).Where("user_id = ? AND status = ?", userID, "active").Count(&count)
|
|
if count == 0 {
|
|
isDefault = true
|
|
}
|
|
|
|
account := model.UserPaymentAccount{
|
|
UserID: userID,
|
|
AccountType: req.AccountType,
|
|
AccountName: req.AccountName,
|
|
AccountNo: encryptedNo,
|
|
BankName: req.BankName,
|
|
BankBranch: req.BankBranch,
|
|
CertificateURLs: certURLs,
|
|
IsDefault: isDefault,
|
|
Status: "active",
|
|
}
|
|
|
|
if err := r.db.Create(&account).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return r.FindByID(userID, account.ID)
|
|
}
|
|
|
|
func (r *Repository) Update(userID, id uint64, req UpdatePaymentAccountRequest) (*PaymentAccountDTO, error) {
|
|
var account model.UserPaymentAccount
|
|
if err := r.db.Where("id = ? AND user_id = ? AND status = ?", id, userID, "active").
|
|
First(&account).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, ErrAccountNotFound
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
updates := make(map[string]interface{})
|
|
|
|
if req.BankBranch != "" {
|
|
updates["bank_branch"] = req.BankBranch
|
|
}
|
|
|
|
if len(req.CertificateURLs) > 0 {
|
|
certURLs, _ := json.Marshal(req.CertificateURLs)
|
|
updates["certificate_urls"] = certURLs
|
|
}
|
|
|
|
if req.IsDefault != nil && *req.IsDefault {
|
|
// 先取消其他默认账号
|
|
r.db.Model(&model.UserPaymentAccount{}).
|
|
Where("user_id = ? AND id != ?", userID, id).
|
|
Update("is_default", false)
|
|
updates["is_default"] = true
|
|
}
|
|
|
|
if len(updates) > 0 {
|
|
if err := r.db.Model(&account).Updates(updates).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return r.FindByID(userID, id)
|
|
}
|
|
|
|
func (r *Repository) Delete(userID, id uint64) error {
|
|
var account model.UserPaymentAccount
|
|
if err := r.db.Where("id = ? AND user_id = ? AND status = ?", id, userID, "active").
|
|
First(&account).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return ErrAccountNotFound
|
|
}
|
|
return err
|
|
}
|
|
|
|
// 软删除
|
|
return r.db.Model(&account).Update("status", "disabled").Error
|
|
}
|
|
|
|
func (r *Repository) SetDefault(userID, id uint64) error {
|
|
// 验证账号存在
|
|
var account model.UserPaymentAccount
|
|
if err := r.db.Where("id = ? AND user_id = ? AND status = ?", id, userID, "active").
|
|
First(&account).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return ErrAccountNotFound
|
|
}
|
|
return err
|
|
}
|
|
|
|
return r.db.Transaction(func(tx *gorm.DB) error {
|
|
// 取消其他默认账号
|
|
if err := tx.Model(&model.UserPaymentAccount{}).
|
|
Where("user_id = ? AND id != ?", userID, id).
|
|
Update("is_default", false).Error; err != nil {
|
|
return err
|
|
}
|
|
// 设置当前为默认
|
|
return tx.Model(&account).Update("is_default", true).Error
|
|
})
|
|
}
|
|
|
|
func (r *Repository) CountByUser(userID uint64) (int64, error) {
|
|
var count int64
|
|
err := r.db.Model(&model.UserPaymentAccount{}).
|
|
Where("user_id = ? AND status = ?", userID, "active").
|
|
Count(&count).Error
|
|
return count, err
|
|
}
|
|
|
|
func (r *Repository) ValidateRealname(userID uint64, accountName string) error {
|
|
var user model.User
|
|
if err := r.db.First(&user, userID).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
if user.RealnameStatus != "verified" {
|
|
return ErrRealnameRequired
|
|
}
|
|
|
|
// 获取实名信息
|
|
var realname model.UserRealname
|
|
if err := r.db.Where("user_id = ? AND status = ?", userID, "verified").
|
|
First(&realname).Error; err != nil {
|
|
return ErrRealnameRequired
|
|
}
|
|
|
|
// 验证姓名匹配 - 使用加密字段进行精确匹配
|
|
if realname.EncryptedName != "" {
|
|
// 有加密字段,解密后精确匹配
|
|
decryptedName, err := crypto.Decrypt(realname.EncryptedName)
|
|
if err != nil {
|
|
// 解密失败,降级到前缀匹配
|
|
return r.validateByMaskedName(realname.MaskedName, accountName)
|
|
}
|
|
// 精确匹配(去除空格)
|
|
if strings.ReplaceAll(decryptedName, " ", "") != strings.ReplaceAll(accountName, " ", "") {
|
|
return ErrAccountNameMismatch
|
|
}
|
|
} else {
|
|
// 没有加密字段(旧数据),使用前缀匹配
|
|
return r.validateByMaskedName(realname.MaskedName, accountName)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// 使用脱敏姓名进行前缀匹配(兼容旧数据)
|
|
func (r *Repository) validateByMaskedName(maskedName, accountName string) error {
|
|
maskedName = strings.ReplaceAll(maskedName, " ", "")
|
|
inputName := strings.ReplaceAll(accountName, " ", "")
|
|
|
|
if strings.Contains(maskedName, "*") {
|
|
// 提取非星号部分(通常是姓氏)
|
|
prefix := strings.Split(maskedName, "*")[0]
|
|
if prefix != "" && !strings.HasPrefix(inputName, prefix) {
|
|
return ErrAccountNameMismatch
|
|
}
|
|
} else {
|
|
// 完整匹配
|
|
if maskedName != inputName {
|
|
return ErrAccountNameMismatch
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *Repository) toDTO(account model.UserPaymentAccount) (*PaymentAccountDTO, error) {
|
|
// 解密账号并脱敏
|
|
decrypted, err := crypto.Decrypt(account.AccountNo)
|
|
if err != nil {
|
|
decrypted = account.AccountNo // 降级处理
|
|
}
|
|
maskedNo := maskAccountNo(decrypted, account.AccountType)
|
|
|
|
// 解析凭证URLs
|
|
var certURLs []string
|
|
if account.CertificateURLs != nil {
|
|
json.Unmarshal(account.CertificateURLs, &certURLs)
|
|
}
|
|
|
|
return &PaymentAccountDTO{
|
|
ID: account.ID,
|
|
UserID: account.UserID,
|
|
AccountType: account.AccountType,
|
|
AccountName: account.AccountName,
|
|
AccountNo: maskedNo,
|
|
BankName: account.BankName,
|
|
BankBranch: account.BankBranch,
|
|
CertificateURLs: certURLs,
|
|
IsDefault: account.IsDefault,
|
|
Status: account.Status,
|
|
CreatedAt: account.CreatedAt,
|
|
UpdatedAt: account.UpdatedAt,
|
|
}, nil
|
|
}
|
|
|
|
// 账号脱敏
|
|
func maskAccountNo(accountNo, accountType string) string {
|
|
length := len(accountNo)
|
|
if length <= 4 {
|
|
return accountNo
|
|
}
|
|
|
|
switch accountType {
|
|
case "alipay", "wechat":
|
|
// 手机号或邮箱
|
|
if length == 11 {
|
|
return accountNo[:3] + "****" + accountNo[7:]
|
|
}
|
|
return accountNo[:2] + "****" + accountNo[length-2:]
|
|
case "bank":
|
|
// 银行卡号
|
|
if length > 8 {
|
|
return accountNo[:4] + "****" + accountNo[length-4:]
|
|
}
|
|
return accountNo[:2] + "****" + accountNo[length-2:]
|
|
}
|
|
return accountNo
|
|
}
|
|
|
|
// 获取解密后的账号(仅供内部使用,如提现申请时)
|
|
func (r *Repository) GetDecryptedAccountNo(userID, id uint64) (string, error) {
|
|
var account model.UserPaymentAccount
|
|
if err := r.db.Where("id = ? AND user_id = ?", id, userID).First(&account).Error; err != nil {
|
|
return "", err
|
|
}
|
|
return crypto.Decrypt(account.AccountNo)
|
|
}
|