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

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