241 lines
8.0 KiB
Go
241 lines
8.0 KiB
Go
|
|
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
|
|||
|
|
}
|