85 lines
2.6 KiB
Go
85 lines
2.6 KiB
Go
package store
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
|
|
"github.com/wangjia/pay/internal/model"
|
|
)
|
|
|
|
var ErrRefundNotFound = errors.New("store: refund not found")
|
|
|
|
type RefundStore struct{ db *gorm.DB }
|
|
|
|
func NewRefundStore(db *gorm.DB) *RefundStore { return &RefundStore{db: db} }
|
|
|
|
func (s *RefundStore) CreateRefund(r *model.Refund) error {
|
|
if r.RefundID == "" {
|
|
return fmt.Errorf("store.CreateRefund: empty RefundID")
|
|
}
|
|
if err := s.db.Create(r).Error; err != nil {
|
|
return fmt.Errorf("store.CreateRefund: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *RefundStore) GetRefund(refundID string) (*model.Refund, error) {
|
|
var r model.Refund
|
|
if err := s.db.Where("refund_id = ?", refundID).First(&r).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, ErrRefundNotFound
|
|
}
|
|
return nil, fmt.Errorf("store.GetRefund: %w", err)
|
|
}
|
|
return &r, nil
|
|
}
|
|
|
|
// RefundSum sums amount_minor of an order's refunds in the given statuses
|
|
// (no statuses = all). 用两种口径:守卫查非失败之和(占额度),态机查 succeeded 之和。
|
|
func (s *RefundStore) RefundSum(outTradeNo string, statuses ...model.RefundStatus) (int64, error) {
|
|
var total int64
|
|
q := s.db.Model(&model.Refund{}).Where("out_trade_no = ?", outTradeNo)
|
|
if len(statuses) > 0 {
|
|
q = q.Where("status IN ?", statuses)
|
|
}
|
|
if err := q.Select("COALESCE(SUM(amount_minor),0)").Scan(&total).Error; err != nil {
|
|
return 0, fmt.Errorf("store.RefundSum: %w", err)
|
|
}
|
|
return total, nil
|
|
}
|
|
|
|
// MarkRefundStatus flips a refund from an expected status to a new one
|
|
// (conditional UPDATE + RowsAffected). Returns false if not in the from-status.
|
|
func (s *RefundStore) MarkRefundStatus(refundID string, from, to model.RefundStatus, providerRefundRef string, at time.Time) (bool, error) {
|
|
updates := map[string]any{"status": to}
|
|
if providerRefundRef != "" {
|
|
updates["provider_refund_ref"] = providerRefundRef
|
|
}
|
|
if to == model.RefundSucceeded || to == model.RefundFailed {
|
|
updates["completed_at"] = at
|
|
}
|
|
res := s.db.Model(&model.Refund{}).
|
|
Where("refund_id = ? AND status = ?", refundID, from).
|
|
Updates(updates)
|
|
if res.Error != nil {
|
|
return false, fmt.Errorf("store.MarkRefundStatus: %w", res.Error)
|
|
}
|
|
return res.RowsAffected > 0, nil
|
|
}
|
|
|
|
// ListManualPending lists refunds awaiting manual (crypto) settlement.
|
|
func (s *RefundStore) ListManualPending(limit int) ([]model.Refund, error) {
|
|
if limit <= 0 || limit > 200 {
|
|
limit = 50
|
|
}
|
|
var out []model.Refund
|
|
if err := s.db.Where("status = ?", model.RefundManualPending).
|
|
Order("id ASC").Limit(limit).Find(&out).Error; err != nil {
|
|
return nil, fmt.Errorf("store.ListManualPending: %w", err)
|
|
}
|
|
return out, nil
|
|
}
|