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

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