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

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