This commit is contained in:
2026-03-13 15:51:59 +08:00
parent 4db2386bbf
commit 4e91f4cede
133 changed files with 19502 additions and 37 deletions

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}