164 lines
4.3 KiB
Go
164 lines
4.3 KiB
Go
package handler
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"soul-api/internal/database"
|
|
"soul-api/internal/model"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func loadOrderWebhookURL(db *gorm.DB) string {
|
|
keys := []string{"order_paid_webhook_url", "ckb_lead_webhook_url"}
|
|
for _, key := range keys {
|
|
var cfg model.SystemConfig
|
|
if err := db.Where("config_key = ?", key).First(&cfg).Error; err != nil {
|
|
continue
|
|
}
|
|
var webhookURL string
|
|
if len(cfg.ConfigValue) > 0 {
|
|
_ = json.Unmarshal(cfg.ConfigValue, &webhookURL)
|
|
}
|
|
webhookURL = strings.TrimSpace(webhookURL)
|
|
if webhookURL != "" && strings.HasPrefix(webhookURL, "http") {
|
|
return webhookURL
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func pushPaidOrderWebhook(db *gorm.DB, order *model.Order) error {
|
|
if order == nil || order.OrderSN == "" {
|
|
return fmt.Errorf("empty order")
|
|
}
|
|
if order.WebhookPushStatus == "sent" {
|
|
return nil
|
|
}
|
|
webhookURL := loadOrderWebhookURL(db)
|
|
if webhookURL == "" {
|
|
return nil
|
|
}
|
|
|
|
var user model.User
|
|
_ = db.Select("id,nickname,phone,open_id").Where("id = ?", order.UserID).First(&user).Error
|
|
|
|
productName := order.ProductType
|
|
if order.Description != nil && strings.TrimSpace(*order.Description) != "" {
|
|
productName = strings.TrimSpace(*order.Description)
|
|
}
|
|
status := ""
|
|
if order.Status != nil {
|
|
status = *order.Status
|
|
}
|
|
if status == "" {
|
|
status = "paid"
|
|
}
|
|
|
|
text := "💰 用户购买成功(实时推送)"
|
|
text += fmt.Sprintf("\n订单号: %s", order.OrderSN)
|
|
if user.Nickname != nil && strings.TrimSpace(*user.Nickname) != "" {
|
|
text += fmt.Sprintf("\n用户: %s", strings.TrimSpace(*user.Nickname))
|
|
}
|
|
if user.Phone != nil && strings.TrimSpace(*user.Phone) != "" {
|
|
text += fmt.Sprintf("\n手机: %s", strings.TrimSpace(*user.Phone))
|
|
}
|
|
text += fmt.Sprintf("\n商品: %s", productName)
|
|
text += fmt.Sprintf("\n金额: %.2f", order.Amount)
|
|
text += fmt.Sprintf("\n状态: %s", status)
|
|
if order.PayTime != nil {
|
|
text += fmt.Sprintf("\n支付时间: %s", order.PayTime.Format("2006-01-02 15:04:05"))
|
|
} else {
|
|
text += fmt.Sprintf("\n支付时间: %s", time.Now().Format("2006-01-02 15:04:05"))
|
|
}
|
|
|
|
var payload []byte
|
|
if strings.Contains(webhookURL, "qyapi.weixin.qq.com") {
|
|
payload, _ = json.Marshal(map[string]interface{}{
|
|
"msgtype": "text",
|
|
"text": map[string]string{"content": text},
|
|
})
|
|
} else {
|
|
payload, _ = json.Marshal(map[string]interface{}{
|
|
"msg_type": "text",
|
|
"content": map[string]string{"text": text},
|
|
})
|
|
}
|
|
resp, err := http.Post(webhookURL, "application/json", bytes.NewReader(payload))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode >= 400 {
|
|
return fmt.Errorf("webhook status=%d", resp.StatusCode)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func markOrderWebhookResult(db *gorm.DB, orderSn string, sent bool, pushErr error) {
|
|
if orderSn == "" {
|
|
return
|
|
}
|
|
updates := map[string]interface{}{
|
|
"webhook_push_attempts": gorm.Expr("COALESCE(webhook_push_attempts, 0) + 1"),
|
|
"updated_at": time.Now(),
|
|
}
|
|
if sent {
|
|
now := time.Now()
|
|
updates["webhook_push_status"] = "sent"
|
|
updates["webhook_pushed_at"] = now
|
|
updates["webhook_push_error"] = ""
|
|
} else {
|
|
errText := ""
|
|
if pushErr != nil {
|
|
errText = strings.TrimSpace(pushErr.Error())
|
|
}
|
|
if len(errText) > 500 {
|
|
errText = errText[:500]
|
|
}
|
|
updates["webhook_push_status"] = "failed"
|
|
updates["webhook_push_error"] = errText
|
|
}
|
|
_ = db.Model(&model.Order{}).Where("order_sn = ?", orderSn).Updates(updates).Error
|
|
}
|
|
|
|
// RetryPendingPaidOrderWebhooks 扫描未推送成功的已支付订单并补推。
|
|
func RetryPendingPaidOrderWebhooks(ctx context.Context, limit int) (retried, sent int, err error) {
|
|
if limit <= 0 {
|
|
limit = 200
|
|
}
|
|
if limit > 2000 {
|
|
limit = 2000
|
|
}
|
|
db := database.DB()
|
|
var rows []model.Order
|
|
if err := db.Where(
|
|
"status IN ? AND COALESCE(webhook_push_status,'') <> ?",
|
|
[]string{"paid", "completed"}, "sent",
|
|
).Order("pay_time ASC, created_at ASC").Limit(limit).Find(&rows).Error; err != nil {
|
|
return 0, 0, err
|
|
}
|
|
for i := range rows {
|
|
select {
|
|
case <-ctx.Done():
|
|
return retried, sent, ctx.Err()
|
|
default:
|
|
}
|
|
retried++
|
|
pushErr := pushPaidOrderWebhook(db, &rows[i])
|
|
if pushErr == nil {
|
|
sent++
|
|
markOrderWebhookResult(db, rows[i].OrderSN, true, nil)
|
|
} else {
|
|
markOrderWebhookResult(db, rows[i].OrderSN, false, pushErr)
|
|
}
|
|
}
|
|
return retried, sent, nil
|
|
}
|