From f13e6554e2e2bf36c36a742ad61a2b43f8e3f402 Mon Sep 17 00:00:00 2001 From: yangduo Date: Tue, 11 Feb 2025 15:35:52 +0800 Subject: [PATCH] wx prepare pay --- bin/payserver/config/config.json | 7 +- .../api/v1/mainservice/mainservice.go | 66 ++++- server/payserver/go.mod | 1 + server/payserver/go.sum | 3 + server/payserver/mt/Config.go | 42 +++- server/payserver/mtb/mtb.auto_gen.go | 50 ++++ server/payserver/proto/mt.proto | 5 + .../router/mainservice/mainservice.go | 4 + server/payserver/service/wxpay.go | 5 + server/payserver/service/wxpay_prepare.go | 228 ++++++++++++++++++ 10 files changed, 392 insertions(+), 19 deletions(-) create mode 100644 server/payserver/service/wxpay_prepare.go diff --git a/bin/payserver/config/config.json b/bin/payserver/config/config.json index f302a2e..66e36ca 100644 --- a/bin/payserver/config/config.json +++ b/bin/payserver/config/config.json @@ -2,5 +2,10 @@ "gameapi_url": "https://game2006api-test.kingsome.cn", "wx_url": "api.weixin.qq.com|api2.weixin.qq.com|sh.api.weixin.qq.com|sz.api.weixin.qq.com|hk.api.weixin.qq.com", "wx_notify_token": "dV93f4FwSGMwkYcvsRHD8egdW5egPMhF", - "wx_notify_encoding_aes_key": "t7zDjlqSow7OY4s61q8wp4EabjWnUtTSi5w0KM48O1K" + "wx_notify_encoding_aes_key": "t7zDjlqSow7OY4s61q8wp4EabjWnUtTSi5w0KM48O1K", + "wx_merchant_id": "1509252791", + "wx_certificate_sn": "1234", + "wx_merchant_api_key": "123", + "wx_msg_notify_token": "AcKipXgrCmNTFM9PA4CvemJxm38vxu3J", + "wx_msg_notify_encoding_aes_key": "U6HmUjnj135RX57PivS9rCz0cHiSi02BPJT89fymwDd" } diff --git a/server/payserver/api/v1/mainservice/mainservice.go b/server/payserver/api/v1/mainservice/mainservice.go index c4d0380..8f86892 100644 --- a/server/payserver/api/v1/mainservice/mainservice.go +++ b/server/payserver/api/v1/mainservice/mainservice.go @@ -142,6 +142,13 @@ func (this *MainServiceApi) WxNotifyPurchase(c *gin.Context) { return } + if wxnotifyobj.MiniGame.IsMock { + rspObj.ErrorCode = 0 + rspObj.ErrMsg = "Success" + c.JSON(200, rspObj) + return + } + payloadobj := new(service.WxPayload) if json.Unmarshal([]byte(wxnotifyobj.MiniGame.Payload), &payloadobj) != nil { c.JSON(200, rspObj) @@ -166,13 +173,6 @@ func (this *MainServiceApi) WxNotifyPurchase(c *gin.Context) { return } - if wxnotifyobj.MiniGame.IsMock { - rspObj.ErrorCode = 0 - rspObj.ErrMsg = "Success" - c.JSON(200, rspObj) - return - } - orderModel := new(model.InAppOrder) if err, found := orderModel.FindByOrderId(payloadobj.OutTradeNo); err != nil { c.JSON(200, rspObj) @@ -261,3 +261,55 @@ func (this *MainServiceApi) WxNotifyPurchase(c *gin.Context) { c.JSON(200, rspObj) } + +func (this *MainServiceApi) WxMsgTNotify(c *gin.Context) { + f5.GetSysLog().Debug("wx msg test notify:%s", c.Request.URL.RawQuery) + + signature := c.Query("signature") + timestamp := c.Query("timestamp") + nonce := c.Query("nonce") + echostr := c.Query("echostr") + strs := []string{mt.Table.Config.GetWxMsgNotifyToken(), timestamp, nonce} + sort.Strings(strs) + sb := strings.Builder{} + sb.WriteString(strs[0]) + sb.WriteString(strs[1]) + sb.WriteString(strs[2]) + m := sha1.New() + io.WriteString(m, sb.String()) + sign := string(hex.EncodeToString(m.Sum(nil))) + + f5.GetSysLog().Debug("wx t msg sign:%s, %s", sign, signature) + + if sign != signature { + c.String(200, "wrong") + return + } + c.String(200, echostr) +} + +func (this *MainServiceApi) WxMsgNotify(c *gin.Context) { + f5.GetSysLog().Debug("wx msg notify:%s", c.Request.URL.RawQuery) + + signature := c.Query("signature") + timestamp := c.Query("timestamp") + nonce := c.Query("nonce") + echostr := c.Query("echostr") + strs := []string{mt.Table.Config.GetWxMsgNotifyToken(), timestamp, nonce} + sort.Strings(strs) + sb := strings.Builder{} + sb.WriteString(strs[0]) + sb.WriteString(strs[1]) + sb.WriteString(strs[2]) + m := sha1.New() + io.WriteString(m, sb.String()) + sign := string(hex.EncodeToString(m.Sum(nil))) + + f5.GetSysLog().Debug("wx msg sign:%s, %s", sign, signature) + + if sign != signature { + c.String(200, "wrong") + return + } + c.String(200, echostr) +} diff --git a/server/payserver/go.mod b/server/payserver/go.mod index df97805..4172b06 100644 --- a/server/payserver/go.mod +++ b/server/payserver/go.mod @@ -9,6 +9,7 @@ require f5 v1.0.0 require jccommon v1.0.0 require ( + github.com/wechatpay-apiv3/wechatpay-go v0.2.20 // indirect golang.org/x/sync v0.6.0 // indirect gopkg.in/go-jose/go-jose.v2 v2.6.2 // indirect ) diff --git a/server/payserver/go.sum b/server/payserver/go.sum index d4e5fdd..0b2013e 100644 --- a/server/payserver/go.sum +++ b/server/payserver/go.sum @@ -1,3 +1,4 @@ +github.com/agiledragon/gomonkey v2.0.2+incompatible/go.mod h1:2NGfXu1a80LLr2cmWXGBDaHEjb1idR6+FVlX5T3D9hw= github.com/auth0/go-jwt-middleware/v2 v2.2.0/go.mod h1:BFCz+RF+1szSkrGNJLYn2ng2PtfzBiKR6fynTvS2A/k= github.com/auth0/go-jwt-middleware/v2 v2.2.1 h1:pqxEIwlCztD0T9ZygGfOrw4NK/F9iotnCnPJVADKbkE= github.com/auth0/go-jwt-middleware/v2 v2.2.1/go.mod h1:CSi0tuu0QrALbWdiQZwqFL8SbBhj4e2MJzkvNfjY0Us= @@ -78,6 +79,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/wechatpay-apiv3/wechatpay-go v0.2.20 h1:gS8oFn1bHGnyapR2Zb4aqTV6l4kJWgbtqjCq6k1L9DQ= +github.com/wechatpay-apiv3/wechatpay-go v0.2.20/go.mod h1:A254AUBVB6R+EqQFo3yTgeh7HtyqRRtN2w9hQSOrd4Q= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= diff --git a/server/payserver/mt/Config.go b/server/payserver/mt/Config.go index 7b904c5..c8f4cb6 100644 --- a/server/payserver/mt/Config.go +++ b/server/payserver/mt/Config.go @@ -15,25 +15,45 @@ type ConfigTable struct { selfConf *Config } -func (this *ConfigTable) GetGameApiUrl() string { - return this.selfConf.GetGameapiUrl() +func (ct *ConfigTable) GetGameApiUrl() string { + return ct.selfConf.GetGameapiUrl() } -func (this *ConfigTable) GetWxUrl() []string { - return strings.Split(this.selfConf.GetWxUrl(), "|") +func (ct *ConfigTable) GetWxUrl() []string { + return strings.Split(ct.selfConf.GetWxUrl(), "|") } -func (this *ConfigTable) GetWxNotifyToken() string { - return this.selfConf.GetWxNotifyToken() +func (ct *ConfigTable) GetWxNotifyToken() string { + return ct.selfConf.GetWxNotifyToken() } -func (this *ConfigTable) GetWxNotifyEncodingAesKey() string { - return this.selfConf.GetWxNotifyEncodingAesKey() +func (ct *ConfigTable) GetWxNotifyEncodingAesKey() string { + return ct.selfConf.GetWxNotifyEncodingAesKey() } -func (this *ConfigTable) PostInit1() { - this.selfConf = this.GetById(int64(0)) - if this.selfConf == nil { +func (ct *ConfigTable) GetWxMerchantId() string { + return ct.selfConf.GetWxMerchantId() +} + +func (ct *ConfigTable) GetWxCertificateSn() string { + return ct.selfConf.GetWxCertificateSn() +} + +func (ct *ConfigTable) GetWxMerchantApiKey() string { + return ct.selfConf.GetWxMerchantApiKey() +} + +func (ct *ConfigTable) GetWxMsgNotifyToken() string { + return ct.selfConf.GetWxMsgNotifyToken() +} + +func (ct *ConfigTable) GetWxMsgNotifyEncodingAesKey() string { + return ct.selfConf.GetWxMsgNotifyEncodingAesKey() +} + +func (ct *ConfigTable) PostInit1() { + ct.selfConf = ct.GetById(int64(0)) + if ct.selfConf == nil { panic("无法读取config.json") } } diff --git a/server/payserver/mtb/mtb.auto_gen.go b/server/payserver/mtb/mtb.auto_gen.go index f3c8485..937583c 100644 --- a/server/payserver/mtb/mtb.auto_gen.go +++ b/server/payserver/mtb/mtb.auto_gen.go @@ -70,6 +70,11 @@ type Config struct { wx_url string wx_notify_token string wx_notify_encoding_aes_key string + wx_merchant_id string + wx_certificate_sn string + wx_merchant_api_key string + wx_msg_notify_token string + wx_msg_notify_encoding_aes_key string _flags1_ uint64 _flags2_ uint64 @@ -419,6 +424,46 @@ func (this *Config) HasWxNotifyEncodingAesKey() bool { return (this._flags1_ & (uint64(1) << 4)) > 0 } +func (this *Config) GetWxMerchantId() string { + return this.wx_merchant_id +} + +func (this *Config) HasWxMerchantId() bool { + return (this._flags1_ & (uint64(1) << 5)) > 0 +} + +func (this *Config) GetWxCertificateSn() string { + return this.wx_certificate_sn +} + +func (this *Config) HasWxCertificateSn() bool { + return (this._flags1_ & (uint64(1) << 6)) > 0 +} + +func (this *Config) GetWxMerchantApiKey() string { + return this.wx_merchant_api_key +} + +func (this *Config) HasWxMerchantApiKey() bool { + return (this._flags1_ & (uint64(1) << 7)) > 0 +} + +func (this *Config) GetWxMsgNotifyToken() string { + return this.wx_msg_notify_token +} + +func (this *Config) HasWxMsgNotifyToken() bool { + return (this._flags1_ & (uint64(1) << 8)) > 0 +} + +func (this *Config) GetWxMsgNotifyEncodingAesKey() string { + return this.wx_msg_notify_encoding_aes_key +} + +func (this *Config) HasWxMsgNotifyEncodingAesKey() bool { + return (this._flags1_ & (uint64(1) << 9)) > 0 +} + func (this *RechargeCurrency) GetCurrencyName() string { return this.currency_name } @@ -695,6 +740,11 @@ func (this *Config) LoadFromKv(kv map[string]interface{}) { f5.ReadMetaTableField(&this.wx_url, "wx_url", &this._flags1_, 2, kv) f5.ReadMetaTableField(&this.wx_notify_token, "wx_notify_token", &this._flags1_, 3, kv) f5.ReadMetaTableField(&this.wx_notify_encoding_aes_key, "wx_notify_encoding_aes_key", &this._flags1_, 4, kv) + f5.ReadMetaTableField(&this.wx_merchant_id, "wx_merchant_id", &this._flags1_, 5, kv) + f5.ReadMetaTableField(&this.wx_certificate_sn, "wx_certificate_sn", &this._flags1_, 6, kv) + f5.ReadMetaTableField(&this.wx_merchant_api_key, "wx_merchant_api_key", &this._flags1_, 7, kv) + f5.ReadMetaTableField(&this.wx_msg_notify_token, "wx_msg_notify_token", &this._flags1_, 8, kv) + f5.ReadMetaTableField(&this.wx_msg_notify_encoding_aes_key, "wx_msg_notify_encoding_aes_key", &this._flags1_, 9, kv) } func (this *RechargeCurrency) LoadFromKv(kv map[string]interface{}) { diff --git a/server/payserver/proto/mt.proto b/server/payserver/proto/mt.proto index a8046f5..d965f87 100644 --- a/server/payserver/proto/mt.proto +++ b/server/payserver/proto/mt.proto @@ -59,6 +59,11 @@ message Config optional string wx_url = 2; optional string wx_notify_token = 3; optional string wx_notify_encoding_aes_key = 4; + optional string wx_merchant_id = 5; + optional string wx_certificate_sn = 6; + optional string wx_merchant_api_key = 7; + optional string wx_msg_notify_token = 8; + optional string wx_msg_notify_encoding_aes_key = 9; } message RechargeCurrency diff --git a/server/payserver/router/mainservice/mainservice.go b/server/payserver/router/mainservice/mainservice.go index e02123f..66e2054 100644 --- a/server/payserver/router/mainservice/mainservice.go +++ b/server/payserver/router/mainservice/mainservice.go @@ -15,4 +15,8 @@ func (this *MainServiceRouter) InitRouter() { api.WxTNotify) f5.GetApp().GetGinEngine().POST("/wx/tnotify", api.WxNotifyPurchase) + f5.GetApp().GetGinEngine().GET("/wx/msgnotify", + api.WxMsgTNotify) + f5.GetApp().GetGinEngine().POST("/wx/msgnotify", + api.WxMsgNotify) } diff --git a/server/payserver/service/wxpay.go b/server/payserver/service/wxpay.go index c64f25f..7a99ec7 100644 --- a/server/payserver/service/wxpay.go +++ b/server/payserver/service/wxpay.go @@ -1,6 +1,7 @@ package service import ( + "context" "encoding/json" "errors" "f5" @@ -8,6 +9,8 @@ import ( "main/mt" "q5" "time" + + "github.com/wechatpay-apiv3/wechatpay-go/core" ) type TokenInfo struct { @@ -25,6 +28,8 @@ type wxpay struct { accessTokens q5.ConcurrentMap[int64, TokenInfo] //[gameid, TokenInfo] expireInfo q5.ConcurrentMap[string, *expireSession] //[accountid, expireSession] refreshflag bool + ctx context.Context + client *core.Client } type WxQuery struct { diff --git a/server/payserver/service/wxpay_prepare.go b/server/payserver/service/wxpay_prepare.go new file mode 100644 index 0000000..954b8a4 --- /dev/null +++ b/server/payserver/service/wxpay_prepare.go @@ -0,0 +1,228 @@ +package service + +import ( + "context" + "f5" + "fmt" + "main/mt" + + "github.com/wechatpay-apiv3/wechatpay-go/core" + "github.com/wechatpay-apiv3/wechatpay-go/core/option" + "github.com/wechatpay-apiv3/wechatpay-go/services/certificates" + "github.com/wechatpay-apiv3/wechatpay-go/services/payments/jsapi" + "github.com/wechatpay-apiv3/wechatpay-go/utils" +) + +func (wp *wxpay) initMch() { + var ( + mchID string = mt.Table.Config.GetWxMerchantId() // 商户号 + mchCertificateSerialNumber string = mt.Table.Config.GetWxCertificateSn() // 商户证书序列号 + mchAPIv3Key string = mt.Table.Config.GetWxMerchantApiKey() // 商户APIv3密钥 + ) + + // 使用 utils 提供的函数从本地文件中加载商户私钥,商户私钥会用来生成请求的签名 + mchPrivateKey, err := utils.LoadPrivateKeyWithPath("../res/apiclient_key.pem") + if err != nil { + f5.GetSysLog().Alert("load merchant private key error") + } + + wp.ctx = context.Background() + // 使用商户私钥等初始化 client,并使它具有自动定时获取微信支付平台证书的能力 + opts := []core.ClientOption{ + option.WithWechatPayAutoAuthCipher(mchID, mchCertificateSerialNumber, mchPrivateKey, mchAPIv3Key), + } + wp.client, err = core.NewClient(wp.ctx, opts...) + if err != nil { + f5.GetSysLog().Alert("new wechat pay client err:%s", err) + } + + // 发送请求,以下载微信支付平台证书为例 + // https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay5_1.shtml + svc := certificates.CertificatesApiService{Client: wp.client} + resp, result, err := svc.DownloadCertificates(wp.ctx) + f5.GetSysLog().Debug("status=%d resp=%s", result.Response.StatusCode, resp) +} + +func (wp *wxpay) GetPrepareid(openid string, gameid int64, userip string, orderid string) (balance int64, wxerrcode int32, err error) { + svc := jsapi.JsapiApiService{Client: wp.client} + // 得到prepay_id,以及调起支付所需的参数和签名 + cfg := mt.Table.Wxconfig.GetById(gameid) + resp, result, err := svc.PrepayWithRequestPayment(wp.ctx, + jsapi.PrepayRequest{ + Appid: core.String(cfg.GetAppid()), + Mchid: core.String(mt.Table.Config.GetWxMerchantId()), + Description: core.String(""), + OutTradeNo: core.String(orderid), + Attach: core.String(userip), + NotifyUrl: core.String("https://www.weixin.qq.com/wxpay/pay.php"), + Amount: &jsapi.Amount{ + Total: core.Int64(100), + }, + Payer: &jsapi.Payer{ + Openid: core.String(openid), + }, + }, + ) + + if err == nil { + f5.GetSysLog().Debug("%s, %s", fmt.Sprintln(resp), result.Response.Status) + } + + return +} + +// func (wp *wxpay) QueryPay(openid string, gameid int64, userip string, sessionkey string, amount int32, billno string) (wxerrcode int32) { +// cfg := mt.Table.Wxconfig.GetById(gameid) +// postbody := struct { +// OpenId string `json:"openid"` +// OfferId string `json:"offer_id"` +// Ts int64 `json:"ts"` +// ZoneId string `json:"zone_id"` +// Env int32 `json:"env"` +// UserIp string `json:"user_ip"` +// Amount int32 `json:"amount"` +// BillNo string `json:"bill_no"` +// }{ +// OpenId: openid, +// OfferId: cfg.GetOfferid(), +// Ts: f5.GetApp().GetRealSeconds(), +// ZoneId: cfg.GetZoneid(), +// UserIp: userip, +// Amount: amount, +// BillNo: billno, +// } + +// if !f5.IsOnlineEnv() { +// postbody.Env = 1 +// } + +// poststr := q5.EncodeJson(postbody) + +// queryuri := "/wxa/game/pay" +// query := WxQuery{ +// AccessToken: wp.getAccessToken(gameid), +// Signature: wp.GenSHA256Signature(poststr, sessionkey), +// SigMethod: "hmac_sha256", +// PaySig: wp.GenSHA256Signature(queryuri+"&"+poststr, cfg.GetAppkey()), +// } + +// params := map[string]string{} +// data, _ := json.Marshal(query) +// json.Unmarshal(data, ¶ms) + +// sendRequest := false +// urls := mt.Table.Config.GetWxUrl() +// for _, wxhost := range urls { +// url := "https://" + wxhost + queryuri +// f5.GetHttpCliMgr().SendGoStylePost( +// url, +// params, +// "Content-Type: application/json", +// poststr, +// func(rsp f5.HttpCliResponse) { +// if rsp.GetErr() != nil { +// return +// } + +// sendRequest = true +// rspJson := WxPayRsp{} +// f5.GetSysLog().Debug("wx pay rsp:%s", rsp.GetRawData()) +// err := q5.DecodeJson(rsp.GetRawData(), &rspJson) +// if err != nil { +// return +// } + +// wxerrcode = rspJson.ErrCode +// switch rspJson.ErrCode { +// case constant.WX_ERRCODE_OK: +// case constant.WX_ERRCODE_BUSY: +// sendRequest = false +// default: +// f5.GetSysLog().Info("err msg:%s", rspJson.ErrMsg) +// wp.checkErrorCode(rspJson.ErrCode) +// } + +// }) +// if sendRequest { +// break +// } +// } + +// return wxerrcode +// } + +// func (wp *wxpay) QueryPurchase(openid string, gameid int64, userip string, sessionkey string, amount int32, tradeno string) (wxerrcode int32) { +// cfg := mt.Table.Wxconfig.GetById(gameid) +// postbody := struct { +// OpenId string `json:"openid"` +// Ts int64 `json:"ts"` +// Env int32 `json:"env"` +// TradeNo string `json:"out_trade_no"` +// BizId int32 `json:"biz_id"` +// OfferId string `json:"offer_id"` +// }{ +// OpenId: openid, +// Ts: f5.GetApp().GetRealSeconds(), +// BizId: 2, //1代币 2道具直购 +// TradeNo: tradeno, +// OfferId: cfg.GetOfferid(), +// } + +// if !f5.IsOnlineEnv() { +// postbody.Env = 1 +// } + +// poststr := q5.EncodeJson(postbody) + +// queryuri := "/wxa/game/queryorderinfo" +// query := WxQuery{ +// AccessToken: wp.getAccessToken(gameid), +// Signature: wp.GenSHA256Signature(poststr, sessionkey), +// SigMethod: "hmac_sha256", +// PaySig: wp.GenSHA256Signature(queryuri+"&"+poststr, cfg.GetAppkey()), +// } + +// params := map[string]string{} +// data, _ := json.Marshal(query) +// json.Unmarshal(data, ¶ms) + +// sendRequest := false +// urls := mt.Table.Config.GetWxUrl() +// for _, wxhost := range urls { +// url := "https://" + wxhost + queryuri +// f5.GetHttpCliMgr().SendGoStylePost( +// url, +// params, +// "Content-Type: application/json", +// poststr, +// func(rsp f5.HttpCliResponse) { +// if rsp.GetErr() != nil { +// return +// } + +// sendRequest = true +// rspJson := WxQueryOrderRsp{} +// f5.GetSysLog().Debug("wx query order rsp:%s", rsp.GetRawData()) +// err := q5.DecodeJson(rsp.GetRawData(), &rspJson) +// if err != nil { +// return +// } + +// wxerrcode = rspJson.ErrCode +// switch rspJson.ErrCode { +// case constant.WX_ERRCODE_OK: +// case constant.WX_ERRCODE_BUSY: +// sendRequest = false +// default: +// f5.GetSysLog().Info("err msg:%s", rspJson.ErrMsg) +// wp.checkErrorCode(rspJson.ErrCode) +// } + +// }) +// if sendRequest { +// break +// } +// } + +// return wxerrcode +// }