// Package channel 抽象在线支付渠道(ADR-0001)。 // 到账确认以主动查单为主(ADR-0002):每个渠道必须实现 QueryOrder, // 回调仅作为可选加速器的挂点。 package channel import ( "context" "errors" "sync" "go.uber.org/zap" "wails-app/internal/global" ) // 渠道代码。渠道代码同时是出口自助支付的支付方式代码(wechat/alipay), // mock 仅用于联调,结算时归入 wechat 方式。 const ( ChannelWechat = "wechat" ChannelAlipay = "alipay" ChannelMock = "mock" ) type CreateOrderRequest struct { OrderNo string // 商户订单号(exit_order.order_no) Amount float64 // 金额(元) Currency string // CNY 等 Description string // 订单标题,如 "停车费-粤A12345" } type CreateOrderResult struct { CodeURL string // 二维码内容,出口屏渲染为订单二维码 } type QueryStatus string const ( QueryPaid QueryStatus = "paid" QueryPending QueryStatus = "pending" QueryClosed QueryStatus = "closed" // 已关闭/过期,订单应转 expired ) type QueryOrderResult struct { Status QueryStatus TransactionID string // 渠道流水号 } // PaymentChannel 在线支付渠道接口。查单是一等能力。 type PaymentChannel interface { Code() string CreateOrder(ctx context.Context, req CreateOrderRequest) (CreateOrderResult, error) QueryOrder(ctx context.Context, orderNo string) (QueryOrderResult, error) } var ( mu sync.RWMutex registry = map[string]PaymentChannel{} ) // Register 注册渠道实现,重复注册覆盖(便于测试)。 func Register(ch PaymentChannel) { mu.Lock() defer mu.Unlock() registry[ch.Code()] = ch } // Get 按代码取渠道;未注册返回错误。 func Get(code string) (PaymentChannel, error) { mu.RLock() defer mu.RUnlock() ch, ok := registry[code] if !ok { return nil, errors.New("支付渠道未注册: " + code) } return ch, nil } // DefaultChannel 出口自助支付使用的默认渠道: // 微信凭据齐全时用 wechat,否则回落 mock(联调)。可用 exit-kiosk.channel 显式覆盖。 func DefaultChannel() string { cfg := global.GVA_CONFIG.ExitKiosk.Channel if cfg != "" { return cfg } if global.GVA_CONFIG.WechatPay.Enabled { return ChannelWechat } return ChannelMock } // SetupChannels 注册内置渠道实现,在路由初始化前调用一次。 func SetupChannels() { Register(NewMockChannel()) if wc, err := NewWechatChannelFromConfig(global.GVA_CONFIG.WechatPay); err == nil { Register(wc) } else if global.GVA_LOG != nil { global.GVA_LOG.Warn("微信支付渠道未启用", zap.Error(err)) } }