From d2b0d2f534def326b595c32b7377978d375166fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E8=BF=9B?= Date: Tue, 27 Oct 2026 11:07:17 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=8E=92=E9=98=9F=E6=BF=80=E6=B4=BB?= =?UTF-8?q?=E6=9C=AA=E8=AF=84=E4=BC=B0=E5=81=9C=E5=A4=8D=E6=9C=BA=E3=80=81?= =?UTF-8?q?=E5=AD=A4=E5=84=BF=E5=A5=97=E9=A4=90=E9=87=8D=E5=A4=8D=E6=BF=80?= =?UTF-8?q?=E6=B4=BB=E4=B8=8E=E5=BE=AE=E4=BF=A1=E6=9F=A5=E5=8D=95=E8=AE=A2?= =?UTF-8?q?=E5=8D=95=E4=B8=8D=E5=AD=98=E5=9C=A8=E5=8F=8D=E5=A4=8D=E9=87=8D?= =?UTF-8?q?=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 排队激活与首次实名激活任务的处理器注入停复机服务:原来传 nil,激活后不重新评估停复机,付款或实名后要等下一轮轮询才复机(生产同样存在) - 孤儿套餐扫描跳过载体未实名的"实名即生效"套餐:激活服务会直接跳过,原来每 10 秒重复提交一次激活任务 - 微信 v2 查单返回 ORDERNOTEXIST 时按"渠道无此订单"处理:代理充值支付单过期后关闭,不再当作结果未知每几分钟重查 Co-Authored-By: Claude Opus 5.5 --- .../application/agentrecharge/online_payment.go | 2 ++ .../agentrecharge/recover_online_payment.go | 6 ++++++ internal/infrastructure/payment/wechat_web.go | 11 +++++++++++ internal/polling/package_activation_handler.go | 16 ++++++++++++++++ pkg/queue/handler.go | 3 ++- pkg/wechat/payment_v2.go | 10 ++++++++++ 6 files changed, 47 insertions(+), 1 deletion(-) diff --git a/internal/application/agentrecharge/online_payment.go b/internal/application/agentrecharge/online_payment.go index f104496b..978c4a05 100644 --- a/internal/application/agentrecharge/online_payment.go +++ b/internal/application/agentrecharge/online_payment.go @@ -34,6 +34,8 @@ const ( OnlinePaymentStateClosed = "closed" // OnlinePaymentStateUnknown 表示渠道状态暂时无法确定。 OnlinePaymentStateUnknown = "unknown" + // OnlinePaymentStateNotFound 表示渠道明确没有这笔订单;恢复任务在支付单过期后按关闭处理。 + OnlinePaymentStateNotFound = "not_found" ) // OnlinePaymentRequest 描述生成支付链接与主动查单所需的最小事实。 diff --git a/internal/application/agentrecharge/recover_online_payment.go b/internal/application/agentrecharge/recover_online_payment.go index 7e40345f..e03f88f9 100644 --- a/internal/application/agentrecharge/recover_online_payment.go +++ b/internal/application/agentrecharge/recover_online_payment.go @@ -139,6 +139,12 @@ func (s *RecoverOnlinePaymentService) queryPayment(ctx context.Context, adapter return err case OnlinePaymentStateClosed: return s.closePending(ctx, payment, recharge) + case OnlinePaymentStateNotFound: + // 渠道没有这笔订单:过期后已不可能再支付,按关闭处理;未过期时可能尚未同步到渠道,继续等待。 + if payment.ExpireAt != nil && time.Now().After(*payment.ExpireAt) { + return s.closePending(ctx, payment, recharge) + } + return nil default: return nil } diff --git a/internal/infrastructure/payment/wechat_web.go b/internal/infrastructure/payment/wechat_web.go index 2aa4a42a..feecfbdf 100644 --- a/internal/infrastructure/payment/wechat_web.go +++ b/internal/infrastructure/payment/wechat_web.go @@ -3,6 +3,7 @@ package payment import ( "context" + stderrors "errors" "strconv" "time" @@ -66,6 +67,16 @@ func (a *WechatWebAdapter) Query(ctx context.Context, request agentrecharge.Onli } startedAt := time.Now() info, callErr := a.queryOrder(ctx, request.Config, request.PaymentNo) + if stderrors.Is(callErr, wechatpay.ErrOrderNotExist) { + if _, err = a.integration.Complete(ctx, attempt.IntegrationID, integrationlog.Completion{ + Result: constants.IntegrationResultSuccess, ProviderCode: "ORDERNOTEXIST", + ResponseSummary: map[string]any{"state": agentrecharge.OnlinePaymentStateNotFound}, + DurationMS: time.Since(startedAt).Milliseconds(), + }); err != nil { + return agentrecharge.OnlinePaymentQueryResult{}, err + } + return agentrecharge.OnlinePaymentQueryResult{State: agentrecharge.OnlinePaymentStateNotFound}, nil + } if callErr != nil { return agentrecharge.OnlinePaymentQueryResult{}, a.completeUnknown(ctx, attempt.IntegrationID, startedAt, callErr) } diff --git a/internal/polling/package_activation_handler.go b/internal/polling/package_activation_handler.go index f6afe879..f74fe4f9 100644 --- a/internal/polling/package_activation_handler.go +++ b/internal/polling/package_activation_handler.go @@ -200,11 +200,27 @@ func (h *PackageActivationHandler) findAndActivateOrphanPackages(ctx context.Con FROM pending_queue AS candidate JOIN tb_package_usage AS usage ON usage.id = candidate.id WHERE candidate.queue_position = 1 + -- 实名即生效的套餐在载体实名前不能激活,激活服务会直接跳过;不排除会每轮重复提交激活任务。 + -- 实名后由实名激活入口激活。口径与 ActivationService.canActivatePendingUsage 一致。 + AND ( + NOT usage.pending_realname_activation + OR (COALESCE(usage.iot_card_id, 0) > 0 AND EXISTS ( + SELECT 1 FROM tb_iot_card AS card + WHERE card.id = usage.iot_card_id AND card.real_name_status = ?)) + OR (COALESCE(usage.iot_card_id, 0) = 0 AND usage.device_id > 0 AND EXISTS ( + SELECT 1 FROM tb_device_sim_binding AS binding + JOIN tb_iot_card AS card ON card.id = binding.iot_card_id + WHERE binding.device_id = usage.device_id AND binding.bind_status = ? + AND binding.deleted_at IS NULL AND card.real_name_status = ?)) + ) ORDER BY candidate.priority ASC, candidate.created_at ASC, candidate.id ASC LIMIT ?`, constants.PackageUsageStatusPending, constants.PackageUsageStatusActive, constants.PackageUsageStatusDepleted, + constants.RealNameStatusVerified, + constants.BindStatusBound, + constants.RealNameStatusVerified, orphanPackageScanLimit, ).Scan(&orphanUsages).Error if err != nil { diff --git a/pkg/queue/handler.go b/pkg/queue/handler.go index 22b07361..dfd00c71 100644 --- a/pkg/queue/handler.go +++ b/pkg/queue/handler.go @@ -309,7 +309,8 @@ func (h *Handler) registerPackageActivationHandlers() { h.redis, h.asynqClient, h.workerResult.Services.ActivationService, - nil, + // 激活成功后须重新评估停复机(客户付款或实名后及时复机);与轮询调度中的激活处理器注入同一服务。 + h.workerResult.Services.StopResumeService, h.logger, ) diff --git a/pkg/wechat/payment_v2.go b/pkg/wechat/payment_v2.go index 12515cbf..f3eafb0b 100644 --- a/pkg/wechat/payment_v2.go +++ b/pkg/wechat/payment_v2.go @@ -6,6 +6,7 @@ import ( "crypto/md5" "crypto/subtle" "encoding/xml" + stderrors "errors" "fmt" "io" "net" @@ -41,6 +42,10 @@ type PaymentV2Service struct { } // NewPaymentV2Service 创建微信支付 v2 服务 + +// ErrOrderNotExist 表示微信明确返回订单不存在(v2 err_code=ORDERNOTEXIST)。 +var ErrOrderNotExist = stderrors.New("微信订单不存在") + func NewPaymentV2Service(appID, mchID, apiKey, notifyURL string, logger *zap.Logger) *PaymentV2Service { return &PaymentV2Service{ appID: appID, @@ -297,6 +302,11 @@ func (s *PaymentV2Service) QueryOrder(ctx context.Context, orderNo string) (*Ord s.logger.Error("解析微信 v2 查单响应失败", zap.String("order_no", orderNo), zap.Error(err)) return nil, errors.New(errors.CodeWechatPayFailed, "查询微信支付订单失败") } + if resp.ReturnCode == "SUCCESS" && resp.ResultCode != "SUCCESS" && resp.ErrCode == "ORDERNOTEXIST" { + // 微信明确表示没有这笔订单:返回专门的错误,由调用方按"渠道无此订单"处理,不当作结果未知反复重查。 + s.logger.Warn("微信 v2 查单:订单不存在", zap.String("order_no", orderNo)) + return nil, ErrOrderNotExist + } if resp.ReturnCode != "SUCCESS" || resp.ResultCode != "SUCCESS" { s.logger.Error("微信 v2 查单失败", zap.String("order_no", orderNo), zap.String("return_code", resp.ReturnCode), zap.String("return_msg", resp.ReturnMsg), zap.String("result_code", resp.ResultCode), zap.String("err_code", resp.ErrCode), zap.String("err_code_des", resp.ErrCodeDes))