You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
335 lines
10 KiB
335 lines
10 KiB
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 {
|
|
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{
|
|
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--
|
|
}
|
|
|
|
time.Sleep(time.Duration(param.Delay) * time.Second)
|
|
|
|
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{
|
|
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"`
|
|
}
|