package pay_service import ( "encoding/xml" "fmt" core2 "github.com/chanxuehong/wechat/mch/core" "github.com/chanxuehong/wechat/mch/pay" "github.com/pkg/errors" "go.uber.org/zap" "hudongzhuanjia/logger" "hudongzhuanjia/models" "hudongzhuanjia/utils" "io" "math" "strconv" "time" ) func init() { go loopUnifiedOrder() } var orderDelayQueue = make(chan *orderDelayQueueParam, math.MaxInt8) type orderDelayQueueParam struct { First bool `json:"first"` Expires int64 `json:"expires"` Delay int `json:"delay"` OutTradeNo string `json:"out_trade_no"` Body string `json:"body"` Amount int `json:"amount"` // 金额 Status int `json:"status"` // 0 订单 3 退款 OpenId string `json:"open_id"` // 被操作人 } func PutOrderDelayQueue(body, outTradeNo, openId string, amount, status int, expires int64, delay int) { if expires == 0 { expires = time.Now().Add(12 * time.Hour).Unix() // 2 个小时 } if delay == 0 { delay = 100 } orderDelayQueue <- &orderDelayQueueParam{ First: true, Expires: expires, Delay: delay, OutTradeNo: outTradeNo, Body: body, Amount: amount, Status: status, OpenId: openId, } } func loopUnifiedOrder() { orders, err := models.GetUserOrdersByStatus(core2.FormatTime(time.Now()), 0, 3) if err != nil { panic(err) } for _, order := range orders { expire, err := core2.ParseTime(order.TimeExpire) if err != nil { expire = time.Now().Add(12 * time.Hour) } PutOrderDelayQueue(order.Body, order.OutTradeNo, order.OpenId, int(order.TotalFee), order.Status, expire.Unix(), 0) } defer func() { if err := recover(); err != nil { logger.Error("订单轮询查询", zap.Any("panic 恢复错误", err)) } // 重启 time.Sleep(5 * time.Second) loopUnifiedOrder() }() for { select { case param, ok := <-orderDelayQueue: if !ok { panic("通道异常关闭") } if param.Expires <= time.Now().Unix() { if param.Status == 0 { order := new(models.UserOrder) _, err = order.UpdateStatusByOutTradeNo(param.OutTradeNo, 7) //Close(param.OutTradeNo) // 超时关闭订单 } continue // 超时 } // 首次进入不延迟 if !param.First { time.Sleep(time.Duration(param.Delay) * time.Millisecond) } param.First = false if param.Status == 0 { res, err := OrderQuery(param.OutTradeNo) // 出现错误 if err != nil { logger.Error("查询订单出现错误", zap.String("错误原因", err.Error()), zap.String("交易订单号", param.OutTradeNo)) orderDelayQueue <- param // 重新进入队列 continue } if res.Query.TradeState == CODE_TRADE_SUCCESS { go Handle(res.Order) } else if res.Query.TradeState == CODE_TRADE_REFUND { param.Status = 3 orderDelayQueue <- param continue } else if res.Query.TradeState == CODE_TRADE_NOTPAY || res.Query.TradeState == CODE_TRADE_USERPAYING { orderDelayQueue <- param continue } } else if param.Status == 3 { _, err = QueryRefund(param.OutTradeNo) if err != nil { logger.Error("退款订单查询错误", zap.String("错误原因", err.Error()), zap.String("交易订单号", param.OutTradeNo)) continue } } else { continue } } } } const CallbackOrderUrl = "https://api.ouxuanhudong.com/PcClient/common/WeChatOauthCtl/callbackOrder" func UnifiedOrder(body, openid string, fee, goodType, userId, activityId int64) (map[string]interface{}, error) { client, err := Client() if err != nil { return nil, err } now := time.Now() timeStart := core2.FormatTime(now) timeExpire := core2.FormatTime(now.Add(12 * time.Hour)) outTradeNo := utils.RandomStr(32) nonceStr := utils.RandomStr(32) resp, err := pay.UnifiedOrder2(client, &pay.UnifiedOrderRequest{ Body: body, OutTradeNo: outTradeNo, TotalFee: fee, NotifyURL: CallbackOrderUrl, TradeType: "JSAPI", DeviceInfo: "WEB", NonceStr: nonceStr, SignType: core2.SignType_MD5, TimeStart: now, OpenId: openid, }) if err != nil { return nil, err } // 记录这次订单 userOrder := new(models.UserOrder) userOrder.DeviceInfo = "WEB" userOrder.Body = body userOrder.UserId = userId userOrder.ActivityId = activityId userOrder.FeeType = "CNY" userOrder.GoodType = goodType userOrder.OpenId = openid userOrder.OutTradeNo = outTradeNo userOrder.TimeStart = timeStart userOrder.TimeExpire = timeExpire userOrder.TotalFee = fee userOrder.TradeType = "JSAPI" userOrder.PrepayId = resp.PrepayId userOrder.Status = 0 userOrder.IsDelete = false userOrder.CreatedAt = time.Now() userOrder.UpdatedAt = time.Now() if _, err = userOrder.AddUserOrder(); err != nil { return nil, err } timestamp := strconv.FormatInt(time.Now().Unix(), 10) //获取H5支付需要的paySign pac := "prepay_id=" + resp.PrepayId paySign := core2.JsapiSign(client.AppId(), timestamp, nonceStr, pac, core2.SignType_MD5, ApiKey) go PutOrderDelayQueue(userOrder.Body, userOrder.OutTradeNo, userOrder.OpenId, int(userOrder.TotalFee), userOrder.Status, 0, 0) return map[string]interface{}{ "appid": Appid, "timestamp": timestamp, "nonce_str": nonceStr, "package": pac, "sign_type": core2.SignType_MD5, "pay_sign": paySign, "out_trade_no": outTradeNo, "user_order_id": userOrder.Id, }, nil } func ReOrder(outTradeNo string) (map[string]interface{}, error) { userOrder := new(models.UserOrder) exist, err := userOrder.GetByOutTradeNo(outTradeNo) if err != nil { return nil, err } if !exist { return nil, errors.New("订单不存在") } timestamp := strconv.FormatInt(time.Now().Unix(), 10) nonceStr := utils.RandomStr(32) //获取H5支付需要的paySign pac := "prepay_id=" + userOrder.PrepayId paySign := core2.JsapiSign(Appid, timestamp, nonceStr, pac, core2.SignType_MD5, ApiKey) if userOrder.TimeExpire <= core2.FormatTime(time.Now()) { go PutOrderDelayQueue(userOrder.Body, userOrder.OutTradeNo, userOrder.OpenId, int(userOrder.TotalFee), userOrder.Status, 0, 0) } return map[string]interface{}{ "appid": Appid, "timestamp": timestamp, "nonce_str": nonceStr, "package": pac, "sign_type": core2.SignType_MD5, "pay_sign": paySign, "out_trade_no": outTradeNo, "user_order_id": userOrder.Id, }, nil } // Notify type NotifyRequest struct { ReturnCode string `xml:"return_code,omitempty" json:"return_code,omitempty"` ReturnMsg string `xml:"return_msg,omitempty" json:"return_msg,omitempty"` ResultCode string `xml:"result_code,omitempty" json:"result_code,omitempty"` ErrCode string `xml:"err_code,omitempty" json:"err_code,omitempty"` ErrCodeDes string `xml:"err_code_des,omitempty" json:"err_code_des,omitempty"` Appid string `xml:"appid,omitempty" json:"appid,omitempty"` MchId string `xml:"mch_id,omitempty" json:"mch_id,omitempty"` DeviceInfo string `xml:"device_info,omitempty" json:"device_info,omitempty"` NonceStr string `xml:"nonce_str,omitempty" json:"nonce_str,omitempty"` Sign string `xml:"sign,omitempty" json:"sign,omitempty"` SignType string `xml:"sign_type,omitempty" json:"sign_type,omitempty"` Openid string `xml:"openid,omitempty" json:"openid,omitempty"` IsSubscribe string `xml:"is_subscribe,omitempty" json:"is_subscribe,omitempty"` TradeType string `xml:"trade_type,omitempty" json:"trade_type,omitempty"` BankType string `xml:"bank_type,omitempty" json:"bank_type,omitempty"` TotalFee int `xml:"total_fee,omitempty" json:"total_fee,omitempty"` SettlementTotalFee int `xml:"settlement_total_fee,omitempty" json:"settlement_total_fee,omitempty"` FeeType string `xml:"fee_type,omitempty" json:"fee_type,omitempty"` CashFee int `xml:"cash_fee,omitempty" json:"cash_fee,omitempty"` CashFeeType string `xml:"cash_fee_type,omitempty" json:"cash_fee_type,omitempty"` CouponFee int `xml:"coupon_fee,omitempty" json:"coupon_fee,omitempty"` CouponCount int `xml:"coupon_count,omitempty" json:"coupon_count,omitempty"` CouponType0 string `xml:"coupon_type_0,omitempty" json:"coupon_type_0,omitempty"` CouponType1 string `xml:"coupon_type_1,omitempty" json:"coupon_type_1,omitempty"` CouponId0 string `xml:"coupon_id_0,omitempty" json:"coupon_id_0,omitempty"` CouponId1 string `xml:"coupon_id_1,omitempty" json:"coupon_id_1,omitempty"` CouponFee0 int `xml:"coupon_fee_0,omitempty" json:"coupon_fee_0,omitempty"` CouponFee1 int `xml:"coupon_fee_1,omitempty" json:"coupon_fee_1,omitempty"` TransactionId string `xml:"transaction_id,omitempty" json:"transaction_id,omitempty"` OutTradeNo string `xml:"out_trade_no,omitempty" json:"out_trade_no,omitempty"` Attach string `xml:"attach,omitempty" json:"attach,omitempty"` TimeEnd string `xml:"time_end,omitempty" json:"time_end,omitempty"` } func NotifyOrder(w io.Reader) (*models.UserOrder, error) { res := new(NotifyRequest) if err := xml.NewDecoder(w).Decode(res); err != nil { return nil, fmt.Errorf("xml.NewDecoder.Decode:%w", err) } if res.ReturnCode != CODE_SUCCESS { return nil, fmt.Errorf("network error, retrun_code: %v and return_msg: %v", res.ReturnCode, res.ReturnMsg) } if res.ResultCode == CODE_SUCCESS && res.TradeType == CODE_TRADE_SUCCESS { userOrder := new(models.UserOrder) userOrder.TimeEnd = res.TimeEnd userOrder.TransactionId = res.TransactionId userOrder.Status = 1 if _, err := userOrder.UpdateStatusByOutTradeNo(res.OutTradeNo, 1); err != nil { return nil, err } return userOrder, nil } else { return nil, fmt.Errorf("trade error, err_code: %v and err_code_des: %v", res.ErrCode, res.ErrCodeDes) } } type OrderQueryResult struct { Order *models.UserOrder Query *pay.OrderQueryResponse } func OrderQuery(outTradeNo string) (*OrderQueryResult, error) { client, err := Client() if err != nil { return nil, err } // 请求订单查询,成功后得到结果 userOrder := new(models.UserOrder) exist, err := userOrder.GetByOutTradeNo(outTradeNo) if err != nil { return nil, err } if !exist { return nil, errors.New("订单不存在") } res, err := pay.OrderQuery2(client, &pay.OrderQueryRequest{ OutTradeNo: outTradeNo, NonceStr: utils.RandomStr(32), SignType: core2.SignType_MD5, }) if err != nil { userOrder.ErrMsg = err.Error() userOrder.UpdateErrByOutTradeNo(outTradeNo) return nil, err } userOrder.TransactionId = res.TransactionId userOrder.TimeEnd = core2.FormatTime(res.TimeEnd) switch res.TradeState { case CODE_TRADE_SUCCESS: userOrder.Status = 1 case CODE_TRADE_REVOKED: userOrder.Status = 2 case CODE_TRADE_REFUND: userOrder.Status = 3 case CODE_TRADE_PAYERROR: userOrder.Status = 5 case CODE_TRADE_CLOSED: userOrder.Status = 6 case CODE_TRADE_NOTPAY: // 超过期限,就关闭 userOrder.Status = 0 } if _, err = userOrder.UpdateStatusByOutTradeNo(outTradeNo, userOrder.Status); err != nil { return nil, err } return &OrderQueryResult{ Order: userOrder, Query: res, }, nil } func Close(outTradeNo string) error { client, err := Client() if err != nil { return err } err = pay.CloseOrder2(client, &pay.CloseOrderRequest{ OutTradeNo: outTradeNo, NonceStr: utils.RandomStr(32), SignType: core2.SignType_MD5, }) // 请求关闭订单,成功后得到结果 if err != nil { return err } return nil } //const CALLBACK_REFUND_URL = "https://api.ouxuanhudong.com/PcClient/common/WeChatOauthCtl/callbackRefund" func Refund(reason, outTradeNo string) (*pay.RefundResponse, error) { userOrder := new(models.UserOrder) exist, err := userOrder.GetByOutTradeNo(outTradeNo) if err != nil { return nil, err } if !exist { return nil, errors.New("订单不存在") } client, err := Client() outRefundNo := utils.RandomStr(64) nonceStr := utils.RandomStr(32) res, err := pay.Refund2(client, &pay.RefundRequest{ TransactionId: "", OutTradeNo: outTradeNo, OutRefundNo: outRefundNo, TotalFee: userOrder.TotalFee, RefundFee: userOrder.TotalFee, NonceStr: nonceStr, SignType: core2.SignType_MD5, RefundFeeType: "CNY", RefundDesc: reason, }) // if err != nil { userOrder.ErrMsg = err.Error() userOrder.UpdateErrByOutTradeNo(outTradeNo) return nil, err } userOrder.Status = 3 _, err = userOrder.UpdateStatusByOutTradeNo(outTradeNo, userOrder.Status) if err != nil { return nil, err } PutOrderDelayQueue(userOrder.Body, userOrder.OutTradeNo, userOrder.OpenId, int(userOrder.TotalFee), userOrder.Status, 0, 0) // 退款查询 return res, nil } type QueryRefundResult struct { Order *models.UserOrder Query *pay.RefundQueryResponse } func QueryRefund(outTradeNo string) (*QueryRefundResult, error) { userOrder := new(models.UserOrder) exist, err := userOrder.GetByOutTradeNo(outTradeNo) if err != nil { return nil, err } if !exist { return nil, errors.New("不存在改笔退款") } client, err := Client() res, err := pay.RefundQuery2(client, &pay.RefundQueryRequest{ OutTradeNo: outTradeNo, NonceStr: utils.RandomStr(32), SignType: core2.SignType_MD5, }) //请求申请退款 if err != nil { userOrder.ErrMsg = err.Error() userOrder.UpdateErrByOutTradeNo(outTradeNo) return nil, err } userOrder.RefundAccount = res.RefundList[0].RefundAccount userOrder.RefundRecvAccount = res.RefundList[0].RefundRecvAccout userOrder.SuccessTime = res.RefundList[0].RefundSuccessTime userOrder.Status = 4 _, err = userOrder.UpdateRefundByOutTradeNo(outTradeNo) if err != nil { return nil, err } return &QueryRefundResult{ Order: userOrder, Query: res, }, nil }