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

416 lines
13 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
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. core2 "github.com/chanxuehong/wechat/mch/core"
  4. "github.com/chanxuehong/wechat/mch/pay"
  5. "github.com/pkg/errors"
  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 loopUnifiedOrder()
  16. }
  17. var orderDelayQueue = make(chan *orderDelayQueueParam, math.MaxInt8)
  18. type orderDelayQueueParam struct {
  19. First bool `json:"first"`
  20. Expires int64 `json:"expires"`
  21. Delay int `json:"delay"`
  22. OutTradeNo string `json:"out_trade_no"`
  23. Body string `json:"body"`
  24. Amount int `json:"amount"` // 金额
  25. Status int `json:"status"` // 0 订单 3 退款
  26. OpenId string `json:"open_id"` // 被操作人
  27. }
  28. func PutOrderDelayQueue(body, outTradeNo, openId string, amount, status int, expires int64, delay int) {
  29. if expires == 0 {
  30. expires = time.Now().Add(2 * time.Hour).Unix() // 2 个小时
  31. }
  32. orderDelayQueue <- &orderDelayQueueParam{
  33. First: true,
  34. Expires: expires,
  35. Delay: delay,
  36. OutTradeNo: outTradeNo,
  37. Body: body,
  38. Amount: amount,
  39. Status: status,
  40. OpenId: openId,
  41. }
  42. }
  43. func loopUnifiedOrder() {
  44. orders, err := models.GetUserOrdersByStatus(0, 3)
  45. if err != nil {
  46. panic(err)
  47. }
  48. for _, order := range orders {
  49. PutOrderDelayQueue(order.Body, order.OutTradeNo, order.OpenId, int(order.TotalFee), order.Status, 0, 0)
  50. }
  51. defer func() {
  52. if err := recover(); err != nil {
  53. logger.Error("订单轮询查询", zap.Any("panic 恢复错误", err))
  54. }
  55. // 重启
  56. time.Sleep(5 * time.Second)
  57. loopUnifiedOrder()
  58. }()
  59. for {
  60. select {
  61. case param, ok := <-orderDelayQueue:
  62. if !ok {
  63. panic("通道异常关闭")
  64. }
  65. //userOrder := new(models.UserOrder)
  66. //exist, err := userOrder.GetByOutTradeNo(outTradeNo)
  67. //if err != nil || !exist {
  68. // logger.Error("通过订单查询UserOrder", zap.String("错误原因", err.Error()),
  69. // zap.Bool("是否存在", exist), zap.String("交易订单号", outTradeNo))
  70. // continue
  71. //}
  72. if param.Expires <= time.Now().Unix() {
  73. Close(param.OutTradeNo) // 超时关闭订单
  74. continue // 超时
  75. }
  76. // 首次进入不延迟
  77. if !param.First {
  78. time.Sleep(time.Duration(param.Delay) * time.Second)
  79. }
  80. param.First = false
  81. if param.Status == 0 {
  82. res, err := OrderQuery(param.OutTradeNo)
  83. // 出现错误
  84. if err != nil {
  85. logger.Error("查询订单出现错误", zap.String("错误原因", err.Error()),
  86. zap.String("交易订单号", param.OutTradeNo))
  87. orderDelayQueue <- param // 重新进入队列
  88. continue
  89. }
  90. if res.TradeState == CODE_TRADE_REFUND {
  91. param.Status = 3
  92. orderDelayQueue <- param
  93. continue
  94. } else if res.TradeState == CODE_TRADE_NOTPAY || res.TradeState == CODE_TRADE_USERPAYING {
  95. orderDelayQueue <- param
  96. continue
  97. }
  98. } else if param.Status == 3 {
  99. _, err = QueryRefund(param.OutTradeNo)
  100. if err != nil {
  101. logger.Error("退款订单查询错误", zap.String("错误原因", err.Error()),
  102. zap.String("交易订单号", param.OutTradeNo))
  103. continue
  104. }
  105. } else {
  106. continue
  107. }
  108. }
  109. }
  110. }
  111. const CallbackOrderUrl = "https://api.ouxuanhudong.com/PcClient/common/WeChatOauthCtl/callbackOrder"
  112. func UnifiedOrder(body, ip, openid string, fee, goodType, userId, activityId int64) (map[string]interface{}, error) {
  113. client, err := Client()
  114. if err != nil {
  115. return nil, err
  116. }
  117. now := time.Now()
  118. timeStart := core2.FormatTime(now)
  119. timeExpire := core2.FormatTime(now.Add(2 * time.Hour))
  120. outTradeNo := utils.RandomStr(32)
  121. nonceStr := utils.RandomStr(32)
  122. resp, err := pay.UnifiedOrder2(client, &pay.UnifiedOrderRequest{
  123. Body: body,
  124. OutTradeNo: outTradeNo,
  125. TotalFee: fee,
  126. SpbillCreateIP: ip,
  127. NotifyURL: CallbackOrderUrl,
  128. TradeType: "JSAPI",
  129. DeviceInfo: "WEB",
  130. NonceStr: nonceStr,
  131. SignType: core2.SignType_MD5,
  132. TimeStart: now,
  133. OpenId: openid,
  134. })
  135. if err != nil {
  136. return nil, err
  137. }
  138. // 记录这次订单
  139. userOrder := new(models.UserOrder)
  140. userOrder.DeviceInfo = "WEB"
  141. userOrder.Body = body
  142. userOrder.UserId = userId
  143. userOrder.ActivityId = activityId
  144. userOrder.FeeType = "CNY"
  145. userOrder.GoodType = goodType
  146. userOrder.OpenId = openid
  147. userOrder.OutTradeNo = outTradeNo
  148. userOrder.TimeStart = timeStart
  149. userOrder.TimeExpire = timeExpire
  150. userOrder.TotalFee = fee
  151. userOrder.TradeType = "JSAPI"
  152. userOrder.PrepayId = resp.PrepayId
  153. userOrder.Status = 0
  154. userOrder.IsDelete = false
  155. userOrder.CreatedAt = time.Now()
  156. userOrder.UpdatedAt = time.Now()
  157. if _, err = userOrder.AddUserOrder(); err != nil {
  158. return nil, err
  159. }
  160. timestamp := strconv.FormatInt(time.Now().Unix(), 10)
  161. //获取H5支付需要的paySign
  162. pac := "prepay_id=" + resp.PrepayId
  163. paySign := core2.JsapiSign(client.AppId(), nonceStr, pac, core2.SignType_MD5, timestamp, ApiKey)
  164. go PutOrderDelayQueue(userOrder.Body, userOrder.OutTradeNo, userOrder.OpenId, int(userOrder.TotalFee), userOrder.Status, 5, 0)
  165. return map[string]interface{}{
  166. "appid": Appid,
  167. "timestamp": timestamp,
  168. "nonce_str": nonceStr,
  169. "package": pac,
  170. "sign_type": core2.SignType_MD5,
  171. "pay_sign": paySign,
  172. "out_trade_no": outTradeNo,
  173. "user_order_id": userOrder.Id,
  174. }, nil
  175. }
  176. // Notify
  177. type NotifyRequest struct {
  178. ReturnCode string `xml:"return_code,omitempty" json:"return_code,omitempty"`
  179. ReturnMsg string `xml:"return_msg,omitempty" json:"return_msg,omitempty"`
  180. ResultCode string `xml:"result_code,omitempty" json:"result_code,omitempty"`
  181. ErrCode string `xml:"err_code,omitempty" json:"err_code,omitempty"`
  182. ErrCodeDes string `xml:"err_code_des,omitempty" json:"err_code_des,omitempty"`
  183. Appid string `xml:"appid,omitempty" json:"appid,omitempty"`
  184. MchId string `xml:"mch_id,omitempty" json:"mch_id,omitempty"`
  185. DeviceInfo string `xml:"device_info,omitempty" json:"device_info,omitempty"`
  186. NonceStr string `xml:"nonce_str,omitempty" json:"nonce_str,omitempty"`
  187. Sign string `xml:"sign,omitempty" json:"sign,omitempty"`
  188. SignType string `xml:"sign_type,omitempty" json:"sign_type,omitempty"`
  189. Openid string `xml:"openid,omitempty" json:"openid,omitempty"`
  190. IsSubscribe string `xml:"is_subscribe,omitempty" json:"is_subscribe,omitempty"`
  191. TradeType string `xml:"trade_type,omitempty" json:"trade_type,omitempty"`
  192. BankType string `xml:"bank_type,omitempty" json:"bank_type,omitempty"`
  193. TotalFee int `xml:"total_fee,omitempty" json:"total_fee,omitempty"`
  194. SettlementTotalFee int `xml:"settlement_total_fee,omitempty" json:"settlement_total_fee,omitempty"`
  195. FeeType string `xml:"fee_type,omitempty" json:"fee_type,omitempty"`
  196. CashFee int `xml:"cash_fee,omitempty" json:"cash_fee,omitempty"`
  197. CashFeeType string `xml:"cash_fee_type,omitempty" json:"cash_fee_type,omitempty"`
  198. CouponFee int `xml:"coupon_fee,omitempty" json:"coupon_fee,omitempty"`
  199. CouponCount int `xml:"coupon_count,omitempty" json:"coupon_count,omitempty"`
  200. CouponType0 string `xml:"coupon_type_0,omitempty" json:"coupon_type_0,omitempty"`
  201. CouponType1 string `xml:"coupon_type_1,omitempty" json:"coupon_type_1,omitempty"`
  202. CouponId0 string `xml:"coupon_id_0,omitempty" json:"coupon_id_0,omitempty"`
  203. CouponId1 string `xml:"coupon_id_1,omitempty" json:"coupon_id_1,omitempty"`
  204. CouponFee0 int `xml:"coupon_fee_0,omitempty" json:"coupon_fee_0,omitempty"`
  205. CouponFee1 int `xml:"coupon_fee_1,omitempty" json:"coupon_fee_1,omitempty"`
  206. TransactionId string `xml:"transaction_id,omitempty" json:"transaction_id,omitempty"`
  207. OutTradeNo string `xml:"out_trade_no,omitempty" json:"out_trade_no,omitempty"`
  208. Attach string `xml:"attach,omitempty" json:"attach,omitempty"`
  209. TimeEnd string `xml:"time_end,omitempty" json:"time_end,omitempty"`
  210. }
  211. //func NotifyOrder(req *http.Request) error {
  212. // res := new(NotifyRequest)
  213. // if err := xml.NewDecoder(req.Body).Decode(res); err != nil {
  214. // return fmt.Errorf("xml.NewDecoder.Decode:%w", err)
  215. // }
  216. //if res.ReturnCode != CODE_SUCCESS {
  217. // return fmt.Errorf("network error, retrun_code: %v and return_msg: %v", res.ReturnCode, res.ReturnMsg)
  218. //}
  219. //
  220. //if res.ResultCode != CODE_FAIL && res.ErrCode == CODE_SYSTEMERROR {
  221. // return fmt.Errorf("trade error, err_code: %v and err_code_des: %v", res.ErrCode, res.ErrCodeDes)
  222. //}
  223. //
  224. //if res.ResultCode == CODE_SUCCESS {
  225. // userOrder := new(models.UserOrder)
  226. // userOrder.TimeEnd = res.TimeEnd
  227. // userOrder.TransactionId = res.TransactionId
  228. // if res.TradeType == CODE_TRADE_SUCCESS {
  229. // userOrder.Status = 1
  230. // } else if _, err := userOrder.UpdateStatusByOutTradeNo(res.OutTradeNo, userOrder.TradeType); err != nil {
  231. // return err
  232. // }
  233. //
  234. //} else {
  235. //}
  236. //// 设置一下
  237. //if userOrder.GoodType == 1 { // 霸屏
  238. // _, err = new(models.BullyScreenHistory).UpdateStatusByUserOrderId(userOrder.Id, 0)
  239. // if err != nil {
  240. // return err
  241. // }
  242. //} else if userOrder.GoodType == 2 {
  243. // _, err = new(models.RewardHistory).UpdateStatusByUserOrderId(userOrder.Id, 0)
  244. // if err != nil {
  245. // return err
  246. // }
  247. //}
  248. // return nil
  249. //}
  250. func OrderQuery(outTradeNo string) (*pay.OrderQueryResponse, error) {
  251. client, err := Client()
  252. if err != nil {
  253. return nil, err
  254. }
  255. // 请求订单查询,成功后得到结果
  256. userOrder := new(models.UserOrder)
  257. exist, err := userOrder.GetByOutTradeNo(outTradeNo)
  258. if err != nil {
  259. return nil, err
  260. }
  261. if !exist {
  262. return nil, errors.New("订单不存在")
  263. }
  264. res, err := pay.OrderQuery2(client, &pay.OrderQueryRequest{
  265. OutTradeNo: outTradeNo,
  266. NonceStr: utils.RandomStr(32),
  267. SignType: core2.SignType_MD5,
  268. })
  269. if err != nil {
  270. userOrder.ErrMsg = err.Error()
  271. userOrder.UpdateErrByOutTradeNo(outTradeNo)
  272. return nil, err
  273. }
  274. userOrder.TransactionId = res.TransactionId
  275. userOrder.TimeEnd = core2.FormatTime(res.TimeEnd)
  276. switch res.TradeState {
  277. case CODE_TRADE_SUCCESS:
  278. userOrder.Status = 1
  279. case CODE_TRADE_REVOKED:
  280. userOrder.Status = 2
  281. case CODE_TRADE_REFUND:
  282. userOrder.Status = 3
  283. case CODE_TRADE_PAYERROR:
  284. userOrder.Status = 5
  285. case CODE_TRADE_CLOSED:
  286. userOrder.Status = 6
  287. case CODE_TRADE_NOTPAY: // 超过期限,就关闭
  288. userOrder.Status = 0
  289. }
  290. if _, err = userOrder.UpdateStatusByOutTradeNo(outTradeNo, userOrder.Status); err != nil {
  291. return nil, err
  292. }
  293. return res, nil
  294. }
  295. func Close(outTradeNo string) error {
  296. client, err := Client()
  297. if err != nil {
  298. return err
  299. }
  300. err = pay.CloseOrder2(client, &pay.CloseOrderRequest{
  301. OutTradeNo: outTradeNo,
  302. NonceStr: utils.RandomStr(32),
  303. SignType: core2.SignType_MD5,
  304. })
  305. // 请求关闭订单,成功后得到结果
  306. if err != nil {
  307. return err
  308. }
  309. return nil
  310. }
  311. //const CALLBACK_REFUND_URL = "https://api.ouxuanhudong.com/PcClient/common/WeChatOauthCtl/callbackRefund"
  312. func Refund(reason, outTradeNo string) (*pay.RefundResponse, error) {
  313. userOrder := new(models.UserOrder)
  314. exist, err := userOrder.GetByOutTradeNo(outTradeNo)
  315. if err != nil {
  316. return nil, err
  317. }
  318. if !exist {
  319. return nil, errors.New("订单不存在")
  320. }
  321. client, err := Client()
  322. outRefundNo := utils.RandomStr(64)
  323. nonceStr := utils.RandomStr(32)
  324. res, err := pay.Refund2(client, &pay.RefundRequest{
  325. TransactionId: "",
  326. OutTradeNo: outTradeNo,
  327. OutRefundNo: outRefundNo,
  328. TotalFee: userOrder.TotalFee,
  329. RefundFee: userOrder.TotalFee,
  330. NonceStr: nonceStr,
  331. SignType: core2.SignType_MD5,
  332. RefundFeeType: "CNY",
  333. RefundDesc: reason,
  334. })
  335. //
  336. if err != nil {
  337. userOrder.ErrMsg = err.Error()
  338. userOrder.UpdateErrByOutTradeNo(outTradeNo)
  339. return nil, err
  340. }
  341. userOrder.Status = 3
  342. _, err = userOrder.UpdateStatusByOutTradeNo(outTradeNo, userOrder.Status)
  343. if err != nil {
  344. return nil, err
  345. }
  346. PutOrderDelayQueue(userOrder.Body, userOrder.OutTradeNo, userOrder.OpenId, int(userOrder.TotalFee), userOrder.Status, 5, 0) // 退款查询
  347. return res, nil
  348. }
  349. func QueryRefund(outTradeNo string) (*pay.RefundQueryResponse, error) {
  350. userOrder := new(models.UserOrder)
  351. //exist, err := userOrder.GetByOutTradeNo(outTradeNo)
  352. //if err != nil {
  353. // return nil, err
  354. //}
  355. //if !exist {
  356. // return nil, errors.New("不存在改笔退款")
  357. //}
  358. client, err := Client()
  359. res, err := pay.RefundQuery2(client, &pay.RefundQueryRequest{
  360. OutTradeNo: outTradeNo,
  361. NonceStr: utils.RandomStr(32),
  362. SignType: core2.SignType_MD5,
  363. })
  364. //请求申请退款
  365. if err != nil {
  366. userOrder.ErrMsg = err.Error()
  367. userOrder.UpdateErrByOutTradeNo(outTradeNo)
  368. return nil, err
  369. }
  370. userOrder.RefundAccount = res.RefundList[0].RefundAccount
  371. userOrder.RefundRecvAccount = res.RefundList[0].RefundRecvAccout
  372. userOrder.SuccessTime = res.RefundList[0].RefundSuccessTime
  373. userOrder.Status = 4
  374. _, err = userOrder.UpdateRefundByOutTradeNo(outTradeNo)
  375. if err != nil {
  376. return nil, err
  377. }
  378. return res, nil
  379. }