package shanghu import ( "duoduo/apis/common" "duoduo/apis/shanghu/models" "duoduo/models/shanghu" "duoduo/tools" "duoduo/tools/app" "encoding/xml" "errors" "fmt" "github.com/gin-gonic/gin" "github.com/go-pay/gopay" "github.com/go-pay/gopay/wechat" wechatV3 "github.com/go-pay/gopay/wechat/v3" "github.com/shopspring/decimal" "net/http" "reflect" "strconv" "time" ) type wechatCallbackResp struct { XMLName xml.Name `xml:"xml"` ReturnCode Cdata `xml:"return_code"` ReturnMsg Cdata `xml:"return_msg"` } type Cdata struct { Value string `xml:",cdata"` } var ( successResp = &wechatCallbackResp{ReturnCode: Cdata{Value: "SUCCESS"}, ReturnMsg: Cdata{Value: "OK"}} failResp = &wechatCallbackResp{ReturnCode: Cdata{Value: "FAIL"}, ReturnMsg: Cdata{Value: "数据处理异常"}} ) func UnifiedOrder(c *gin.Context) { var inData models.UnifiedOrderRequest var sqlData shanghu.ClientPayTrans var outData models.UnifiedOrderReply err := c.ShouldBindJSON(&inData) if err != nil { app.Error(c, 400, err, err.Error()) return } if inData.ClientOpenId == "" && inData.ClientOpenId == "omaEn40y0w3-KOhz0L8ZgX27ujKI" { app.OK(c, outData, app.Success) } //校验 防止同一笔记录存在 sqlData.RequestID = inData.RequestId if sqlData.GetRequestNum() > 0 { app.Error(c, 400, errors.New("交易已存在"), "交易已存在") return } //校验金额 // 检查微信相关参数 if !inData.Amount.GreaterThan(decimal.NewFromInt(0)) { app.Error(c, 400, errors.New("amount:金额必须大于0"), "amount:金额必须大于0") return } if inData.Amount.Round(2).String() != inData.Amount.String() { app.Error(c, 400, errors.New("total_fee:金额最多只能保留两位小数"), "total_fee:金额最多只能保留两位小数") return } //创建支付记录 sqlData.ClientOpenID = inData.ClientOpenId sqlData.RequestID = inData.RequestId sqlData.CreatedAt = time.Now() sqlData.UpdatedAt = time.Now() sqlData.Amount = inData.Amount sqlData.OutTradeNo = strconv.FormatInt(inData.MerchantCardId, 10) + strconv.FormatInt(time.Now().UnixNano(), 10) sqlData.Status = 1 //未支付 sqlData.MerchantCardID = inData.MerchantCardId sqlData.InvitationCode = inData.InvitationCode _, err = sqlData.Create() if err != nil { app.Error(c, 400, err, "创建支付失败") return } fmt.Println(sqlData.OutTradeNo) bm := make(gopay.BodyMap) bm.Set("nonce_str", common.GetRandomString(32)) bm.Set("body", "商户卡") bm.Set("out_trade_no", sqlData.OutTradeNo) bm.Set("total_fee", inData.Amount.Mul(decimal.NewFromInt(100)).IntPart()) bm.Set("spbill_create_ip", "127.0.0.1") bm.Set("notify_url", "https://shisanmiao.com/v1/client/pay/callback") bm.Set("device_info", "WEB") bm.Set("trade_type", "JSAPI") bm.Set("sign_type", wechat.SignType_MD5) bm.Set("openid", inData.ClientOpenId) client := NewWechatService() //请求支付下单,成功后得到结果 wxResp, err := client.UnifiedOrder(c, bm) if err != nil { app.Error(c, 400, err, "下单失败") return } if wxResp.ReturnCode != "SUCCESS" { app.Error(c, 400, errors.New(wxResp.ReturnMsg), "下单失败") return } if wxResp.ResultCode != "SUCCESS" { app.Error(c, 400, errors.New(wxResp.ErrCode+"--"+wxResp.ErrCodeDes), "下单失败") return } timestamp := strconv.FormatInt(time.Now().Unix(), 10) pac := "prepay_id=" + wxResp.PrepayId paySign := wechat.GetMiniPaySign("wx25357518f710b8ce", wxResp.NonceStr, pac, wechat.SignType_MD5, timestamp, "1RKRJBVH4vaRrF0XPW9GX2M3ZSImukIz") outData.Timestamp = timestamp outData.NonceStr = wxResp.NonceStr outData.Package = pac outData.PaySign = paySign outData.SignType = wechat.SignType_MD5 //merchant, count, err := sqlData.GetOpenIdList(pageSize, pageIndex) //if err != nil { // app.Error(c, 500, err, err.Error()) // return //} app.OK(c, outData, app.Success) } func PayCashOut(c *gin.Context) { var inData models.PayCashOutRequest var merchantAccountSql shanghu.MerchantAccount var clientAccountSql shanghu.MerchantClientAccount var cashOut shanghu.CashOut var trans []models.TransferDetailList var transDetail models.TransferDetailList err := c.ShouldBindJSON(&inData) if err != nil { app.Error(c, 400, err, err.Error()) return } if inData.Appid == "" { //appid 不能为空 app.Error(c, 400, errors.New("Appid不能为空"), "Appid不能为空") return } if inData.Amount.Cmp(decimal.NewFromInt(500)) > 0 { app.Error(c, 400, errors.New("单笔金额不能大于500"), "单笔金额不能大于500") return } if inData.Amount.Cmp(decimal.NewFromInt(1)) < 0 { app.Error(c, 400, errors.New("单笔金额不能小于1"), "单笔金额不能大于1") return } // cashOut.OpenID = inData.OpenId cashOut.AppID = inData.Appid cashOut.Status = 1 //提现中 status := []int{1, 3} //提现中、金额待扣减 cashNum := cashOut.GetCashOutByStatusNum(status) if cashNum > 0 { app.Error(c, 400, errors.New("有一笔交易正在提现中"), "有一笔交易正在提现中") return } //校验金额是否够 if inData.AccountType == "client" { clientAccountSql.ClientOpenID = inData.OpenId clientAccountInfo, err := clientAccountSql.GetClientAccount() if err != nil { app.Error(c, 400, err, err.Error()) return } if inData.Amount.Cmp(clientAccountInfo.Amount) > 0 { app.Error(c, 400, errors.New("账号余额不够"), "账号余额不够") return } var client shanghu.MerchantClientUser client.Code = "7jb6" clientInfo, _ := client.GetUserInfoByCode() if clientInfo.ClientOpenID == inData.OpenId { cashOut.Fee = decimal.NewFromInt(0) } else { cashOut.Fee = inData.Amount.Mul(decimal.NewFromFloat32(0.05)) } } else if inData.AccountType == "merchant" { merchantAccountSql.MerchantOpenID = inData.OpenId merchantAccountInfo, err := merchantAccountSql.GetMerchantAccount() if err != nil { app.Error(c, 400, err, err.Error()) return } if inData.Amount.Cmp(merchantAccountInfo.Amount) > 0 { app.Error(c, 400, errors.New("账号余额不够"), "账号余额不够") return } cashOut.Fee = decimal.NewFromInt(0) } else { app.Error(c, 400, errors.New("账户类型错误"), "账户类型错误") return } clientV3, err := NewWechatServiceV3(inData.Appid) if err != nil { app.Error(c, 400, err, err.Error()) return } userName, err := clientV3.V3EncryptText(inData.UserName) if err != nil { app.Error(c, 400, err, err.Error()) return } transDetail.OutDetailNo = common.GetRandomString(32) transDetail.TransferAmount = inData.Amount.Sub(cashOut.Fee).Mul(decimal.NewFromInt(100)).IntPart() transDetail.UserName = userName transDetail.Openid = inData.OpenId transDetail.TransferRemark = "提现" trans = append(trans, transDetail) partnerTradeNo := common.GetRandomString(32) var cashOutCreate shanghu.CashOut //创建提现记录 cashOutCreate.AppID = inData.Appid cashOutCreate.Status = 1 //提现中 cashOutCreate.OpenID = inData.OpenId cashOutCreate.Amount = inData.Amount cashOutCreate.CreatedAt = time.Now() cashOutCreate.UpdatedAt = time.Now() cashOutCreate.PartnerTradeNo = partnerTradeNo cashOutCreate.Fee = cashOut.Fee cashOutInfo, err := cashOutCreate.Create() if err != nil { app.Error(c, 400, err, err.Error()) return } var bMap []gopay.BodyMap bm := make(gopay.BodyMap) bm.Set("appid", inData.Appid) bm.Set("out_batch_no", partnerTradeNo) bm.Set("batch_name", "体现") bm.Set("batch_remark", "提现") bm.Set("total_amount", inData.Amount.Sub(cashOut.Fee).Mul(decimal.NewFromInt(100)).IntPart()) bm.Set("total_num", 1) bMap = append(bMap, structToMap(&transDetail)) bm.Set("transfer_detail_list", bMap) bm.Set("transfer_scene_id", "1001") fmt.Println(bm.JsonBody()) reply, err := clientV3.V3Transfer(c, bm) if err != nil { cashOut.ID = cashOutInfo.ID cashOut.FailRes = err.Error() cashOut.Status = 2 //提现失败 cashOut.UpdateMerchantStatus() app.Error(c, 500, err, err.Error()) return } if reply.Code != 0 { cashOut.ID = cashOutInfo.ID cashOut.FailRes = reply.Error cashOut.Status = 2 //提现失败 cashOut.UpdateMerchantStatus() app.Error(c, 500, errors.New(reply.Error), reply.Error) return } cashOut.WxPartnerTradeNo = reply.Response.BatchId cashOut.PartnerTradeNo = reply.Response.OutBatchNo cashOut.UpdateCashOutWxBachNo() app.OK(c, nil, app.Success) } func PayCallBack(c *gin.Context) { var payLog shanghu.PayCallbackLog var payTrans shanghu.ClientPayTrans wxNotify, err := wechat.ParseNotifyToBodyMap(c.Request) if err != nil { c.XML(http.StatusOK, failResp) return } //通知回调log payLog.CallBackLog = wxNotify.JsonBody() payLog.ThirdTradeNo = wxNotify.Get("transaction_id") payLog.OutTradeNo = wxNotify.Get("out_trade_no") payLog.CreatedAt = time.Now() payLog.UpdatedAt = time.Now() _, err = payLog.Create() if err != nil { c.XML(http.StatusOK, failResp) return } if wxNotify.Get("return_code") != "SUCCESS" || wxNotify.Get("result_code") != "SUCCESS" { payLog.ErrLog = "微信返回错误:" + wxNotify.Get("return_code") + "--" + wxNotify.Get("result_code") payLog.UpdateMerchant() c.XML(http.StatusOK, failResp) return } //校验金额 payTrans.OutTradeNo = wxNotify.Get("out_trade_no") payTransInfo, err := payTrans.GetPayTransByTradeNo() if err != nil { payLog.ErrLog = "查询交易信息错误:" + " err=" + err.Error() payLog.UpdateMerchant() c.XML(http.StatusOK, failResp) return } // 判断金额与支付流水是否一致 totalFee, err := decimal.NewFromString(wxNotify.Get("total_fee")) if err != nil { payLog.ErrLog = "解析总金额报错:" + "err=" + err.Error() payLog.UpdateMerchant() c.XML(http.StatusOK, failResp) return } if !totalFee.Equal(payTransInfo.Amount.Mul(decimal.NewFromInt(100))) { payLog.ErrLog = "验证金额报错:total_fee=" + wxNotify.Get("total_fee") + " amount=" + payTransInfo.Amount.String() payLog.UpdateMerchant() c.XML(http.StatusOK, failResp) return } // 解析支付时间 timeEnd, err := time.ParseInLocation("20060102150405", wxNotify.Get("time_end"), tools.TimeLocation) if err != nil { payLog.ErrLog = "付款时间解析出错:err=" + err.Error() payLog.UpdateMerchant() c.XML(http.StatusOK, failResp) return } payTrans.ThirdTradeNo = wxNotify.Get("transaction_id") payTrans.Status = 2 //支付成功 payTrans.PayTime = timeEnd payTrans.AccountStatus = 1 //未分账 err = payTrans.UpdatePayTransByTradeNo() if err != nil { payLog.ErrLog = "更新支付状态失败:err=" + err.Error() payLog.UpdateMerchant() c.XML(http.StatusOK, failResp) return } c.XML(http.StatusOK, successResp) } func NewWechatService() *wechat.Client { client := wechat.NewClient("wx25357518f710b8ce", "1501641641", "1RKRJBVH4vaRrF0XPW9GX2M3ZSImukIz", true) //设置国家 client.SetCountry(wechat.China) return client } func NewWechatServiceAppid(appid string) *wechat.Client { client := wechat.NewClient(appid, "1501641641", "1RKRJBVH4vaRrF0XPW9GX2M3ZSImukIz", true) //设置国家 client.SetCountry(wechat.China) return client } func NewWechatServiceV3(appid string) (*wechatV3.ClientV3, error) { key := `-----BEGIN PRIVATE KEY----- MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQD/RTrS1Tycrskr SkuzdLNmOic4UklFtoyKtKDIeklbQFvynJBC1wYaitoOqFkdGWagkcuqRqJOcgEQ T8DRDD/rnVKTrnntevUSW3vZYwMscomK3dgjRRui5GLY5wYHuKhp02bldLrhPKNy wT5IYhoDwQLmf5IrURbtT8bKpdC+NyEEfw+d/mHMK56w80YKfzFtSmjoB9SkWDLg YFBlw91C78E73uqhCKnArN7p2iJe+tsp7EGpDVcdowqcMKjY2YiNPY6ABZaI9YJx nRQs5Sjm6YSsJujMR5rQVZ3HCfLlzxLpPyHfqffbJ+vviend1PJ/ugJeM2hehn1d LrpKuWyHAgMBAAECggEAIbow6nhYGM+TLtATLnPF3ETkt7FPkxFqgk0ZTUOy+4aG X4jGGr60RL+Bzhv6Ijkf8SkyQp4whbLUZyZScIxwyZ2wsmiEHZd4V+OUeoV1fuLn P1zOWOKhoyUP1l630j9YqRrQZpLEuku7wMa9huzHSSWwT2odkvGU2OgIeO/to6P7 gIE4Q+3pNmTdaXJweUlVoz6sdqsSWAZ729SxzY34zcRyXI+feBQb9n/C1hr9+Ge4 KbBsvrECy0eUNiAT9f5dPwgwjyRpR9gJ8xxaezIYfxdpTTW3EB1Gf+xPX4X7ml2B zObPN2HEwLHhrwbOXLFP8F/62TK4fIGe2yXPIKIe8QKBgQD/t4Xy9pTFrmNhDLMe ZMHdj9XMoM/izcquI6rq4pGWLtnyxIcFuXiqvPszbJL+urCc1+HqfPAVScryEg5W b6AvLENVhC5E3GcZA50ciAr+PUrB1UXH0vbbc7HP/Mofsz61kxEW2SvKaPoKhW8M IavoVWDJkcLHnjM20Gt7tioEXwKBgQD/jZR7FDSB8VCRp687QcH4s0HyAWaRVhmC qId2xVbPFVlbm4BWcQVWPdsUei29sPPtcvzREFStKwTmu2FSUfoDv3uM4Nl2+h/Y pbhfuYfHd79UZa0OtT9njJHZcYPwKRmZQIVBVHSJFSZy2HSGLe9AdI7gAsNMx/Mm eqZDU79I2QKBgQCBSNoSIpTI9QgNkwwkO7DAQe5IDK3N71mffSz2oCIXGgza7n2N aV4WhIFEWIpg+yY7xfHUSeJgAPT4OiTBkqIb93b7j16NNhlxzh/qwuU78OUQ5rDm /EQOY4nsq9PM/ySfTIGBWb8IENcJ5rhkG8n8Jt5OSsF9hwBBoFIXM9w+ZQKBgQD1 rArw43Sy8uTskZKA2e96ggHEgBo1X9s4Y4GO6ZlRjQmRaoVPFGn4BZEGN4qfkGx/ egqXhSaSLwgQNFUUCWDbl4pT3ZjRqxVQdcgwpjBkzratkO10dUOV7WoM6vbWuvwz +vXf3ywE2MNUpsgmciROB3+O1LkhqBsVg9UwZmM+yQKBgQD6yBvxOaHXBSZncz+m 3SpdbcRjK82i3IUJ3sl18J7YEPer0FclsUcQOluqBbHTOAr8letWPrKnjZGuyFfg gAjwa8uLSyfEEcWb9WcObvud2GNxS/LiI0GnW9ittvT29JvOhmUUtnty5/TiWIsi slm2kO53RSw9brymV8PAX2+SXg== -----END PRIVATE KEY-----` client, err := wechatV3.NewClientV3("1501641641", "219B3AF3B5F17D4C2F145EE318188708318DD7BD", "9x9ydkdk0nzsa4mr2ucq75grlvt9n8l3", key) if err != nil { return nil, err } // 启用自动同步返回验签,并定时更新微信平台API证书(开启自动验签时,无需单独设置微信平台API证书和序列号) err = client.AutoVerifySign() if err != nil { return nil, err } // 打开Debug开关,输出日志,默认是关闭的 //client.DebugSwitch = gopay.DebugOn return client, nil } func structToMap(obj interface{}) gopay.BodyMap { result := make(gopay.BodyMap) value := reflect.ValueOf(obj).Elem() // 获取指针的值 typ := value.Type() // 获取类型信息 for i := 0; i < typ.NumField(); i++ { field := typ.Field(i) // 获取字段信息 tag := field.Tag.Get("json") // 获取标签(如果有) if tag != "" && !field.Anonymous { // 只处理非匿名字段且有标签的情况 key := field.Name // 默认使用字段名作为Key if tag != "-" { // 若标签不等于-则使用标签作为Key key = tag } result[key] = value.Field(i).Interface() // 存入Map } } return result }