package handler import ( "fmt" "io" "net/http" "time" "soul-api/internal/database" "soul-api/internal/model" "soul-api/internal/wechat" "github.com/gin-gonic/gin" "gorm.io/gorm" ) // PaymentAlipayNotify POST /api/payment/alipay/notify func PaymentAlipayNotify(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"success": true}) } // PaymentCallback POST /api/payment/callback func PaymentCallback(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"success": true}) } // PaymentCreateOrder POST /api/payment/create-order func PaymentCreateOrder(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"success": true}) } // PaymentMethods GET /api/payment/methods func PaymentMethods(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"success": true, "data": []interface{}{}}) } // PaymentQuery GET /api/payment/query func PaymentQuery(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"success": true}) } // PaymentStatusOrderSn GET /api/payment/status/:orderSn func PaymentStatusOrderSn(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"success": true}) } // PaymentVerify POST /api/payment/verify func PaymentVerify(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"success": true}) } // PaymentWechatNotify POST /api/payment/wechat/notify func PaymentWechatNotify(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"success": true}) } // logWechatTransferCallback 写入微信转账回调日志到 wechat_callback_logs func logWechatTransferCallback(db *gorm.DB, outBillNo, transferBillNo, state, failReason, outBatchNo, handlerResult, handlerError string) { entry := model.WechatCallbackLog{ CallbackType: "transfer", OutDetailNo: outBillNo, TransferBillNo: transferBillNo, State: state, FailReason: failReason, OutBatchNo: outBatchNo, HandlerResult: handlerResult, HandlerError: handlerError, } if err := db.Create(&entry).Error; err != nil { fmt.Printf("[TransferNotify] 写回调日志失败: %v\n", err) } } // PaymentWechatTransferNotify POST /api/payment/wechat/transfer/notify // 使用 PowerWeChat 验签、解密密文后更新提现状态,并返回微信要求的应答;同时写入 wechat_callback_logs // GET 同一路径时仅返回 200 与说明(便于探活或浏览器访问,不写库) func PaymentWechatTransferNotify(c *gin.Context) { if c.Request.Method == "GET" { c.String(http.StatusOK, "转账结果通知请使用 POST") return } fmt.Printf("[TransferNotify] 收到微信转账回调请求 method=%s path=%s\n", c.Request.Method, c.Request.URL.Path) resp, err := wechat.HandleTransferNotify(c.Request, func(outBillNo, transferBillNo, state, failReason string) error { fmt.Printf("[TransferNotify] 解密成功: out_bill_no=%s, transfer_bill_no=%s, state=%s\n", outBillNo, transferBillNo, state) db := database.DB() var w model.Withdrawal if err := db.Where("detail_no = ?", outBillNo).First(&w).Error; err != nil { fmt.Printf("[TransferNotify] 未找到 detail_no=%s 的提现记录: %v\n", outBillNo, err) logWechatTransferCallback(db, outBillNo, transferBillNo, state, failReason, "", "success", "未找到提现记录") return nil } outBatchNo := "" if w.BatchNo != nil { outBatchNo = *w.BatchNo } cur := "" if w.Status != nil { cur = *w.Status } if cur != "processing" && cur != "pending_confirm" { logWechatTransferCallback(db, outBillNo, transferBillNo, state, failReason, outBatchNo, "success", "状态已变更跳过") return nil } now := time.Now() up := map[string]interface{}{"processed_at": now} switch state { case "SUCCESS": up["status"] = "success" case "FAIL", "CANCELLED": up["status"] = "failed" if failReason != "" { up["fail_reason"] = failReason } default: logWechatTransferCallback(db, outBillNo, transferBillNo, state, failReason, outBatchNo, "success", "") return nil } if err := db.Model(&w).Updates(up).Error; err != nil { logWechatTransferCallback(db, outBillNo, transferBillNo, state, failReason, outBatchNo, "fail", err.Error()) return fmt.Errorf("更新提现状态失败: %w", err) } fmt.Printf("[TransferNotify] 已更新提现 id=%s -> status=%s\n", w.ID, up["status"]) logWechatTransferCallback(db, outBillNo, transferBillNo, state, failReason, outBatchNo, "success", "") return nil }) if err != nil { fmt.Printf("[TransferNotify] 验签/解密/处理失败: %v\n", err) db := database.DB() logWechatTransferCallback(db, "", "", "", "", "", "fail", err.Error()) c.JSON(http.StatusInternalServerError, gin.H{"code": "FAIL", "message": err.Error()}) return } defer resp.Body.Close() for k, v := range resp.Header { if len(v) > 0 { c.Header(k, v[0]) } } c.Status(resp.StatusCode) io.Copy(c.Writer, resp.Body) }