支付,OSS 服务重构完成

This commit is contained in:
GeekMaster
2025-08-24 19:32:45 +08:00
parent 7fb0aad3c7
commit 536b4b8056
57 changed files with 1663 additions and 1358 deletions

View File

@@ -20,109 +20,90 @@ import (
)
type AlipayService struct {
config *types.AlipayConfig
client *alipay.Client
config *types.AlipayConfig
}
var logger = logger2.GetLogger()
func NewAlipayService(appConfig *types.AppConfig) (*AlipayService, error) {
config := appConfig.AlipayConfig
func NewAlipayService(sysConfig *types.SystemConfig) (*AlipayService, error) {
config := sysConfig.Payment.Alipay
if !config.Enabled {
logger.Info("Disabled Alipay service")
return nil, nil
logger.Debug("Disabled Alipay service")
}
service := &AlipayService{config: &config}
if config.Enabled {
err := service.UpdateConfig(&config)
if err != nil {
logger.Errorf("支付宝服务初始化失败: %v", err)
}
}
return service, nil
}
func (s *AlipayService) UpdateConfig(config *types.AlipayConfig) error {
client, err := alipay.NewClient(config.AppId, config.PrivateKey, !config.SandBox)
if err != nil {
return nil, fmt.Errorf("error with initialize alipay service: %v", err)
return fmt.Errorf("error with initialize alipay service: %v", err)
}
return &AlipayService{config: &config, client: client}, nil
s.client = client
s.config = config
if os.Getenv("GEEKAI_DEBUG") == "true" {
logger.Info("Alipay Debug mode is enabled")
client.DebugSwitch = gopay.DebugOn
}
return nil
}
type AlipayParams struct {
OutTradeNo string `json:"out_trade_no"`
Subject string `json:"subject"`
TotalFee string `json:"total_fee"`
ReturnURL string `json:"return_url"`
NotifyURL string `json:"notify_url"`
}
func (s *AlipayService) PayMobile(params AlipayParams) (string, error) {
bm := make(gopay.BodyMap)
bm.Set("subject", params.Subject)
bm.Set("out_trade_no", params.OutTradeNo)
bm.Set("quit_url", params.ReturnURL)
bm.Set("total_amount", params.TotalFee)
bm.Set("product_code", "QUICK_WAP_WAY")
return s.client.SetNotifyUrl(params.NotifyURL).SetReturnUrl(params.ReturnURL).TradeWapPay(context.Background(), bm)
}
func (s *AlipayService) PayPC(params AlipayParams) (string, error) {
func (s *AlipayService) Pay(params PayRequest) (string, error) {
bm := make(gopay.BodyMap)
bm.Set("subject", params.Subject)
bm.Set("out_trade_no", params.OutTradeNo)
bm.Set("total_amount", params.TotalFee)
bm.Set("product_code", "FAST_INSTANT_TRADE_PAY")
return s.client.SetNotifyUrl(params.NotifyURL).SetReturnUrl(params.ReturnURL).TradePagePay(context.Background(), bm)
return s.client.TradeWapPay(context.Background(), bm)
}
func (s *AlipayService) Query(outTradeNo string) (OrderInfo, error) {
bm := make(gopay.BodyMap)
bm.Set("out_trade_no", outTradeNo)
rsp, err := s.client.TradeQuery(context.Background(), bm)
if err != nil {
return OrderInfo{}, fmt.Errorf("error with trade query: %v", err)
}
switch rsp.Response.TradeStatus {
case "TRADE_SUCCESS":
logger.Debugf("支付宝查询订单成功:%+v", rsp.Response)
return OrderInfo{
OutTradeNo: rsp.Response.OutTradeNo,
TradeId: rsp.Response.TradeNo,
Amount: rsp.Response.TotalAmount,
Status: Success,
PayTime: rsp.Response.SendPayDate,
}, nil
case "TRADE_CLOSED":
return OrderInfo{Status: Closed}, nil
default:
return OrderInfo{}, fmt.Errorf("error with trade query: %v", rsp.Response.TradeStatus)
}
}
// TradeVerify 交易验证
func (s *AlipayService) TradeVerify(request *http.Request) NotifyVo {
func (s *AlipayService) TradeVerify(request *http.Request) (OrderInfo, error) {
notifyReq, err := alipay.ParseNotifyToBodyMap(request) // c.Request 是 gin 框架的写法
if err != nil {
return NotifyVo{
Status: Failure,
Message: "error with parse notify request: " + err.Error(),
}
return OrderInfo{}, fmt.Errorf("error with parse notify request: %v", err)
}
_, err = alipay.VerifySignWithCert(s.config.AlipayPublicKey, notifyReq)
if err != nil {
return NotifyVo{
Status: Failure,
Message: "error with verify sign: " + err.Error(),
}
return OrderInfo{}, fmt.Errorf("error with verify sign: %v", err)
}
return s.TradeQuery(request.Form.Get("out_trade_no"))
return s.Query(request.Form.Get("out_trade_no"))
}
func (s *AlipayService) TradeQuery(outTradeNo string) NotifyVo {
bm := make(gopay.BodyMap)
bm.Set("out_trade_no", outTradeNo)
//查询订单
rsp, err := s.client.TradeQuery(context.Background(), bm)
if err != nil {
return NotifyVo{
Status: Failure,
Message: "异步查询验证订单信息发生错误" + outTradeNo + err.Error(),
}
}
if rsp.Response.TradeStatus == "TRADE_SUCCESS" {
return NotifyVo{
Status: Success,
OutTradeNo: rsp.Response.OutTradeNo,
TradeId: rsp.Response.TradeNo,
Amount: rsp.Response.TotalAmount,
Subject: rsp.Response.Subject,
Message: "OK",
}
} else {
return NotifyVo{
Status: Failure,
Message: "异步查询验证订单信息发生错误" + outTradeNo,
}
}
}
func readKey(filename string) (string, error) {
data, err := os.ReadFile(filename)
if err != nil {
return "", err
}
return string(data), nil
}
var _ PayService = (*AlipayService)(nil)

View File

@@ -22,41 +22,30 @@ import (
"time"
)
// GeekPayService Geek 支付服务
type GeekPayService struct {
config *types.GeekPayConfig
// EPayService 支付服务
type EPayService struct {
config *types.EpayConfig
}
func NewJPayService(appConfig *types.AppConfig) *GeekPayService {
return &GeekPayService{
config: &appConfig.GeekPayConfig,
func NewEPayService(sysConfig *types.SystemConfig) *EPayService {
return &EPayService{
config: &sysConfig.Payment.Epay,
}
}
type GeekPayParams struct {
Method string `json:"method"` // 接口类型
Device string `json:"device"` // 设备类型
Type string `json:"type"` // 支付方式
OutTradeNo string `json:"out_trade_no"` // 商户订单号
Name string `json:"name"` // 商品名称
Money string `json:"money"` // 商品金额
ClientIP string `json:"clientip"` //用户IP地址
SubOpenId string `json:"sub_openid"` // 微信用户 openid仅小程序支付需要
SubAppId string `json:"sub_appid"` // 小程序 AppId仅小程序支付需要
NotifyURL string `json:"notify_url"`
ReturnURL string `json:"return_url"`
func (s *EPayService) UpdateConfig(config *types.EpayConfig) {
s.config = config
}
// Pay 支付订单
func (s *GeekPayService) Pay(params GeekPayParams) (*GeekPayResp, error) {
func (s *EPayService) Pay(params PayRequest) (string, error) {
p := map[string]string{
"pid": s.config.AppId,
//"method": params.Method,
"pid": s.config.AppId,
"device": params.Device,
"type": params.Type,
"type": params.PayWay,
"out_trade_no": params.OutTradeNo,
"name": params.Name,
"money": params.Money,
"name": params.Subject,
"money": params.TotalFee,
"clientip": params.ClientIP,
"notify_url": params.NotifyURL,
"return_url": params.ReturnURL,
@@ -64,10 +53,21 @@ func (s *GeekPayService) Pay(params GeekPayParams) (*GeekPayResp, error) {
}
p["sign"] = s.Sign(p)
p["sign_type"] = "MD5"
return s.sendRequest(s.config.ApiURL, p)
resp, err := s.sendRequest(s.config.ApiURL, p)
if err != nil {
return "", err
}
if resp.Code != 1 {
return "", errors.New(resp.Msg)
}
if resp.PayURL != "" {
return resp.PayURL, nil
} else {
return resp.QrCode, nil
}
}
func (s *GeekPayService) Sign(params map[string]string) string {
func (s *EPayService) Sign(params map[string]string) string {
// 按字母顺序排序参数
var keys []string
for k := range params {
@@ -100,7 +100,7 @@ type GeekPayResp struct {
UrlScheme string `json:"urlscheme"` // 小程序跳转支付链接
}
func (s *GeekPayService) sendRequest(endpoint string, params map[string]string) (*GeekPayResp, error) {
func (s *EPayService) sendRequest(endpoint string, params map[string]string) (*GeekPayResp, error) {
form := url.Values{}
for k, v := range params {
form.Add(k, v)
@@ -137,3 +137,61 @@ func (s *GeekPayService) sendRequest(endpoint string, params map[string]string)
}
return &r, nil
}
func (s *EPayService) Query(outTradeNo string) (OrderInfo, error) {
params := url.Values{}
params.Set("act", "order")
params.Set("pid", s.config.AppId)
params.Set("key", s.config.PrivateKey)
params.Set("out_trade_no", outTradeNo)
apiURL := fmt.Sprintf("%s/api.php?%s", s.config.ApiURL, params.Encode())
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
resp, err := client.Get(apiURL)
if err != nil {
return OrderInfo{}, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return OrderInfo{}, err
}
logger.Debugf(string(body))
var result struct {
Code int `json:"code"`
Msg string `json:"msg"`
Status string `json:"status"`
Name string `json:"name"`
Money string `json:"money"`
EndTime string `json:"endtime"`
TradeNo string `json:"trade_no"`
}
if err := json.Unmarshal(body, &result); err != nil {
return OrderInfo{}, errors.New("订单查询响应解析失败")
}
if result.Code != 1 {
return OrderInfo{}, errors.New(result.Msg)
}
logger.Debugf("订单信息:%+v", result)
orderInfo := OrderInfo{
OutTradeNo: outTradeNo,
TradeId: result.TradeNo,
Amount: result.Money,
PayTime: result.EndTime,
}
if result.Status == "1" {
orderInfo.Status = Success
} else {
orderInfo.Status = Failure
}
return orderInfo, nil
}
var _ PayService = (*EPayService)(nil)

View File

@@ -1,171 +0,0 @@
package payment
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// * Copyright 2023 The Geek-AI Authors. All rights reserved.
// * Use of this source code is governed by a Apache-2.0 license
// * that can be found in the LICENSE file.
// * @Author yangjian102621@163.com
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import (
"crypto/md5"
"encoding/hex"
"errors"
"fmt"
"geekai/core/types"
"geekai/utils"
"io"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"time"
)
type HuPiPayService struct {
appId string
appSecret string
apiURL string
}
func NewHuPiPay(config *types.AppConfig) *HuPiPayService {
return &HuPiPayService{
appId: config.HuPiPayConfig.AppId,
appSecret: config.HuPiPayConfig.AppSecret,
apiURL: config.HuPiPayConfig.ApiURL,
}
}
type HuPiPayParams struct {
AppId string `json:"appid"`
Version string `json:"version"`
TradeOrderId string `json:"trade_order_id"`
TotalFee string `json:"total_fee"`
Title string `json:"title"`
NotifyURL string `json:"notify_url"`
ReturnURL string `json:"return_url"`
WapName string `json:"wap_name"`
CallbackURL string `json:"callback_url"`
Time string `json:"time"`
NonceStr string `json:"nonce_str"`
Type string `json:"type"`
WapUrl string `json:"wap_url"`
}
type HuPiPayResp struct {
Openid interface{} `json:"openid"`
UrlQrcode string `json:"url_qrcode"`
URL string `json:"url"`
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg,omitempty"`
}
// Pay 执行支付请求操作
func (s *HuPiPayService) Pay(params HuPiPayParams) (HuPiPayResp, error) {
data := url.Values{}
simple := strconv.FormatInt(time.Now().Unix(), 10)
params.AppId = s.appId
params.Time = simple
params.NonceStr = simple
encode := utils.JsonEncode(params)
m := make(map[string]string)
_ = utils.JsonDecode(encode, &m)
for k, v := range m {
data.Add(k, fmt.Sprintf("%v", v))
}
// 生成签名
data.Add("hash", s.Sign(data))
// 发送支付请求
apiURL := fmt.Sprintf("%s/payment/do.html", s.apiURL)
resp, err := http.PostForm(apiURL, data)
if err != nil {
return HuPiPayResp{}, fmt.Errorf("error with requst api: %v", err)
}
defer resp.Body.Close()
all, err := io.ReadAll(resp.Body)
if err != nil {
return HuPiPayResp{}, fmt.Errorf("error with reading response: %v", err)
}
var res HuPiPayResp
err = utils.JsonDecode(string(all), &res)
if err != nil {
return HuPiPayResp{}, fmt.Errorf("error with decode payment result: %v", err)
}
if res.ErrCode != 0 {
return HuPiPayResp{}, fmt.Errorf("error with generate pay url: %s", res.ErrMsg)
}
return res, nil
}
// Sign 签名方法
func (s *HuPiPayService) Sign(params url.Values) string {
params.Del(`Sign`)
var keys = make([]string, 0, 0)
for key := range params {
if params.Get(key) != `` {
keys = append(keys, key)
}
}
sort.Strings(keys)
var pList = make([]string, 0, 0)
for _, key := range keys {
var value = strings.TrimSpace(params.Get(key))
if len(value) > 0 {
pList = append(pList, key+"="+value)
}
}
var src = strings.Join(pList, "&")
src += s.appSecret
md5bs := md5.Sum([]byte(src))
return hex.EncodeToString(md5bs[:])
}
// Check 校验订单状态
func (s *HuPiPayService) Check(outTradeNo string) error {
data := url.Values{}
data.Add("appid", s.appId)
data.Add("out_trade_order", outTradeNo)
stamp := strconv.FormatInt(time.Now().Unix(), 10)
data.Add("time", stamp)
data.Add("nonce_str", stamp)
data.Add("hash", s.Sign(data))
apiURL := fmt.Sprintf("%s/payment/query.html", s.apiURL)
resp, err := http.PostForm(apiURL, data)
if err != nil {
return fmt.Errorf("error with http reqeust: %v", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("error with reading response: %v", err)
}
var r struct {
ErrCode int `json:"errcode"`
Data struct {
Status string `json:"status"`
OpenOrderId string `json:"open_order_id"`
} `json:"data,omitempty"`
ErrMsg string `json:"errmsg"`
Hash string `json:"hash"`
}
err = utils.JsonDecode(string(body), &r)
if err != nil {
return fmt.Errorf("error with decode response: %v", err)
}
if r.ErrCode == 0 && r.Data.Status == "OD" {
return nil
} else {
logger.Debugf("%+v", r)
return errors.New("order not paid" + r.ErrMsg)
}
}

View File

@@ -0,0 +1,54 @@
package payment
// 支付渠道定义
const PayChannelAL = "alipay" // 支付宝
const PayChannelWX = "wxpay" // 微信支付
const PayChannelEpay = "epay" // 易支付
// 支付方式
const PayWayAL = "alipay"
const PayWayWX = "wxpay"
const (
Success = 0
Failure = 1
Closed = 2
)
type PayRequest struct {
OutTradeNo string // 商户订单号
Subject string // 商品名称
TotalFee string // 商品金额
ReturnURL string // 回调地址
NotifyURL string // 回调地址
// 易支付专有参数
Method string // 接口类型
Device string // 设备类型
PayWay string // 支付方式
ClientIP string //用户IP地址
OpenID string // 用户openid
}
type OrderInfo struct {
Mchid string // 商户号
OutTradeNo string // 商户订单号
TradeId string // 交易号
Amount string // 金额
Status int // 状态 0: 未支付 1: 已支付 2: 已关闭
PayTime string // 完成支付时间
}
func (o OrderInfo) Closed() bool {
return o.Status == Closed
}
func (o OrderInfo) Success() bool {
return o.Status == Success
}
type PayService interface {
Pay(params PayRequest) (string, error) // 生成支付链接
Query(outTradeNo string) (OrderInfo, error) // 查询订单
}

View File

@@ -1,19 +0,0 @@
package payment
type NotifyVo struct {
Status int
OutTradeNo string // 商户订单号
TradeId string // 交易ID
Amount string // 交易金额
Message string
Subject string
}
func (v NotifyVo) Success() bool {
return v.Status == Success
}
const (
Success = 0
Failure = 1
)

View File

@@ -1,141 +0,0 @@
package payment
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// * Copyright 2023 The Geek-AI Authors. All rights reserved.
// * Use of this source code is governed by a Apache-2.0 license
// * that can be found in the LICENSE file.
// * @Author yangjian102621@163.com
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import (
"context"
"fmt"
"geekai/core/types"
"net/http"
"time"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/wechat/v3"
)
type WechatPayService struct {
config *types.WechatPayConfig
client *wechat.ClientV3
}
func NewWechatService(appConfig *types.AppConfig) (*WechatPayService, error) {
config := appConfig.WechatPayConfig
if !config.Enabled {
logger.Info("Disabled WechatPay service")
return nil, nil
}
client, err := wechat.NewClientV3(config.MchId, config.SerialNo, config.ApiV3Key, config.PrivateKey)
if err != nil {
return nil, fmt.Errorf("error with initialize WechatPay service: %v", err)
}
err = client.AutoVerifySign()
if err != nil {
return nil, fmt.Errorf("error with autoVerifySign: %v", err)
}
//client.DebugSwitch = gopay.DebugOn
return &WechatPayService{config: &config, client: client}, nil
}
type WechatPayParams struct {
OutTradeNo string `json:"out_trade_no"`
TotalFee int `json:"total_fee"`
Subject string `json:"subject"`
ClientIP string `json:"client_ip"`
ReturnURL string `json:"return_url"`
NotifyURL string `json:"notify_url"`
}
func (s *WechatPayService) PayUrlNative(params WechatPayParams) (string, error) {
expire := time.Now().Add(10 * time.Minute).Format(time.RFC3339)
// 初始化 BodyMap
bm := make(gopay.BodyMap)
bm.Set("appid", s.config.AppId).
Set("mchid", s.config.MchId).
Set("description", params.Subject).
Set("out_trade_no", params.OutTradeNo).
Set("time_expire", expire).
Set("notify_url", params.NotifyURL).
SetBodyMap("amount", func(bm gopay.BodyMap) {
bm.Set("total", params.TotalFee).
Set("currency", "CNY")
})
wxRsp, err := s.client.V3TransactionNative(context.Background(), bm)
if err != nil {
return "", fmt.Errorf("error with client v3 transaction Native: %v", err)
}
if wxRsp.Code != wechat.Success {
return "", fmt.Errorf("error status with generating pay url: %v", wxRsp.Error)
}
return wxRsp.Response.CodeUrl, nil
}
func (s *WechatPayService) PayUrlH5(params WechatPayParams) (string, error) {
expire := time.Now().Add(10 * time.Minute).Format(time.RFC3339)
// 初始化 BodyMap
bm := make(gopay.BodyMap)
bm.Set("appid", s.config.AppId).
Set("mchid", s.config.MchId).
Set("description", params.Subject).
Set("out_trade_no", params.OutTradeNo).
Set("time_expire", expire).
Set("notify_url", params.NotifyURL).
SetBodyMap("amount", func(bm gopay.BodyMap) {
bm.Set("total", params.TotalFee).
Set("currency", "CNY")
}).
SetBodyMap("scene_info", func(bm gopay.BodyMap) {
bm.Set("payer_client_ip", params.ClientIP).
SetBodyMap("h5_info", func(bm gopay.BodyMap) {
bm.Set("type", "Wap")
})
})
wxRsp, err := s.client.V3TransactionH5(context.Background(), bm)
if err != nil {
return "", fmt.Errorf("error with client v3 transaction H5: %v", err)
}
if wxRsp.Code != wechat.Success {
return "", fmt.Errorf("error with generating pay url: %v", wxRsp.Error)
}
return wxRsp.Response.H5Url, nil
}
type NotifyResponse struct {
Code string `json:"code"`
Message string `xml:"message"`
}
// TradeVerify 交易验证
func (s *WechatPayService) TradeVerify(request *http.Request) NotifyVo {
notifyReq, err := wechat.V3ParseNotify(request)
if err != nil {
return NotifyVo{Status: 1, Message: fmt.Sprintf("error with client v3 parse notify: %v", err)}
}
// TODO: 这里验签程序有 Bug一直报错crypto/rsa: verification error先暂时取消验签
//err = notifyReq.VerifySignByPK(s.client.WxPublicKey())
//if err != nil {
// return fmt.Errorf("error with client v3 verify sign: %v", err)
//}
// 解密支付密文,验证订单信息
result, err := notifyReq.DecryptPayCipherText(s.config.ApiV3Key)
if err != nil {
return NotifyVo{Status: Failure, Message: fmt.Sprintf("error with client v3 decrypt: %v", err)}
}
return NotifyVo{
Status: Success,
OutTradeNo: result.OutTradeNo,
TradeId: result.TransactionId,
Amount: fmt.Sprintf("%.2f", float64(result.Amount.Total)/100),
}
}

View File

@@ -0,0 +1,216 @@
package payment
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// * Copyright 2023 The Geek-AI Authors. All rights reserved.
// * Use of this source code is governed by a Apache-2.0 license
// * that can be found in the LICENSE file.
// * @Author yangjian102621@163.com
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import (
"context"
"fmt"
"geekai/core/types"
"geekai/utils"
"net/http"
"os"
"time"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/wechat/v3"
)
type WxPayService struct {
config *types.WxPayConfig
client *wechat.ClientV3
}
func NewWxpayService(sysConfig *types.SystemConfig) (*WxPayService, error) {
config := sysConfig.Payment.WxPay
if !config.Enabled {
logger.Debug("Disabled WechatPay service")
}
service := &WxPayService{config: &config}
if config.Enabled {
err := service.UpdateConfig(&config)
if err != nil {
logger.Errorf("微信支付服务初始化失败: %v", err)
}
}
return service, nil
}
func (s *WxPayService) UpdateConfig(config *types.WxPayConfig) error {
client, err := wechat.NewClientV3(config.MchId, config.SerialNo, config.ApiV3Key, config.PrivateKey)
if err != nil {
return fmt.Errorf("error with initialize WechatPay service: %v", err)
}
err = client.AutoVerifySign()
if err != nil {
return fmt.Errorf("error with autoVerifySign: %v", err)
}
s.client = client
if os.Getenv("GEEKAI_DEBUG") == "true" {
logger.Info("WechatPay Debug mode is enabled")
client.DebugSwitch = gopay.DebugOn
}
s.config = config
return nil
}
func (s *WxPayService) Pay(params PayRequest) (string, error) {
expire := time.Now().Add(10 * time.Minute).Format(time.RFC3339)
// 初始化 BodyMap
bm := make(gopay.BodyMap)
bm.Set("appid", s.config.AppId).
Set("mchid", s.config.MchId).
Set("description", params.Subject).
Set("out_trade_no", params.OutTradeNo).
Set("time_expire", expire).
Set("notify_url", params.NotifyURL).
SetBodyMap("amount", func(bm gopay.BodyMap) {
bm.Set("total", utils.IntValue(params.TotalFee, 0)).
Set("currency", "CNY")
})
if params.Device == "mobile" {
bm.SetBodyMap("scene_info", func(bm gopay.BodyMap) {
bm.Set("payer_client_ip", params.ClientIP)
}).SetBodyMap("payer", func(bm gopay.BodyMap) {
bm.Set("openid", params.OpenID)
})
wxRsp, err := s.client.V3TransactionJsapi(context.Background(), bm)
if err != nil {
return "", fmt.Errorf("error with client v3 transaction Jsapi: %v", err)
}
if wxRsp.Code != wechat.Success {
return "", fmt.Errorf("error status with generating pay url: %v", wxRsp.Error)
}
return wxRsp.Response.PrepayId, nil
} else if params.Device == "pc" {
wxRsp, err := s.client.V3TransactionNative(context.Background(), bm)
if err != nil {
return "", fmt.Errorf("error with client v3 transaction Native: %v", err)
}
if wxRsp.Code != wechat.Success {
return "", fmt.Errorf("error status with generating pay url: %v", wxRsp.Error)
}
return wxRsp.Response.CodeUrl, nil
}
return "", nil
}
func (s *WxPayService) Query(outTradeNo string) (OrderInfo, error) {
wxRsp, err := s.client.V3TransactionQueryOrder(context.Background(), wechat.OutTradeNo, outTradeNo)
if err != nil {
return OrderInfo{}, fmt.Errorf("error with client v3 transaction query: %v", err)
}
if wxRsp.Code != wechat.Success {
return OrderInfo{}, fmt.Errorf("error status with querying order: %v", wxRsp.Error)
}
if wxRsp.Response.TradeState == "CLOSED" {
return OrderInfo{Status: Closed}, nil
}
orderInfo := OrderInfo{
OutTradeNo: wxRsp.Response.OutTradeNo,
TradeId: wxRsp.Response.TransactionId,
Amount: fmt.Sprintf("%d", wxRsp.Response.Amount.Total/100),
PayTime: wxRsp.Response.SuccessTime,
}
if wxRsp.Response.TradeState == "SUCCESS" {
orderInfo.Status = Success
} else {
orderInfo.Status = Failure
}
return orderInfo, nil
}
// TradeVerify 交易验证
func (s *WxPayService) TradeVerify(request *http.Request) (OrderInfo, error) {
notifyReq, err := wechat.V3ParseNotify(request)
if err != nil {
return OrderInfo{}, fmt.Errorf("error with client v3 parse notify: %v", err)
}
// 解密支付密文,验证订单信息
result, err := notifyReq.DecryptPayCipherText(s.config.ApiV3Key)
if err != nil {
return OrderInfo{}, fmt.Errorf("error with client v3 decrypt: %v", err)
}
return OrderInfo{
Status: Success,
OutTradeNo: result.OutTradeNo,
TradeId: result.TransactionId,
Amount: fmt.Sprintf("%.2f", float64(result.Amount.Total)/100),
PayTime: result.SuccessTime,
}, nil
}
// func (s *WechatPayService) PayUrlNative(params WechatPayParams) (string, error) {
// expire := time.Now().Add(10 * time.Minute).Format(time.RFC3339)
// // 初始化 BodyMap
// bm := make(gopay.BodyMap)
// bm.Set("appid", s.config.AppId).
// Set("mchid", s.config.MchId).
// Set("description", params.Subject).
// Set("out_trade_no", params.OutTradeNo).
// Set("time_expire", expire).
// Set("notify_url", params.NotifyURL).
// SetBodyMap("amount", func(bm gopay.BodyMap) {
// bm.Set("total", params.TotalFee).
// Set("currency", "CNY")
// })
// wxRsp, err := s.client.V3TransactionNative(context.Background(), bm)
// if err != nil {
// return "", fmt.Errorf("error with client v3 transaction Native: %v", err)
// }
// if wxRsp.Code != wechat.Success {
// return "", fmt.Errorf("error status with generating pay url: %v", wxRsp.Error)
// }
// return wxRsp.Response.CodeUrl, nil
// }
// func (s *WechatPayService) PayUrlH5(params WechatPayParams) (string, error) {
// expire := time.Now().Add(10 * time.Minute).Format(time.RFC3339)
// // 初始化 BodyMap
// bm := make(gopay.BodyMap)
// bm.Set("appid", s.config.AppId).
// Set("mchid", s.config.MchId).
// Set("description", params.Subject).
// Set("out_trade_no", params.OutTradeNo).
// Set("time_expire", expire).
// Set("notify_url", params.NotifyURL).
// SetBodyMap("amount", func(bm gopay.BodyMap) {
// bm.Set("total", params.TotalFee).
// Set("currency", "CNY")
// }).
// SetBodyMap("scene_info", func(bm gopay.BodyMap) {
// bm.Set("payer_client_ip", params.ClientIP).
// SetBodyMap("h5_info", func(bm gopay.BodyMap) {
// bm.Set("type", "Wap")
// })
// })
// wxRsp, err := s.client.V3TransactionH5(context.Background(), bm)
// if err != nil {
// return "", fmt.Errorf("error with client v3 transaction H5: %v", err)
// }
// if wxRsp.Code != wechat.Success {
// return "", fmt.Errorf("error with generating pay url: %v", wxRsp.Error)
// }
// return wxRsp.Response.H5Url, nil
// }
// type NotifyResponse struct {
// Code string `json:"code"`
// Message string `xml:"message"`
// }
var _ PayService = (*WxPayService)(nil)