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.

379 lines
9.6 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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,
}
(&param).GenerateSignResult()
result, err := requestCreateOrder(&param)
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,
}
(&param).GenerateSignResult()
result, err := requestCreateOrder(&param)
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(&param, "", "\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
}