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

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