mirror of
https://github.com/yangjian102621/geekai.git
synced 2026-04-29 14:34:27 +08:00
支付,OSS 服务重构完成
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
54
api/service/payment/pay_service.go
Normal file
54
api/service/payment/pay_service.go
Normal 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) // 查询订单
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
216
api/service/payment/wxpay_service.go
Normal file
216
api/service/payment/wxpay_service.go
Normal 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)
|
||||
Reference in New Issue
Block a user