|
|
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(2 * time.Hour).Unix() // 2 个小时
}
orderDelayQueue <- &orderDelayQueueParam{ First: true, Expires: expires, Delay: delay, OutTradeNo: outTradeNo, Body: body, Amount: amount, Status: status, OpenId: openId, } }
func loopUnifiedOrder() { orders, err := models.GetUserOrdersByStatus(0, 3) if err != nil { panic(err) }
for _, order := range orders { PutOrderDelayQueue(order.Body, order.OutTradeNo, order.OpenId, int(order.TotalFee), order.Status, 0, 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 {
// Close(param.OutTradeNo) // 超时关闭订单
//}
continue // 超时
}
// 首次进入不延迟
if !param.First { time.Sleep(time.Duration(param.Delay) * time.Second) } 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(2 * 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 }
// 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 }
|