package wxminipay import ( "encoding/xml" "errors" "fmt" "github.com/gin-gonic/gin" "github.com/jinzhu/gorm" "github.com/json-iterator/go" "github.com/shopspring/decimal" "io/ioutil" "log" "net/http" "recook/internal/api/mobile/pay/public" "recook/internal/back" "recook/internal/dbc" "recook/internal/model/order" "recook/internal/model/pay" "recook/internal/model/user" "recook/tools" "strings" "time" ) // 统一下单参数 type CreateOrderParam struct { XMLName xml.Name `xml:"xml"` AppID string `xml:"appid"` MchId string `xml:"mch_id"` // 商户号 NonceStr string `xml:"nonce_str"` // 随机字符串 Body string `xml:"body"` // 商品描述 Openid string `xml:"openid"` OutTradeNo string `xml:"out_trade_no"` IP string `xml:"spbill_create_ip"` // 终端IP TimeExpire string `xml:"time_expire"` // 交易结束时间 订单失效时间,格式为yyyyMMddHHmmss, 如2009年12月27日9点10分10秒表示为20091227091010。 TotalFee uint `xml:"total_fee"` TradeType string `xml:"trade_type"` NotifyUrl string `xml:"notify_url"` // 接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数。 Sign string `xml:"sign"` } func (c *CreateOrderParam) GenerateSignResult() { c.Init() signStr := "appid=" + AppID signStr = signStr + "&" + "body=" + c.Body signStr = signStr + "&" + "mch_id=" + MchID signStr = signStr + "&" + "nonce_str=" + c.NonceStr signStr = signStr + "&" + "notify_url=" + PayCallbackUrl signStr = signStr + "&" + "openid=" + c.Openid signStr = signStr + "&" + "out_trade_no=" + c.OutTradeNo signStr = signStr + "&" + "spbill_create_ip=" + c.IP signStr = signStr + "&" + "time_expire=" + c.TimeExpire signStr = signStr + "&" + "total_fee=" + fmt.Sprintf("%d", c.TotalFee) signStr = signStr + "&" + "trade_type=JSAPI" signStr = signStr + "&key=" + APIKey c.Sign = strings.ToUpper(tools.MD5(signStr)) } func (c *CreateOrderParam) Init() { c.AppID = AppID c.MchId = MchID c.NotifyUrl = PayCallbackUrl c.TradeType = "JSAPI" } type CreateOrderResult struct { XMLName xml.Name `xml:"xml"` ReturnCode string `xml:"return_code" json:"return_code"` ResultCode string `xml:"result_code" json:"result_code"` ReturnMsg string `xml:"return_msg" json:"return_msg"` AppID string `xml:"appid" json:"appid"` MchId string `xml:"mch_id" json:"mch_id"` NonceStr string `xml:"nonce_str" json:"nonce_str"` Sign string `xml:"sign" json:"sign"` PrepayID string `xml:"prepay_id" json:"prepay_id"` TradeType string `xml:"trade_type" json:"trade_type"` ErrCodeDes string `xml:"err_code_des" json:"err_code_des"` } func (r *CreateOrderResult) IsOK() bool { return r.ReturnCode == "SUCCESS" && r.ResultCode == "SUCCESS" } type appCreateOrderParam struct { UserID uint `json:"userId" validate:"required"` OrderID uint `json:"orderId" validate:"required"` } var json = jsoniter.ConfigCompatibleWithStandardLibrary func createOrder(orderID uint, ip string, userID uint) (error, *AppCreateOrderResult) { now := time.Now().Unix() var od []order.Information if err := dbc.DB.Find(&od, "virtual_id = ?", orderID).Error; err != nil { return err, nil } if err := public.UpdateVirtualPay(orderID); err != nil { return err, nil } amount := decimal.Zero for _, o := range od { if err := public.ValidateOrderInfo(&o, now); err != nil { return err, nil } amount = amount.Add(o.ActualTotalAmount) } // 应该调用零支付 if amount.IsZero() { return errors.New("接口调用错误"), nil } var u user.Information if err := dbc.DB.Select("wx_mini_open_id").First(&u, "id = ?", userID).Error; err != nil && err != gorm.ErrRecordNotFound { return err, nil } var detail pay.WxMiniPayDetail err := dbc.DB.First(&detail, "order_id = ?", orderID).Error if err != nil && !gorm.IsRecordNotFoundError(err) { return err, nil } if detail.ID > 0 { var result AppCreateOrderResult err = json.Unmarshal([]byte(detail.CreateOrderSign), &result) if err != nil { return err, nil } tx := dbc.DB.Begin() for _, o := range od { if o.PayMethod != order.WechatMiniPayForOrderInfo { err = dbc.DB.Model(&o).Updates(order.Information{ PayIP: ip, TradeNo: detail.TradeNo, PayMethod: order.WechatMiniPayForOrderInfo, }).Error if err != nil { tx.Rollback() return err, nil } } } tx.Commit() return nil, &result } else { afterTime, _ := time.ParseDuration(public.TimeExpireDuration) expireTime := od[0].ExpireTime.Time.Add(afterTime) outTradeNo := public.GenerateOrderWxPayOutTradeNo(userID, orderID) param := CreateOrderParam{ NonceStr: tools.Token(), Body: od[0].Title, OutTradeNo: outTradeNo, IP: ip, TimeExpire: expireTime.Format("20060102150405"), TotalFee: uint(amount.Mul(decimal.NewFromInt(100)).IntPart()), // 微信必须转化成分做单位 Openid: u.WxMiniOpenId, } (¶m).GenerateSignResult() result, err := requestCreateOrder(¶m) if err != nil { return err, nil } appResult := (&AppCreateOrderResult{}).GetSignResult(result) js, err := json.Marshal(&appResult) if err != nil { return err, nil } detail = pay.WxMiniPayDetail{ OrderID: orderID, TradeNo: outTradeNo, CreateOrderSign: string(js), } tx := dbc.DB.Begin() { err = tx.Create(&detail).Error if err != nil { return err, nil } for _, o := range od { err = tx.Model(&o).Updates(order.Information{ PayIP: ip, TradeNo: detail.TradeNo, PayMethod: 4, }).Error if err != nil { tx.Rollback() return err, nil } } } tx.Commit() return nil, appResult } } func PayOrder(c *gin.Context) { var p appCreateOrderParam err := tools.ParseParams(&p, c) if err != nil { back.Fail(c, err.Error()) return } if !public.Judge(p.OrderID) { err, result := createOrder(p.OrderID, c.ClientIP(), p.UserID) if err != nil { back.Err(c, err.Error()) return } back.Suc(c, "", result) return } if err = public.UpdateNormalPay(p.OrderID); err != nil { back.Err(c, err.Error()) return } now := time.Now().Unix() var orderInfo order.Information err = dbc.DB.First(&orderInfo, "id = ?", p.OrderID).Error if err != nil { back.Err(c, err.Error()) return } err = public.ValidateOrderInfo(&orderInfo, now) if err != nil { back.Fail(c, err.Error()) return } // 应该调用零支付 if orderInfo.ActualTotalAmount.Equal(decimal.NewFromFloat(0.0)) { back.Fail(c, "请使用零支付接口") return } var u user.Information err = dbc.DB.Select("wx_mini_open_id").First(&u, "id = ?", p.UserID).Error if err != nil && false == gorm.IsRecordNotFoundError(err) { back.Err(c, err.Error()) return } var detail pay.WxMiniPayDetail err = dbc.DB.First(&detail, "order_id = ?", p.OrderID).Error if err != nil && !gorm.IsRecordNotFoundError(err) { back.Err(c, err.Error()) return } if detail.ID > 0 { var result AppCreateOrderResult err = json.Unmarshal([]byte(detail.CreateOrderSign), &result) if err != nil { back.Err(c, err.Error()) return } if orderInfo.PayMethod != 4 { err = dbc.DB.Model(&orderInfo).Updates(order.Information{ PayIP: c.ClientIP(), TradeNo: detail.TradeNo, PayMethod: 4, }).Error if err != nil { back.Err(c, err.Error()) return } } back.Suc(c, "", &result) } else { afterTime, _ := time.ParseDuration(public.TimeExpireDuration) expireTime := orderInfo.ExpireTime.Time.Add(afterTime) outTradeNo := public.GenerateOrderWxMiniPayOutTradeNo(p.UserID, orderInfo.ID) param := CreateOrderParam{ NonceStr: tools.Token(), Body: orderInfo.Title, OutTradeNo: outTradeNo, IP: c.ClientIP(), TimeExpire: expireTime.Format("20060102150405"), TotalFee: uint(orderInfo.ActualTotalAmount.Mul(decimal.NewFromInt(100)).IntPart()), // 微信必须转化成分做单位 Openid: u.WxMiniOpenId, } (¶m).GenerateSignResult() result, err := requestCreateOrder(¶m) if err != nil { back.Err(c, err.Error()) return } appResult := (&AppCreateOrderResult{}).GetSignResult(result) js, err := json.Marshal(&appResult) if err != nil { back.Err(c, err.Error()) return } detail = pay.WxMiniPayDetail{ OrderID: p.OrderID, TradeNo: outTradeNo, CreateOrderSign: string(js), } tx := dbc.DB.Begin() { err = tx.Create(&detail).Error if err != nil { back.Err(c, err.Error()) tx.Rollback() return } err = tx.Model(&orderInfo).Updates(order.Information{ PayIP: c.ClientIP(), TradeNo: detail.TradeNo, PayMethod: 4, }).Error if err != nil { back.Err(c, err.Error()) tx.Rollback() return } } tx.Commit() back.Suc(c, "", appResult) } } func requestCreateOrder(param *CreateOrderParam) (*CreateOrderResult, error) { xmlStr, err := xml.MarshalIndent(¶m, "", "\t") if err != nil { panic(err) } client := &http.Client{} request, err := http.NewRequest("POST", CreateOrderURL, strings.NewReader(string(xmlStr))) if err != nil { panic(err) } request.Header.Set("Content-Type", "application/xml; charset=UTF-8") response, err := client.Do(request) if err != nil { panic(err) } defer func() { _ = response.Body.Close() }() result, err := ioutil.ReadAll(response.Body) if err != nil { panic(err) } log.Println(string(result)) var r CreateOrderResult err = xml.Unmarshal(result, &r) if err != nil { panic(err) } if r.IsOK() == false { log.Println(string(xmlStr)) return nil, errors.New(r.ErrCodeDes) } return &r, nil }