k.zhang 1 year ago
parent
commit
9d6373691a

BIN
apiclient_cert.p12


+ 1 - 0
apis/shanghu/base.go

@@ -36,5 +36,6 @@ func InitShangHuRouter(engine *gin.RouterGroup) {
 		v1.POST("/merchant/update/whxy", UpdateMerchantCardWXYZ)            //xyz                               //更新坐标
 		v1.POST("/merchant/user/code", MerchantUserCode)                    //
 		v1.POST("/account/amount", GetAccountAmount)                        //获取金额
+		v1.POST("/cash/out", PayCashOut)                                    //提现
 	}
 }

+ 13 - 3
apis/shanghu/models/pay.go

@@ -20,7 +20,17 @@ type UnifiedOrderReply struct {
 }
 
 type PayCashOutRequest struct {
-	OpenId   string `json:"open_id"`
-	UserName string `json:"user_name"` //收款人姓名
-	CashType string `json:"cash_type"`
+	OpenId      string          `json:"open_id"`
+	UserName    string          `json:"user_name"`    //收款人姓名
+	Appid       string          `json:"appid"`        //
+	Amount      decimal.Decimal `json:"amount"`       //金额
+	AccountType string          `json:"account_type"` //账号类型  client merchant
+}
+
+type TransferDetailList struct {
+	OutDetailNo    string `json:"out_detail_no"`
+	TransferAmount int64  `json:"transfer_amount"`
+	TransferRemark string `json:"transfer_remark"`
+	Openid         string `json:"openid"`
+	UserName       string `json:"user_name"`
 }

+ 209 - 28
apis/shanghu/pay.go

@@ -15,6 +15,7 @@ import (
 	wechatV3 "github.com/go-pay/gopay/wechat/v3"
 	"github.com/shopspring/decimal"
 	"net/http"
+	"reflect"
 	"strconv"
 	"time"
 )
@@ -129,54 +130,205 @@ func UnifiedOrder(c *gin.Context) {
 
 func PayCashOut(c *gin.Context) {
 	var inData models.PayCashOutRequest
-	//var sqlData
+	var merchantAccountSql shanghu.MerchantAccount
+	var clientAccountSql shanghu.MerchantClientAccount
+	var cashOut shanghu.CashOut
+	var trans []models.TransferDetailList
+	var transDetail models.TransferDetailList
 
-	if inData.CashType == "client" {
-		//先设置账户状态 为提现中,提现中不允许再操作,提现金额默认全部
+	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} //提现中、金额待扣减
 
-	} else if inData.CashType == "merchant" {
+	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
+		}
+	} 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
+		}
 	} else {
-		app.Error(c, 400, errors.New("类型错误"), "类型错误")
+		app.Error(c, 400, errors.New("账户类型错误"), "账户类型错误")
 		return
 	}
 
-	client := NewWechatService()
+	clientV3, err := NewWechatServiceV3(inData.Appid)
+	if err != nil {
+		app.Error(c, 400, err, err.Error())
+		return
+	}
 
-	err := client.AddCertPkcs12FilePath("./apiclient_cert.p12")
+	//clientV3.WxPublicKeyMap()
+	userName, err := clientV3.V3EncryptText(inData.UserName)
 	if err != nil {
 		app.Error(c, 400, err, err.Error())
 		return
 	}
 
-	bm := make(gopay.BodyMap)
+	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 = "报销"
 
-	partnerTradeNo := common.GetRandomString(32)
+	trans = append(trans, transDetail)
 
-	bm.Set("nonce_str", common.GetRandomString(32))
-	bm.Set("partner_trade_no", partnerTradeNo)
-	bm.Set("openid", inData.OpenId)
-	bm.Set("check_name", "FORCE_CHECK")
-	bm.Set("re_user_name", inData.UserName)
-	bm.Set("amount", 30)   // 企业付款金额,单位为分
-	bm.Set("desc", "测试转账") // 企业付款备注,必填。注意:备注中的敏感词会被转成字符*
-	bm.Set("spbill_create_ip", "127.0.0.1")
+	//transBody, err := json.Marshal(transDetail)
+	//if err != nil {
+	//	app.Error(c, 500, err, err.Error())
+	//	return
+	//}
 
-	wxResp, err := client.Transfer(c, bm)
+	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 = inData.Amount.Mul(decimal.NewFromFloat32(0.1))
+	cashOutInfo, err := cashOutCreate.Create()
 	if err != nil {
-		app.Error(c, 400, err, "提现失败")
+		app.Error(c, 400, err, err.Error())
 		return
 	}
 
-	if wxResp.ReturnCode != "SUCCESS" {
-		app.Error(c, 400, errors.New(wxResp.ReturnMsg), "提现")
+	//client := NewWechatServiceAppid(inData.Appid)
+
+	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(cashOutCreate.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())
+
+	//{
+	//	"appid": "wxf636efh567hg4356",
+	//	"out_batch_no": "plfk2020042013",
+	//	"batch_name": "2019年1月深圳分部报销单",
+	//	"batch_remark": "2019年1月深圳分部报销单",
+	//	"total_amount": 4000000,
+	//	"total_num": 200,
+	//	"transfer_detail_list": [
+	//{
+	//"out_detail_no": "x23zy545Bd5436",
+	//"transfer_amount": 200000,
+	//"transfer_remark": "2020年4月报销",
+	//"openid": "o-MYE42l80oelYMDE34nYD456Xoy",
+	//"user_name": "757b340b45ebef5467rter35gf464344v3542sdf4t6re4tb4f54ty45t4yyry45"
+	//}
+	//]
+	//}
+
+	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 wxResp.ResultCode != "SUCCESS" {
-		app.Error(c, 400, errors.New(wxResp.ErrCode+"--"+wxResp.ErrCodeDes), "提现")
+
+	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()
+
+	//wxResp, err := client.Transfer(c, bm)
+	//if err != nil {
+	//	cashOut.ID = cashOutInfo.ID
+	//	cashOut.FailRes = err.Error()
+	//	cashOut.Status = 2 //提现失败
+	//	cashOut.UpdateMerchantStatus()
+	//
+	//	app.Error(c, 400, err, "提现失败")
+	//	return
+	//}
+	//
+	//if wxResp.ReturnCode != "SUCCESS" {
+	//	cashOut.ID = cashOutInfo.ID
+	//	cashOut.FailRes = wxResp.ReturnMsg
+	//	cashOut.Status = 2 //提现失败
+	//	cashOut.UpdateMerchantStatus()
+	//	app.Error(c, 400, errors.New(wxResp.ReturnMsg), "提现")
+	//	return
+	//}
+	//if wxResp.ResultCode != "SUCCESS" {
+	//	cashOut.ID = cashOutInfo.ID
+	//	cashOut.FailRes = wxResp.ErrCode + "--" + wxResp.ErrCodeDes
+	//	cashOut.Status = 2 //提现失败
+	//	cashOut.UpdateMerchantStatus()
+	//	app.Error(c, 400, errors.New(wxResp.ErrCode+"--"+wxResp.ErrCodeDes), "提现")
+	//	return
+	//}
+
+	app.OK(c, nil, app.Success)
+
 }
 
 func PayCallBack(c *gin.Context) {
@@ -266,9 +418,19 @@ func NewWechatService() *wechat.Client {
 	return client
 }
 
-func NewWechatServiceV3() (*wechatV3.ClientV3, error) {
+func NewWechatServiceAppid(appid string) *wechat.Client {
 
-	key := `MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQD/RTrS1Tycrskr
+	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
@@ -293,9 +455,9 @@ egqXhSaSLwgQNFUUCWDbl4pT3ZjRqxVQdcgwpjBkzratkO10dUOV7WoM6vbWuvwz
 +vXf3ywE2MNUpsgmciROB3+O1LkhqBsVg9UwZmM+yQKBgQD6yBvxOaHXBSZncz+m
 3SpdbcRjK82i3IUJ3sl18J7YEPer0FclsUcQOluqBbHTOAr8letWPrKnjZGuyFfg
 gAjwa8uLSyfEEcWb9WcObvud2GNxS/LiI0GnW9ittvT29JvOhmUUtnty5/TiWIsi
-slm2kO53RSw9brymV8PAX2+SXg==`
-
-	client, err := wechatV3.NewClientV3("wx25357518f710b8ce", "219B3AF3B5F17D4C2F145EE318188708318DD7BD", "9x9ydkdk0nzsa4mr2ucq75grlvt9n8l3", key)
+slm2kO53RSw9brymV8PAX2+SXg==
+-----END PRIVATE KEY-----`
+	client, err := wechatV3.NewClientV3("1501641641", "219B3AF3B5F17D4C2F145EE318188708318DD7BD", "9x9ydkdk0nzsa4mr2ucq75grlvt9n8l3", key)
 	if err != nil {
 		return nil, err
 	}
@@ -311,3 +473,22 @@ slm2kO53RSw9brymV8PAX2+SXg==`
 
 	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
+}

+ 1 - 0
go.mod

@@ -39,6 +39,7 @@ require (
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
 	github.com/smartystreets/goconvey v1.7.2 // indirect
 	github.com/ugorji/go/codec v1.2.7 // indirect
+	github.com/wechatpay-apiv3/wechatpay-go v0.2.18 // indirect
 	golang.org/x/crypto v0.14.0 // indirect
 	golang.org/x/net v0.10.0 // indirect
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect

+ 6 - 0
go.sum

@@ -6,6 +6,7 @@ github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWX
 github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
 github.com/Unknwon/goconfig v0.0.0-20200908083735-df7de6a44db8 h1:1TrMV1HmBApBbM+Hy7RCKZD6UlYWYIPPfoeXomG7+zE=
 github.com/Unknwon/goconfig v0.0.0-20200908083735-df7de6a44db8/go.mod h1:wngxua9XCNjvHjDiTiV26DaKDT+0c63QR6H5hjVUUxw=
+github.com/agiledragon/gomonkey v2.0.2+incompatible/go.mod h1:2NGfXu1a80LLr2cmWXGBDaHEjb1idR6+FVlX5T3D9hw=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
@@ -169,6 +170,7 @@ github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -176,11 +178,15 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
 github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
 github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
 github.com/unrolled/secure v1.10.0 h1:TBNP42z2AB+2pW9PR6vdbqhlQuv1iTeSVzK1qHjOBzA=
 github.com/unrolled/secure v1.10.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40=
+github.com/wechatpay-apiv3/wechatpay-go v0.2.18 h1:vj5tvSmnEIz3ZsnFNNUzg+3Z46xgNMJbrO4aD4wP15w=
+github.com/wechatpay-apiv3/wechatpay-go v0.2.18/go.mod h1:A254AUBVB6R+EqQFo3yTgeh7HtyqRRtN2w9hQSOrd4Q=
 go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=

+ 129 - 0
models/shanghu/cash.out.go

@@ -0,0 +1,129 @@
+package shanghu
+
+import (
+	orm "duoduo/database"
+	"github.com/shopspring/decimal"
+	"time"
+)
+
+type CashOut struct {
+	ID               int64           `gorm:"column:id;type:bigint(20);primary_key" json:"id"`                        // Id
+	OpenID           string          `gorm:"column:open_id;type:varchar(255)" json:"open_id"`                        // Openid
+	AppID            string          `gorm:"column:app_id;type:varchar(255)" json:"app_id"`                          // Appid
+	Status           int             `gorm:"column:status;type:int(11)" json:"status"`                               // 0-待提现 1-提现中 2-提现失败 3-钱已到账待账户扣减 99-提现成功
+	FailRes          string          `gorm:"column:fail_res;type:varchar(255)" json:"fail_res"`                      // 失败原因
+	PartnerTradeNo   string          `gorm:"column:partner_trade_no;type:varchar(50)" json:"partner_trade_no"`       // 商户卡提现id
+	Amount           decimal.Decimal `gorm:"column:amount;type:decimal(10,2)" json:"amount"`                         // 金额
+	CornTime         time.Time       `gorm:"column:corn_time;type:datetime;default:null" json:"corn_time"`           // 定时任务下次查询状态时间
+	CreateBy         int64           `gorm:"column:create_by;type:bigint(20)" json:"create_by"`                      // 创建者
+	UpdateBy         int64           `gorm:"column:update_by;type:bigint(20)" json:"update_by"`                      // 更新者
+	CreatedAt        time.Time       `gorm:"column:created_at;type:datetime(3)" json:"created_at"`                   // 创建时间
+	UpdatedAt        time.Time       `gorm:"column:updated_at;type:datetime(3)" json:"updated_at"`                   // 最后更新时间
+	DeletedAt        time.Time       `gorm:"column:deleted_at;type:datetime(3);default:null" json:"deleted_at"`      // 删除时间
+	Fee              decimal.Decimal `gorm:"column:fee;type:decimal(10,2)" json:"fee"`                               // 手续费
+	WxPartnerTradeNo string          `gorm:"column:wx_partner_trade_no;type:varchar(50)" json:"wx_partner_trade_no"` // wx商户卡提现id
+
+}
+
+func (m *CashOut) TableName() string {
+	return "cash_out"
+}
+
+func (m *CashOut) GetCashOutByStatusNum(status []int) int {
+	var count int
+
+	table := orm.ShMysql.Table(m.TableName())
+	table = table.Where("open_id = ? and status in (?) and app_id = ? ", m.OpenID, status, m.AppID)
+	table.Count(&count)
+
+	return count
+
+}
+
+func (u *CashOut) Create() (CashOut, error) {
+	var doc CashOut
+	var err error
+
+	doc = *u
+	err = orm.ShMysql.Table(u.TableName()).Create(&doc).Error
+	if err != nil {
+		return doc, err
+	}
+
+	return doc, nil
+}
+
+func (m *CashOut) UpdateMerchantStatus() error {
+
+	if err := orm.ShMysql.Table(m.TableName()).Model(&m).Where("id = ? ", m.ID).Updates(
+		map[string]interface{}{
+			"status":     m.Status,
+			"fail_res":   m.FailRes,
+			"updated_at": time.Now()}).Error; err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *CashOut) UpdateCashOutWxBachNo() error {
+
+	if err := orm.ShMysql.Table(m.TableName()).Model(&m).Where("partner_trade_no = ? ", m.PartnerTradeNo).Updates(
+		map[string]interface{}{
+			"wx_partner_trade_no": m.WxPartnerTradeNo,
+			"updated_at":          time.Now()}).Error; err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *CashOut) UpdateCashOut() error {
+
+	if err := orm.ShMysql.Table(m.TableName()).Model(&m).Where("partner_trade_no = ? ", m.PartnerTradeNo).Updates(
+		map[string]interface{}{
+			"wx_partner_trade_no": m.WxPartnerTradeNo,
+			"updated_at":          time.Now()}).Error; err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *CashOut) UpdateCashOutStatus() error {
+
+	if err := orm.ShMysql.Table(m.TableName()).Model(&m).Where("partner_trade_no = ? ", m.PartnerTradeNo).Updates(
+		map[string]interface{}{
+			"status":     m.Status,
+			"updated_at": time.Now()}).Error; err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *CashOut) UpdateCashOutStatusClose() error {
+
+	if err := orm.ShMysql.Table(m.TableName()).Model(&m).Where("partner_trade_no = ? ", m.PartnerTradeNo).Updates(
+		map[string]interface{}{
+			"status":     m.Status,
+			"fail_res":   m.FailRes,
+			"updated_at": time.Now()}).Error; err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *CashOut) GetCashOutByStatus() (CashOut, error) {
+	var doc CashOut
+
+	table := orm.ShMysql.Table(m.TableName())
+	table = table.Where("status = ? ", m.Status)
+
+	if err := table.Select("*").First(&doc).Error; err != nil {
+		return doc, err
+	}
+	return doc, nil
+
+}

+ 20 - 0
models/shanghu/client.account.go

@@ -36,3 +36,23 @@ func (m *MerchantClientAccount) GetClientAccount() (MerchantClientAccount, error
 	return doc, nil
 
 }
+
+func (m *MerchantClientAccount) UpdateClientAccountStatusById(amount decimal.Decimal) error {
+	var merchantClientLog MerchantClientAccountLog
+	tx := orm.ShMysql.Begin()
+	var err error
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+		} else {
+			tx.Commit()
+		}
+	}()
+
+	merchantClientLog.ClientOpenID = m.ClientOpenID
+	merchantClientLog.Amount = amount
+	merchantClientLog.TransType = 2 //提现中
+
+	return nil
+
+}

+ 1 - 1
models/shanghu/client.account.log.go

@@ -11,7 +11,7 @@ type MerchantClientAccountLog struct {
 	ReviewAmountPre   decimal.Decimal `gorm:"column:review_amount_pre;type:decimal(10,2)" json:"review_amount_pre"`     // 审核资金交易前
 	AmountPre         decimal.Decimal `gorm:"column:amount_pre;type:decimal(10,2)" json:"amount_pre"`                   // 交易前
 	AmountAfter       decimal.Decimal `gorm:"column:amount_after;type:decimal(10,2)" json:"amount_after"`               // 交易后
-	TransType         int             `gorm:"column:trans_type;type:int(11)" json:"trans_type"`                         // 交易类型  1-买卡入账 2-提现
+	TransType         int             `gorm:"column:trans_type;type:int(11)" json:"trans_type"`                         // 交易类型  1-买卡入账 99-提现
 	ClientOpenID      string          `gorm:"column:client_open_id;type:varchar(255)" json:"client_open_id"`            // 商户openid
 	CreateBy          int64           `gorm:"column:create_by;type:bigint(20)" json:"create_by"`                        // 创建者
 	UpdateBy          int64           `gorm:"column:update_by;type:bigint(20)" json:"update_by"`                        // 更新者

+ 71 - 0
report/cash.out.go

@@ -0,0 +1,71 @@
+package report
+
+import (
+	"context"
+	apiShanghu "duoduo/apis/shanghu"
+	"duoduo/models/shanghu"
+	"encoding/json"
+	"fmt"
+	"github.com/go-pay/gopay"
+	"time"
+)
+
+func CashOut() {
+	for true {
+		var cashOut shanghu.CashOut
+		bm := make(gopay.BodyMap)
+
+		cashOut.Status = 1 // 提现中
+		cashOutInfo, err := cashOut.GetCashOutByStatus()
+		if err != nil {
+			fmt.Println("GetCashOutByStatus err ", err.Error())
+			break
+		}
+		wechatV3, err := apiShanghu.NewWechatServiceV3("")
+		if err != nil {
+			break
+		}
+		bm.Set("need_query_detail", false)
+
+		transferBatch, err := wechatV3.V3TransferMerchantQuery(context.Background(), cashOutInfo.PartnerTradeNo, bm)
+		if err != nil {
+			break
+		}
+
+		by, _ := json.Marshal(transferBatch)
+		fmt.Println(string(by))
+
+		if transferBatch.Code != 0 {
+			break
+		}
+
+		if transferBatch.Response.TransferBatch.FailNum == 0 && transferBatch.Response.TransferBatch.BatchStatus == "FINISHED" { //转账成功
+			cashOut.PartnerTradeNo = transferBatch.Response.TransferBatch.OutBatchNo
+			cashOut.Status = 3
+			err = cashOut.UpdateCashOutStatus()
+			if err != nil {
+				break
+			}
+
+		} else if transferBatch.Response.TransferBatch.FailNum > 0 { //失败
+			cashOut.PartnerTradeNo = transferBatch.Response.TransferBatch.OutBatchNo
+			cashOut.Status = 2
+			err = cashOut.UpdateCashOutStatus()
+			if err != nil {
+				break
+			}
+
+		} else if transferBatch.Response.TransferBatch.BatchStatus == "CLOSE" {
+			cashOut.PartnerTradeNo = transferBatch.Response.TransferBatch.OutBatchNo
+			cashOut.Status = 2
+			cashOut.FailRes = transferBatch.Response.TransferBatch.CloseReason
+			err = cashOut.UpdateCashOutStatusClose()
+			if err != nil {
+				break
+			}
+		} else {
+			time.Sleep(time.Second * 1)
+			continue
+		}
+	}
+}

+ 1 - 0
report/report.go

@@ -11,5 +11,6 @@ const (
 func SysCronStart() {
 	c := cron.New()
 	c.AddFunc(cronMinuteStart, ClientAccount) //每分钟同步一下订单
+	c.AddFunc(cronMinuteStart, CashOut)       //每分钟同步一下订单
 	c.Start()
 }