diff --git a/config.yaml b/config.yaml index 4fae3f9..176bbf8 100644 --- a/config.yaml +++ b/config.yaml @@ -17,4 +17,13 @@ database: link: "pgsql:root:root@tcp(101.200.127.15:5431)/yuledui" debug: true weixin: - appId:"aaabbcc" \ No newline at end of file + 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" \ No newline at end of file diff --git a/controller/pay.go b/controller/pay.go index 2482b18..4d38ddf 100644 --- a/controller/pay.go +++ b/controller/pay.go @@ -11,6 +11,7 @@ type Pay struct { payService service.Pay //服务类 } +// 微信小程序,支付接口 func (t *Pay) RouterGroup(group *ghttp.RouterGroup) { group.POST("/payQrCode", t.payService.PayQrCode) } diff --git a/controller/ysePay.go b/controller/ysePay.go new file mode 100644 index 0000000..934464d --- /dev/null +++ b/controller/ysePay.go @@ -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) +} diff --git a/go.mod b/go.mod index 01b6ad8..8d61ad4 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28 // indirect github.com/clbanning/mxj/v2 v2.7.0 // 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/fatih/color v1.18.0 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect @@ -39,6 +40,7 @@ require ( go.opentelemetry.io/otel/trace v1.31.0 // indirect golang.org/x/crypto v0.28.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/text v0.19.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 8b9f8e7..e6faea2 100644 --- a/go.sum +++ b/go.sum @@ -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/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo= 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/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= 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.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= 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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/log/2024-11-05.log b/log/2024-11-05.log index 3f059d4..031e36d 100644 --- a/log/2024-11-05.log +++ b/log/2024-11-05.log @@ -2,3 +2,11 @@ 2024-11-05 08:52:00.651 {d8d007fb40ec04185998a93c02952ba0} main.go:31: 初始化成功 2024-11-05 08:52:29.385 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: 初始化成功 diff --git a/main.go b/main.go index 0299339..8d867a5 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "yuleduiPay/controller" "yuleduiPay/middle" + "yuleduiPay/service" "github.com/gogf/gf/v2/frame/g" @@ -10,11 +11,18 @@ import ( ) func main() { + s := g.Server() s.Use(middle.MiddlewareCORS, middle.MiddlewareHandlerResponse) payRouter := controller.Pay{} 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, "初始化成功") s.Run() diff --git a/README b/readme.md similarity index 100% rename from README rename to readme.md diff --git a/repo/pay_order.go b/repo/pay_order.go index 605a0e6..8a386f2 100644 --- a/repo/pay_order.go +++ b/repo/pay_order.go @@ -13,3 +13,13 @@ func (t *PayOrder) CreatePayOrder(payOrder *po.PayOrder) error { _, err := g.Model("pay_order").Data(payOrder).Insert() 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 +} diff --git a/service/mqtt.go b/service/mqtt.go new file mode 100644 index 0000000..80f95a9 --- /dev/null +++ b/service/mqtt.go @@ -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() +} diff --git a/service/pay.go b/service/pay.go index b4837d3..a7d4bc4 100644 --- a/service/pay.go +++ b/service/pay.go @@ -1,8 +1,14 @@ package service import ( + "bytes" + "encoding/json" "errors" + "fmt" + "io" + "net/http" "strconv" + "time" "yuleduiPay/repo" "yuleduiPay/service/po" "yuleduiPay/service/vo" @@ -18,7 +24,11 @@ type Pay struct { shopRepo repo.Shop } +var YSEPAYURL = "https://qrcode.ysepay.com/gateway.do" //银盛的微信小程序接口地址 + +// 微信小程序,扫码支付 func (t *Pay) PayQrCode(r *ghttp.Request) { + g.Log().Line().Print(nil, r.Request) //todo req := vo.PayQrCodeReq{} err := r.Parse(&req) if err != nil { @@ -61,32 +71,100 @@ func (t *Pay) PayQrCode(r *ghttp.Request) { r.SetError(err) return } - //调用银盛的微信小程序接口(银盛接口) - mMap := g.Map{} - 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") + + jsonBytes, err := t.ysePayRequestJson(shop, &payOrder) //组织银盛请求json if err != nil { r.SetError(err) return } - mMap["appid"] = appId - - err = errors.New("cuowu!!!!!") - r.SetError(err) + //java 项目处理sign字段 + jsonResp, err := t.ysePayPost(jsonBytes) //Post请求银盛接口 + if err != nil { + r.SetError(err) + return + } + yseResp, err := t.yseRespHandler(jsonResp) //解析银盛应答报文 + if err != nil { + r.SetError(err) + 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 - /* - resp := vo.PayQrCodeResp{} - resp.Code = 0 - resp.Message = "success" - r.Response.WriteJson(resp) - */ +} +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) + } diff --git a/service/vo/mqtt.go b/service/vo/mqtt.go new file mode 100644 index 0000000..8d90982 --- /dev/null +++ b/service/vo/mqtt.go @@ -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"` //付款用户手机号 +} diff --git a/service/vo/ysePay.go b/service/vo/ysePay.go new file mode 100644 index 0000000..64c07cf --- /dev/null +++ b/service/vo/ysePay.go @@ -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"` //参考总优惠金额 100.00 + 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 +} diff --git a/service/ysepay.go b/service/ysepay.go new file mode 100644 index 0000000..80157bc --- /dev/null +++ b/service/ysepay.go @@ -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 +}