This commit is contained in:
Mrx
2026-06-07 14:47:50 +08:00
parent 0dada87a54
commit 079b5b39ef
7 changed files with 145 additions and 0 deletions

1
.gitignore vendored
View File

@@ -24,6 +24,7 @@ data/*
/app/main/api/_* /app/main/api/_*
__debug_bin.exe __debug_bin.exe
**/.../ **/.../
*.exe
# 文档目录 # 文档目录

View File

@@ -45,6 +45,10 @@ service main {
@handler PaymentCheck @handler PaymentCheck
post /pay/check (PaymentCheckReq) returns (PaymentCheckResp) post /pay/check (PaymentCheckReq) returns (PaymentCheckResp)
// 小程序虚拟支付:上报微信/Apple 客户端提示与步骤(写入服务端日志并可选查微信单)
@handler XpayClientEvent
post /pay/xpay/client-event (XpayClientEventReq) returns (XpayClientEventResp)
} }
type ( type (
@@ -69,6 +73,23 @@ type (
WxOrderStatus int `json:"wx_order_status,optional"` // 微信侧订单 status WxOrderStatus int `json:"wx_order_status,optional"` // 微信侧订单 status
WxOrderDetail string `json:"wx_order_detail,optional"` // query_order 原始 order 摘要(排查用) WxOrderDetail string `json:"wx_order_detail,optional"` // query_order 原始 order 摘要(排查用)
} }
XpayClientEventReq {
OrderNo string `json:"order_no,optional"`
Stage string `json:"stage" validate:"required"` // prepay_ok, invoke, success, fail, check
EventType string `json:"event_type" validate:"required,oneof=info tip fail success"`
Message string `json:"message,optional"` // 微信/Apple 展示给用户的话术(非 HTTP 报错)
ErrMsg string `json:"err_msg,optional"`
ErrCode int `json:"err_code,optional"`
Errno int `json:"errno,optional"`
SignData string `json:"sign_data,optional"`
Device string `json:"device,optional"`
Extra string `json:"extra,optional"`
}
XpayClientEventResp {
WxOrderStatus int `json:"wx_order_status,optional"`
WxOrderDetail string `json:"wx_order_detail,optional"`
WxSyncError string `json:"wx_sync_error,optional"`
}
) )
type ( type (

View File

@@ -0,0 +1,30 @@
package pay
import (
"net/http"
"qnc-server/app/main/api/internal/logic/pay"
"qnc-server/app/main/api/internal/svc"
"qnc-server/app/main/api/internal/types"
"qnc-server/common/result"
"qnc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func XpayClientEventHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.XpayClientEventReq
if err := httpx.Parse(r, &req); err != nil {
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := pay.NewXpayClientEventLogic(r.Context(), svcCtx)
resp, err := l.XpayClientEvent(&req)
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -976,6 +976,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
Path: "/pay/payment", Path: "/pay/payment",
Handler: pay.PaymentHandler(serverCtx), Handler: pay.PaymentHandler(serverCtx),
}, },
{
Method: http.MethodPost,
Path: "/pay/xpay/client-event",
Handler: pay.XpayClientEventHandler(serverCtx),
},
}..., }...,
), ),
rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret), rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret),

View File

@@ -0,0 +1,65 @@
package pay
import (
"context"
"qnc-server/app/main/api/internal/svc"
"qnc-server/app/main/api/internal/types"
"qnc-server/common/ctxdata"
"github.com/zeromicro/go-zero/core/logx"
)
type XpayClientEventLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewXpayClientEventLogic(ctx context.Context, svcCtx *svc.ServiceContext) *XpayClientEventLogic {
return &XpayClientEventLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
// XpayClientEvent 记录小程序虚拟支付各步骤及微信/Apple 客户端提示(非 HTTP 异常)
func (l *XpayClientEventLogic) XpayClientEvent(req *types.XpayClientEventReq) (*types.XpayClientEventResp, error) {
userID, _ := ctxdata.GetUidFromCtx(l.ctx)
l.Infof("[xpay:client-event] user=%s order_no=%s stage=%s event=%s message=%q err_msg=%q err_code=%d errno=%d sign_data=%s device=%s extra=%s",
userID, req.OrderNo, req.Stage, req.EventType, req.Message, req.ErrMsg, req.ErrCode, req.Errno,
req.SignData, req.Device, req.Extra)
resp := &types.XpayClientEventResp{}
if req.OrderNo == "" || l.svcCtx.XpayService == nil || !l.svcCtx.XpayService.Enabled() {
return resp, nil
}
// 客户端 fail/tip 时主动 query_order把微信服务端侧状态一并记入日志
if req.EventType == "fail" || req.EventType == "tip" {
openid, err := l.svcCtx.XpayService.GetWxMiniOpenID(l.ctx, l.svcCtx.UserAuthModel, userID)
if err != nil {
resp.WxSyncError = "获取 openid 失败: " + err.Error()
l.Errorf("[xpay:client-event] query_order skip order_no=%s reason=%v", req.OrderNo, err)
return resp, nil
}
status, qErr := l.svcCtx.XpayService.QueryOrder(l.ctx, openid, req.OrderNo, "")
if qErr != nil {
resp.WxSyncError = qErr.Error()
l.Errorf("[xpay:client-event] query_order FAIL order_no=%s client_msg=%q wx_err=%v",
req.OrderNo, req.Message, qErr)
return resp, nil
}
resp.WxOrderStatus = status.Status
resp.WxOrderDetail = status.RawOrder
l.Infof("[xpay:client-event] query_order SNAPSHOT order_no=%s client_msg=%q wx_status=%d wx_order_id=%s paid_fee=%d err_msg=%s raw=%s",
req.OrderNo, req.Message, status.Status, status.WxOrderID, status.PaidFee, status.ErrMsg, status.RawOrder)
}
return resp, nil
}

View File

@@ -7,6 +7,7 @@ import (
"qnc-server/app/main/api/internal/service" "qnc-server/app/main/api/internal/service"
"qnc-server/app/main/api/internal/svc" "qnc-server/app/main/api/internal/svc"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
) )
@@ -61,6 +62,9 @@ func (l *XpayPushLogic) HandlePOST(w http.ResponseWriter, r *http.Request) {
} }
ctx := r.Context() ctx := r.Context()
logx.WithContext(ctx).Infof("[xpay:push] recv event=%s order_no=%s openid=%s env=%d body=%s",
notify.Event, orderNo, notify.OpenId, notify.Env, string(body))
already, _ := l.svcCtx.XpayService.AlreadyNotified(ctx, orderNo) already, _ := l.svcCtx.XpayService.AlreadyNotified(ctx, orderNo)
if already { if already {
l.writePushResp(w, 0, "already notified") l.writePushResp(w, 0, "already notified")

View File

@@ -1935,6 +1935,25 @@ type PaymentCheckResp struct {
WxOrderDetail string `json:"wx_order_detail,optional"` // query_order 原始 order 摘要(排查用) WxOrderDetail string `json:"wx_order_detail,optional"` // query_order 原始 order 摘要(排查用)
} }
type XpayClientEventReq struct {
OrderNo string `json:"order_no,optional"`
Stage string `json:"stage" validate:"required"` // prepay_ok, invoke, success, fail, check
EventType string `json:"event_type" validate:"required,oneof=info tip fail success"`
Message string `json:"message,optional"` // 微信/Apple 展示给用户的话术
ErrMsg string `json:"err_msg,optional"`
ErrCode int `json:"err_code,optional"`
Errno int `json:"errno,optional"`
SignData string `json:"sign_data,optional"`
Device string `json:"device,optional"`
Extra string `json:"extra,optional"`
}
type XpayClientEventResp struct {
WxOrderStatus int `json:"wx_order_status,optional"`
WxOrderDetail string `json:"wx_order_detail,optional"`
WxSyncError string `json:"wx_sync_error,optional"`
}
type PaymentReq struct { type PaymentReq struct {
Id string `json:"id"` Id string `json:"id"`
PayMethod string `json:"pay_method"` // 支付方式: wechat, alipay, appleiap, test(仅开发环境), test_empty(仅开发环境-空报告模式) PayMethod string `json:"pay_method"` // 支付方式: wechat, alipay, appleiap, test(仅开发环境), test_empty(仅开发环境-空报告模式)