feat: integrate unified payment and wallet refunds
This commit is contained in:
68
backend/api/internal/logic/payment/close.go
Normal file
68
backend/api/internal/logic/payment/close.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package payment
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.apinb.com/heqiapp/platforms/backend/api/internal/config"
|
||||
"git.apinb.com/heqiapp/platforms/backend/api/internal/impl"
|
||||
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/smartwalle/alipay/v3"
|
||||
)
|
||||
|
||||
// CloseExpired 批量关闭已过期的渠道支付单;单笔失败留待下一轮安全重试。
|
||||
func CloseExpired(ctx context.Context, limit int) (int, error) {
|
||||
var orders []models.PaymentOrder
|
||||
if err := impl.DBService.Where("payment_status = ? AND expires_at <= ?", StatusPending, time.Now()).Order("expires_at asc").Limit(limit).Find(&orders).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
closed := 0
|
||||
for _, order := range orders {
|
||||
if err := closeChannelOrder(ctx, order); err != nil {
|
||||
continue
|
||||
}
|
||||
now := time.Now()
|
||||
result := impl.DBService.Model(&models.PaymentOrder{}).Where("id = ? AND payment_status = ?", order.ID, StatusPending).Updates(map[string]any{"payment_status": StatusClosed, "closed_at": &now})
|
||||
if result.Error == nil && result.RowsAffected == 1 {
|
||||
closed++
|
||||
}
|
||||
}
|
||||
return closed, nil
|
||||
}
|
||||
|
||||
func closeChannelOrder(ctx context.Context, order models.PaymentOrder) error {
|
||||
if order.Channel == "alipay" {
|
||||
client, err := alipayClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = client.TradeClose(ctx, alipay.TradeClose{OutTradeNo: order.PaymentNo})
|
||||
return err
|
||||
}
|
||||
if order.Channel == "wechat" {
|
||||
client, err := wechatClient(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = client.Post(ctx, fmt.Sprintf("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/%s/close", order.PaymentNo), map[string]string{"mchid": config.Spec.Payment.Wechat.MerchantID})
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseExpiredHandler 只接受 Worker 共享凭证,不暴露为平台用户动作。
|
||||
func CloseExpiredHandler(ctx *gin.Context) {
|
||||
if config.Spec.Payment.InternalServiceToken == "" || ctx.GetHeader("X-Heqi-Worker-Token") != config.Spec.Payment.InternalServiceToken {
|
||||
ctx.AbortWithStatus(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
count, err := CloseExpired(ctx, 100)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "close expired payments failed"})
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, gin.H{"closed": count})
|
||||
}
|
||||
Reference in New Issue
Block a user