package pay import ( "context" "encoding/json" "net/http" "ycc-server/app/main/api/internal/svc" "ycc-server/app/main/model" "ycc-server/pkg/lzkit/lzUtils" "github.com/google/uuid" "github.com/smartwalle/alipay/v3" "github.com/zeromicro/go-zero/core/logx" ) type AlipayFromLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewAlipayFromLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AlipayFromLogic { return &AlipayFromLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } // TradeComplainContent 交易投诉通知的 biz_content 内容 type TradeComplainContent struct { ComplainEventID string `json:"complain_event_id"` // 支付宝侧投诉单号 ComplainNotifyAppID string `json:"complain_notify_app_id"` // 投诉消息通知的应用ID Status string `json:"status"` // 投诉单状态,可能的枚举值如下(用Go语法注释): /* MERCHANT_PROCESSING // 待处理 MERCHANT_FEEDBACKED // 已处理 FINISHED // 投诉完结 CANCELLED // 投诉关闭 PLATFORM_PROCESSING // 客服处理中 PLATFORM_FINISH // 客服处理完结 CLOSED // 投诉关闭 */ BizType string `json:"biz_type"` // 业务类型 OrderID string `json:"order_id"` // 订单ID } // SecurityRiskComplaintsNotifyContent 安全风险投诉商户通知的 biz_content 内容 type SecurityRiskComplaintsNotifyContent struct { ComplaintID string `json:"complaint_id"` // 投诉ID MessageType string `json:"message_type"` // 消息类型,可能的枚举值如下(用Go语法注释): /* USER_REPLY // 用户回复 MERCHANT_REPLY // 商户回复 SYSTEM_NOTIFY // 系统通知 */ ReplyContent string `json:"reply_content"` // 回复内容 ReplyTime string `json:"reply_time"` // 回复时间,格式:YYYY-MM-DD HH:mm:ss } func (l *AlipayFromLogic) AlipayFrom(w http.ResponseWriter, r *http.Request) error { // 1. 解析表单 err := r.ParseForm() if err != nil { logx.Errorf("支付宝from消息回调,解析表单失败: %v", err) // 保存失败记录 l.saveCallbackRecord(r, "", "", "failed", "解析表单失败: "+err.Error()) return nil } // 获取基本信息 appID := r.Form.Get("app_id") msgMethod := r.Form.Get("msg_method") // 2. 验签 client := l.svcCtx.AlipayService.AlipayClient if err := client.VerifySign(r.Form); err != nil { logx.Errorf("支付宝from消息回调,验签失败: %v", err) // 保存失败记录 l.saveCallbackRecord(r, appID, msgMethod, "failed", "验签失败: "+err.Error()) return nil } // 3. 验证 app_id if appID == "" { logx.Errorf("支付宝from消息回调,app_id为空") // 保存失败记录 l.saveCallbackRecord(r, appID, msgMethod, "failed", "app_id为空") return nil } if appID != l.svcCtx.Config.Alipay.AppID { logx.Errorf("支付宝from消息回调,app_id不匹配,期望: %s, 实际: %s", l.svcCtx.Config.Alipay.AppID, appID) // 保存失败记录 l.saveCallbackRecord(r, appID, msgMethod, "failed", "app_id不匹配") return nil } // 4. 获取 msg_method 判断业务类型 if msgMethod == "" { logx.Errorf("支付宝from消息回调,msg_method为空") // 保存失败记录 l.saveCallbackRecord(r, appID, msgMethod, "failed", "msg_method为空") return nil } // 5. 先保存回调记录(pending状态) callbackID := l.saveCallbackRecord(r, appID, msgMethod, "pending", "") if callbackID == "" { logx.Errorf("支付宝from消息回调,保存回调记录失败") // 即使保存失败也继续处理,避免影响业务 } // 6. 根据 msg_method 路由到对应的处理函数 var handleErr error switch msgMethod { case "alipay.merchant.tradecomplain.changed": // 交易投诉通知回调 handleErr = l.handleTradeComplainChanged(w, r, callbackID) case "alipay.security.risk.complaints.merchants.notify": // 安全风险投诉商户通知回调 handleErr = l.handleSecurityRiskComplaintsNotify(w, r, callbackID) default: logx.Infof("支付宝from消息回调,未处理的msg_method: %s", msgMethod) // 更新为已处理(未处理的也标记为已处理) if callbackID != "" { l.updateCallbackStatus(callbackID, "processed", "") } alipay.ACKNotification(w) return nil } // 7. 根据处理结果更新状态 if callbackID != "" { if handleErr != nil { l.updateCallbackStatus(callbackID, "failed", handleErr.Error()) } else { l.updateCallbackStatus(callbackID, "processed", "") } } return handleErr } // saveCallbackRecord 保存回调记录到数据库 func (l *AlipayFromLogic) saveCallbackRecord(r *http.Request, appID, msgMethod, status, errorMsg string) string { callback := &model.AlipayFromCallback{ Id: uuid.NewString(), MsgMethod: msgMethod, AppId: appID, NotifyId: lzUtils.StringToNullString(r.Form.Get("notify_id")), BizContent: r.Form.Get("biz_content"), Status: status, ErrorMessage: lzUtils.StringToNullString(errorMsg), } _, err := l.svcCtx.AlipayFromCallbackModel.Insert(l.ctx, nil, callback) if err != nil { logx.Errorf("保存支付宝from回调记录失败: %v", err) return "" } return callback.Id } // updateCallbackStatus 更新回调记录状态 func (l *AlipayFromLogic) updateCallbackStatus(callbackID, status, errorMsg string) { callback, err := l.svcCtx.AlipayFromCallbackModel.FindOne(l.ctx, callbackID) if err != nil { logx.Errorf("查找支付宝from回调记录失败: %v", err) return } callback.Status = status if errorMsg != "" { callback.ErrorMessage = lzUtils.StringToNullString(errorMsg) } err = l.svcCtx.AlipayFromCallbackModel.UpdateWithVersion(l.ctx, nil, callback) if err != nil { logx.Errorf("更新支付宝from回调记录状态失败: %v", err) } } // handleTradeComplainChanged 处理交易投诉通知回调 func (l *AlipayFromLogic) handleTradeComplainChanged(w http.ResponseWriter, r *http.Request, callbackID string) error { // 获取 biz_content bizContent := r.Form.Get("biz_content") if bizContent == "" { logx.Errorf("支付宝交易投诉通知回调,biz_content为空") return nil } // 解析 biz_content JSON var content TradeComplainContent if err := json.Unmarshal([]byte(bizContent), &content); err != nil { logx.Errorf("支付宝交易投诉通知回调,解析biz_content失败: %v, content: %s", err, bizContent) return nil } // 记录日志 logx.Infof("支付宝交易投诉通知回调,投诉事件ID: %s, 订单ID: %s, 状态: %s, 业务类型: %s", content.ComplainEventID, content.OrderID, content.Status, content.BizType) // TODO: 后续在这里添加具体的业务处理逻辑 // 例如:根据订单ID查找订单,更新订单状态,发送通知等 alipay.ACKNotification(w) return nil } // handleSecurityRiskComplaintsNotify 处理安全风险投诉商户通知回调 func (l *AlipayFromLogic) handleSecurityRiskComplaintsNotify(w http.ResponseWriter, r *http.Request, callbackID string) error { // 获取 biz_content bizContent := r.Form.Get("biz_content") if bizContent == "" { logx.Errorf("支付宝安全风险投诉商户通知回调,biz_content为空") return nil } // 解析 biz_content JSON var content SecurityRiskComplaintsNotifyContent if err := json.Unmarshal([]byte(bizContent), &content); err != nil { logx.Errorf("支付宝安全风险投诉商户通知回调,解析biz_content失败: %v, content: %s", err, bizContent) return nil } // 记录日志 logx.Infof("支付宝安全风险投诉商户通知回调,投诉ID: %s, 消息类型: %s, 回复内容: %s, 回复时间: %s", content.ComplaintID, content.MessageType, content.ReplyContent, content.ReplyTime) // 根据投诉ID查询详情并更新投诉记录 if err := l.svcCtx.AlipayComplaintService.QueryComplaintByTaskId(l.ctx, content.ComplaintID); err != nil { logx.Errorf("查询并更新投诉记录失败, complaint_id: %s, error: %v", content.ComplaintID, err) // 即使失败也返回成功,避免支付宝重复通知 } alipay.ACKNotification(w) return nil }