Merge branch 'dev-lwl/wechat'

This commit is contained in:
lwl0608 2024-02-05 23:25:47 +08:00
commit ccf4417659
15 changed files with 350 additions and 73 deletions

View File

@ -25,9 +25,15 @@ type GetMembershipProductListData struct {
// 创建订单 // 创建订单
const ( const (
PayTypeOp = "op" // op直冲 PayTypeOp = "op" // op直冲
PayTypeAlipay = "alipay" // 支付宝 PayTypeAlipay = "alipay" // 支付宝
PayTypeAlipayH5 = "alipay_h5" // 支付宝 h5 PayTypeAlipayH5 = "alipay_h5" // 支付宝 h5
PayTypeWxpayNative = "wxpay_native" // 微信native
)
const (
CallBackPayTypeAlipay = "alipay"
CallBackPayTypeWxpay = "wxpay"
) )
const ( const (
@ -53,9 +59,10 @@ type CreateOrderReq struct {
} }
type CreateOrderData struct { type CreateOrderData struct {
OrderId string `json:"order_id"` // 订单id OrderId string `json:"order_id"` // 订单id
AlipayParamStr string `json:"alipay_param_str"` // 支付宝 app支付参数 AlipayParamStr string `json:"alipay_param_str"` // 支付宝 app支付参数
AlipayH5ParamStr string `json:"alipay_h5_param_str"` // 支付宝 h5支付参数 AlipayH5ParamStr string `json:"alipay_h5_param_str"` // 支付宝 h5支付参数
WxpayNativeParamStr string `json:"wxpay_native_param_str"` // 微信支付 native支付参数
} }
// 预解锁联系方式 // 预解锁联系方式
@ -132,10 +139,11 @@ type H5DirectUnlockWechatReq struct {
} }
type H5DirectUnlockWechatData struct { type H5DirectUnlockWechatData struct {
CoinEnough int32 `json:"coin_enough"` // 0:不够(用下面的支付宝参数)1:够 CoinEnough int32 `json:"coin_enough"` // 0:不够(用下面的支付宝参数)1:够
OrderId string `json:"order_id"` // 订单id OrderId string `json:"order_id"` // 订单id
AlipayParamStr string `json:"alipay_param_str"` // 支付宝 app支付参数 AlipayParamStr string `json:"alipay_param_str"` // 支付宝 app支付参数
AlipayH5ParamStr string `json:"alipay_h5_param_str"` // 支付宝 h5支付参数 AlipayH5ParamStr string `json:"alipay_h5_param_str"` // 支付宝 h5支付参数
WxpayNativeParamStr string `json:"wxpay_native_param_str"` // 微信支付 native支付参数
} }
// 支付宝回调参数 // 支付宝回调参数
@ -144,6 +152,13 @@ type AlipayCallbackParamIn struct {
AlipayOrderId string `json:"alipay_order_id"` // 支付宝订单id AlipayOrderId string `json:"alipay_order_id"` // 支付宝订单id
} }
// 支付回调参数
type PayCallbackParamIn struct {
OrderId string `json:"order_id"` // 我们自己服务的订单id
OutOrderId string `json:"out_order_id"` // 外部订单id比如支付宝、微信
CallbackPayType string `json:"callback_pay_type"` // 支付类型
}
// 解锁会员资格 // 解锁会员资格
type UnlockMembershipReq struct { type UnlockMembershipReq struct {
base.BaseRequest base.BaseRequest

View File

@ -19,9 +19,10 @@ func AlipayCallback(ctx *gin.Context) {
} }
if bm.GetString("trade_status") == "TRADE_SUCCESS" { if bm.GetString("trade_status") == "TRADE_SUCCESS" {
service.DefaultService.AlipayCallback(ctx, &vasproto.AlipayCallbackParamIn{ service.DefaultService.PayCallback(ctx, &vasproto.PayCallbackParamIn{
OrderId: bm.GetString("out_trade_no"), OrderId: bm.GetString("out_trade_no"),
AlipayOrderId: bm.GetString("trade_no"), OutOrderId: bm.GetString("trade_no"),
CallbackPayType: vasproto.CallBackPayTypeAlipay,
}) })
} }
ctx.String(200, "success") ctx.String(200, "success")

View File

@ -226,6 +226,7 @@ func Init(r *gin.Engine) {
extVasPayGroup := r.Group("/ext/vas") extVasPayGroup := r.Group("/ext/vas")
extVasPayGroup.POST("alipay_callback", AlipayCallback) extVasPayGroup.POST("alipay_callback", AlipayCallback)
extVasPayGroup.POST("wxpay_callback", WxpayCallback)
opVasPayGroup := r.Group("/op/vas", PrepareOp()) opVasPayGroup := r.Group("/op/vas", PrepareOp())
opVasPayGroup.POST("create_order", middleware.JSONParamValidator(vasproto.OpCreateOrderReq{}), OpCreateOrder) opVasPayGroup.POST("create_order", middleware.JSONParamValidator(vasproto.OpCreateOrderReq{}), OpCreateOrder)

View File

@ -0,0 +1,33 @@
package controller
import (
"github.com/gin-gonic/gin"
wxpay "github.com/go-pay/gopay/wechat/v3"
vasproto "service/api/proto/vas/proto"
"service/app/mix/service"
"service/bizcommon/util"
"service/library/logger"
"service/library/payclients/wxpaycli"
)
func WxpayCallback(ctx *gin.Context) {
notify, err := wxpaycli.GetDefaultWxpayClient().ParseNotify(ctx.Request)
if err != nil {
logger.Error("ParseNotify fail, req: %v, err: %v", util.ToJson(notify), err)
return
}
if notify == nil {
logger.Error("ParseNotify nil, req: %v, err: %v", util.ToJson(notify), err)
return
}
logger.Info("WxpayCallback, notify: %v", util.ToJson(notify))
if notify.TradeState == wxpay.TradeStateSuccess {
service.DefaultService.PayCallback(ctx, &vasproto.PayCallbackParamIn{
OrderId: notify.OutTradeNo,
OutOrderId: notify.TransactionId,
CallbackPayType: vasproto.CallBackPayTypeWxpay,
})
}
ctx.String(200, "success")
}

View File

@ -20,6 +20,7 @@ import (
"service/library/idgenerator" "service/library/idgenerator"
"service/library/logger" "service/library/logger"
"service/library/payclients/alipaycli" "service/library/payclients/alipaycli"
"service/library/payclients/wxpaycli"
"time" "time"
"github.com/go-pay/gopay/alipay" "github.com/go-pay/gopay/alipay"
@ -93,8 +94,9 @@ func (v *Vas) CreateOrder(ctx *gin.Context, req *vasproto.CreateOrderReq) (data
orderId = idgenerator.GenOrderId() // 订单id orderId = idgenerator.GenOrderId() // 订单id
) )
var ( var (
alipayParamStr string alipayParamStr string
alipayH5ParamStr string alipayH5ParamStr string
wxpayNativeParamStr string
) )
defer func() { defer func() {
@ -180,16 +182,29 @@ func (v *Vas) CreateOrder(ctx *gin.Context, req *vasproto.CreateOrderReq) (data
} }
case vasproto.PayTypeAlipayH5: case vasproto.PayTypeAlipayH5:
alipayCli := alipaycli.GetDefaultAlipayClient() alipayCli := alipaycli.GetDefaultAlipayClient()
appPayParam := &alipaycli.WapPayParam{ wapPayParam := &alipaycli.WapPayParam{
OutTradeNo: orderId, OutTradeNo: orderId,
Subject: product.Subject, Subject: product.Subject,
TotalAmount: product.RealPrice, TotalAmount: product.RealPrice,
TimeOutSeconds: 900, TimeOutSeconds: 900,
ReturnUrl: req.ReturnUrl, ReturnUrl: req.ReturnUrl,
} }
alipayH5ParamStr, err = alipayCli.WapPay(ctx, appPayParam) alipayH5ParamStr, err = alipayCli.WapPay(ctx, wapPayParam)
if err != nil { if err != nil {
logger.Error("alipay WapPay fail, req: %v, wapPayParam: %v, err: %v", util.ToJson(req), util.ToJson(appPayParam), err) logger.Error("alipay WapPay fail, req: %v, wapPayParam: %v, err: %v", util.ToJson(req), util.ToJson(wapPayParam), err)
return
}
case vasproto.PayTypeWxpayNative:
wxpayCli := wxpaycli.GetDefaultWxpayClient()
nativePayParam := &wxpaycli.NativePayParam{
Description: product.Subject,
OutTradeNo: orderId,
TotalAmount: product.RealPrice,
TimeOutSeconds: 900,
}
wxpayNativeParamStr, err = wxpayCli.NativePay(ctx, nativePayParam)
if err != nil {
logger.Error("wxpay NativePay fail, req: %v, wapPayParam: %v, err: %v", util.ToJson(req), util.ToJson(nativePayParam), err)
return return
} }
} }
@ -230,9 +245,10 @@ func (v *Vas) CreateOrder(ctx *gin.Context, req *vasproto.CreateOrderReq) (data
} }
data = &vasproto.CreateOrderData{ data = &vasproto.CreateOrderData{
OrderId: orderId, OrderId: orderId,
AlipayParamStr: alipayParamStr, AlipayParamStr: alipayParamStr,
AlipayH5ParamStr: alipayH5ParamStr, AlipayH5ParamStr: alipayH5ParamStr,
WxpayNativeParamStr: wxpayNativeParamStr,
} }
return return
} }
@ -1497,9 +1513,10 @@ func (v *Vas) H5DirectUnlockWechat(ctx *gin.Context, req *vasproto.H5DirectUnloc
return return
} }
data = &vasproto.H5DirectUnlockWechatData{ data = &vasproto.H5DirectUnlockWechatData{
OrderId: cData.OrderId, OrderId: cData.OrderId,
AlipayParamStr: cData.AlipayParamStr, AlipayParamStr: cData.AlipayParamStr,
AlipayH5ParamStr: cData.AlipayH5ParamStr, AlipayH5ParamStr: cData.AlipayH5ParamStr,
WxpayNativeParamStr: cData.WxpayNativeParamStr,
} }
return return
} }
@ -1524,13 +1541,14 @@ func (v *Vas) GetUserWechatUnlock(ctx *gin.Context, mid, uid int64) (uu *dbstruc
return return
} }
// 支付宝【支付】回调 // 【支付】回调
func (v *Vas) AlipayCallback(ctx *gin.Context, p *vasproto.AlipayCallbackParamIn) { func (v *Vas) PayCallback(ctx *gin.Context, p *vasproto.PayCallbackParamIn) {
var ( var (
orderId = p.OrderId orderId = p.OrderId
alipayOrderId = p.AlipayOrderId outOrderId = p.OutOrderId
afterStatus int32 afterStatus int32
) )
logger.Info("PayCallback, param: %v", util.ToJson(p))
// 获取订单 // 获取订单
checkOrder, err := v.store.GetOrderById(ctx, nil, orderId) checkOrder, err := v.store.GetOrderById(ctx, nil, orderId)
@ -1548,8 +1566,8 @@ func (v *Vas) AlipayCallback(ctx *gin.Context, p *vasproto.AlipayCallbackParamIn
return return
} }
// ali_order_id检查 // out_order_id检查
outOrder, err := v.store.GetOrderByOutOrderId(ctx, nil, alipayOrderId) outOrder, err := v.store.GetOrderByOutOrderId(ctx, nil, outOrderId)
switch err { switch err {
case sql.ErrNoRows: case sql.ErrNoRows:
err = nil err = nil
@ -1665,8 +1683,8 @@ func (v *Vas) AlipayCallback(ctx *gin.Context, p *vasproto.AlipayCallbackParamIn
} }
afterStatus = dbstruct.VasOrderStatusPaySuccess afterStatus = dbstruct.VasOrderStatusPaySuccess
// 更新支付宝订单 // 更新外部订单
err = v.store.UpdateOutOrderId(ctx, tx, orderId, alipayOrderId) err = v.store.UpdateOutOrderId(ctx, tx, orderId, outOrderId)
if err != nil { if err != nil {
logger.Error("UpdateOutOrderId fail, p: %v", util.ToJson(p)) logger.Error("UpdateOutOrderId fail, p: %v", util.ToJson(p))
return return
@ -2062,28 +2080,35 @@ func (v *Vas) DealOneOrder(ctx *gin.Context, orderId string) (err error) {
} }
// 支付宝查询订单 // 支付宝查询订单
alipayCli := alipaycli.GetDefaultAlipayClient() switch order.GetPayType() {
alipayResp, err := alipayCli.QueryOrder(ctx, &alipaycli.QueryOrderParam{ case vasproto.PayTypeAlipayH5:
OutTradeNo: orderId, alipayCli := alipaycli.GetDefaultAlipayClient()
}) var alipayResp *alipay.TradeQueryResponse
if err != nil { alipayResp, err = alipayCli.QueryOrder(ctx, &alipaycli.QueryOrderParam{
logger.Error("alipayCli.QueryOrder fail, id: %v, err: %v", orderId, err) OutTradeNo: orderId,
return })
}
if alipayResp == nil {
err = errors.New("alipayCli.QueryOrder resp nil")
logger.Error("alipayCli.QueryOrder nil, id: %v, err: %v", orderId, err)
return
}
// 已退款
if alipayResp.Response.TradeStatus == "TRADE_CLOSED" {
// 更新订单状态
err = v.store.UpdateOrderStatus(ctx, nil, order.GetID(), dbstruct.VasOrderStatusPaySuccess, dbstruct.VasOrderStatusRefund)
if err != nil { if err != nil {
logger.Error("UpdateOrderStatus fail, orderId: %v, err: %v", order.GetID(), err) logger.Error("alipayCli.QueryOrder fail, id: %v, err: %v", orderId, err)
return return
} }
err = errors.New("closed order") if alipayResp == nil {
err = errors.New("alipayCli.QueryOrder resp nil")
logger.Error("alipayCli.QueryOrder nil, id: %v, err: %v", orderId, err)
return
}
// 已退款
if alipayResp.Response.TradeStatus == "TRADE_CLOSED" {
// 更新订单状态
err = v.store.UpdateOrderStatus(ctx, nil, order.GetID(), dbstruct.VasOrderStatusPaySuccess, dbstruct.VasOrderStatusRefund)
if err != nil {
logger.Error("UpdateOrderStatus fail, orderId: %v, err: %v", order.GetID(), err)
return
}
err = errors.New("closed order")
return
}
case vasproto.CallBackPayTypeWxpay:
err = errors.New("暂不处理微信订单")
return return
} }

View File

@ -49,6 +49,7 @@ import (
"service/library/melody" "service/library/melody"
"service/library/mycrypto" "service/library/mycrypto"
"service/library/payclients/alipaycli" "service/library/payclients/alipaycli"
"service/library/payclients/wxpaycli"
"service/library/redis" "service/library/redis"
"go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo"
@ -129,7 +130,13 @@ func (s *Service) Init(c any) (err error) {
err = alipaycli.Init(cfg.Alipay) err = alipaycli.Init(cfg.Alipay)
if err != nil { if err != nil {
logger.Error("alipaycli.Init fail, cfg: %v, err: %v", util.ToJson(cfg), err) logger.Error("alipaycli.Init fail, cfg: %v, err: %v", util.ToJson(cfg.Alipay), err)
return
}
err = wxpaycli.Init(cfg.Wxpay)
if err != nil {
logger.Error("wxpaycli.Init fail, cfg: %v, err: %v", util.ToJson(cfg.Wxpay), err)
return return
} }

View File

@ -369,9 +369,9 @@ func (s *Service) H5DirectUnlockWechat(ctx *gin.Context, req *vasproto.H5DirectU
return return
} }
// 支付回调 // 支付回调
func (s *Service) AlipayCallback(ctx *gin.Context, req *vasproto.AlipayCallbackParamIn) (data *vasproto.H5DirectUnlockWechatData, ec errcode.ErrCode) { func (s *Service) PayCallback(ctx *gin.Context, req *vasproto.PayCallbackParamIn) (data *vasproto.H5DirectUnlockWechatData, ec errcode.ErrCode) {
_DefaultVas.AlipayCallback(ctx, req) _DefaultVas.PayCallback(ctx, req)
return return
} }

View File

@ -27,11 +27,6 @@ mix_mysql:
read_timeout_s: 5 read_timeout_s: 5
write_timeout_s: 3 write_timeout_s: 3
wxpay:
mchid: "1111"
serial_no: "1111"
apiv3_key: "1111"
private_key: "af21aw65aw23efwa132f1waef56wa56awef"
crypto: crypto:
aes: aes:
@ -48,6 +43,14 @@ alipay:
private_key: "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCtj6Nu2olEB8c8SDSANExaPbmk8LoYPEGB/APZDskhezO/w4OwPmG1Ak79XDpUUIihdmTZ8i2nBLhhRpFZGU4MMdil4X7a5nYHBm5dkGo3isIInn/qV7AYDqek4pGpMUPI6fbR05NWnUTZ+3AvMjTNBa979MgLyqS3jzpvuK6yXM5RinKVv8IF9KgthRKjH4LHyyRXBGu2cJSA5utjzFCL9KIu/T4XXZ09HOgz+JKPxXDj25Ob5eTtcBDsoJfXChcOvwCA7nxb1jzvGDrkqF36H1CC8KT1aoLRLisBsM+7sjEfNcB4RG5pGHSDGQJwZ0stGAhXf/fuWiLmI/zMOXODAgMBAAECggEAEL7CAuj8w16Iv20r+46QK0i3R42eNsZhf5wD9wYxK5TKal7/rppmLOObIWCrlATtGb7lfg2aj/mpnGEFlvYVDKImh+KYrZ/8lTLupQJQ7SjrDY/VQZPSPo/zZrohWZSSOKkyEg56samcwfc7XKJYa8t3odr9Df4wJDGibrL/z99xrJrz85le+NHBISXUyddS5ojuDNfGwE39wgLu52P74QYxv6s0xZKj2aALXaLXyTzF4ys34nVhhv75gXFtENiUet5/PVRYe7OE4cndcOP9WI3nXP5ojak8N4fY15S0YOm/0NZP8w6nWUemFjUNRXYRY42J+W+/myW8RIppaGuwkQKBgQDYH263Lh8C9EkjGA2po68LUTBhM2fT2L3batzHjXo0jvLT0IR88/9V7xhC3ZQZNcxQCK0xaCYfzFx3UV9veC1zeVRW0i/hyJ8B40w3WXCoGDiHgzQ/kcu/Iaw2qmJyhEFzOwAfrxIgco9EiZBaY7fXyy7YkZLGqXNFcLsuWR5qWwKBgQDNlc19CESIukT31Ap2QBe6t9YNXHZuKTePBRaIUCan3vZwPfZePLJQCgtoVhnCGoHZJ+ZlNhYri9x6DNLZpxCSHZNjccmqFGj3N7xWBqU38gu2dcPdMAxT1ERwf2gdlhgWAhVzD56hsWyrbP9YZACXy7TlevSj7s/5GaNaVuHT+QKBgQC2VZZ3zt51BJnrlLB6LVFRz/ZsGw1+qj5LLpYDeXXff7aYQzRzovsJigVC7GO0/TFZWGid5Us8ypI8TBejGJXn9TXVZdDlwPd9hUFY9QlZl82hbm0XMK7fms4K9KbIDJKXX/CTyoUVgPEkFpcF21lQIuhr6C0XlABfVmNlD+TcPwKBgHJuot2ov3UXsZH8/gHKNSsibswrHmS9HobGPz+K1al1Stk5NCxKPrqcjLL70gSf+ozkT7MggwCkLgnln2u1OV0Lh2HAEY9RIwgQhw2fT1GvseNS873no8T5j0rLMCnfxPJjIItWM2cvOhzFY/BQYaAcrElbwlaJdEvkgG+lkrgJAoGBAInYRdHo9V1exDUS0ucQuraoEtuvgOrqatnCbuNZ9EpgjFJxDbHXOIJYPvgfpyws4amdksNQL0Ux9+p+esJOS+JYYkE1nmCTCrfb6SNKr+PdeUteKtTiHJb4zJhGVC2Lk4uBg9AGa/d7j5apWw4RsZ/R1ky1S6KQUkqItbVrhC8l" private_key: "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCtj6Nu2olEB8c8SDSANExaPbmk8LoYPEGB/APZDskhezO/w4OwPmG1Ak79XDpUUIihdmTZ8i2nBLhhRpFZGU4MMdil4X7a5nYHBm5dkGo3isIInn/qV7AYDqek4pGpMUPI6fbR05NWnUTZ+3AvMjTNBa979MgLyqS3jzpvuK6yXM5RinKVv8IF9KgthRKjH4LHyyRXBGu2cJSA5utjzFCL9KIu/T4XXZ09HOgz+JKPxXDj25Ob5eTtcBDsoJfXChcOvwCA7nxb1jzvGDrkqF36H1CC8KT1aoLRLisBsM+7sjEfNcB4RG5pGHSDGQJwZ0stGAhXf/fuWiLmI/zMOXODAgMBAAECggEAEL7CAuj8w16Iv20r+46QK0i3R42eNsZhf5wD9wYxK5TKal7/rppmLOObIWCrlATtGb7lfg2aj/mpnGEFlvYVDKImh+KYrZ/8lTLupQJQ7SjrDY/VQZPSPo/zZrohWZSSOKkyEg56samcwfc7XKJYa8t3odr9Df4wJDGibrL/z99xrJrz85le+NHBISXUyddS5ojuDNfGwE39wgLu52P74QYxv6s0xZKj2aALXaLXyTzF4ys34nVhhv75gXFtENiUet5/PVRYe7OE4cndcOP9WI3nXP5ojak8N4fY15S0YOm/0NZP8w6nWUemFjUNRXYRY42J+W+/myW8RIppaGuwkQKBgQDYH263Lh8C9EkjGA2po68LUTBhM2fT2L3batzHjXo0jvLT0IR88/9V7xhC3ZQZNcxQCK0xaCYfzFx3UV9veC1zeVRW0i/hyJ8B40w3WXCoGDiHgzQ/kcu/Iaw2qmJyhEFzOwAfrxIgco9EiZBaY7fXyy7YkZLGqXNFcLsuWR5qWwKBgQDNlc19CESIukT31Ap2QBe6t9YNXHZuKTePBRaIUCan3vZwPfZePLJQCgtoVhnCGoHZJ+ZlNhYri9x6DNLZpxCSHZNjccmqFGj3N7xWBqU38gu2dcPdMAxT1ERwf2gdlhgWAhVzD56hsWyrbP9YZACXy7TlevSj7s/5GaNaVuHT+QKBgQC2VZZ3zt51BJnrlLB6LVFRz/ZsGw1+qj5LLpYDeXXff7aYQzRzovsJigVC7GO0/TFZWGid5Us8ypI8TBejGJXn9TXVZdDlwPd9hUFY9QlZl82hbm0XMK7fms4K9KbIDJKXX/CTyoUVgPEkFpcF21lQIuhr6C0XlABfVmNlD+TcPwKBgHJuot2ov3UXsZH8/gHKNSsibswrHmS9HobGPz+K1al1Stk5NCxKPrqcjLL70gSf+ozkT7MggwCkLgnln2u1OV0Lh2HAEY9RIwgQhw2fT1GvseNS873no8T5j0rLMCnfxPJjIItWM2cvOhzFY/BQYaAcrElbwlaJdEvkgG+lkrgJAoGBAInYRdHo9V1exDUS0ucQuraoEtuvgOrqatnCbuNZ9EpgjFJxDbHXOIJYPvgfpyws4amdksNQL0Ux9+p+esJOS+JYYkE1nmCTCrfb6SNKr+PdeUteKtTiHJb4zJhGVC2Lk4uBg9AGa/d7j5apWw4RsZ/R1ky1S6KQUkqItbVrhC8l"
notify_url: "https://api.tiefen.fun/ext/vas/alipay_callback" notify_url: "https://api.tiefen.fun/ext/vas/alipay_callback"
wxpay:
mchid: "1665016206"
appid: "wxc28fd8aaf31984b6"
serial_no: "51D8E75620B9569F10FF5363022F3C9D2152DF50"
apiv3_key: "UoaswgnuKYwIadqxCVag1IZlq5n9USvt"
private_key_path: "/app/wishpal-ironfan/etc/mix/wxpaycert/apiclient_key.pem"
notify_url: "https://api.tiefen.fun/ext/vas/wxpay_callback"
apollo: apollo:
app_id: "wishpal_live_service" app_id: "wishpal_live_service"
cluster: "dev" cluster: "dev"

View File

@ -28,10 +28,12 @@ mix_mysql:
write_timeout_s: 3 write_timeout_s: 3
wxpay: wxpay:
mchid: "1111" mchid: "1665016206"
serial_no: "1111" appid: "wxc28fd8aaf31984b6"
apiv3_key: "1111" serial_no: "51D8E75620B9569F10FF5363022F3C9D2152DF50"
private_key: "af21aw65aw23efwa132f1waef56wa56awef" apiv3_key: "UoaswgnuKYwIadqxCVag1IZlq5n9USvt"
private_key_path: "/app/wishpal-ironfan/etc/mix/wxpaycert/apiclient_key.pem"
notify_url: "https://api.wishpal.cn/ext/vas/wxpay_callback"
crypto: crypto:
aes: aes:

Binary file not shown.

View File

@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIEKDCCAxCgAwIBAgIUUdjnViC5Vp8Q/1NjAi88nSFS31AwDQYJKoZIhvcNAQEL
BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT
FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg
Q0EwHhcNMjQwMTMwMDIxMDEyWhcNMjkwMTI4MDIxMDEyWjCBgTETMBEGA1UEAwwK
MTY2NTAxNjIwNjEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMS0wKwYDVQQL
DCTmiJDpg73lv4PmhI/liLDkuobnp5HmioDmnInpmZDlhazlj7gxCzAJBgNVBAYT
AkNOMREwDwYDVQQHDAhTaGVuWmhlbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAODFaaohlVkRV079c07NzrpSTJgBLr8vNMBY/pZb/Ah+KVoNIMB732NS
ykUqXVia5smQMnljZZodw7KKuB9hfcYcyuvXHu+2o43p5A+IZKGV/0eU5AJ2LXOZ
SsF4xaPlNyWX6V3JrNhqpHjIbpqGGPUGeypn5bpJr3G1N8y1NSsG8m4ebh1YBBHr
YDZN562T1g31zGI7AvsFdomjmFhq6Y2hvPukjPpceoGASEEfpC/yRrvjo05HVQVK
8x6oda/67IHNS2F0goUsJESBCYVOhezugmy4XaeyFkqcw+1rde+LzBs9sBlzhvgr
Asu/1LEEqJGHO1qAXwqpifWThEHyYUMCAwEAAaOBuTCBtjAJBgNVHRMEAjAAMAsG
A1UdDwQEAwID+DCBmwYDVR0fBIGTMIGQMIGNoIGKoIGHhoGEaHR0cDovL2V2Y2Eu
aXRydXMuY29tLmNuL3B1YmxpYy9pdHJ1c2NybD9DQT0xQkQ0MjIwRTUwREJDMDRC
MDZBRDM5NzU0OTg0NkMwMUMzRThFQkQyJnNnPUhBQ0M0NzFCNjU0MjJFMTJCMjdB
OUQzM0E4N0FEMUNERjU5MjZFMTQwMzcxMA0GCSqGSIb3DQEBCwUAA4IBAQAcaVb/
Eg+fOt1qPr9we38kzUwVRktgamFB3hRfKvZpva3hx4lh8ZC3Et7bIP/r4IV5qCNC
wzN1Niq0aBXtnehOWL+mnnBmmW5QnB80UX1/yFxtaBEK+6R6IObAj7ClUSb0F8ak
0fRkCyWGl985sAUyZVE5g0jQEEcWFhdV9lpwPdprY4S9vck+cCfjPi4EyxEzRh7z
knsvZbjoj/0oTSKgg39D4UEQl6X0e3YR74kIXfKmFK08WxoKKet201pL3bzJKBAY
s+8VBqAKsTvNZJ1/GYPqax7ArpACX4LjgmzCv7/rrM75ce6rXWlkIOBSAz2dHuF0
Q6iPI92vMh32p19Q
-----END CERTIFICATE-----

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDgxWmqIZVZEVdO
/XNOzc66UkyYAS6/LzTAWP6WW/wIfilaDSDAe99jUspFKl1YmubJkDJ5Y2WaHcOy
irgfYX3GHMrr1x7vtqON6eQPiGShlf9HlOQCdi1zmUrBeMWj5Tcll+ldyazYaqR4
yG6ahhj1BnsqZ+W6Sa9xtTfMtTUrBvJuHm4dWAQR62A2Teetk9YN9cxiOwL7BXaJ
o5hYaumNobz7pIz6XHqBgEhBH6Qv8ka746NOR1UFSvMeqHWv+uyBzUthdIKFLCRE
gQmFToXs7oJsuF2nshZKnMPta3Xvi8wbPbAZc4b4KwLLv9SxBKiRhztagF8KqYn1
k4RB8mFDAgMBAAECggEAFfMxXmvpKcmHvS8DE68FgSyITk/PQNxbSm1mb0iMVEf9
wc2GZUWziv+KwTZh50U5RHXQeAo84dAGTGk/kdDzd2VYa6+WVdKAJluw6dNoAF+l
jlf77EGeLqvJoRsqMdcwi3tKTt5jAr9nUGRCaNSvmz4GyR1cUdTgTTplOJh5mLnm
GGZQPVR87pex00QCTtQ7dggG/vg8QM+XbBeSAkWByeUDsrCo4pKbsY+kxKGCkhV0
3hxgHz2v7QZ8kanbgVPZzB76tOxRf9ghUQ5TFPc9kjgtQmn5ViqzMTXdotFXlE/u
n82BreEK03E9kWE3Uvt9jf21oJkQGQsRqTNYd8uIAQKBgQDyNOIIOnOgwt3coiZk
LYmo+F/zQQm6R8gebYWO1XCKLkMto9JaeJyEpehZQngMJvJc4r+4tWNFv8rcOHu5
/7SnteVUJK4RBXpKosLqswqD0ySEXzNLloumvxXz8HnXIiTSQ3rtISQSHzp0uunq
fkTzezc9ZRT08Jx6rmLvpKsJ3QKBgQDtklbus1fRQ/zbdddKbTfYMZsWSi7qQiA3
AKM7rY6wrS1gUJtVk9mhNgUe0uIvkkdQvtm/QKiSrtgGkre59g9sekZppCyWId6a
i1tlFvkG16Oc/TsAJDBNJgtWrkPDsjjHSx2VExTeRJ30XGd8lx3EEWHEbSboGPbf
G3gxm9a1nwKBgAdH06uPpj4k3WpubV0BiWvM24WCZPp2get4O1WJ0PI2ZcqPbBlQ
GtRZ8FwOhXFIEmz8W+r/eNZ153ErOXzj1NhdvWEEIT9dvMlVjypi62P/Cs/31KDn
C5edktlcVy1CV30CjkVmg6EP00ADBlkIJqZzA7wSt8iNGwcNCuhNON75AoGBAOGA
GXIj24RwM1Agr8UenHZix6HFsnh7YdazjT11RU7gYoTcnkUBvP5vpzaV10puX7D+
JvOJrTjmK3k5xJkIPaWq/rEBu6yZ45DwHEV9I81h3BSErX852nswVGznprzXq1tI
KoE5BoLfwMqU90nkqsVT9mgbb2W5ZINrsI/uK0RjAoGBAL+9L8iJx6/M4yQosTrQ
IL6SOxPU3HlfkqZzObiQ8vs7VdIpGhB1EMc9UEKe8RcnpTUeLsVeNHdiDZzRHtJ3
RxZS/AsjJtYAZ2f/maJhgQsh6JN1AceDhMhx1iTsjr/ockyUZuXKiCr5Q43tzrMJ
tMrOrkquEUSn+XDIomat7Bnw
-----END PRIVATE KEY-----

View File

@ -63,10 +63,12 @@ type RedisConfig struct {
// 微信支付客户端配置 // 微信支付客户端配置
type WxpayClientConfig struct { type WxpayClientConfig struct {
MchId string `json:"mchid" yaml:"mchid"` // 商户id MchId string `json:"mchid" yaml:"mchid"` // 商户id
SerialNo string `json:"serial_no" yaml:"serial_no"` // 商户API证书的证书序列号 AppId string `json:"appid" yaml:"appid"` // appid
ApiV3Key string `json:"apiv3_key" yaml:"apiv3_key"` // APIv3Key商户平台获取 SerialNo string `json:"serial_no" yaml:"serial_no"` // 商户API证书的证书序列号
PrivateKey string `json:"private_key" yaml:"private_key"` // 商户API证书下载后私钥 apiclient_key.pem 读取后的字符串内容 ApiV3Key string `json:"apiv3_key" yaml:"apiv3_key"` // APIv3Key商户平台获取
PrivateKeyPath string `json:"private_key_path" yaml:"private_key_path"` // 商户API证书下载后私钥 apiclient_key.pem 读取后的字符串内容
NotifyUrl string `json:"notify_url" yaml:"notify_url"` // 回调地址
} }
// 账号相关验密配置 // 账号相关验密配置

View File

@ -1,24 +1,117 @@
package wxpaycli package wxpaycli
import ( import (
"context"
"net/http"
"os"
"time"
"github.com/go-pay/gopay"
wxpay "github.com/go-pay/gopay/wechat/v3" wxpay "github.com/go-pay/gopay/wechat/v3"
"service/bizcommon/util" "service/bizcommon/util"
"service/library/configcenter" "service/library/configcenter"
"service/library/logger" "service/library/logger"
) )
const (
DefaultOrderTimeoutSeconds = 900 // 默认订单超时时间,单位: s
)
var defaultWxpayClient *WxpayClient
type WxpayClient struct { type WxpayClient struct {
*wxpay.ClientV3 *wxpay.ClientV3
AppId string `json:"app_id"`
NotifyUrl string `json:"notify_url"`
} }
func NewWxpayClient(cfg *configcenter.WxpayClientConfig) (cli *WxpayClient, err error) { func GetDefaultWxpayClient() *WxpayClient {
wxPayCli, err := wxpay.NewClientV3(cfg.MchId, cfg.SerialNo, cfg.ApiV3Key, cfg.PrivateKey) return defaultWxpayClient
}
func Init(cfg *configcenter.WxpayClientConfig) (err error) {
// private key
bs, err := os.ReadFile(cfg.PrivateKeyPath)
if err != nil {
logger.Error("real PrivateKeyPath fail, cfg: %v, err: %v", util.ToJson(cfg), err)
return
}
privateKey := string(bs)
wxpayCli, err := wxpay.NewClientV3(cfg.MchId, cfg.SerialNo, cfg.ApiV3Key, privateKey)
if err != nil { if err != nil {
logger.Error("NewClientV3 fail, cfg: %v, err: %v", util.ToJson(cfg), err) logger.Error("NewClientV3 fail, cfg: %v, err: %v", util.ToJson(cfg), err)
return return
} }
cli = &WxpayClient{
wxPayCli, defaultWxpayClient = &WxpayClient{
ClientV3: wxpayCli,
AppId: cfg.AppId,
NotifyUrl: cfg.NotifyUrl,
} }
return return
} }
// 验签
func (c *WxpayClient) ParseNotify(req *http.Request) (notify *wxpay.V3DecryptResult, err error) {
notifyReq, err := wxpay.V3ParseNotify(req)
if err != nil {
logger.Error("V3ParseNotify fail, notifyReq: %v, err: %v", util.ToJson(notifyReq), err)
return
}
if notifyReq == nil {
logger.Error("V3ParseNotify nil, err: %v", err)
return
}
notifyTmp, err := notifyReq.DecryptCipherText(string(c.ApiV3Key))
if err != nil {
logger.Error("DecryptCipherText fail, notifyTmp: %v, err: %v", util.ToJson(notifyTmp), err)
return
}
if notifyTmp == nil {
logger.Error("DecryptCipherText nil, err: %v", err)
return
}
logger.Info("Wxpay ParseNotify, %v", util.ToJson(notifyTmp))
notify = notifyTmp
return
}
// 微信支付 native支付
type NativePayParam struct {
Description string
OutTradeNo string // 商家订单id我们自己的订单id
TotalAmount int64 // 金额,单位:分
TimeOutSeconds int // 订单有效时间,单位:秒
}
func (c *WxpayClient) NativePay(ctx context.Context, param *NativePayParam) (wxpayNativeParamStr string, err error) {
if param.TimeOutSeconds <= 0 {
param.TimeOutSeconds = DefaultOrderTimeoutSeconds
}
bm := gopay.BodyMap{
"appid": c.AppId,
"description": param.Description,
"out_trade_no": param.OutTradeNo,
"time_expire": time.Now().Add(time.Second * time.Duration(param.TimeOutSeconds)).Format(time.RFC3339),
"notify_url": c.NotifyUrl,
"amount": gopay.BodyMap{
"total": param.TotalAmount,
"currency": "CNY",
},
}
resp, err := c.V3TransactionNative(ctx, bm)
if err != nil {
return
}
if resp.Code != wxpay.Success {
logger.Info("wxpay NativePay fail, code: %v, error: %v, response: %v", resp.Code, resp.Error, util.ToJson(resp.Response))
return
}
wxpayNativeParamStr = resp.Response.CodeUrl
logger.Info("wxpay NativePay success, code: %v, error: %v, response: %v", resp.Code, resp.Error, util.ToJson(resp.Response))
return
}

View File

@ -0,0 +1,42 @@
package wxpaycli
import (
"context"
"fmt"
"os"
"service/app/mix/conf"
"service/library/configcenter"
"service/library/idgenerator"
"testing"
)
func TestMain(m *testing.M) {
cfg := new(conf.ConfigSt)
err := configcenter.LoadConfig("/Users/erwin/wishpalv2/service/etc/mix/mix-test.yaml", cfg)
if err != nil {
fmt.Printf("%v\n", err)
}
err = Init(cfg.Wxpay)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
m.Run()
}
func TestWxpayClient_NativePay(t *testing.T) {
cli := GetDefaultWxpayClient()
resp, err := cli.NativePay(context.Background(), &NativePayParam{
Description: "测试哈哈",
OutTradeNo: idgenerator.GenOrderId(),
TotalAmount: 1,
TimeOutSeconds: 3600 * 24,
})
if err != nil {
fmt.Println(err.Error())
return
}
t.Log(fmt.Sprintf("%#v", resp))
return
}