互动
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

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. package pay_service
  2. import (
  3. "fmt"
  4. "github.com/chanxuehong/wechat/mch/mmpaymkttransfers"
  5. "github.com/chanxuehong/wechat/mch/mmpaymkttransfers/promotion"
  6. "go.uber.org/zap"
  7. "hudongzhuanjia/logger"
  8. "hudongzhuanjia/models"
  9. "hudongzhuanjia/utils"
  10. "math"
  11. "strconv"
  12. "time"
  13. )
  14. func init() {
  15. go loopTransfer()
  16. }
  17. var transferDelayQueue = make(chan *transferDelayQueueParam, math.MaxInt8)
  18. type transferDelayQueueParam struct {
  19. Retries int `json:"retries"` // 尝试次数
  20. Delay int `json:"delay"` // 延迟时间, 单位second
  21. Amount int `json:"amount"` // 转账金额
  22. Desc string `json:"desc"` // 转账描述
  23. OpenId string `json:"open_id"` // 被转账人
  24. PartnerTradeNo string `json:"partner_trade_no"` // 转账账单单号
  25. }
  26. func loopTransfer() {
  27. //初始化
  28. transfers, err := models.GetUserTransferByStatus(0, 1, 3)
  29. if err != nil {
  30. panic(err)
  31. }
  32. transferDelayQueue = make(chan *transferDelayQueueParam, math.MaxInt8)
  33. for _, transfer := range transfers {
  34. transferDelayQueue <- &transferDelayQueueParam{
  35. Retries: 5,
  36. Delay: 2 * 60,
  37. Amount: transfer.PaymentAmount,
  38. Desc: transfer.Desc,
  39. OpenId: transfer.OpenId,
  40. PartnerTradeNo: transfer.PartnerTradeNo,
  41. }
  42. }
  43. defer func() {
  44. if errRec := recover(); errRec != nil {
  45. logger.Error("转账轮询 loop transfer panic", zap.Any("错误原因", errRec))
  46. }
  47. loopTransfer()
  48. }()
  49. for {
  50. select {
  51. case param, ok := <-transferDelayQueue:
  52. if !ok {
  53. panic("转账延迟通道异常关闭")
  54. }
  55. // 尝试次数
  56. if param.Retries <= 0 {
  57. logger.Info("微信转账尝试3次失败", zap.String("转账账单单号", param.PartnerTradeNo))
  58. userTransfer := new(models.UserTransfer)
  59. userTransfer.Status = 4
  60. _, err = userTransfer.UpdateByPartnerTradeNo(param.PartnerTradeNo)
  61. if err != nil {
  62. logger.Info("微信转账更新状态失败", zap.String("失败原因", err.Error()),
  63. zap.String("转账账单单号", param.PartnerTradeNo))
  64. }
  65. continue
  66. } else {
  67. param.Retries--
  68. }
  69. time.Sleep(time.Duration(param.Delay) * time.Second)
  70. res, err := TransferInfo(param.PartnerTradeNo)
  71. if err != nil {
  72. logger.Error("微信转账查询出现的错误", zap.String("错误原因", err.Error()),
  73. zap.String("转账账单", param.PartnerTradeNo))
  74. transferDelayQueue <- param
  75. continue
  76. }
  77. if res.Status == CODE_SUCCESS {
  78. continue
  79. } else if res.Status == CODE_TRANSFER_PROCESSING {
  80. transferDelayQueue <- param
  81. continue
  82. } else {
  83. //失败 --> 重新转账
  84. _, err = Transfer(param.Desc, param.OpenId, param.PartnerTradeNo, param.Amount)
  85. if err != nil {
  86. logger.Error("微信转账出现的错误", zap.String("错误原因", err.Error()),
  87. zap.String("转账账单", param.PartnerTradeNo))
  88. }
  89. transferDelayQueue <- param // 重新确认
  90. continue
  91. }
  92. }
  93. }
  94. }
  95. // 企业向微信用户个人付款(不支持沙箱环境)
  96. type TransferResponse struct {
  97. DeviceInfo string `xml:"device_info,omitempty" json:"device_info,omitempty"`
  98. NonceStr string `xml:"nonce_str,omitempty" json:"nonce_str,omitempty"`
  99. PartnerTradeNo string `xml:"partner_trade_no,omitempty" json:"partner_trade_no,omitempty"`
  100. PaymentNo string `xml:"payment_no,omitempty" json:"payment_no,omitempty"`
  101. PaymentTime string `xml:"payment_time,omitempty" json:"payment_time,omitempty"`
  102. }
  103. func PutTransferDelayQueue(desc, openId, partnerTradeNo string, amount, retries, delay int) {
  104. if retries <= 0 {
  105. retries = 3
  106. }
  107. if delay == 0 {
  108. delay = 30
  109. }
  110. transferDelayQueue <- &transferDelayQueueParam{
  111. Retries: retries,
  112. Delay: delay,
  113. Amount: amount,
  114. Desc: desc,
  115. OpenId: openId,
  116. PartnerTradeNo: partnerTradeNo,
  117. }
  118. }
  119. func Transfer(desc, openId, partnerTradeNo string, amount int) (*TransferResponse, error) {
  120. nonceStr := utils.RandomStr(32)
  121. if partnerTradeNo == "" { // 需要提前存入
  122. partnerTradeNo = utils.RandomStr(32)
  123. }
  124. // 初始化参数结构体
  125. body := make(map[string]string)
  126. body["nonce_str"] = nonceStr
  127. body["partner_trade_no"] = partnerTradeNo
  128. body["openid"] = openId
  129. body["check_name"] = "NO_CHECK" // NO_CHECK:不校验真实姓名 , FORCE_CHECK:强校验真实姓名
  130. body["amount"] = fmt.Sprintf("%d", amount) // 企业付款金额,单位为分
  131. body["desc"] = desc // 企业付款备注,必填。注意:备注中的敏感词会被转成字符*
  132. body["mchid"] = Mchid
  133. body["mch_appid"] = Appid
  134. client, err := Client()
  135. m, err := promotion.Transfers(client, body)
  136. // 记录错误信息
  137. if err != nil {
  138. transfer := new(models.UserTransfer)
  139. transfer.UpdateErrMsg(partnerTradeNo, err.Error())
  140. return nil, err
  141. }
  142. res := &TransferResponse{
  143. DeviceInfo: m["device_info"],
  144. NonceStr: m["nonce_str"],
  145. PartnerTradeNo: m["partner_trade_no"],
  146. PaymentNo: m["payment_no"],
  147. PaymentTime: m["payment_time"],
  148. }
  149. // 获取某些信息
  150. transfer := new(models.UserTransfer)
  151. exist, err := transfer.GetByPartnerTradeNo(partnerTradeNo)
  152. if err != nil {
  153. return nil, err
  154. }
  155. transfer.Desc = desc
  156. transfer.OpenId = openId
  157. transfer.PartnerTradeNo = partnerTradeNo
  158. transfer.PaymentAmount = amount
  159. transfer.PaymentNo = res.PaymentNo
  160. transfer.DeviceInfo = res.DeviceInfo
  161. transfer.PaymentTime = res.PaymentTime
  162. if !exist {
  163. _, err = transfer.Add()
  164. if err != nil {
  165. return nil, err
  166. }
  167. } else {
  168. _, err = transfer.UpdateByPartnerTradeNo(partnerTradeNo)
  169. if err != nil {
  170. return nil, err
  171. }
  172. }
  173. return res, nil
  174. }
  175. type TransferInfoResponse struct {
  176. ErrCode string `json:"err_code"`
  177. Status string `json:"status"`
  178. Reason string `json:"reason"`
  179. Openid string `json:"openid"`
  180. PaymentAmount int `json:"payment_amount"`
  181. PaymentTime string `json:"payment_time"`
  182. TransferTime string `json:"transfer_time"`
  183. Desc string `json:"desc"`
  184. }
  185. func TransferInfo(partnerTradeNo string) (*TransferInfoResponse, error) {
  186. client, err := Client()
  187. if err != nil {
  188. return nil, err
  189. }
  190. nonceStr := utils.RandomStr(32)
  191. var body = make(map[string]string, 0)
  192. body["nonce_str"] = nonceStr
  193. body["partner_trade_no"] = partnerTradeNo
  194. m, err := mmpaymkttransfers.GetTransferInfo(client, body)
  195. if err != nil {
  196. transfer := new(models.UserTransfer)
  197. transfer.UpdateErrMsg(partnerTradeNo, err.Error())
  198. return nil, err
  199. }
  200. amount, _ := strconv.Atoi(m["payment_amount"])
  201. res := &TransferInfoResponse{
  202. Status: m["status"],
  203. Reason: m["reason"],
  204. Openid: m["openid"],
  205. PaymentAmount: amount,
  206. PaymentTime: m["payment_time"],
  207. TransferTime: m["transfer_time"],
  208. Desc: m["desc"],
  209. ErrCode: m["err_code"],
  210. }
  211. transfer := new(models.UserTransfer)
  212. if res.Status == CODE_SUCCESS {
  213. transfer.Status = 2
  214. } else if res.Status == CODE_TRANSFER_PROCESSING {
  215. transfer.Status = 1
  216. } else if res.Status == CODE_FAIL {
  217. transfer.Status = 3
  218. }
  219. transfer.Reason = res.Reason
  220. transfer.TransferTime = res.TransferTime
  221. _, err = transfer.UpdateByPartnerTradeNo(partnerTradeNo)
  222. if err != nil {
  223. return nil, err
  224. }
  225. return res, nil
  226. }
  227. type RedPackResult struct {
  228. MchBillno string `xml:"mch_billno,omitempty" json:"mch_billno,omitempty"`
  229. ReOpenid string `xml:"re_openid,omitempty" json:"re_openid,omitempty"`
  230. TotalAmount int `xml:"total_amount,omitempty" json:"total_amount,omitempty"`
  231. SendListid string `xml:"send_listid,omitempty" json:"send_listid,omitempty"`
  232. }
  233. // 发送红包
  234. func SendRedPack(sendName, openId, wishing, ip, actName, remark string, totalAmount, totalNum, scene int) (*RedPackResult, error) {
  235. client, err := Client()
  236. if err != nil {
  237. return nil, err
  238. }
  239. mchBillNo := utils.RandomStr(28)
  240. nonceStr := utils.RandomStr(32)
  241. body := make(map[string]string, 0)
  242. body["wxappid"] = client.AppId()
  243. body["mch_id"] = client.MchId()
  244. body["nonce_str"] = nonceStr
  245. body["mch_billno"] = mchBillNo
  246. body["send_name"] = sendName
  247. body["re_openid"] = openId
  248. body["total_amount"] = fmt.Sprintf("%d", totalAmount)
  249. body["total_num"] = fmt.Sprintf("%d", totalNum)
  250. body["wishing"] = wishing
  251. body["client_ip"] = ip
  252. body["act_name"] = actName
  253. body["remark"] = remark
  254. body["scene_id"] = fmt.Sprintf("PRODUCT_%d", scene)
  255. m, err := mmpaymkttransfers.SendRedPack(client, body)
  256. if err != nil {
  257. return nil, err
  258. }
  259. res := &RedPackResult{
  260. MchBillno: m["mch_billno"],
  261. ReOpenid: m["re_openid"],
  262. TotalAmount: totalAmount,
  263. SendListid: m["send_listid"],
  264. }
  265. userRedPack := new(models.UserRedPack)
  266. userRedPack.MchBillno = mchBillNo
  267. userRedPack.ReOpenid = openId
  268. userRedPack.SceneId = scene
  269. userRedPack.SendListid = res.SendListid
  270. userRedPack.TotalAmount = totalAmount
  271. userRedPack.TotalNum = totalNum
  272. userRedPack.IsDelete = false
  273. userRedPack.UpdatedAt = time.Now()
  274. userRedPack.CreatedAt = time.Now()
  275. _, err = userRedPack.AddRedPack()
  276. if err != nil {
  277. return nil, err
  278. }
  279. return res, nil
  280. }
  281. type QueryRedPackResult struct {
  282. CommonReturn
  283. CommonResult
  284. MchBillno string `xml:"mch_billno,omitempty" json:"mch_billno,omitempty"`
  285. Mchid string `xml:"mchid,omitempty" json:"mchid,omitempty"`
  286. DetailId string `xml:"detail_id,omitempty" json:"detail_id,omitempty"`
  287. Status string `xml:"status,omitempty" json:"status,omitempty"`
  288. SendType string `xml:"send_type,omitempty" json:"send_type,omitempty"`
  289. HbType string `xml:"hb_type,omitempty" json:"hb_type,omitempty"`
  290. TotalNum int `xml:"total_num,omitempty" json:"total_num,omitempty"`
  291. TotalAmount int `xml:"total_amount,omitempty" json:"total_amount,omitempty"`
  292. Reason string `xml:"reason,omitempty" json:"reason,omitempty"`
  293. SendTime string `xml:"send_time,omitempty" json:"send_time,omitempty"`
  294. RefundTime string `xml:"refund_time,omitempty" json:"refund_time,omitempty"`
  295. RefundAmount int `xml:"refund_amount,omitempty" json:"refund_amount,omitempty"`
  296. Wishing string `xml:"wishing,omitempty" json:"wishing,omitempty"`
  297. Remark string `xml:"remark,omitempty" json:"remark,omitempty"`
  298. ActName string `xml:"act_name,omitempty" json:"act_name,omitempty"`
  299. }