|
|
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 { Order *models.UserOrder First bool `json:"first"` Expires int64 `json:"expires"` Delay int `json:"delay"` }
func PutOrderDelayQueue(order *models.UserOrder, expires int64, delay int) { if expires == 0 { expires = time.Now().Add(12 * time.Hour).Unix() // 2 个小时
}
if delay == 0 { delay = 0 }
orderDelayQueue <- &OrderDelayQueueParam{ First: true, Expires: expires, Delay: delay, Order: order, } }
func loopUnifiedOrder() { orders, err := models.GetUserOrdersByStatus(core2.FormatTime(time.Now().Add(12*time.Hour)), 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, expire.Add(12*time.Hour).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.Order.Status == 0 { order := new(models.UserOrder) _, err = order.UpdateStatusByOutTradeNo(param.Order.OutTradeNo, 7) Close(param.Order.OutTradeNo) // 超时关闭订单
go HandleFailed(param) } continue // 超时
}
// 首次进入不延迟
if !param.First { time.Sleep(time.Duration(param.Delay) * time.Millisecond) } param.First = false
if param.Order.Status == 0 { res, err := OrderQuery(param.Order.OutTradeNo)
// 出现错误
if err != nil { logger.Error("查询订单出现错误", zap.String("错误原因", err.Error()), zap.String("交易订单号", param.Order.OutTradeNo)) orderDelayQueue <- param // 重新进入队列
continue }
if res.Query.TradeState == CODE_TRADE_SUCCESS { param.Order = res.Order go HandleSuccess(param) } else if res.Query.TradeState == CODE_TRADE_REFUND { param.Order.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.Order.Status == 3 { _, err = QueryRefund(param.Order.OutTradeNo) if err != nil { logger.Error("退款订单查询错误", zap.String("错误原因", err.Error()), zap.String("交易订单号", param.Order.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 = models.Add(userOrder); 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, 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) go PutOrderDelayQueue(userOrder, 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) (order *models.UserOrder, err error) { var exist bool res := new(NotifyRequest) err = xml.NewDecoder(w).Decode(res) if err != nil { err = fmt.Errorf("xml.NewDecoder.Decode:%w", err) return }
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 _, err = userOrder.UpdateStatusByOutTradeNo(res.OutTradeNo, 1) if err != nil { return nil, err } exist, err = userOrder.GetByOutTradeNo(res.OutTradeNo) if err == nil || !exist { err = fmt.Errorf("network error, retrun_code: %v and return_msg: %v", res.ReturnCode, res.ReturnMsg) return }
return userOrder, nil } else { err = fmt.Errorf("trade error, err_code: %v and err_code_des: %v", res.ErrCode, res.ErrCodeDes) return } }
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, 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 }
|