From de6bed55ef3e7f75533613b8dda08ececf9918db Mon Sep 17 00:00:00 2001 From: wolter <11@gmail> Date: Fri, 25 Apr 2025 14:56:44 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20v1=E6=94=AF=E4=BB=98=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E5=B0=8F=E7=A8=8B=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/http/controllers/front/api.go | 1 + app/http/entities/front/pay.go | 5 +- app/http/entities/front/wechat.go | 41 +++++++++++++++ app/http/entities/front/wx.go | 16 ++++++ app/services/thirdpay/do/pay.go | 77 +++++++++++++++++++++-------- app/services/thirdpay/do/wx_mini.go | 59 +++++++++++++++++++--- 6 files changed, 168 insertions(+), 31 deletions(-) diff --git a/app/http/controllers/front/api.go b/app/http/controllers/front/api.go index eb37208..4cf868b 100644 --- a/app/http/controllers/front/api.go +++ b/app/http/controllers/front/api.go @@ -35,6 +35,7 @@ func PayUrl(c *gin.Context) { res.Order = thirdpay.NewOrdersResp(pay.Order) res.Url = pay.Url + res.JsInfo = pay.JsApiInfo controllers.ApiRes(c, res, pay.PayCode) return } diff --git a/app/http/entities/front/pay.go b/app/http/entities/front/pay.go index 7395e79..46696f9 100644 --- a/app/http/entities/front/pay.go +++ b/app/http/entities/front/pay.go @@ -57,8 +57,9 @@ type CloseReqs struct { // api 接口返回数据, 统一返回结构, order数据会进行加密 type ApiResponse struct { - Order interface{} `json:"order,omitempty"` - Url string `json:"url,omitempty"` + Order interface{} `json:"order,omitempty"` + Url string `json:"url,omitempty"` + JsInfo *WxMiniPayResponse `json:"js_info,omitempty"` } type PayChannelListRequest struct { diff --git a/app/http/entities/front/wechat.go b/app/http/entities/front/wechat.go index 02436c7..91fafba 100644 --- a/app/http/entities/front/wechat.go +++ b/app/http/entities/front/wechat.go @@ -1,5 +1,16 @@ package front +import ( + "PaymentCenter/app/utils" + "crypto" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "fmt" +) + type GetWxAuthUrlRequest struct { PayChannelId string `json:"pay_channel_id" form:"pay_channel_id" validate:"required"` } @@ -77,6 +88,36 @@ type WxMiniPayResponse struct { ReturnUrl string `json:"return_url"` } +func (this *WxMiniPayResponse) SignPaySign(PrivateKey string) (err error) { + //组装被加密的字符串 + targetStr := this.AppId + "\n" + this.TimeStamp + "\n" + this.NonceStr + "\n" + this.Package + "\n" + + //var keypath = "商户证书私钥地址" + //key, err := ioutil.ReadFile(keypath) + var key []byte + if PrivateKey == "" { + err = fmt.Errorf("配置的私钥不存在") + return + } + key = []byte(PrivateKey) + + blocks, _ := pem.Decode(key) + if blocks == nil || blocks.Type != "PRIVATE KEY" { + utils.Log(nil, "failed to decode PRIVATE KEY") + return + + } + privateKey, err := x509.ParsePKCS8PrivateKey(blocks.Bytes) + + h := sha256.New() + h.Write([]byte(targetStr)) + digest := h.Sum(nil) + s, _ := rsa.SignPKCS1v15(nil, privateKey.(*rsa.PrivateKey), crypto.SHA256, digest) + this.PaySign = base64.StdEncoding.EncodeToString(s) + + return +} + type GetWxMiniSchemeRequest struct { PayChannelId string `json:"pay_channel_id" form:"pay_channel_id" validate:"required"` GenerateSchemeRequest diff --git a/app/http/entities/front/wx.go b/app/http/entities/front/wx.go index 4658b0f..e4bb9af 100644 --- a/app/http/entities/front/wx.go +++ b/app/http/entities/front/wx.go @@ -11,3 +11,19 @@ type JumpWxa struct { Query string `json:"query"` EnvVersion string `json:"env_version"` } + +type GenerateUrlLinkRequest struct { + Path string `json:"path"` + Query string `json:"query"` + ExpireType int `json:"expire_type"` + ExpireInterval int `json:"expire_interval"` + EnvVersion string `json:"env_version"` + CloudBase CloudBase `json:"cloud_base"` +} + +type CloudBase struct { + Env string `json:"env"` + Domain string `json:"domain"` + Path string `json:"path"` + Query string `json:"query"` +} diff --git a/app/services/thirdpay/do/pay.go b/app/services/thirdpay/do/pay.go index 8373266..26da8d6 100644 --- a/app/services/thirdpay/do/pay.go +++ b/app/services/thirdpay/do/pay.go @@ -4,7 +4,6 @@ import ( "PaymentCenter/app/constants/common" "PaymentCenter/app/constants/errorcode" "PaymentCenter/app/http/entities/front" - "PaymentCenter/app/models/merchantmodel" "PaymentCenter/app/models/paychannelmodel" "PaymentCenter/app/services" @@ -14,6 +13,7 @@ import ( "context" "net/url" "strconv" + "time" "xorm.io/builder" "PaymentCenter/app/models/ordersmodel" @@ -38,9 +38,10 @@ type Pay struct { PayParam *PayParam RelationOrder *ordersmodel.Orders Order *ordersmodel.Orders - PayCode int - Url string - ThirdMsg string + PayCode int // 支付状态码 + Url string // 支付链接 + ThirdMsg string // 第三方错误信息 + JsApiInfo *front.WxMiniPayResponse // jsapi支付参数 } func NewPayWithPayCheck(paycheck *PayCheck) *Pay { @@ -143,22 +144,36 @@ func (w *Pay) PayUrl() (url string) { res.Result = config.GetConf().PayService.Host + "/pay/front/api/v1/brokerWechatUrl" + "?url=" + res.Result } case common.PAY_CHANNEL_WECHAT_MINI: - req := front.GetWxMiniSchemeRequest{ - PayChannelId: strconv.Itoa(int(w.PayParam.Channel.Id)), - GenerateSchemeRequest: front.GenerateSchemeRequest{ - JumpWxa: front.JumpWxa{Query: "?order_id=" + strconv.Itoa(int(w.Order.Id))}, - }} - if config.GetEnv() == config.LocalEnv { - req.GenerateSchemeRequest.JumpWxa.EnvVersion = "develop" - } - // 微信小程序支付,返回小程序的scheme, todo 拼接订单id参数 - wx := NewWxMini().WithPayChannel(w.PayParam.Channel) - url, err = wx.GetWxMiniScheme(req) - res.Result = url - if err != nil { - w.PayCode = errorcode.PayChannelExtJsonError - w.ThirdMsg = err.Error() - return + // 微信小程序支付 + + // 如果传openid,获取支付参数 + if w.PayParam.OpenId != "" { + res = paymentService.PaymentService(*w.ctx, *thirdPay) + } else { + // 如果没有传openid,获取小程序scheme链接 + req := front.GetWxMiniSchemeRequest{ + PayChannelId: strconv.Itoa(int(w.PayParam.Channel.Id)), + GenerateSchemeRequest: front.GenerateSchemeRequest{ + JumpWxa: front.JumpWxa{Query: "?order_id=" + strconv.Itoa(int(w.Order.Id))}, + }} + if config.GetEnv() == config.LocalEnv { + req.GenerateSchemeRequest.JumpWxa.EnvVersion = "develop" + } else if config.GetEnv() == config.DevEnv { + req.GenerateSchemeRequest.JumpWxa.EnvVersion = "trial" + } + + // 微信小程序支付,返回小程序的scheme, + wx := NewWxMini().WithPayChannel(w.PayParam.Channel) + + // 拼接订单id参数 + req.JumpWxa.Query = "?order_id=" + strconv.Itoa(int(w.Order.Id)) + url, err = wx.GetWxMiniScheme(req) + res.Result = url + if err != nil { + w.PayCode = errorcode.PayChannelExtJsonError + w.ThirdMsg = err.Error() + return + } } default: @@ -172,7 +187,27 @@ func (w *Pay) PayUrl() (url string) { if code != errorcode.Success { w.PayCode = code } - w.Url = res.Result + + // 微信小程序 + if w.PayParam.Channel.ChannelType == common.PAY_CHANNEL_WECHAT_MINI { + // 组装返回数据 + if res.Result != "" { + w.JsApiInfo = &front.WxMiniPayResponse{ + AppId: w.PayParam.Channel.AppId, + TimeStamp: strconv.Itoa(int(time.Now().Unix())), + NonceStr: strconv.Itoa(int(time.Now().Unix())), + Package: "prepay_id=" + res.Result, + SignType: "RSA", + ThirdMsg: res.ErrorMsg, + ReturnUrl: w.PayParam.ReturnUrl, + } + + // 获取签名 + err = w.JsApiInfo.SignPaySign(thirdPay.Wx.PrivateKey) + } + } else { + w.Url = res.Result + } } else { w.PayCode = errorcode.PrePayFail w.ThirdMsg = res.ErrorMsg diff --git a/app/services/thirdpay/do/wx_mini.go b/app/services/thirdpay/do/wx_mini.go index 18fb5c2..e0925e1 100644 --- a/app/services/thirdpay/do/wx_mini.go +++ b/app/services/thirdpay/do/wx_mini.go @@ -102,14 +102,14 @@ func (wm *WxMini) generateScheme(accessToken string, param front.GenerateSchemeR // 小程序 获取sechema 链接 通过 pay_channel_id 获取 微信小程序的appid 和 secret 生成 access_token 然后生成scheme func (wm *WxMini) GetWxMiniScheme(param front.GetWxMiniSchemeRequest) (url string, err error) { var ( - has bool - repo = data.NewPayChannelRepo(paychannelmodel.GetInstance().GetDb()) - payChannel = paychannelmodel.PayChannel{} - payConfig = paymentService.PayOrderRequest{} + has bool + repo = data.NewPayChannelRepo(paychannelmodel.GetInstance().GetDb()) + payConfig = paymentService.PayOrderRequest{} ) // 获取支付渠道 if wm.payChannel == nil { + payChannel := paychannelmodel.PayChannel{} has, err = repo.PayChannelGet(&payChannel, builder.Eq{"id": param.PayChannelId}) if err != nil { return @@ -117,14 +117,15 @@ func (wm *WxMini) GetWxMiniScheme(param front.GetWxMiniSchemeRequest) (url strin err = fmt.Errorf("获取支付渠道不存在") return } + wm.payChannel = &payChannel } // 支付渠道支付配置解析 - if configFun, ok := PayWayList[payChannel.ChannelType]; !ok { + if configFun, ok := PayWayList[wm.payChannel.ChannelType]; !ok { err = fmt.Errorf("解析支付方式不存在") return } else { - err = configFun(&payConfig, &payChannel) + err = configFun(&payConfig, wm.payChannel) if err != nil { return } @@ -137,14 +138,14 @@ func (wm *WxMini) GetWxMiniScheme(param front.GetWxMiniSchemeRequest) (url strin // 小程序的配置参数解析, 适配不同小程序配置需求 if param.JumpWxa.Query == "" { - err = json.Unmarshal([]byte(payChannel.ExtJson), ¶m.GenerateSchemeRequest) + err = json.Unmarshal([]byte(wm.payChannel.ExtJson), ¶m.GenerateSchemeRequest) if err != nil { return } } // 获取access_token - accessToken, err := wm.GetAccessToken(payChannel.AppId, payConfig.Wx.Secret) + accessToken, err := wm.GetAccessToken(wm.payChannel.AppId, payConfig.Wx.Secret) if err != nil { return } @@ -209,3 +210,45 @@ func (wm *WxMini) GetWxAuthMini(param front.GetWxAuthRequest) (rsp front.GetWxAu return } + +// 小程序 获取加密URLLink +func (wm *WxMini) generateUrlLink(accessToken string, param front.GenerateUrlLinkRequest) (url string, err error) { + var ( + targetUrl = "https://api.weixin.qq.com/wxa/generate_urllink?access_token=" + accessToken + header = map[string]string{ + "Content-Type": "application/json", + } + body []byte + ) + + type UrlLinkResponse struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + UrlLink string `json:"url_link"` + } + + body, err = json.Marshal(param) + if err != nil { + return + } + + // 发送请求 + response, err := httpclient.FastHttpPost(targetUrl, header, body, 0) + if err != nil { + err = fmt.Errorf("GenerateUrlLink 获取加密scheme码 err:%s", err.Error()) + return + } + utils.Log(nil, "GenerateUrlLink 获取加密scheme码 ", "response ="+string(response)) + + var rsp UrlLinkResponse + err = json.Unmarshal(response, &rsp) + if err != nil { + err = fmt.Errorf("GenerateUrlLink 获取加密scheme码 err:%s", err.Error()) + return + } else if rsp.Errcode != 0 { + err = fmt.Errorf("GenerateUrlLink 获取加密scheme码 code:%d err:%s", rsp.Errcode, rsp.Errmsg) + return + } + + return +}