@@ -123,6 +123,12 @@ func (h *PaymentHandler) WechatPayCallback(c *fiber.Ctx) error {
return errors . Wrap ( errors . CodeWechatCallbackInvalid , err , "微信 v2 回调验签失败" )
}
if result . TradeState != "SUCCESS" {
if err := h . recordIgnoredPaymentCallback ( ctx , verifiedPaymentCallback {
PaymentNo : result . OutTradeNo , TransactionID : result . TransactionID ,
Provider : constants . IntegrationProviderWechatPay , RawPayload : body , ContentType : c . Get ( "Content-Type" ) ,
} , result . TradeState ) ; err != nil {
return err
}
return h . wechatV2SuccessResponse ( c )
}
// TotalFee 为字符串格式的分,解析失败则降级为 0( 后续 handlePaymentCallback 会记录日志)
@@ -145,7 +151,10 @@ func (h *PaymentHandler) WechatPayCallback(c *fiber.Ctx) error {
fasthttpadaptor . ConvertRequest ( c . Context ( ) , & httpReq , true )
_ , err := h . wechatPayment . HandlePaymentNotify ( & httpReq , func ( result * wechat . PaymentNotifyResult ) error {
if result . TradeState != "SUCCESS" {
return nil
return h . recordIgnoredPaymentCallback ( ctx , verifiedPaymentCallback {
PaymentNo : result . OutTradeNo , TransactionID : result . TransactionID ,
Provider : constants . IntegrationProviderWechatPay , RawPayload : body , ContentType : c . Get ( "Content-Type" ) ,
} , result . TradeState )
}
paidAt , _ := time . Parse ( time . RFC3339 , result . SuccessTime )
return h . dispatchWechatCallback ( ctx , verifiedPaymentCallback {
@@ -170,19 +179,35 @@ func (h *PaymentHandler) dispatchPaymentRecordCallback(ctx context.Context, call
if h . paymentStore != nil {
payment , err := h . paymentStore . GetByPaymentNo ( ctx , callback . PaymentNo )
if err == nil {
log , err := h . recordPaymentCallback ( ctx , callback , payment )
if err != nil {
return true , err
}
var processErr error
switch payment . OrderType {
case model . PaymentOrderTypePackage :
return true , h . orderService . HandlePaymentRecordCallback ( ctx , callback . PaymentNo , callback . PaymentMethod , callback . TransactionID , callback . Amount )
processErr = h . orderService . HandlePaymentRecordCallback ( ctx , callback . PaymentNo , callback . PaymentMethod , callback . TransactionID , callback . Amount )
case model . PaymentOrderTypeRecharge :
if h . rechargeOrderService != nil {
return true , h . rechargeOrderService . HandlePaymentCallback ( ctx , callback . PaymentNo , callback . PaymentMethod , callback . TransactionID )
processErr = h . rechargeOrderService . HandlePaymentCallback ( ctx , callback . PaymentNo , callback . PaymentMethod , callback . TransactionID )
} else {
processErr = errors . New ( errors . CodeInternalError , "充值订单服务未配置" )
}
return true , errors . New ( errors . CodeInternalError , "充值订单服务未配置" )
case model . PaymentOrderTypeAgentRecharge :
return true , h . confirmAgentRechargePayment ( ctx , callback )
return true , h . confirmAgentRechargePayment ( ctx , callback , log )
default :
return true , errors . New ( errors . CodeInvalidStatus , "未知支付记录类型" )
processErr = errors . New ( errors . CodeInvalidStatus , "未知支付记录类型" )
}
completion := integrationlog . Completion { Result : constants . IntegrationResultSuccess , ResponseSummary : map [ string ] any { "confirmed" : true } }
if processErr != nil {
completion . Result = constants . IntegrationResultFailed
completion . SafeProviderMessage = "支付回调业务确认失败"
completion . ResponseSummary = map [ string ] any { "confirmed" : false }
} else if current , currentErr := h . paymentStore . GetByPaymentNo ( ctx , callback . PaymentNo ) ; currentErr == nil {
completion . StateChanged = payment . Status != model . PaymentRecordStatusPaid && current . Status == model . PaymentRecordStatusPaid
}
h . completePaymentCallbackLog ( ctx , log , completion )
return true , processErr
}
if err != gorm . ErrRecordNotFound {
return true , errors . Wrap ( errors . CodeDatabaseError , err , "查询支付记录失败" )
@@ -198,31 +223,14 @@ func (h *PaymentHandler) dispatchWechatCallback(ctx context.Context, callback ve
return err
}
switch {
case strings . HasPrefix ( callback . PaymentNo , "ORD" ) :
return h . orderService . HandlePaymentCallback ( ctx , callback . PaymentNo , model . PaymentMethodWechat , callback . Amount )
case strings . HasPrefix ( callback . PaymentNo , constants . AssetRechargeOrderPrefix ) :
if h . rechargeOrderService != nil {
return h . rechargeOrderService . HandlePaymentCallback ( ctx , callback . PaymentNo , model . PaymentByWechat , callback . TransactionID )
}
return errors . New ( errors . CodeInternalError , "充值订单服务未配置" )
case strings . HasPrefix ( callback . PaymentNo , constants . AgentRechargeOrderPrefix ) :
if h . agentRechargeService != nil {
return h . agentRechargeService . HandlePaymentCallback ( ctx , callback . PaymentNo , model . PaymentMethodWechat , callback . TransactionID , callback . Amount )
}
return errors . New ( errors . CodeInternalError , "代理充值服务未配置" )
default :
return errors . New ( errors . CodeInvalidStatus , "未知订单号前缀" )
}
return h . dispatchLegacyPaymentCallback ( ctx , callback )
}
func ( h * PaymentHandler ) confirmAgentRechargePayment ( ctx context . Context , callback verifiedPaymentCallback ) error {
if h . agentPaymentConfirm == nil || h . integration == nil {
return errors . New ( errors . CodeInternalError , "代理充值支付回调能力 未配置" )
func ( h * PaymentHandler ) dispatchLegacyPaymentCallback ( ctx context . Context , callback verifiedPaymentCallback ) error {
if h . integration == nil {
return errors . New ( errors . CodeInvalidStatus , "支付回调 Integration Log 接缝 未配置" )
}
resourceKey , correlationID := callback . PaymentNo , callback . PaymentNo
ctx = auditcontext . With ( ctx , auditcontext . Context { CorrelationID : correlationID } )
linkage := auditcontext . From ( ctx )
idempotencyKey := callback . TransactionID
if idempotencyKey == "" {
idempotencyKey = callback . PaymentNo
@@ -230,13 +238,51 @@ func (h *PaymentHandler) confirmAgentRechargePayment(ctx context.Context, callba
log , _ , err := h . integration . RecordInbound ( ctx , integrationlog . InboundAttempt {
IdempotencyKey : idempotencyKey , Provider : callback . Provider ,
Operation : constants . IntegrationOperationPaymentCallback , ExternalID : callback . TransactionID ,
ResourceType : constants . IntegrationResourceTypeAgentRecharge Payment , ResourceKey : & resourceKey ,
ResourceType : constants . IntegrationResourceTypePayment , ResourceKey : & resourceKey ,
RawPayload : callback . RawPayload , ContentType : callback . ContentType ,
RequestID : pkgmiddleware . GetRequestIDFromContext ( ctx ) , CorrelationID : & correlationID ,
} )
if err != nil {
return err
}
if err := h . preparePaymentCallbackRetry ( ctx , log ) ; err != nil {
return err
}
var processErr error
switch {
case strings . HasPrefix ( callback . PaymentNo , "ORD" ) :
processErr = h . orderService . HandlePaymentCallback ( ctx , callback . PaymentNo , callback . PaymentMethod , callback . Amount )
case strings . HasPrefix ( callback . PaymentNo , constants . AssetRechargeOrderPrefix ) :
if h . rechargeOrderService != nil {
processErr = h . rechargeOrderService . HandlePaymentCallback ( ctx , callback . PaymentNo , callback . PaymentMethod , callback . TransactionID )
} else {
processErr = errors . New ( errors . CodeInternalError , "充值订单服务未配置" )
}
case strings . HasPrefix ( callback . PaymentNo , constants . AgentRechargeOrderPrefix ) :
if h . agentRechargeService != nil {
processErr = h . agentRechargeService . HandlePaymentCallback ( ctx , callback . PaymentNo , callback . PaymentMethod , callback . TransactionID , callback . Amount )
} else {
processErr = errors . New ( errors . CodeInternalError , "代理充值服务未配置" )
}
default :
processErr = errors . New ( errors . CodeInvalidStatus , "未知订单号前缀" )
}
completion := integrationlog . Completion { Result : constants . IntegrationResultSuccess , ResponseSummary : map [ string ] any { "confirmed" : processErr == nil } }
if processErr != nil {
completion . Result = constants . IntegrationResultFailed
completion . SafeProviderMessage = "旧支付回调业务确认失败"
}
h . completePaymentCallbackLog ( ctx , log , completion )
return processErr
}
func ( h * PaymentHandler ) confirmAgentRechargePayment ( ctx context . Context , callback verifiedPaymentCallback , log * model . IntegrationLog ) error {
if h . agentPaymentConfirm == nil || h . integration == nil {
return errors . New ( errors . CodeInternalError , "代理充值支付回调能力未配置" )
}
correlationID := callback . PaymentNo
ctx = auditcontext . With ( ctx , auditcontext . Context { CorrelationID : correlationID } )
linkage := auditcontext . From ( ctx )
result , confirmErr := h . agentPaymentConfirm . Execute ( ctx , agentrechargeApp . ConfirmOnlinePaymentCommand {
PaymentNo : callback . PaymentNo , PaymentMethod : callback . PaymentMethod , ConfigID : callback . ConfigID ,
MerchantIdentity : callback . MerchantIdentity , ThirdPartyTradeNo : callback . TransactionID ,
@@ -269,6 +315,69 @@ func (h *PaymentHandler) confirmAgentRechargePayment(ctx context.Context, callba
return nil
}
func ( h * PaymentHandler ) recordPaymentCallback ( ctx context . Context , callback verifiedPaymentCallback , payment * model . Payment ) ( * model . IntegrationLog , error ) {
if h . integration == nil || payment == nil {
return nil , errors . New ( errors . CodeInvalidStatus , "支付回调 Integration Log 接缝未配置" )
}
resourceID , resourceKey , correlationID := strconv . FormatUint ( uint64 ( payment . ID ) , 10 ) , payment . PaymentNo , payment . PaymentNo
idempotencyKey := callback . TransactionID
if idempotencyKey == "" {
idempotencyKey = callback . PaymentNo
}
ctx = auditcontext . With ( ctx , auditcontext . Context { CorrelationID : correlationID } )
log , _ , err := h . integration . RecordInbound ( ctx , integrationlog . InboundAttempt {
IdempotencyKey : idempotencyKey , Provider : callback . Provider ,
Operation : constants . IntegrationOperationPaymentCallback , ExternalID : callback . TransactionID ,
ResourceType : constants . IntegrationResourceTypePayment , ResourceID : & resourceID , ResourceKey : & resourceKey ,
RawPayload : callback . RawPayload , ContentType : callback . ContentType ,
RequestID : pkgmiddleware . GetRequestIDFromContext ( ctx ) , CorrelationID : & correlationID ,
} )
if err != nil {
return nil , err
}
if err := h . preparePaymentCallbackRetry ( ctx , log ) ; err != nil {
return nil , err
}
return log , nil
}
func ( h * PaymentHandler ) preparePaymentCallbackRetry ( ctx context . Context , log * model . IntegrationLog ) error {
if log == nil || log . Result != constants . IntegrationResultFailed {
return nil
}
claimed , err := h . integration . ClaimFailedInbound ( ctx , log . IntegrationID )
if err != nil {
return err
}
if claimed {
log . Result = constants . IntegrationResultPending
}
return nil
}
func ( h * PaymentHandler ) recordIgnoredPaymentCallback ( ctx context . Context , callback verifiedPaymentCallback , providerCode string ) error {
if h . integration == nil {
return errors . New ( errors . CodeInvalidStatus , "支付回调 Integration Log 接缝未配置" )
}
resourceKey , correlationID := callback . PaymentNo , callback . PaymentNo
idempotencyKey := callback . PaymentNo + ":" + providerCode
log , _ , err := h . integration . RecordInbound ( ctx , integrationlog . InboundAttempt {
IdempotencyKey : idempotencyKey , Provider : callback . Provider ,
Operation : constants . IntegrationOperationPaymentCallback , ExternalID : callback . TransactionID ,
ResourceType : constants . IntegrationResourceTypePayment , ResourceKey : & resourceKey ,
RawPayload : callback . RawPayload , ContentType : callback . ContentType ,
RequestID : pkgmiddleware . GetRequestIDFromContext ( ctx ) , CorrelationID : & correlationID ,
} )
if err != nil || log . Result != constants . IntegrationResultPending {
return err
}
_ , err = h . integration . Complete ( ctx , log . IntegrationID , integrationlog . Completion {
Result : constants . IntegrationResultIgnored , ProviderCode : providerCode ,
ResponseSummary : map [ string ] any { "confirmed" : false } ,
} )
return err
}
func ( h * PaymentHandler ) completePaymentCallbackLog ( ctx context . Context , log * model . IntegrationLog , completion integrationlog . Completion ) {
if log == nil || log . Result != constants . IntegrationResultPending {
return
@@ -346,6 +455,12 @@ func (h *PaymentHandler) AlipayCallback(c *fiber.Ctx) error {
zap . String ( "out_trade_no" , outTradeNo ) ,
zap . String ( "trade_status" , tradeStatus ) ,
)
if err := h . recordIgnoredPaymentCallback ( ctx , verifiedPaymentCallback {
PaymentNo : outTradeNo , TransactionID : notification . TradeNo ,
Provider : constants . IntegrationProviderAlipay , RawPayload : c . Body ( ) , ContentType : c . Get ( "Content-Type" ) ,
} , tradeStatus ) ; err != nil {
return err
}
return c . SendString ( "success" )
}
@@ -409,57 +524,13 @@ func (h *PaymentHandler) AlipayCallback(c *fiber.Ctx) error {
return errors . New ( errors . CodeWechatCallbackInvalid , "支付金额校验失败" )
}
// 新代理充值由统一确认事务原子写入交易号,旧业务保持原处理方式。
if p ayment. OrderType != model . PaymentOrderTypeAgentRecharge {
if err := h . paymentStore . UpdatePaymentInfo ( ctx , payment . ID, notification . TradeNo , nil ) ; err != nil {
h . logger . Error ( "支付宝回调:写入 third_party_trade_no 失败" ,
zap . String ( "out_trade_no" , outTradeNo ) ,
zap . String ( "trade_no" , notification . TradeNo ) ,
zap . Error ( err ) ,
)
// 不中断存量业务,继续由原幂等业务层处理状态。
}
callback := verifiedPaymentCallback {
P aymentNo : outTradeNo , PaymentMethod : model . PaymentByAlipay ,
Transaction ID: notification . TradeNo , Amount : notifyAmountFen , ConfigID : cfg . ID ,
MerchantIdentity : notification . AppId , Provider : constants . IntegrationProviderAlipay ,
RawPayload : c . Body ( ) , ContentType : c . Get ( "Content-Type" ) ,
}
// 按支付单 order_type 分发业务
switch payment . OrderType {
case model . PaymentOrderTypePackage :
if err := h . orderService . HandlePaymentRecordCallback ( ctx , outTradeNo , model . PaymentByAlipay , notification . TradeNo , payment . Amount ) ; err != nil {
h . logger . Error ( "支付宝回调:推进套餐订单失败" ,
zap . String ( "out_trade_no" , outTradeNo ) ,
zap . String ( "trade_no" , notification . TradeNo ) ,
zap . Error ( err ) ,
)
return errors . Wrap ( errors . CodeInternalError , err , "处理支付宝支付回调失败" )
}
h . logger . Info ( "支付宝回调:套餐订单支付成功" ,
zap . String ( "out_trade_no" , outTradeNo ) ,
zap . String ( "trade_no" , notification . TradeNo ) ,
zap . String ( "order_type" , payment . OrderType ) ,
)
case model . PaymentOrderTypeRecharge :
if h . rechargeOrderService == nil {
h . logger . Error ( "支付宝回调:充值订单服务未配置" ,
zap . String ( "out_trade_no" , outTradeNo ) ,
)
return errors . New ( errors . CodeInternalError , "充值订单服务未配置" )
}
if err := h . rechargeOrderService . HandlePaymentCallback ( ctx , outTradeNo , model . PaymentByAlipay , notification . TradeNo ) ; err != nil {
h . logger . Error ( "支付宝回调:推进充值订单失败" ,
zap . String ( "out_trade_no" , outTradeNo ) ,
zap . String ( "trade_no" , notification . TradeNo ) ,
zap . Error ( err ) ,
)
return errors . Wrap ( errors . CodeInternalError , err , "处理支付宝充值回调失败" )
}
h . logger . Info ( "支付宝回调:充值订单支付成功" ,
zap . String ( "out_trade_no" , outTradeNo ) ,
zap . String ( "trade_no" , notification . TradeNo ) ,
zap . String ( "order_type" , payment . OrderType ) ,
)
case model . PaymentOrderTypeAgentRecharge :
if payment . OrderType == model . PaymentOrderTypeAgentRecharge {
paidAt , parseErr := time . ParseInLocation ( "2006-01-02 15:04:05" , notification . GmtPayment , time . Local )
if parseErr != nil {
h . logger . Error ( "支付宝回调:付款时间格式无效" ,
@@ -468,25 +539,13 @@ func (h *PaymentHandler) AlipayCallback(c *fiber.Ctx) error {
)
return errors . New ( errors . CodeWechatCallbackInvalid , "支付宝付款时间格式错误" )
}
if err := h . confirmAgentRechargePayment ( ctx , verifiedPaymentCallback {
PaymentNo : outTradeNo , PaymentMethod : model . PaymentByAlipay ,
TransactionID : notification . TradeNo , Amount : notifyAmountFen , ConfigID : cfg . ID ,
MerchantIdentity : notification . AppId , PaidAt : paidAt , Provider : constants . IntegrationProviderAlipay ,
RawPayload : c . Body ( ) , ContentType : c . Get ( "Content-Type ") ,
} ) ; err != nil {
h . logger . Error ( "支付宝回调:确认代理充值支付 失败" ,
zap . String ( "out_trade_no" , outTradeNo ) ,
zap . Error ( err ) ,
)
return errors . Wrap ( errors . CodeInternalError , err , "处理支付宝代理充值回调失败" )
}
default :
h . logger . Error ( "支付宝回调:未知支付记录类型" ,
zap . String ( "out_trade_no" , outTradeNo ) ,
zap . String ( "order_type" , payment . OrderType ) ,
)
return errors . New ( errors . CodeInternalError , "未知支付记录类型" )
callback . PaidAt = paidAt
}
if handled , dispatchErr := h . dispatchPaymentRecordCallback ( ctx , callback ) ; dispatchErr != nil {
h . logger . Error ( "支付宝回调:确认支付失败" , zap . String ( "out_trade_no" , outTradeNo ) , zap . Error ( dispatchErr ) )
return errors . Wrap ( errors . CodeInternalError , dispatchErr , "处理支付宝支付回调失败 ")
} else if ! handled {
return errors . New ( errors . CodeInternal Error , "支付记录分发 失败" )
}
return c . SendString ( "success" )
@@ -563,6 +622,12 @@ func (h *PaymentHandler) FuiouPayCallback(c *fiber.Ctx) error {
h . logger . Warn ( "富友回调:非成功结果" ,
zap . String ( "result_code" , notify . ResultCode ) ,
zap . String ( "result_msg" , notify . ResultMsg ) )
if recordErr := h . recordIgnoredPaymentCallback ( ctx , verifiedPaymentCallback {
PaymentNo : notify . MchntOrderNo , TransactionID : notify . TransactionId ,
Provider : constants . IntegrationProviderFuiou , RawPayload : body , ContentType : c . Get ( "Content-Type" ) ,
} , notify . ResultCode ) ; recordErr != nil {
return c . Send ( fuiou . BuildNotifyFailResponse ( "integration log failed" ) )
}
return c . Send ( fuiou . BuildNotifySuccessResponse ( ) )
}
h . logger . Error ( "富友回调:验签或解析失败" ,
@@ -581,9 +646,10 @@ func (h *PaymentHandler) FuiouPayCallback(c *fiber.Ctx) error {
orderNo := notify . MchntOrderNo
// OrderAmt 为字符串格式的分,解析失败则降级为 0
orderAmt , _ := strconv . ParseInt ( notify . OrderAmt , 10 , 64 )
paidAt , _ := time . ParseInLocation ( "20060102150405" , notify . TxnFinTs , time . Local )
if handled , err := h . dispatchPaymentRecordCallback ( ctx , verifiedPaymentCallback {
PaymentNo : orderNo , PaymentMethod : "fuiou" , TransactionID : notify . TransactionId , Amount : orderAmt ,
ConfigID : cfg . ID , MerchantIdentity : cfg . FyMchntCd , Provider : model . ProviderType Fuiou ,
ConfigID : cfg . ID , MerchantIdentity : cfg . FyMchntCd , PaidAt : paidAt , Provider : constants . Integration ProviderFuiou ,
RawPayload : body , ContentType : c . Get ( "Content-Type" ) ,
} ) ; err != nil {
return c . Send ( fuiou . BuildNotifyFailResponse ( err . Error ( ) ) )
@@ -591,27 +657,11 @@ func (h *PaymentHandler) FuiouPayCallback(c *fiber.Ctx) error {
return c . Send ( fuiou . BuildNotifySuccessResponse ( ) )
}
switch {
case strings . HasPrefix ( orderNo , "ORD" ) :
if err := h . orderService . HandlePaymentCallback ( ctx , orderNo , "fuiou" , orderAmt ) ; err != nil {
return c . Send ( fuiou . BuildNotifyFailResponse ( err . Error ( ) ) )
}
case strings . HasPrefix ( orderNo , constants . AssetRechargeOrderPrefix ) :
if h . rechargeOrderService != nil {
if err := h . rechargeOrderService . HandlePaymentCallback ( ctx , orderNo , "fuiou" , notify . TransactionId ) ; err != nil {
return c . Send ( fuiou . BuildNotifyFailResponse ( err . Error ( ) ) )
}
return c . Send ( fuiou . BuildNotifySuccessResponse ( ) )
}
return c . Send ( fuiou . BuildNotifyFailResponse ( "充值订单服务未配置" ) )
case strings . HasPrefix ( orderNo , constants . AgentRechargeOrderPrefix ) :
if h . agentRechargeService != nil {
if err := h . agentRechargeService . HandlePaymentCallback ( ctx , orderNo , model . ProviderTypeFuiou , notify . TransactionId , orderAmt ) ; err != nil {
return c . Send ( fuiou . BuildNotifyFailResponse ( err . Error ( ) ) )
}
}
default :
return c . Send ( fuiou . BuildNotifyFailResponse ( "unknown order prefix" ) )
if err := h . dispatchLegacyPaymentCallback ( ctx , verifiedPaymentCallback {
PaymentNo : orderNo , PaymentMethod : model . ProviderTypeFuiou , TransactionID : notify . TransactionId , Amount : orderAmt ,
Provider : constants . IntegrationProviderFuiou , RawPayload : body , ContentType : c . Get ( "Content-Type" ) ,
} ) ; err != nil {
return c . Send ( fuiou . BuildNotifyFailResponse ( err . Error ( ) ) )
}
return c . Send ( fuiou . BuildNotifySuccessResponse ( ) )