package channel import ( "context" "fmt" "net/http" "github.com/smartwalle/alipay/v3" "github.com/wangjia/pay/internal/model" ) type alipayChannel struct { client *alipay.Client } func newAlipay(m *model.Merchant) (Channel, error) { if m.AppID == "" || m.AppPrivateKey == "" || m.AlipayPublicKey == "" { return nil, fmt.Errorf("商户 %s 的支付宝凭证不完整(app_id/app_private_key/alipay_public_key)", m.Code) } client, err := alipay.New(m.AppID, m.AppPrivateKey, m.Production) if err != nil { return nil, fmt.Errorf("初始化支付宝客户端失败: %w", err) } // 公钥模式(沙箱常用)。若改用证书模式,这里换成 LoadAppCertPublicKey 等。 if err := client.LoadAliPayPublicKey(m.AlipayPublicKey); err != nil { return nil, fmt.Errorf("加载支付宝公钥失败: %w", err) } return &alipayChannel{client: client}, nil } func (a *alipayChannel) Name() string { return "alipay" } func (a *alipayChannel) PagePay(_ context.Context, req CreateReq) (string, error) { var p = alipay.TradePagePay{} p.OutTradeNo = req.OutTradeNo p.Subject = req.Subject p.TotalAmount = req.Amount p.ProductCode = "FAST_INSTANT_TRADE_PAY" p.NotifyURL = req.NotifyURL p.ReturnURL = req.ReturnURL u, err := a.client.TradePagePay(p) if err != nil { return "", fmt.Errorf("支付宝下单失败: %w", err) } return u.String(), nil } func (a *alipayChannel) PreCreate(ctx context.Context, req CreateReq) (string, error) { var p = alipay.TradePreCreate{} p.OutTradeNo = req.OutTradeNo p.Subject = req.Subject p.TotalAmount = req.Amount p.NotifyURL = req.NotifyURL rsp, err := a.client.TradePreCreate(ctx, p) if err != nil { return "", fmt.Errorf("支付宝预下单调用失败: %w", err) } if rsp.IsFailure() { return "", fmt.Errorf("支付宝预下单失败: %s / %s", rsp.Msg, rsp.SubMsg) } return rsp.QRCode, nil } func (a *alipayChannel) VerifyNotify(ctx context.Context, r *http.Request) (*NotifyResult, error) { if err := r.ParseForm(); err != nil { return nil, fmt.Errorf("解析回调表单失败: %w", err) } // DecodeNotification 内部用已加载的支付宝公钥验签,验签不过会返回错误。 noti, err := a.client.DecodeNotification(ctx, r.Form) if err != nil { return nil, fmt.Errorf("回调验签失败: %w", err) } paid := noti.TradeStatus == alipay.TradeStatusSuccess || noti.TradeStatus == alipay.TradeStatusFinished return &NotifyResult{ OutTradeNo: noti.OutTradeNo, TradeNo: noti.TradeNo, Amount: noti.TotalAmount, BuyerLogonID: noti.BuyerLogonId, Paid: paid, Raw: r.Form.Encode(), }, nil } func (a *alipayChannel) Query(ctx context.Context, outTradeNo string) (*QueryResult, error) { var p = alipay.TradeQuery{OutTradeNo: outTradeNo} rsp, err := a.client.TradeQuery(ctx, p) if err != nil { return nil, fmt.Errorf("支付宝查单调用失败: %w", err) } if rsp.IsFailure() { // 交易不存在(TRADE_NOT_EXIST)等:视为未找到,不报错,交由上层决定。 return &QueryResult{Found: false}, nil } paid := rsp.TradeStatus == alipay.TradeStatusSuccess || rsp.TradeStatus == alipay.TradeStatusFinished return &QueryResult{ Found: true, OutTradeNo: rsp.OutTradeNo, TradeNo: rsp.TradeNo, Amount: rsp.TotalAmount, Paid: paid, }, nil }