draft
This commit is contained in:
30
backend/internal/repository/admin_user.go
Normal file
30
backend/internal/repository/admin_user.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"pay-bridge/internal/model"
|
||||
)
|
||||
|
||||
type AdminUserRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewAdminUserRepository(db *gorm.DB) *AdminUserRepository {
|
||||
return &AdminUserRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *AdminUserRepository) GetByUsername(ctx context.Context, username string) (*model.AdminUser, error) {
|
||||
var user model.AdminUser
|
||||
err := r.db.WithContext(ctx).Where("username = ? AND status = 1", username).First(&user).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (r *AdminUserRepository) Create(ctx context.Context, user *model.AdminUser) error {
|
||||
return r.db.WithContext(ctx).Create(user).Error
|
||||
}
|
||||
71
backend/internal/repository/app.go
Normal file
71
backend/internal/repository/app.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"pay-bridge/internal/model"
|
||||
)
|
||||
|
||||
// AppRepository app 数据访问
|
||||
type AppRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewAppRepository(db *gorm.DB) *AppRepository {
|
||||
return &AppRepository{db: db}
|
||||
}
|
||||
|
||||
// GetByAppID 根据 appId 查询
|
||||
func (r *AppRepository) GetByAppID(ctx context.Context, appID string) (*model.App, error) {
|
||||
var app model.App
|
||||
err := r.db.WithContext(ctx).Where("app_id = ? AND status = 1", appID).First(&app).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return &app, err
|
||||
}
|
||||
|
||||
// Create 创建应用
|
||||
func (r *AppRepository) Create(ctx context.Context, app *model.App) error {
|
||||
return r.db.WithContext(ctx).Create(app).Error
|
||||
}
|
||||
|
||||
// ListActive 查询所有启用的应用
|
||||
func (r *AppRepository) ListActive(ctx context.Context) ([]*model.App, error) {
|
||||
var apps []*model.App
|
||||
err := r.db.WithContext(ctx).Where("status = 1").Find(&apps).Error
|
||||
return apps, err
|
||||
}
|
||||
|
||||
// List 分页查询所有应用(不过滤状态)
|
||||
func (r *AppRepository) List(ctx context.Context, limit, offset int) ([]*model.App, error) {
|
||||
var apps []*model.App
|
||||
err := r.db.WithContext(ctx).Order("id DESC").Limit(limit).Offset(offset).Find(&apps).Error
|
||||
return apps, err
|
||||
}
|
||||
|
||||
// GetByAppIDUnscoped 不过滤状态地查询(用于管理接口)
|
||||
func (r *AppRepository) GetByAppIDUnscoped(ctx context.Context, appID string) (*model.App, error) {
|
||||
var app model.App
|
||||
err := r.db.WithContext(ctx).Where("app_id = ?", appID).First(&app).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return &app, err
|
||||
}
|
||||
|
||||
// UpdateStatus 更新应用状态
|
||||
func (r *AppRepository) UpdateStatus(ctx context.Context, appID string, status int8) error {
|
||||
return r.db.WithContext(ctx).Model(&model.App{}).
|
||||
Where("app_id = ?", appID).
|
||||
Update("status", status).Error
|
||||
}
|
||||
|
||||
// UpdateSecret 更新应用密钥
|
||||
func (r *AppRepository) UpdateSecret(ctx context.Context, appID, encSecret string) error {
|
||||
return r.db.WithContext(ctx).Model(&model.App{}).
|
||||
Where("app_id = ?", appID).
|
||||
Update("app_secret", encSecret).Error
|
||||
}
|
||||
45
backend/internal/repository/channel_config.go
Normal file
45
backend/internal/repository/channel_config.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"pay-bridge/internal/model"
|
||||
)
|
||||
|
||||
// ChannelConfigRepository 渠道配置数据访问
|
||||
type ChannelConfigRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewChannelConfigRepository(db *gorm.DB) *ChannelConfigRepository {
|
||||
return &ChannelConfigRepository{db: db}
|
||||
}
|
||||
|
||||
// GetByAppChannel 按 app_id + channel_code 查询
|
||||
func (r *ChannelConfigRepository) GetByAppChannel(ctx context.Context, appID, channelCode string) (*model.ChannelConfig, error) {
|
||||
var cfg model.ChannelConfig
|
||||
err := r.db.WithContext(ctx).Where("app_id = ? AND channel_code = ? AND status = 1", appID, channelCode).First(&cfg).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return &cfg, err
|
||||
}
|
||||
|
||||
// Create 创建渠道配置
|
||||
func (r *ChannelConfigRepository) Create(ctx context.Context, cfg *model.ChannelConfig) error {
|
||||
return r.db.WithContext(ctx).Create(cfg).Error
|
||||
}
|
||||
|
||||
// Update 更新渠道配置
|
||||
func (r *ChannelConfigRepository) Update(ctx context.Context, id uint64, updates map[string]any) error {
|
||||
return r.db.WithContext(ctx).Model(&model.ChannelConfig{}).Where("id = ?", id).Updates(updates).Error
|
||||
}
|
||||
|
||||
// ListByApp 查询应用下所有启用的渠道配置
|
||||
func (r *ChannelConfigRepository) ListByApp(ctx context.Context, appID string) ([]*model.ChannelConfig, error) {
|
||||
var cfgs []*model.ChannelConfig
|
||||
err := r.db.WithContext(ctx).Where("app_id = ? AND status = 1", appID).Find(&cfgs).Error
|
||||
return cfgs, err
|
||||
}
|
||||
115
backend/internal/repository/merchant.go
Normal file
115
backend/internal/repository/merchant.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"pay-bridge/internal/model"
|
||||
)
|
||||
|
||||
// MerchantRepository 商户数据访问
|
||||
type MerchantRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewMerchantRepository(db *gorm.DB) *MerchantRepository {
|
||||
return &MerchantRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *MerchantRepository) Create(ctx context.Context, m *model.Merchant) error {
|
||||
return r.db.WithContext(ctx).Create(m).Error
|
||||
}
|
||||
|
||||
func (r *MerchantRepository) GetByMerchantID(ctx context.Context, merchantID string) (*model.Merchant, error) {
|
||||
var m model.Merchant
|
||||
err := r.db.WithContext(ctx).Where("merchant_id = ?", merchantID).First(&m).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return &m, err
|
||||
}
|
||||
|
||||
func (r *MerchantRepository) UpdateStatus(ctx context.Context, merchantID string, status model.MerchantStatus, updates map[string]any) error {
|
||||
if updates == nil {
|
||||
updates = make(map[string]any)
|
||||
}
|
||||
updates["status"] = status
|
||||
return r.db.WithContext(ctx).Model(&model.Merchant{}).Where("merchant_id = ?", merchantID).Updates(updates).Error
|
||||
}
|
||||
|
||||
func (r *MerchantRepository) List(ctx context.Context, status model.MerchantStatus, limit, offset int) ([]*model.Merchant, error) {
|
||||
var merchants []*model.Merchant
|
||||
q := r.db.WithContext(ctx)
|
||||
if status != "" {
|
||||
q = q.Where("status = ?", status)
|
||||
}
|
||||
err := q.Order("created_at DESC").Limit(limit).Offset(offset).Find(&merchants).Error
|
||||
return merchants, err
|
||||
}
|
||||
|
||||
// ListAnomalous 查询状态异常的商户(Frozen/Rejected)
|
||||
func (r *MerchantRepository) ListAnomalous(ctx context.Context) ([]*model.Merchant, error) {
|
||||
var merchants []*model.Merchant
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("status IN ?", []model.MerchantStatus{
|
||||
model.MerchantStatusFrozen,
|
||||
model.MerchantStatusRejected,
|
||||
}).Find(&merchants).Error
|
||||
return merchants, err
|
||||
}
|
||||
|
||||
// GetByMerchantIDAndAppID 带 appID 隔离查询(业务侧用)
|
||||
func (r *MerchantRepository) GetByMerchantIDAndAppID(ctx context.Context, merchantID, appID string) (*model.Merchant, error) {
|
||||
var m model.Merchant
|
||||
err := r.db.WithContext(ctx).Where("merchant_id = ? AND app_id = ?", merchantID, appID).First(&m).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return &m, err
|
||||
}
|
||||
|
||||
// ListByAppID 按 appID 分页查询(业务侧用)
|
||||
func (r *MerchantRepository) ListByAppID(ctx context.Context, appID string, status model.MerchantStatus, limit, offset int) ([]*model.Merchant, error) {
|
||||
var merchants []*model.Merchant
|
||||
q := r.db.WithContext(ctx).Where("app_id = ?", appID)
|
||||
if status != "" {
|
||||
q = q.Where("status = ?", status)
|
||||
}
|
||||
err := q.Limit(limit).Offset(offset).Order("id DESC").Find(&merchants).Error
|
||||
return merchants, err
|
||||
}
|
||||
|
||||
// CreateApplication 创建进件申请
|
||||
func (r *MerchantRepository) CreateApplication(ctx context.Context, app *model.MerchantApplication) error {
|
||||
return r.db.WithContext(ctx).Create(app).Error
|
||||
}
|
||||
|
||||
// GetLatestApplication 获取商户最新进件申请
|
||||
func (r *MerchantRepository) GetLatestApplication(ctx context.Context, merchantID string) (*model.MerchantApplication, error) {
|
||||
var app model.MerchantApplication
|
||||
err := r.db.WithContext(ctx).Where("merchant_id = ?", merchantID).
|
||||
Order("created_at DESC").First(&app).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return &app, err
|
||||
}
|
||||
|
||||
// GetApprovedApplicationByChannel 查询指定商户在指定渠道已审核通过的进件记录
|
||||
func (r *MerchantRepository) GetApprovedApplicationByChannel(ctx context.Context, merchantID, channelCode string) (*model.MerchantApplication, error) {
|
||||
var app model.MerchantApplication
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("merchant_id = ? AND channel_code = ? AND audit_status = ?", merchantID, channelCode, model.AuditStatusApproved).
|
||||
First(&app).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return &app, err
|
||||
}
|
||||
|
||||
// UpdateApplication 更新进件申请状态
|
||||
func (r *MerchantRepository) UpdateApplication(ctx context.Context, applicationID string, updates map[string]any) error {
|
||||
return r.db.WithContext(ctx).Model(&model.MerchantApplication{}).
|
||||
Where("application_id = ?", applicationID).Updates(updates).Error
|
||||
}
|
||||
75
backend/internal/repository/notify_log.go
Normal file
75
backend/internal/repository/notify_log.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
"pay-bridge/internal/model"
|
||||
)
|
||||
|
||||
// NotifyLogRepository 通知记录数据访问
|
||||
type NotifyLogRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewNotifyLogRepository(db *gorm.DB) *NotifyLogRepository {
|
||||
return &NotifyLogRepository{db: db}
|
||||
}
|
||||
|
||||
// Upsert 创建或更新通知记录
|
||||
func (r *NotifyLogRepository) Upsert(ctx context.Context, log *model.NotifyLog) error {
|
||||
return r.db.WithContext(ctx).Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "trade_no"}, {Name: "notify_type"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"notify_url", "status", "retry_count", "next_retry_time", "last_response"}),
|
||||
}).Create(log).Error
|
||||
}
|
||||
|
||||
// GetByTradeNo 按 trade_no + notify_type 查询
|
||||
func (r *NotifyLogRepository) GetByTradeNo(ctx context.Context, tradeNo string, notifyType model.NotifyType) (*model.NotifyLog, error) {
|
||||
var log model.NotifyLog
|
||||
err := r.db.WithContext(ctx).Where("trade_no = ? AND notify_type = ?", tradeNo, notifyType).First(&log).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return &log, err
|
||||
}
|
||||
|
||||
// ListPendingRetry 查询到期需要重试的通知
|
||||
func (r *NotifyLogRepository) ListPendingRetry(ctx context.Context, before time.Time, limit int) ([]*model.NotifyLog, error) {
|
||||
var logs []*model.NotifyLog
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("status IN ? AND next_retry_time <= ?",
|
||||
[]model.NotifyStatus{model.NotifyStatusPending, model.NotifyStatusRetry},
|
||||
before).
|
||||
Order("next_retry_time ASC").
|
||||
Limit(limit).
|
||||
Find(&logs).Error
|
||||
return logs, err
|
||||
}
|
||||
|
||||
// IncrRetryCount 重试次数+1,更新下次重试时间和最后响应
|
||||
func (r *NotifyLogRepository) IncrRetryCount(ctx context.Context, id uint64, status model.NotifyStatus, nextRetryTime *time.Time, lastResponse string) error {
|
||||
return r.db.WithContext(ctx).Model(&model.NotifyLog{}).Where("id = ?", id).Updates(map[string]any{
|
||||
"retry_count": gorm.Expr("retry_count + 1"),
|
||||
"status": status,
|
||||
"next_retry_time": nextRetryTime,
|
||||
"last_response": lastResponse,
|
||||
}).Error
|
||||
}
|
||||
|
||||
// MarkSuccess 标记通知成功
|
||||
func (r *NotifyLogRepository) MarkSuccess(ctx context.Context, id uint64, lastResponse string) error {
|
||||
return r.db.WithContext(ctx).Model(&model.NotifyLog{}).Where("id = ?", id).Updates(map[string]any{
|
||||
"status": model.NotifyStatusSuccess,
|
||||
"last_response": lastResponse,
|
||||
}).Error
|
||||
}
|
||||
|
||||
// MarkGiveup 标记放弃通知
|
||||
func (r *NotifyLogRepository) MarkGiveup(ctx context.Context, id uint64) error {
|
||||
return r.db.WithContext(ctx).Model(&model.NotifyLog{}).Where("id = ?", id).
|
||||
Update("status", model.NotifyStatusGiveup).Error
|
||||
}
|
||||
95
backend/internal/repository/payment_match.go
Normal file
95
backend/internal/repository/payment_match.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"pay-bridge/internal/model"
|
||||
)
|
||||
|
||||
// PaymentMatchRepository 收款匹配数据访问
|
||||
type PaymentMatchRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewPaymentMatchRepository(db *gorm.DB) *PaymentMatchRepository {
|
||||
return &PaymentMatchRepository{db: db}
|
||||
}
|
||||
|
||||
// GetAccountByNo 按账号查询子商户收款账户
|
||||
func (r *PaymentMatchRepository) GetAccountByNo(ctx context.Context, accountNo string) (*model.SubMerchantAccount, error) {
|
||||
var acc model.SubMerchantAccount
|
||||
err := r.db.WithContext(ctx).Where("account_no = ? AND status = 1", accountNo).First(&acc).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return &acc, err
|
||||
}
|
||||
|
||||
// GetAccountByID 按 id 查询
|
||||
func (r *PaymentMatchRepository) GetAccountByID(ctx context.Context, id uint64) (*model.SubMerchantAccount, error) {
|
||||
var acc model.SubMerchantAccount
|
||||
err := r.db.WithContext(ctx).Where("id = ?", id).First(&acc).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return &acc, err
|
||||
}
|
||||
|
||||
// ListAccountsByApp 查询应用下所有收款账户
|
||||
func (r *PaymentMatchRepository) ListAccountsByApp(ctx context.Context, appID string) ([]*model.SubMerchantAccount, error) {
|
||||
var accs []*model.SubMerchantAccount
|
||||
err := r.db.WithContext(ctx).Where("app_id = ? AND status = 1", appID).Find(&accs).Error
|
||||
return accs, err
|
||||
}
|
||||
|
||||
// CreateAccount 创建收款账户
|
||||
func (r *PaymentMatchRepository) CreateAccount(ctx context.Context, acc *model.SubMerchantAccount) error {
|
||||
return r.db.WithContext(ctx).Create(acc).Error
|
||||
}
|
||||
|
||||
// CreateMatchLog 创建匹配记录
|
||||
func (r *PaymentMatchRepository) CreateMatchLog(ctx context.Context, log *model.PaymentMatchLog) error {
|
||||
return r.db.WithContext(ctx).Create(log).Error
|
||||
}
|
||||
|
||||
// GetMatchLogByBillNo 按渠道流水号查询(幂等检查)
|
||||
func (r *PaymentMatchRepository) GetMatchLogByBillNo(ctx context.Context, channelBillNo string) (*model.PaymentMatchLog, error) {
|
||||
var log model.PaymentMatchLog
|
||||
err := r.db.WithContext(ctx).Where("channel_bill_no = ?", channelBillNo).First(&log).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return &log, err
|
||||
}
|
||||
|
||||
// UpdateMatchLog 更新匹配记录
|
||||
func (r *PaymentMatchRepository) UpdateMatchLog(ctx context.Context, id uint64, updates map[string]any) error {
|
||||
return r.db.WithContext(ctx).Model(&model.PaymentMatchLog{}).Where("id = ?", id).Updates(updates).Error
|
||||
}
|
||||
|
||||
// ListPendingManual 查询待人工确认的记录
|
||||
func (r *PaymentMatchRepository) ListPendingManual(ctx context.Context, appID string, limit, offset int) ([]*model.PaymentMatchLog, error) {
|
||||
var logs []*model.PaymentMatchLog
|
||||
err := r.db.WithContext(ctx).
|
||||
Joins("JOIN sub_merchant_account ON sub_merchant_account.id = payment_match_log.account_id").
|
||||
Where("sub_merchant_account.app_id = ? AND payment_match_log.match_status = ?",
|
||||
appID, model.MatchStatusPendingManual).
|
||||
Order("payment_match_log.created_at DESC").
|
||||
Limit(limit).Offset(offset).
|
||||
Find(&logs).Error
|
||||
return logs, err
|
||||
}
|
||||
|
||||
// ListPayingByAmount 按金额查询指定时间窗口内的待支付订单(用于收款匹配降级)
|
||||
func (r *PaymentMatchRepository) ListPayingByAmount(ctx context.Context, appID string, amount int64, window time.Duration) ([]*model.TradeOrder, error) {
|
||||
var orders []*model.TradeOrder
|
||||
since := time.Now().Add(-window)
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("app_id = ? AND amount = ? AND status = ? AND created_at >= ?",
|
||||
appID, amount, model.TradeStatusPaying, since).
|
||||
Find(&orders).Error
|
||||
return orders, err
|
||||
}
|
||||
90
backend/internal/repository/profit_sharing.go
Normal file
90
backend/internal/repository/profit_sharing.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"pay-bridge/internal/model"
|
||||
)
|
||||
|
||||
// ProfitSharingRepository 分润数据访问
|
||||
type ProfitSharingRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewProfitSharingRepository(db *gorm.DB) *ProfitSharingRepository {
|
||||
return &ProfitSharingRepository{db: db}
|
||||
}
|
||||
|
||||
// GetConfigByAppID 按 app_id 获取分润配置
|
||||
func (r *ProfitSharingRepository) GetConfigByAppID(ctx context.Context, appID string) (*model.ProfitSharingConfig, error) {
|
||||
var cfg model.ProfitSharingConfig
|
||||
err := r.db.WithContext(ctx).Where("app_id = ? AND status = 1", appID).First(&cfg).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return &cfg, err
|
||||
}
|
||||
|
||||
// SaveConfig 创建或更新分润配置
|
||||
func (r *ProfitSharingRepository) SaveConfig(ctx context.Context, cfg *model.ProfitSharingConfig) error {
|
||||
return r.db.WithContext(ctx).Save(cfg).Error
|
||||
}
|
||||
|
||||
// CreateOrder 创建分润记录
|
||||
func (r *ProfitSharingRepository) CreateOrder(ctx context.Context, order *model.ProfitSharingOrder) error {
|
||||
return r.db.WithContext(ctx).Create(order).Error
|
||||
}
|
||||
|
||||
// GetOrderByTradeNo 按 trade_no 查询分润记录
|
||||
func (r *ProfitSharingRepository) GetOrderByTradeNo(ctx context.Context, tradeNo string) (*model.ProfitSharingOrder, error) {
|
||||
var order model.ProfitSharingOrder
|
||||
err := r.db.WithContext(ctx).Where("trade_no = ?", tradeNo).First(&order).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return &order, err
|
||||
}
|
||||
|
||||
// GetOrderBySharingNo 按 sharing_no 查询
|
||||
func (r *ProfitSharingRepository) GetOrderBySharingNo(ctx context.Context, sharingNo string) (*model.ProfitSharingOrder, error) {
|
||||
var order model.ProfitSharingOrder
|
||||
err := r.db.WithContext(ctx).Where("sharing_no = ?", sharingNo).First(&order).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return &order, err
|
||||
}
|
||||
|
||||
// UpdateOrderStatus 更新分润状态
|
||||
func (r *ProfitSharingRepository) UpdateOrderStatus(ctx context.Context, sharingNo string, fromStatus, toStatus model.ProfitSharingStatus, updates map[string]any) (bool, error) {
|
||||
if updates == nil {
|
||||
updates = make(map[string]any)
|
||||
}
|
||||
updates["status"] = toStatus
|
||||
result := r.db.WithContext(ctx).Model(&model.ProfitSharingOrder{}).
|
||||
Where("sharing_no = ? AND status = ?", sharingNo, fromStatus).
|
||||
Updates(updates)
|
||||
if result.Error != nil {
|
||||
return false, result.Error
|
||||
}
|
||||
return result.RowsAffected > 0, nil
|
||||
}
|
||||
|
||||
// CreateLog 记录分润流水
|
||||
func (r *ProfitSharingRepository) CreateLog(ctx context.Context, log *model.ProfitSharingLog) error {
|
||||
return r.db.WithContext(ctx).Create(log).Error
|
||||
}
|
||||
|
||||
// ListPendingOrders 查询需要补偿的分润单(PROCESSING 超时)
|
||||
func (r *ProfitSharingRepository) ListPendingOrders(ctx context.Context, limit int) ([]*model.ProfitSharingOrder, error) {
|
||||
var orders []*model.ProfitSharingOrder
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("status IN ?", []model.ProfitSharingStatus{
|
||||
model.ProfitSharingStatusPending,
|
||||
model.ProfitSharingStatusProcessing,
|
||||
}).
|
||||
Limit(limit).Find(&orders).Error
|
||||
return orders, err
|
||||
}
|
||||
62
backend/internal/repository/reconciliation.go
Normal file
62
backend/internal/repository/reconciliation.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"pay-bridge/internal/model"
|
||||
)
|
||||
|
||||
// ReconciliationRepository 对账数据访问
|
||||
type ReconciliationRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewReconciliationRepository(db *gorm.DB) *ReconciliationRepository {
|
||||
return &ReconciliationRepository{db: db}
|
||||
}
|
||||
|
||||
// CreateReport 创建对账报告
|
||||
func (r *ReconciliationRepository) CreateReport(ctx context.Context, report *model.ReconciliationReport) error {
|
||||
return r.db.WithContext(ctx).Create(report).Error
|
||||
}
|
||||
|
||||
// GetReport 查询对账报告
|
||||
func (r *ReconciliationRepository) GetReport(ctx context.Context, appID, billDate, channelCode string) (*model.ReconciliationReport, error) {
|
||||
var report model.ReconciliationReport
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("app_id = ? AND bill_date = ? AND channel_code = ?", appID, billDate, channelCode).
|
||||
First(&report).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return &report, err
|
||||
}
|
||||
|
||||
// UpdateReport 更新对账报告
|
||||
func (r *ReconciliationRepository) UpdateReport(ctx context.Context, id uint64, updates map[string]any) error {
|
||||
return r.db.WithContext(ctx).Model(&model.ReconciliationReport{}).Where("id = ?", id).Updates(updates).Error
|
||||
}
|
||||
|
||||
// CreateException 创建对账异常记录
|
||||
func (r *ReconciliationRepository) CreateException(ctx context.Context, ex *model.ReconciliationException) error {
|
||||
return r.db.WithContext(ctx).Create(ex).Error
|
||||
}
|
||||
|
||||
// ListExceptions 查询报告下的异常明细
|
||||
func (r *ReconciliationRepository) ListExceptions(ctx context.Context, reportID uint64) ([]*model.ReconciliationException, error) {
|
||||
var exs []*model.ReconciliationException
|
||||
err := r.db.WithContext(ctx).Where("report_id = ?", reportID).Find(&exs).Error
|
||||
return exs, err
|
||||
}
|
||||
|
||||
// ListPaidOrdersByDate 查询指定日期的已支付订单(用于对账)
|
||||
func (r *ReconciliationRepository) ListPaidOrdersByDate(ctx context.Context, appID, date string) ([]*model.TradeOrder, error) {
|
||||
var orders []*model.TradeOrder
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("app_id = ? AND status = ? AND DATE(pay_time) = ?",
|
||||
appID, model.TradeStatusPaid, date).
|
||||
Find(&orders).Error
|
||||
return orders, err
|
||||
}
|
||||
63
backend/internal/repository/refund_order.go
Normal file
63
backend/internal/repository/refund_order.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"pay-bridge/internal/model"
|
||||
)
|
||||
|
||||
// RefundOrderRepository 退款记录数据访问
|
||||
type RefundOrderRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewRefundOrderRepository(db *gorm.DB) *RefundOrderRepository {
|
||||
return &RefundOrderRepository{db: db}
|
||||
}
|
||||
|
||||
// Create 创建退款单
|
||||
func (r *RefundOrderRepository) Create(ctx context.Context, refund *model.RefundOrder) error {
|
||||
return r.db.WithContext(ctx).Create(refund).Error
|
||||
}
|
||||
|
||||
// GetByRefundNo 按 refund_no 查询
|
||||
func (r *RefundOrderRepository) GetByRefundNo(ctx context.Context, refundNo string) (*model.RefundOrder, error) {
|
||||
var refund model.RefundOrder
|
||||
err := r.db.WithContext(ctx).Where("refund_no = ?", refundNo).First(&refund).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return &refund, err
|
||||
}
|
||||
|
||||
// SumRefundedAmount 统计某笔交易已退款总额(成功+处理中)
|
||||
func (r *RefundOrderRepository) SumRefundedAmount(ctx context.Context, tradeNo string) (int64, error) {
|
||||
var total int64
|
||||
err := r.db.WithContext(ctx).Model(&model.RefundOrder{}).
|
||||
Where("trade_no = ? AND status IN ?", tradeNo, []model.RefundStatus{
|
||||
model.RefundStatusPending,
|
||||
model.RefundStatusProcessing,
|
||||
model.RefundStatusSuccess,
|
||||
}).
|
||||
Select("COALESCE(SUM(refund_amount), 0)").
|
||||
Scan(&total).Error
|
||||
return total, err
|
||||
}
|
||||
|
||||
// UpdateStatus 更新退款状态
|
||||
func (r *RefundOrderRepository) UpdateStatus(ctx context.Context, refundNo string, fromStatus, toStatus model.RefundStatus, updates map[string]any) (bool, error) {
|
||||
if updates == nil {
|
||||
updates = make(map[string]any)
|
||||
}
|
||||
updates["status"] = toStatus
|
||||
|
||||
result := r.db.WithContext(ctx).Model(&model.RefundOrder{}).
|
||||
Where("refund_no = ? AND status = ?", refundNo, fromStatus).
|
||||
Updates(updates)
|
||||
if result.Error != nil {
|
||||
return false, result.Error
|
||||
}
|
||||
return result.RowsAffected > 0, nil
|
||||
}
|
||||
90
backend/internal/repository/sequence.go
Normal file
90
backend/internal/repository/sequence.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"pay-bridge/internal/model"
|
||||
)
|
||||
|
||||
// SequenceRepository 序列数据访问
|
||||
type SequenceRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewSequenceRepository(db *gorm.DB) *SequenceRepository {
|
||||
return &SequenceRepository{db: db}
|
||||
}
|
||||
|
||||
// IncrAndGet 原子自增并返回新值(行级锁)
|
||||
func (r *SequenceRepository) IncrAndGet(ctx context.Context, appID string, seqType model.SeqType) (uint64, error) {
|
||||
var seq model.OrderSequence
|
||||
|
||||
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
// 加行锁读取
|
||||
if err := tx.Set("gorm:query_option", "FOR UPDATE").
|
||||
Where("app_id = ? AND seq_type = ?", appID, seqType).
|
||||
First(&seq).Error; err != nil {
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
// 自动初始化序列
|
||||
prefix := defaultPrefix(seqType)
|
||||
seq = model.OrderSequence{
|
||||
AppID: appID,
|
||||
SeqType: seqType,
|
||||
Prefix: prefix,
|
||||
CurrentValue: 0,
|
||||
Step: 1,
|
||||
}
|
||||
if err := tx.Create(&seq).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// 重新加锁读
|
||||
return tx.Set("gorm:query_option", "FOR UPDATE").
|
||||
Where("app_id = ? AND seq_type = ?", appID, seqType).
|
||||
First(&seq).Error
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// 自增
|
||||
newVal := seq.CurrentValue + uint64(seq.Step)
|
||||
if err := r.db.WithContext(ctx).Model(&model.OrderSequence{}).
|
||||
Where("id = ?", seq.ID).
|
||||
Update("current_value", newVal).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return newVal, nil
|
||||
}
|
||||
|
||||
func defaultPrefix(t model.SeqType) string {
|
||||
switch t {
|
||||
case model.SeqTypeTrade:
|
||||
return "PAY"
|
||||
case model.SeqTypeRefund:
|
||||
return "REF"
|
||||
case model.SeqTypeSharing:
|
||||
return "SHA"
|
||||
default:
|
||||
return "ORD"
|
||||
}
|
||||
}
|
||||
|
||||
// GetPrefix 获取序列前缀
|
||||
func (r *SequenceRepository) GetPrefix(ctx context.Context, appID string, seqType model.SeqType) (string, error) {
|
||||
var seq model.OrderSequence
|
||||
err := r.db.WithContext(ctx).Where("app_id = ? AND seq_type = ?", appID, seqType).First(&seq).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fmt.Sprintf("%s", defaultPrefix(seqType)), nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return seq.Prefix, nil
|
||||
}
|
||||
66
backend/internal/repository/service_fee.go
Normal file
66
backend/internal/repository/service_fee.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"pay-bridge/internal/model"
|
||||
)
|
||||
|
||||
// ServiceFeeRepository 服务费数据访问
|
||||
type ServiceFeeRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewServiceFeeRepository(db *gorm.DB) *ServiceFeeRepository {
|
||||
return &ServiceFeeRepository{db: db}
|
||||
}
|
||||
|
||||
// GetConfig 按 app_id + 支付方式分组查询配置
|
||||
func (r *ServiceFeeRepository) GetConfig(ctx context.Context, appID string, group model.PayMethodGroup) (*model.ServiceFeeConfig, error) {
|
||||
var cfg model.ServiceFeeConfig
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("app_id = ? AND pay_method_group = ? AND status = 1", appID, group).
|
||||
First(&cfg).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return &cfg, err
|
||||
}
|
||||
|
||||
// ListConfigs 查询应用所有服务费配置
|
||||
func (r *ServiceFeeRepository) ListConfigs(ctx context.Context, appID string) ([]*model.ServiceFeeConfig, error) {
|
||||
var cfgs []*model.ServiceFeeConfig
|
||||
err := r.db.WithContext(ctx).Where("app_id = ? AND status = 1", appID).Find(&cfgs).Error
|
||||
return cfgs, err
|
||||
}
|
||||
|
||||
// SaveConfig 保存配置(创建或更新)
|
||||
func (r *ServiceFeeRepository) SaveConfig(ctx context.Context, cfg *model.ServiceFeeConfig) error {
|
||||
return r.db.WithContext(ctx).Save(cfg).Error
|
||||
}
|
||||
|
||||
// CreateLog 创建服务费流水
|
||||
func (r *ServiceFeeRepository) CreateLog(ctx context.Context, log *model.ServiceFeeLog) error {
|
||||
return r.db.WithContext(ctx).Create(log).Error
|
||||
}
|
||||
|
||||
// GetLog 按 trade_no + action 查询流水
|
||||
func (r *ServiceFeeRepository) GetLog(ctx context.Context, tradeNo, action string) (*model.ServiceFeeLog, error) {
|
||||
var log model.ServiceFeeLog
|
||||
err := r.db.WithContext(ctx).Where("trade_no = ? AND action = ?", tradeNo, action).First(&log).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return &log, err
|
||||
}
|
||||
|
||||
// UpdateLogStatus 更新流水状态
|
||||
func (r *ServiceFeeRepository) UpdateLogStatus(ctx context.Context, id uint64, status, channelSharingNo string) error {
|
||||
updates := map[string]any{"status": status}
|
||||
if channelSharingNo != "" {
|
||||
updates["channel_sharing_no"] = channelSharingNo
|
||||
}
|
||||
return r.db.WithContext(ctx).Model(&model.ServiceFeeLog{}).Where("id = ?", id).Updates(updates).Error
|
||||
}
|
||||
80
backend/internal/repository/trade_order.go
Normal file
80
backend/internal/repository/trade_order.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"pay-bridge/internal/model"
|
||||
)
|
||||
|
||||
// TradeOrderRepository 交易订单数据访问
|
||||
type TradeOrderRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewTradeOrderRepository(db *gorm.DB) *TradeOrderRepository {
|
||||
return &TradeOrderRepository{db: db}
|
||||
}
|
||||
|
||||
// Create 创建订单
|
||||
func (r *TradeOrderRepository) Create(ctx context.Context, order *model.TradeOrder) error {
|
||||
return r.db.WithContext(ctx).Create(order).Error
|
||||
}
|
||||
|
||||
// GetByTradeNo 按 trade_no 查询
|
||||
func (r *TradeOrderRepository) GetByTradeNo(ctx context.Context, tradeNo string) (*model.TradeOrder, error) {
|
||||
var order model.TradeOrder
|
||||
err := r.db.WithContext(ctx).Where("trade_no = ?", tradeNo).First(&order).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return &order, err
|
||||
}
|
||||
|
||||
// GetByMerchantOrderNo 按 app_id + merchant_order_no 查询
|
||||
func (r *TradeOrderRepository) GetByMerchantOrderNo(ctx context.Context, appID, merchantOrderNo string) (*model.TradeOrder, error) {
|
||||
var order model.TradeOrder
|
||||
err := r.db.WithContext(ctx).Where("app_id = ? AND merchant_order_no = ?", appID, merchantOrderNo).First(&order).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return &order, err
|
||||
}
|
||||
|
||||
// GetByChannelTradeNo 按渠道交易号查询
|
||||
func (r *TradeOrderRepository) GetByChannelTradeNo(ctx context.Context, channelTradeNo string) (*model.TradeOrder, error) {
|
||||
var order model.TradeOrder
|
||||
err := r.db.WithContext(ctx).Where("channel_trade_no = ?", channelTradeNo).First(&order).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return &order, err
|
||||
}
|
||||
|
||||
// UpdateStatus 乐观锁更新状态(只允许从 fromStatus 流转到 toStatus)
|
||||
// 返回 bool 表示是否更新成功(false = 已被其他 goroutine 更新)
|
||||
func (r *TradeOrderRepository) UpdateStatus(ctx context.Context, tradeNo string, fromStatus, toStatus model.TradeStatus, updates map[string]any) (bool, error) {
|
||||
if updates == nil {
|
||||
updates = make(map[string]any)
|
||||
}
|
||||
updates["status"] = toStatus
|
||||
|
||||
result := r.db.WithContext(ctx).Model(&model.TradeOrder{}).
|
||||
Where("trade_no = ? AND status = ?", tradeNo, fromStatus).
|
||||
Updates(updates)
|
||||
if result.Error != nil {
|
||||
return false, result.Error
|
||||
}
|
||||
return result.RowsAffected > 0, nil
|
||||
}
|
||||
|
||||
// ListPayingExpired 查询已过期的 PAYING 订单(用于定时关单补偿)
|
||||
func (r *TradeOrderRepository) ListPayingExpired(ctx context.Context, before time.Time, limit int) ([]*model.TradeOrder, error) {
|
||||
var orders []*model.TradeOrder
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("status = ? AND expire_time < ?", model.TradeStatusPaying, before).
|
||||
Limit(limit).Find(&orders).Error
|
||||
return orders, err
|
||||
}
|
||||
43
backend/internal/repository/wechat.go
Normal file
43
backend/internal/repository/wechat.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"pay-bridge/internal/model"
|
||||
)
|
||||
|
||||
// WechatRepository 微信通知数据访问
|
||||
type WechatRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewWechatRepository(db *gorm.DB) *WechatRepository {
|
||||
return &WechatRepository{db: db}
|
||||
}
|
||||
|
||||
// GetBinding 查询应用微信绑定配置
|
||||
func (r *WechatRepository) GetBinding(ctx context.Context, appID string) (*model.WechatBinding, error) {
|
||||
var b model.WechatBinding
|
||||
err := r.db.WithContext(ctx).Where("app_id = ? AND status = 1", appID).First(&b).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return &b, err
|
||||
}
|
||||
|
||||
// UpsertBinding 创建或更新绑定
|
||||
func (r *WechatRepository) UpsertBinding(ctx context.Context, b *model.WechatBinding) error {
|
||||
return r.db.WithContext(ctx).Save(b).Error
|
||||
}
|
||||
|
||||
// CreateMessageLog 记录消息发送日志
|
||||
func (r *WechatRepository) CreateMessageLog(ctx context.Context, log *model.WechatMessageLog) error {
|
||||
return r.db.WithContext(ctx).Create(log).Error
|
||||
}
|
||||
|
||||
// UpdateMessageLog 更新消息日志状态
|
||||
func (r *WechatRepository) UpdateMessageLog(ctx context.Context, id uint64, updates map[string]any) error {
|
||||
return r.db.WithContext(ctx).Model(&model.WechatMessageLog{}).Where("id = ?", id).Updates(updates).Error
|
||||
}
|
||||
Reference in New Issue
Block a user