Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions common/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,4 +205,6 @@ const (
TopUpStatusSuccess = "success"
TopUpStatusExpired = "expired"
TopUpStatusFailed = "failed"
TopUpStatusRefundPending = "refund_pending"
TopUpStatusRefunded = "refunded"
)
125 changes: 124 additions & 1 deletion controller/topup.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"github.com/gin-gonic/gin"
)

const topUpRefundWindowSeconds = int64(24 * 60 * 60)

func GetTopUpInfo(c *gin.Context) {
// 获取支付方式
payMethods := operation_setting.PayMethods
Expand Down Expand Up @@ -70,8 +72,129 @@ func GetUserTopUps(c *gin.Context) {
return
}

now := common.GetTimestamp()
eligibleRefs := make([]string, 0, len(topups))
for _, t := range topups {
if t == nil {
continue
}
if t.Status != common.TopUpStatusSuccess {
continue
}
if t.PaymentMethod != PaymentMethodStripe {
continue
}
paidAt := t.CompleteTime
if paidAt == 0 {
paidAt = t.CreateTime
}
if paidAt == 0 {
continue
}
if now-paidAt > topUpRefundWindowSeconds {
continue
}
eligibleRefs = append(eligibleRefs, t.TradeNo)
}

type creditGrantAgg struct {
Reference string `gorm:"column:reference"`
TotalQuota int64 `gorm:"column:total_quota"`
UsedQuota int64 `gorm:"column:used_quota"`
}
grantAgg := map[string]creditGrantAgg{}
if len(eligibleRefs) > 0 {
var rows []creditGrantAgg
if err := model.DB.Model(&model.CreditGrant{}).
Where("user_id = ? AND grant_type = ? AND reference IN ?", userId, "topup", eligibleRefs).
Select("reference, SUM(quota) as total_quota, SUM(used_quota) as used_quota").
Group("reference").
Scan(&rows).Error; err != nil {
common.ApiError(c, err)
return
}
for _, r := range rows {
grantAgg[r.Reference] = r
}
}

type topUpWithRefund struct {
model.TopUp
Refundable bool `json:"refundable"`
RefundIneligibleReason string `json:"refund_ineligible_reason,omitempty"`
RefundWindowSecondsLeft int64 `json:"refund_window_seconds_left,omitempty"`
}

decorateRefund := func(t *model.TopUp) topUpWithRefund {
row := topUpWithRefund{TopUp: *t}
switch t.Status {
case common.TopUpStatusRefunded:
row.Refundable = false
row.RefundIneligibleReason = "Already refunded"
return row
case common.TopUpStatusRefundPending:
row.Refundable = false
row.RefundIneligibleReason = "Refund in progress"
return row
}
if t.Status != common.TopUpStatusSuccess {
row.Refundable = false
row.RefundIneligibleReason = "Only successful payments can be refunded"
return row
}
if t.PaymentMethod != PaymentMethodStripe {
row.Refundable = false
row.RefundIneligibleReason = "Only Stripe payments can be refunded"
return row
}
paidAt := t.CompleteTime
if paidAt == 0 {
paidAt = t.CreateTime
}
if paidAt == 0 {
row.Refundable = false
row.RefundIneligibleReason = "Missing payment time"
return row
}
age := now - paidAt
if age > topUpRefundWindowSeconds {
row.Refundable = false
row.RefundIneligibleReason = "Refund window expired"
row.RefundWindowSecondsLeft = 0
return row
}
row.RefundWindowSecondsLeft = topUpRefundWindowSeconds - age
agg, ok := grantAgg[t.TradeNo]
if !ok {
row.Refundable = false
row.RefundIneligibleReason = "Credits not found"
return row
}
if agg.UsedQuota > 0 {
row.Refundable = false
row.RefundIneligibleReason = "Credits already used"
return row
}
if agg.TotalQuota <= 0 {
row.Refundable = false
row.RefundIneligibleReason = "Invalid credit grant"
return row
}
row.Refundable = true
row.RefundIneligibleReason = ""
return row
}

items := make([]topUpWithRefund, 0, len(topups))
for _, t := range topups {
if t == nil {
continue
}
items = append(items, decorateRefund(t))
}

pageInfo.SetTotal(int(total))
pageInfo.SetItems(topups)
pageInfo.SetItems(items)
common.ApiSuccess(c, pageInfo)
}

Expand Down
Loading