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

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