This commit is contained in:
duancheng 2024-11-05 17:34:58 +08:00
parent 76198aac02
commit 75cc72ffc4
14 changed files with 323 additions and 25 deletions

View File

@ -17,4 +17,13 @@ database:
link: "pgsql:root:root@tcp(101.200.127.15:5431)/yuledui" link: "pgsql:root:root@tcp(101.200.127.15:5431)/yuledui"
debug: true debug: true
weixin: weixin:
appId:"aaabbcc" appId: "wx5078c9d7b9eca030" #"wx231bd3f08954da9e" #微信小程序appid
ysepay:
notifyUrl: "http://xxxxxxxxxxxxx/ysePay/notifyWxPay"
CERTID: "826452972730006"
MQTT:
topic: "pay"
ip: "101.200.88.58"
port: 1883
user: "yuledui"
passwd: "yuledui"

View File

@ -11,6 +11,7 @@ type Pay struct {
payService service.Pay //服务类 payService service.Pay //服务类
} }
// 微信小程序,支付接口
func (t *Pay) RouterGroup(group *ghttp.RouterGroup) { func (t *Pay) RouterGroup(group *ghttp.RouterGroup) {
group.POST("/payQrCode", t.payService.PayQrCode) group.POST("/payQrCode", t.payService.PayQrCode)
} }

17
controller/ysePay.go Normal file
View File

@ -0,0 +1,17 @@
package controller
import (
"yuleduiPay/service"
"github.com/gogf/gf/v2/net/ghttp"
)
// 银盛路由控制类
type YsePay struct {
ysePayService service.YsePay //服务类
}
// 银盛异步通知余乐兑支付结果接口
func (t *YsePay) RouterGroup(group *ghttp.RouterGroup) {
group.POST("/notifyWxPay", t.ysePayService.NotifyWxPay)
}

2
go.mod
View File

@ -10,6 +10,7 @@ require (
github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28 // indirect github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/denisenkom/go-mssqldb v0.12.3 // indirect github.com/denisenkom/go-mssqldb v0.12.3 // indirect
github.com/eclipse/paho.mqtt.golang v1.5.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect
github.com/fatih/color v1.18.0 // indirect github.com/fatih/color v1.18.0 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect
@ -39,6 +40,7 @@ require (
go.opentelemetry.io/otel/trace v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect
golang.org/x/crypto v0.28.0 // indirect golang.org/x/crypto v0.28.0 // indirect
golang.org/x/net v0.30.0 // indirect golang.org/x/net v0.30.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.26.0 // indirect golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect golang.org/x/text v0.19.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect

4
go.sum
View File

@ -19,6 +19,8 @@ github.com/denisenkom/go-mssqldb v0.0.0-20200206145737-bbfc9a55622e/go.mod h1:xb
github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw= github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw=
github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo= github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/eclipse/paho.mqtt.golang v1.5.0 h1:EH+bUVJNgttidWFkLLVKaQPGmkTUfQQqjOsyvMGvD6o=
github.com/eclipse/paho.mqtt.golang v1.5.0/go.mod h1:du/2qNQVqJf/Sqs4MEL77kR8QTqANF7XU7Fk0aOTAgk=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
@ -152,6 +154,8 @@ golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@ -2,3 +2,11 @@
2024-11-05 08:52:00.651 {d8d007fb40ec04185998a93c02952ba0} main.go:31: 初始化成功 2024-11-05 08:52:00.651 {d8d007fb40ec04185998a93c02952ba0} main.go:31: 初始化成功
2024-11-05 08:52:29.385 main.go:18: 初始化成功 2024-11-05 08:52:29.385 main.go:18: 初始化成功
2024-11-05 09:42:18.488 main.go:18: 初始化成功 2024-11-05 09:42:18.488 main.go:18: 初始化成功
2024-11-05 15:21:03.977 main.go:18: 初始化成功
2024-11-05 16:09:55.574 main.go:18: 初始化成功
2024-11-05 16:55:56.689 main.go:20: 初始化成功
2024-11-05 17:01:39.141 main.go:20: 初始化成功
2024-11-05 17:26:42.774 mqtt.go:49: no servers defined to connect to
2024-11-05 17:26:42.774 main.go:26: 初始化成功
2024-11-05 17:34:28.482 mqtt.go:49: no servers defined to connect to
2024-11-05 17:34:28.482 main.go:26: 初始化成功

10
main.go
View File

@ -3,6 +3,7 @@ package main
import ( import (
"yuleduiPay/controller" "yuleduiPay/controller"
"yuleduiPay/middle" "yuleduiPay/middle"
"yuleduiPay/service"
"github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/frame/g"
@ -10,11 +11,18 @@ import (
) )
func main() { func main() {
s := g.Server() s := g.Server()
s.Use(middle.MiddlewareCORS, middle.MiddlewareHandlerResponse) s.Use(middle.MiddlewareCORS, middle.MiddlewareHandlerResponse)
payRouter := controller.Pay{} payRouter := controller.Pay{}
s.Group("/wxApi", payRouter.RouterGroup) s.Group("/wxApi", payRouter.RouterGroup)
yesPayRouter := controller.YsePay{}
s.Group("/ysePay", yesPayRouter.RouterGroup)
mqtt := service.MQTT{}
err := mqtt.InitMQTT()
if err != nil {
return
}
g.Log().Line().Print(nil, "初始化成功") g.Log().Line().Print(nil, "初始化成功")
s.Run() s.Run()

View File

View File

@ -13,3 +13,13 @@ func (t *PayOrder) CreatePayOrder(payOrder *po.PayOrder) error {
_, err := g.Model("pay_order").Data(payOrder).Insert() _, err := g.Model("pay_order").Data(payOrder).Insert()
return err return err
} }
func (t *PayOrder) UpdatePayOrderByOrderId(updateMap g.Map, orderId string) error {
_, err := g.Model("pay_order").Data(updateMap).Where("orderId = ?", orderId).Update()
return err
}
func (t *PayOrder) GetPayOrderByOrderId(orderId string) (*po.PayOrder, error) {
payOrder := &po.PayOrder{}
err := g.Model("pay_order").Where("orderId = ?", orderId).Scan(payOrder)
return payOrder, err
}

77
service/mqtt.go Normal file
View File

@ -0,0 +1,77 @@
package service
import (
"fmt"
"time"
mqtt "github.com/eclipse/paho.mqtt.golang"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/guid"
)
type MQTT struct {
mc mqtt.Client
}
func (t *MQTT) InitMQTT() error {
ip, err := g.Cfg().Get(nil, "MQTT.ip")
if err != nil {
g.Log().Line().Print(nil, err)
return err
}
port, err := g.Cfg().Get(nil, "MQTT.port")
if err != nil {
g.Log().Line().Print(nil, err)
return err
}
user, err := g.Cfg().Get(nil, "MQTT.user")
if err != nil {
g.Log().Line().Print(nil, err)
return err
}
passwd, err := g.Cfg().Get(nil, "MQTT.passwd")
if err != nil {
g.Log().Line().Print(nil, err)
return err
}
opts := mqtt.NewClientOptions()
opts.AddBroker(fmt.Sprintf("mqtt://%s:%d", ip, port))
opts.SetKeepAlive(time.Hour * 24)
opts.SetPingTimeout(time.Second * 30)
opts.SetClientID(guid.S())
opts.SetUsername(user.String())
opts.SetPassword(passwd.String())
//opts.SetDefaultPublishHandler(messagePubHandler)
//opts.OnConnect = connectHandler
opts.OnConnectionLost = t.connectLostHandler
t.mc = mqtt.NewClient(opts)
if token := t.mc.Connect(); token.Wait() && token.Error() != nil {
g.Log().Line().Print(nil, token.Error())
return err
}
return nil
}
// MQTT 推送订单结果消息
//func (t *MQTT) Publish(client mqtt.Client,msg *vo.MQTTOrder) string {
// 返回支付结果消息
/*
func publishResult(client mqtt.Client, msg *vo.MQTTOrder) string {
//command.Msg_Id = guid.S()
command.SendTime = time.Now()
j, err := json.Marshal(msg)
if err == nil {
token := client.Publish(command.Topic, 0, false, string(j))
token.Wait()
return ""
} else {
return err.Error()
}
}
*/
func (t *MQTT) connectLostHandler(client mqtt.Client, err error) {
token := t.mc.Connect()
g.Log().Line().Print(nil, "MQTT 重新连接成功")
token.Wait()
}

View File

@ -1,8 +1,14 @@
package service package service
import ( import (
"bytes"
"encoding/json"
"errors" "errors"
"fmt"
"io"
"net/http"
"strconv" "strconv"
"time"
"yuleduiPay/repo" "yuleduiPay/repo"
"yuleduiPay/service/po" "yuleduiPay/service/po"
"yuleduiPay/service/vo" "yuleduiPay/service/vo"
@ -18,7 +24,11 @@ type Pay struct {
shopRepo repo.Shop shopRepo repo.Shop
} }
var YSEPAYURL = "https://qrcode.ysepay.com/gateway.do" //银盛的微信小程序接口地址
// 微信小程序,扫码支付
func (t *Pay) PayQrCode(r *ghttp.Request) { func (t *Pay) PayQrCode(r *ghttp.Request) {
g.Log().Line().Print(nil, r.Request) //todo
req := vo.PayQrCodeReq{} req := vo.PayQrCodeReq{}
err := r.Parse(&req) err := r.Parse(&req)
if err != nil { if err != nil {
@ -61,32 +71,100 @@ func (t *Pay) PayQrCode(r *ghttp.Request) {
r.SetError(err) r.SetError(err)
return return
} }
//调用银盛的微信小程序接口(银盛接口)
mMap := g.Map{} jsonBytes, err := t.ysePayRequestJson(shop, &payOrder) //组织银盛请求json
mMap["orderId"] = payOrder.OpenId //订单编号
mMap["shopdate"] = payOrder.Created.Format("20060102") //商户系统的交易发生日期格式
mMap["subject"] = "余乐兑小程序订单" //订单备注
mMap["total_amount"] = payOrder.Amount //该笔订单的资金总额????????
mMap["currency"] = "CNY" //默认人民币 //订单备注
mMap["seller_id"] = shop.AccountNumber //商户ID
mMap["seller_name"] = shop.ShopName //店铺名称
mMap["timeout_express"] = "1h" //设置未付款交易的超时时间,一个小时
mMap["sub_openid"] = payOrder.OpenId //微信OpenId
mMap["is_minipg"] = "1" //微信小程序支付:1
appId, err := g.Cfg().Get(nil, "weixin.appId")
if err != nil { if err != nil {
r.SetError(err) r.SetError(err)
return return
} }
mMap["appid"] = appId //java 项目处理sign字段
jsonResp, err := t.ysePayPost(jsonBytes) //Post请求银盛接口
err = errors.New("cuowu!!!!!") if err != nil {
r.SetError(err) r.SetError(err)
return return
/* }
resp := vo.PayQrCodeResp{} yseResp, err := t.yseRespHandler(jsonResp) //解析银盛应答报文
resp.Code = 0 if err != nil {
resp.Message = "success" r.SetError(err)
r.Response.WriteJson(resp) return
*/ }
if yseResp.OutTradeNo != payOrder.OpenId {
errStr := fmt.Sprintf("银盛返回订单号错误,银盛订单号:%s,余乐兑订单号:%s", yseResp.OutTradeNo, payOrder.OpenId)
r.SetError(errors.New(errStr))
return
}
//更新支付订单表字段
updates := g.Map{"ysePayStatus": yseResp.TradeStatus, "updated": time.Now()}
err = t.payOrderRepo.UpdatePayOrderByOrderId(updates, payOrder.OpenId)
if err != nil {
r.SetError(err)
}
return
}
func (t *Pay) yseRespHandler(jsonResp []byte) (*vo.WeixinPayResp, error) {
ysePayResp := &vo.PayResp{}
err := json.Unmarshal(jsonResp, ysePayResp)
if err != nil {
return nil, err
}
yseWeiXinPayResp := &vo.WeixinPayResp{}
err = json.Unmarshal([]byte(ysePayResp.YsepayOnlineWeixinPayResponse), yseWeiXinPayResp)
if yseWeiXinPayResp.Code != "10000" {
return nil, errors.New("银盛应答返回错误:" + yseWeiXinPayResp.Msg)
}
return yseWeiXinPayResp, err
}
func (t *Pay) ysePayPost(jsonBytes []byte) ([]byte, error) {
resp, err := http.Post(YSEPAYURL, "application/json", bytes.NewReader(jsonBytes))
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
func (t *Pay) ysePayRequestJson(shop *po.Shop, payOrder *po.PayOrder) ([]byte, error) {
jsonMap := g.Map{}
//公共请求参数
jsonMap["method"] = "ysepay.online.weixin.pay" //接口名称,固定值
certId, err := g.Cfg().Get(nil, "ysepay.CERTID")
if err != nil {
return nil, err
}
jsonMap["partner_id"] = certId //在银盛支付开设的服务商商户号
jsonMap["timestamp"] = time.Now().Format("2006-01-02 15:04:05") //发送请求的时间
jsonMap["charset"] = "UTF-8" //商户网站使用的编码格式
jsonMap["sign_type"] = "SM" //报文签名算法
//sign 需要java项目处理 todo
notifyUrl, err := g.Cfg().Get(nil, "ysepay.notifyUrl")
if err != nil {
return nil, err
}
jsonMap["notify_url"] = notifyUrl //交易成功异步通知到商户的后台地址
jsonMap["version"] = "3.0" //接口版本
//业务请求参数
businessMap := g.Map{}
businessMap["orderId"] = payOrder.OpenId //订单编号
businessMap["shopdate"] = payOrder.Created.Format("20060102") //商户系统的交易发生日期格式
businessMap["subject"] = "余乐兑小程序订单" //订单备注
businessMap["total_amount"] = payOrder.Price //该笔订单的资金总额
businessMap["currency"] = "CNY" //默认人民币 //订单备注
businessMap["seller_id"] = shop.AccountNumber //商户ID
businessMap["seller_name"] = shop.ShopName //店铺名称
businessMap["timeout_express"] = "1h" //设置未付款交易的超时时间,一个小时
businessMap["sub_openid"] = payOrder.OpenId //微信OpenId
businessMap["is_minipg"] = "1" //微信小程序支付:1
appId, err := g.Cfg().Get(nil, "weixin.appId")
if err != nil {
return nil, err
}
businessMap["appid"] = appId //微信小程序APPID
b, err := json.Marshal(businessMap)
if err != nil {
return nil, err
}
jsonMap["biz_content"] = string(b)
return json.Marshal(jsonMap)
} }

12
service/vo/mqtt.go Normal file
View File

@ -0,0 +1,12 @@
package vo
type MQTTOrder struct {
OrderId string `json:"orderId"` //订单号
OrderEndTime int64 `json:"orderEndTime"` //支付完成时间unix时间戳
Price string `json:"price"` //订单原价小数点后6位
Amount string `json:"amount"` //支付金额小数点后6位
Bean string `json:"bean"` //抵扣金豆
ShopId int64 `json:"shopId"` //店铺ID
OpenId string `json:"openId"` //微信用户openId
Mobile string `json:"mobile"` //付款用户手机号
}

31
service/vo/ysePay.go Normal file
View File

@ -0,0 +1,31 @@
package vo
type PayResp struct {
Sign string `json:"sign"` //签名字符串 Base64编码
YsepayOnlineWeixinPayResponse string `json:"ysepay_online_weixin_pay_response"` //业务响应参数的集合,最大长度不限
}
type WeixinPayResp struct {
Code string `json:"code"` //响应代码
Msg string `json:"msg"` //响应代码描述
OutTradeNo string `json:"out_trade_no"` //商户系统生成的订单号
TradeNo string `json:"trade_no"` //银盛支付交易流水号
TradeStatus string `json:"trade_status"` //交易状态,成功状态的值: TRADE_SUCCESS参考附录8.1
TotalAmount int `json:"total_amount"` //该笔订单的资金总额
Currency string `json:"currency"` //交易币种 默认CNY
ExtraCommonParam string `json:"extra_common_param"` //公用回传参数 商户自定义数据域,原样返回
JsapiPayInfo string `json:"jsapi_pay_info"` //Json格式字符串作用于原生态的js支付时的参数
IsDiscount string `json:"is_discount"` //是否参与优惠Y表示参与N表示不参与
TotalDisCount float64 `json:"total_discount"` //参考总优惠金额 10000
ChannelSendSN string `json:"channel_send_sn"` //发往渠道流水号
}
// 银盛异步通知
type NotifyPayReq struct {
SignType string `json:"sign_type"` //签名类型
Sign string `json:"sign"` //签名字符串
NotifyType string `json:"notify_type"` //通知类型
NotifyTime string `json:"notify_time"` //发送请求的时间
OutTradeNo string `json:"out_trade_no"` //商户生成的订单号
TradeStatus string `json:"trade_status"` //交易目前所处的状态 成功状态的值: TRADE_SUCCESS
}

41
service/ysepay.go Normal file
View File

@ -0,0 +1,41 @@
package service
import (
"time"
"yuleduiPay/repo"
"yuleduiPay/service/vo"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
type YsePay struct {
payOrderRepo repo.PayOrder
shopRepo repo.Shop
}
// 银盛 异步通知余乐兑
func (t *YsePay) NotifyWxPay(r *ghttp.Request) {
//r.Request
req := vo.NotifyPayReq{}
err := r.Parse(&req)
if err != nil {
r.SetError(err)
return
}
/*
payOrder,err := t.payOrderRepo.GetPayOrderByOrderId(req.OutTradeNo) //获取订单
if err != nil {
r.SetError(err)
return
}
*/
updates := g.Map{"ysePayStatus": req.TradeStatus, "updated": time.Now()}
err = t.payOrderRepo.UpdatePayOrderByOrderId(updates, req.OutTradeNo)
if err != nil {
r.SetError(err)
return
}
//将支付结果写入MQTT中
return
}