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

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"
"io/ioutil"
"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 := NotifyRequest{}
body, err := ioutil.ReadAll(w)
if err != nil {
err = fmt.Errorf("body read all error:%+v", err)
return nil, err
}
err = xml.Unmarshal(body, &res)
if err != nil {
err = fmt.Errorf("xml unmarsal error:%+v", 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("user order get by out_trade_no: %+v, error: %+v, exist: %+v", res.OutTradeNo, err, exist)
return
}
err = HandleSuccess(&OrderDelayQueueParam{
Order: userOrder,
})
if err != nil {
logger.Error(fmt.Sprintf("handle success: %v", err))
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
}