Browse Source

todo: sendLiveRedPack

master
黄梓健 5 years ago
parent
commit
5de029065c
  1. 127
      controllers/client/live.go
  2. 15
      models/live_red_pack.go
  3. 16
      models/live_red_pack_info.go
  4. 41
      services/pay/order.go
  5. 49
      services/pay/transfer.go
  6. 2
      test/pay_test.go

127
controllers/client/live.go

@ -1,16 +1,125 @@
package client
import (
"go.uber.org/zap"
"hudongzhuanjia/controllers"
"hudongzhuanjia/libs/filter"
"hudongzhuanjia/logger"
"hudongzhuanjia/models"
pay_service "hudongzhuanjia/services/pay"
red_envelope_service "hudongzhuanjia/services/red_envelope"
"hudongzhuanjia/utils"
"hudongzhuanjia/utils/code"
"strings"
"time"
)
var MaxQueueSize = 10000
var RequestLimit = 60 * time.Second
var GetRedPackQueue = make(chan int64, MaxQueueSize)
func loopGetRedPack() {
redPacks, err := models.GetRedPacksByStatus(1)
if err != nil {
panic(err)
}
// 保证容量足够
GetRedPackQueue = make(chan int64, MaxQueueSize+len(redPacks))
// 初始化
for _, redPack := range redPacks {
GetRedPackQueue <- redPack.Id
}
defer func() {
if errRec := recover(); errRec != nil {
logger.Error("用户转账轮询发生错误",
zap.String("函数", "loopGetRedPack"), zap.Any("错误原因", errRec))
}
time.Sleep(5 * time.Second)
loopGetRedPack()
}()
for {
select {
case redPackId, ok := <-GetRedPackQueue:
if !ok {
panic("GetRedPackQueue通道关闭")
}
time.Sleep(RequestLimit) // 请求频率
redPack := new(models.LiveRedPack)
exist, err := models.GetById(redPack, redPackId)
if err != nil {
GetRedPackQueue <- redPackId
logger.Error("获取LiveRedPack表数据出现错误",
zap.Any("错误原因", err), zap.Int64("LiveRedPack表的id", redPack.Id))
continue
}
if !exist {
logger.Error("不存在LiveRedPack表数据", zap.Int64("LiveRedPack表的id", redPack.Id))
continue
}
// todo: 增加一个查询机制 --查询此次付款是否成功
// todo: 是否需要发放系统通知
resp, err := pay_service.TransferInfo(redPack.TransferNo)
if err != nil {
GetRedPackQueue <- redPackId
logger.Error("微信企业转账查询API",
zap.Any("错误原因", err), zap.Int64("LiveRedPack表的id", redPack.Id))
continue
}
if resp.Status == pay_service.CODE_FAIL { // 转账失败
_, err = pay_service.Transfer("欧轩互动-红包活动", redPack.OpenId, redPack.TransferNo, redPack.Amount)
if err != nil {
GetRedPackQueue <- redPackId
logger.Error("微信企业转账API",
zap.Any("错误原因", err), zap.Int64("LiveRedPack表的id", redPack.Id))
continue
}
_, err = redPack.UpdateStatusById(redPack.Id, 2)
if err != nil {
GetRedPackQueue <- redPackId
logger.Error("微信企业转账,LiveRedPack表更新错误",
zap.Any("错误原因", err), zap.Int64("LiveRedPack表id", redPack.Id))
continue
}
} else if resp.Status == pay_service.CODE_SUCCESS {
_, err = redPack.UpdateStatusById(redPack.Id, 2)
if err != nil {
logger.Error("微信企业查询,转账成功",
zap.Any("错误原因", err), zap.Int64("LiveRedPack表id", redPack.Id))
continue
}
}
}
}
}
var SendRedPackQueue = make(chan int64, MaxQueueSize)
func loopSendRedPack() {
redPackInfo :=
defer func() {
if err := recover(); err != nil {
logger.Error("用户发送红包轮询出现错误",
zap.String("函数", "loopSendRedPack"), zap.Any("错误", err))
}
time.Sleep(5 * time.Second)
loopSendRedPack()
}()
for {
select {
case redPackInfoId, ok := <-SendRedPackQueue:
if !ok {
panic("SendRedPackQueue通道异常关闭")
}
}
}
}
type LiveCtl struct {
controllers.AuthorCtl
//controllers.BaseCtl
@ -76,7 +185,7 @@ func (t *LiveCtl) SendLiveRedPack() {
userId := t.MustGetUID() // 用户 uid
activityId := t.MustGetInt64("activity_id") // activity_id
num := t.MustGetInt("num") // 红包数量
amount := t.MustGetDouble("amount") // 金额
amount := t.MustGetInt64("amount") // 金额
prompt := t.MustGet("prompt") // 提示
user := models.User{}
@ -85,11 +194,10 @@ func (t *LiveCtl) SendLiveRedPack() {
t.Assert(exist, code.MSG_USER_NOT_EXIST, "用户不存在")
ip := strings.Split(t.Request.OriginRequest.RemoteAddr, ":")
res, err := pay_service.Order("欧轩互动-直播红包", ip[0], user.Openid, int(amount*100), 3, userId, activityId)
res, err := pay_service.UnifiedOrder("欧轩互动-直播红包", ip[0], user.Openid, amount)
t.CheckErr(err)
info := models.LiveRedPackInfo{}
info.UserOrderId = res["user_order_id"].(int64)
info.Amount = amount
info.UserId = userId
info.ActivityId = activityId
@ -136,18 +244,17 @@ func (t *LiveCtl) GetRedPack() {
}
// 乐观锁 ==> 防止并发
row, err := redPack.Receive(userId)
redPack.OpenId = user.Openid
redPack.Receiver = user.Id
redPack.TransferType = 1
redPack.TransferNo = utils.RandomStr(32)
row, err := redPack.UpdateStatusById(redPack.Id, 1)
t.CheckErr(err)
if row != 1 {
t.ERROR("红包被领完了", code.MSG_LIVE_RED_PACK_NOT_EXIST)
return
}
ip := strings.Split(t.Request.OriginRequest.RemoteAddr, ":")
res, err := pay_service.Transfer("欧轩互动-红包活动", ip[0], user.Openid, redPack.Amount)
redPack.TransferType = 1
redPack.TransferNo = res.PartnerTradeNo
_, err = redPack.UpdateStatusById(redPack, 2)
t.CheckErr(err)
GetRedPackQueue <- redPack.Id // 进入队列
t.JSON(redPack)
}

15
models/live_red_pack.go

@ -16,10 +16,11 @@ type LiveRedPack struct {
LiveRedPackInfoId int64 `json:"live_red_pack_info_id" xorm:"not null default 0 comment('红包信息id') INT(11)"`
ActivityId int64 `json:"activity_id" xorm:"not null default 0 comment('互动id') INT(11)"`
Receiver int64 `json:"receiver" xorm:"not null default 0 comment('[0未领取/非0领取用户id]') INT(11)"`
OpenId string `json:"open_id" xorm:"not null default '' comment('用户openid') VARCHAR(128)"`
Amount int `json:"amount" xorm:"not null default 0 comment('红包金额, 分') INT(18)"`
TransferType int `json:"transfer_type" xorm:"not null default 0 comment('转账方式[0微信红包1微信零钱]')"`
TransferType int `json:"transfer_type" xorm:"not null default 0 comment('转账方式[0微信红包1微信零钱]') TINYINT(1)"`
TransferNo string `json:"transfer_no" xorm:"not null default '' comment('转账账号') VARCHAR(128) "`
Status int `json:"status" xorm:"not null default 0 comment('0 未被领取 1 已被领取 2 已发送')"`
Status int `json:"status" xorm:"not null default 0 comment('0 未被领取 1 已被领取 2 已发送') TINYINT(1)"`
Version int `json:"version" xorm:"not null version comment('乐观锁') INT(11)"`
}
@ -36,12 +37,18 @@ func (t *LiveRedPack) GetByInfoId(infoId int64) (bool, error) {
}
func (t *LiveRedPack) Receive(userId int64) (int64, error) {
t.Receiver = userId
t.Status = 1
return core.GetXormAuto().Where("id=?", t.Id).Cols("user_id, status").Update(t)
}
func (t *LiveRedPack) UpdateStatusById(id interface{}, status int) (int64, error) {
t.Status = status
return core.GetXormAuto().Where("id=?", t.Id).Cols("status").Update(t)
return core.GetXormAuto().Where("id=?", t.Id).
Cols("receiver, open_id, transfer_type, transfer_no, status").Update(t)
}
func GetRedPacksByStatus(status interface{}) ([]*LiveRedPack, error) {
redPacks := make([]*LiveRedPack, 0)
err := core.GetXormAuto().Where("is_delete=0 and status=?", status).Find(&redPacks)
return redPacks, err
}

16
models/live_red_pack_info.go

@ -13,11 +13,12 @@ type LiveRedPackInfo struct {
CreatedAt time.Time `json:"created_at" xorm:"not null created comment('创建时间') DATETIME"`
UpdatedAt time.Time `json:"updated_at" xorm:"not null updated default CURRENT_TIMESTAMP comment('更新时间') TIMESTAMP"`
UserOrderId int64 `json:"user_order_id" xorm:"not null default 0 comment('用户微信订单id') INT(11)"`
UserId int64 `json:"user_id" xorm:"not null default 0 comment('用户id') INT(11)"`
ActivityId int64 `json:"activity_id" xorm:"not null default 0 comment('互动id') INT(11)"`
Prompt string `json:"prompt" xorm:"not null default 0 comment('祝福语') VARCHAR(255)"`
Amount float64 `json:"amount" xorm:"not null default 0.0 comment('红包金额') DECIMAL(18,2)"`
UserId int64 `json:"user_id" xorm:"not null default 0 comment('用户id') INT(11)"`
ActivityId int64 `json:"activity_id" xorm:"not null default 0 comment('互动id') INT(11)"`
Prompt string `json:"prompt" xorm:"not null default 0 comment('祝福语') VARCHAR(255)"`
Amount int64 `json:"amount" xorm:"not null default 0 comment('红包金额, 分') INT(18)"`
OutTradeNo string `json:"out_trade_no" xorm:"not null default '' comment('订单号') VARCHAR(128)"`
Status int `json:"status" xorm:"not null default 0 comment('0尚未支付1取消支付2支付成功3订单关闭')"`
}
func (t *LiveRedPackInfo) TableName() string {
@ -27,3 +28,8 @@ func (t *LiveRedPackInfo) TableName() string {
func (t *LiveRedPackInfo) Add() (int64, error) {
return core.GetXormAuto().InsertOne(t)
}
func GetLiveRedPackInfos() {
infos := make([]*LiveRedPackInfo, 0)
core.GetXormAuto().Where("is_delete=0 and ")
}

41
services/pay/order.go

@ -81,6 +81,47 @@ func Order(content, ip, openid string, fee, goodType int, userId, activityId int
}, nil
}
func UnifiedOrder(content, ip, openid string, fee int64) (map[string]interface{}, error) {
client, err := Client()
if err != nil {
return nil, err
}
now := time.Now()
outTradeNo := utils.RandomStr(32)
nonceStr := utils.RandomStr(32)
resp, err := pay.UnifiedOrder2(client, &pay.UnifiedOrderRequest{
Body: content,
OutTradeNo: outTradeNo,
TotalFee: int64(fee),
SpbillCreateIP: ip,
NotifyURL: CallbackOrderUrl,
TradeType: "JSAPI",
DeviceInfo: "WEB",
NonceStr: nonceStr,
SignType: core2.SignType_MD5,
TimeStart: now,
OpenId: openid,
})
if err != nil {
return nil, err
}
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
//获取H5支付需要的paySign
pac := "prepay_id=" + resp.PrepayId
paySign := core2.JsapiSign(client.AppId(), nonceStr, pac, core2.SignType_MD5, timestamp, ApiKey)
return map[string]interface{}{
"appid": Appid,
"timestamp": timestamp,
"nonce_str": nonceStr,
"package": pac,
"sign_type": core2.SignType_MD5,
"pay_sign": paySign,
"out_trade_no": outTradeNo,
}, nil
}
// Notify
type NotifyRequest struct {
ReturnCode string `xml:"return_code,omitempty" json:"return_code,omitempty"`

49
services/pay/transfer.go

@ -6,6 +6,7 @@ import (
"github.com/chanxuehong/wechat/mch/mmpaymkttransfers/promotion"
"hudongzhuanjia/models"
"hudongzhuanjia/utils"
"strconv"
"time"
)
@ -18,10 +19,12 @@ type TransferResponse struct {
PaymentTime string `xml:"payment_time,omitempty" json:"payment_time,omitempty"`
}
func Transfer(desc, ip, openId string, amount int) (*TransferResponse, error) {
func Transfer(desc, openId, partnerTradeNo string, amount int) (*TransferResponse, error) {
nonceStr := utils.RandomStr(32)
partnerTradeNo := utils.RandomStr(32)
if partnerTradeNo == "" { // 需要提前存入
partnerTradeNo = utils.RandomStr(32)
}
// 初始化参数结构体
body := make(map[string]string)
@ -31,7 +34,6 @@ func Transfer(desc, ip, openId string, amount int) (*TransferResponse, error) {
body["check_name"] = "NO_CHECK" // NO_CHECK:不校验真实姓名 , FORCE_CHECK:强校验真实姓名
body["amount"] = fmt.Sprintf("%d", amount) // 企业付款金额,单位为分
body["desc"] = desc // 企业付款备注,必填。注意:备注中的敏感词会被转成字符*
body["spbill_create_ip"] = ip
body["mchid"] = Mchid
body["mch_appid"] = Appid
@ -50,6 +52,47 @@ func Transfer(desc, ip, openId string, amount int) (*TransferResponse, error) {
return res, nil
}
type TransferInfoResponse struct {
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 {
return nil, err
}
amount, _ := strconv.Atoi(m["payment_amount"])
resp := &TransferInfoResponse{
Status: m["status"],
Reason: m["reason"],
Openid: m["openid"],
PaymentAmount: amount,
PaymentTime: m["payment_time"],
TransferTime: m["transfer_time"],
Desc: m["desc"],
}
return resp, nil
}
type RedPackResult struct {
MchBillno string `xml:"mch_billno,omitempty" json:"mch_billno,omitempty"`
ReOpenid string `xml:"re_openid,omitempty" json:"re_openid,omitempty"`

2
test/pay_test.go

@ -9,7 +9,7 @@ import (
var openId = "o9XM41s_NN8Y0QK6_MbM-aYMV3TE"
func TestTransfer(t *testing.T) {
res, err := pay_service.Transfer("欧轩互动-转账测试", "127.0.0.1", openId, 0.30*100)
res, err := pay_service.Transfer("欧轩互动-转账测试", "127.0.0.1", openId, 0.30*10)
fmt.Println(err)
fmt.Printf("%+v\n", res)
}

Loading…
Cancel
Save