|
|
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
|
|
|
}
|