package pay_service import ( "fmt" "github.com/chanxuehong/wechat/mch/mmpaymkttransfers" "github.com/chanxuehong/wechat/mch/mmpaymkttransfers/promotion" "go.uber.org/zap" "hudongzhuanjia/logger" "hudongzhuanjia/models" "hudongzhuanjia/utils" "math" "strconv" "time" ) func init() { go loopTransfer() } var transferDelayQueue = make(chan *transferDelayQueueParam, math.MaxInt8) type transferDelayQueueParam struct { first bool // 首次跳过 Retries int `json:"retries"` // 尝试次数 Delay int `json:"delay"` // 延迟时间, 单位second Amount int `json:"amount"` // 转账金额 Desc string `json:"desc"` // 转账描述 OpenId string `json:"open_id"` // 被转账人 PartnerTradeNo string `json:"partner_trade_no"` // 转账账单单号 } func loopTransfer() { //初始化 transfers, err := models.GetUserTransferByStatus(0, 1, 3) if err != nil { panic(err) } transferDelayQueue = make(chan *transferDelayQueueParam, math.MaxInt8) for _, transfer := range transfers { transferDelayQueue <- &transferDelayQueueParam{ first: true, Retries: 5, Delay: 2 * 60, Amount: transfer.PaymentAmount, Desc: transfer.Desc, OpenId: transfer.OpenId, PartnerTradeNo: transfer.PartnerTradeNo, } } defer func() { if errRec := recover(); errRec != nil { logger.Error("转账轮询 loop transfer panic", zap.Any("错误原因", errRec)) } loopTransfer() }() for { select { case param, ok := <-transferDelayQueue: if !ok { panic("转账延迟通道异常关闭") } // 尝试次数 if param.Retries <= 0 { logger.Info("微信转账尝试3次失败", zap.String("转账账单单号", param.PartnerTradeNo)) userTransfer := new(models.UserTransfer) userTransfer.Status = 4 _, err = userTransfer.UpdateByPartnerTradeNo(param.PartnerTradeNo) if err != nil { logger.Info("微信转账更新状态失败", zap.String("失败原因", err.Error()), zap.String("转账账单单号", param.PartnerTradeNo)) } continue } else { param.Retries-- } if !param.first { time.Sleep(time.Duration(param.Delay) * time.Second) } param.first = false res, err := TransferInfo(param.PartnerTradeNo) if err != nil { logger.Error("微信转账查询出现的错误", zap.String("错误原因", err.Error()), zap.String("转账账单", param.PartnerTradeNo)) transferDelayQueue <- param continue } if res.Status == CODE_SUCCESS { continue } else if res.Status == CODE_TRANSFER_PROCESSING { transferDelayQueue <- param continue } else { //失败 --> 重新转账 _, err = Transfer(param.Desc, param.OpenId, param.PartnerTradeNo, param.Amount) if err != nil { logger.Error("微信转账出现的错误", zap.String("错误原因", err.Error()), zap.String("转账账单", param.PartnerTradeNo)) } transferDelayQueue <- param // 重新确认 continue } } } } // 企业向微信用户个人付款(不支持沙箱环境) type TransferResponse struct { DeviceInfo string `xml:"device_info,omitempty" json:"device_info,omitempty"` NonceStr string `xml:"nonce_str,omitempty" json:"nonce_str,omitempty"` PartnerTradeNo string `xml:"partner_trade_no,omitempty" json:"partner_trade_no,omitempty"` PaymentNo string `xml:"payment_no,omitempty" json:"payment_no,omitempty"` PaymentTime string `xml:"payment_time,omitempty" json:"payment_time,omitempty"` } func PutTransferDelayQueue(desc, openId, partnerTradeNo string, amount, retries, delay int) { if retries <= 0 { retries = 3 } if delay == 0 { delay = 30 } transferDelayQueue <- &transferDelayQueueParam{ first: true, Retries: retries, Delay: delay, Amount: amount, Desc: desc, OpenId: openId, PartnerTradeNo: partnerTradeNo, } } func Transfer(desc, openId, partnerTradeNo string, amount int) (*TransferResponse, error) { nonceStr := utils.RandomStr(32) if partnerTradeNo == "" { // 需要提前存入 partnerTradeNo = utils.RandomStr(32) } // 初始化参数结构体 body := make(map[string]string) body["nonce_str"] = nonceStr body["partner_trade_no"] = partnerTradeNo body["openid"] = openId body["check_name"] = "NO_CHECK" // NO_CHECK:不校验真实姓名 , FORCE_CHECK:强校验真实姓名 body["amount"] = fmt.Sprintf("%d", amount) // 企业付款金额,单位为分 body["desc"] = desc // 企业付款备注,必填。注意:备注中的敏感词会被转成字符* body["mchid"] = Mchid body["mch_appid"] = Appid client, err := Client() m, err := promotion.Transfers(client, body) // 记录错误信息 if err != nil { transfer := new(models.UserTransfer) transfer.UpdateErrMsg(partnerTradeNo, err.Error()) return nil, err } res := &TransferResponse{ DeviceInfo: m["device_info"], NonceStr: m["nonce_str"], PartnerTradeNo: m["partner_trade_no"], PaymentNo: m["payment_no"], PaymentTime: m["payment_time"], } // 获取某些信息 transfer := new(models.UserTransfer) exist, err := transfer.GetByPartnerTradeNo(partnerTradeNo) if err != nil { return nil, err } transfer.Desc = desc transfer.OpenId = openId transfer.PartnerTradeNo = partnerTradeNo transfer.PaymentAmount = amount transfer.PaymentNo = res.PaymentNo transfer.DeviceInfo = res.DeviceInfo transfer.PaymentTime = res.PaymentTime if !exist { _, err = transfer.Add() if err != nil { return nil, err } } else { _, err = transfer.UpdateByPartnerTradeNo(partnerTradeNo) if err != nil { return nil, err } } return res, nil } type TransferInfoResponse struct { ErrCode string `json:"err_code"` Status string `json:"status"` Reason string `json:"reason"` Openid string `json:"openid"` PaymentAmount int `json:"payment_amount"` PaymentTime string `json:"payment_time"` TransferTime string `json:"transfer_time"` Desc string `json:"desc"` } func TransferInfo(partnerTradeNo string) (*TransferInfoResponse, error) { client, err := Client() if err != nil { return nil, err } nonceStr := utils.RandomStr(32) var body = make(map[string]string, 0) body["nonce_str"] = nonceStr body["partner_trade_no"] = partnerTradeNo m, err := mmpaymkttransfers.GetTransferInfo(client, body) if err != nil { transfer := new(models.UserTransfer) transfer.UpdateErrMsg(partnerTradeNo, err.Error()) return nil, err } amount, _ := strconv.Atoi(m["payment_amount"]) res := &TransferInfoResponse{ Status: m["status"], Reason: m["reason"], Openid: m["openid"], PaymentAmount: amount, PaymentTime: m["payment_time"], TransferTime: m["transfer_time"], Desc: m["desc"], ErrCode: m["err_code"], } transfer := new(models.UserTransfer) if res.Status == CODE_SUCCESS { transfer.Status = 2 } else if res.Status == CODE_TRANSFER_PROCESSING { transfer.Status = 1 } else if res.Status == CODE_FAIL { transfer.Status = 3 } transfer.Reason = res.Reason transfer.TransferTime = res.TransferTime _, err = transfer.UpdateByPartnerTradeNo(partnerTradeNo) if err != nil { return nil, err } return res, nil } type RedPackResult struct { MchBillno string `xml:"mch_billno,omitempty" json:"mch_billno,omitempty"` ReOpenid string `xml:"re_openid,omitempty" json:"re_openid,omitempty"` TotalAmount int `xml:"total_amount,omitempty" json:"total_amount,omitempty"` SendListid string `xml:"send_listid,omitempty" json:"send_listid,omitempty"` } // 发送红包 func SendRedPack(sendName, openId, wishing, ip, actName, remark string, totalAmount, totalNum, scene int) (*RedPackResult, error) { client, err := Client() if err != nil { return nil, err } mchBillNo := utils.RandomStr(28) nonceStr := utils.RandomStr(32) body := make(map[string]string, 0) body["wxappid"] = client.AppId() body["mch_id"] = client.MchId() body["nonce_str"] = nonceStr body["mch_billno"] = mchBillNo body["send_name"] = sendName body["re_openid"] = openId body["total_amount"] = fmt.Sprintf("%d", totalAmount) body["total_num"] = fmt.Sprintf("%d", totalNum) body["wishing"] = wishing body["client_ip"] = ip body["act_name"] = actName body["remark"] = remark body["scene_id"] = fmt.Sprintf("PRODUCT_%d", scene) m, err := mmpaymkttransfers.SendRedPack(client, body) if err != nil { return nil, err } res := &RedPackResult{ MchBillno: m["mch_billno"], ReOpenid: m["re_openid"], TotalAmount: totalAmount, SendListid: m["send_listid"], } userRedPack := new(models.UserRedPack) userRedPack.MchBillno = mchBillNo userRedPack.ReOpenid = openId userRedPack.SceneId = scene userRedPack.SendListid = res.SendListid userRedPack.TotalAmount = totalAmount userRedPack.TotalNum = totalNum userRedPack.IsDelete = false userRedPack.UpdatedAt = time.Now() userRedPack.CreatedAt = time.Now() _, err = userRedPack.AddRedPack() if err != nil { return nil, err } return res, nil } type QueryRedPackResult struct { CommonReturn CommonResult MchBillno string `xml:"mch_billno,omitempty" json:"mch_billno,omitempty"` Mchid string `xml:"mchid,omitempty" json:"mchid,omitempty"` DetailId string `xml:"detail_id,omitempty" json:"detail_id,omitempty"` Status string `xml:"status,omitempty" json:"status,omitempty"` SendType string `xml:"send_type,omitempty" json:"send_type,omitempty"` HbType string `xml:"hb_type,omitempty" json:"hb_type,omitempty"` TotalNum int `xml:"total_num,omitempty" json:"total_num,omitempty"` TotalAmount int `xml:"total_amount,omitempty" json:"total_amount,omitempty"` Reason string `xml:"reason,omitempty" json:"reason,omitempty"` SendTime string `xml:"send_time,omitempty" json:"send_time,omitempty"` RefundTime string `xml:"refund_time,omitempty" json:"refund_time,omitempty"` RefundAmount int `xml:"refund_amount,omitempty" json:"refund_amount,omitempty"` Wishing string `xml:"wishing,omitempty" json:"wishing,omitempty"` Remark string `xml:"remark,omitempty" json:"remark,omitempty"` ActName string `xml:"act_name,omitempty" json:"act_name,omitempty"` }