Files
ycc-proxy-server/app/main/api/internal/logic/pay/alipayfromlogic.go
2026-01-12 16:43:08 +08:00

241 lines
8.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}