mirror of
https://github.com/silenceper/wechat.git
synced 2026-02-04 12:52:27 +08:00
288 lines
8.2 KiB
Go
288 lines
8.2 KiB
Go
package order
|
||
|
||
import (
|
||
"encoding/xml"
|
||
"errors"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/silenceper/wechat/v2/pay/config"
|
||
"github.com/silenceper/wechat/v2/util"
|
||
)
|
||
|
||
// https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
|
||
var payGateway = "https://api.mch.weixin.qq.com/pay/unifiedorder"
|
||
|
||
// SUCCESS 表示支付成功
|
||
const SUCCESS = "SUCCESS"
|
||
|
||
// Order struct extends context
|
||
type Order struct {
|
||
*config.Config
|
||
}
|
||
|
||
// NewOrder return an instance of order package
|
||
func NewOrder(cfg *config.Config) *Order {
|
||
order := Order{cfg}
|
||
return &order
|
||
}
|
||
|
||
// Params was NEEDED when request Unified order
|
||
// 传入的参数,用于生成 prepay_id 的必需参数
|
||
type Params struct {
|
||
TotalFee string
|
||
CreateIP string
|
||
Body string
|
||
OutTradeNo string
|
||
TimeExpire string // 订单失效时间,格式为 yyyyMMddHHmmss,如 2009 年 12 月 27 日 9 点 10 分 10 秒表示为 20091227091010。
|
||
OpenID string
|
||
TradeType string
|
||
SignType string
|
||
Detail string
|
||
Attach string
|
||
GoodsTag string
|
||
NotifyURL string
|
||
}
|
||
|
||
// Config 是传出用于 js sdk 用的参数
|
||
type Config struct {
|
||
Timestamp string `json:"timestamp"`
|
||
NonceStr string `json:"nonceStr"`
|
||
PrePayID string `json:"prePayId"`
|
||
SignType string `json:"signType"`
|
||
Package string `json:"package"`
|
||
PaySign string `json:"paySign"`
|
||
}
|
||
|
||
// ConfigForApp 是传出用于 app sdk 用的参数
|
||
type ConfigForApp struct {
|
||
AppID string `json:"appid"`
|
||
MchID string `json:"partnerid"` // 微信支付分配的商户号
|
||
PrePayID string `json:"prepayid"`
|
||
Package string `json:"package"`
|
||
NonceStr string `json:"nonceStr"`
|
||
Timestamp string `json:"timestamp"`
|
||
Sign string `json:"sign"`
|
||
}
|
||
|
||
// PreOrder 是 Unified order 接口的返回
|
||
type PreOrder struct {
|
||
ReturnCode string `xml:"return_code"`
|
||
ReturnMsg string `xml:"return_msg"`
|
||
AppID string `xml:"appid,omitempty"`
|
||
MchID string `xml:"mch_id,omitempty"`
|
||
NonceStr string `xml:"nonce_str,omitempty"`
|
||
Sign string `xml:"sign,omitempty"`
|
||
ResultCode string `xml:"result_code,omitempty"`
|
||
TradeType string `xml:"trade_type,omitempty"`
|
||
PrePayID string `xml:"prepay_id,omitempty"`
|
||
CodeURL string `xml:"code_url,omitempty"`
|
||
MWebURL string `xml:"mweb_url,omitempty"`
|
||
ErrCode string `xml:"err_code,omitempty"`
|
||
ErrCodeDes string `xml:"err_code_des,omitempty"`
|
||
}
|
||
|
||
// payRequest 接口请求参数
|
||
type payRequest struct {
|
||
AppID string `xml:"appid"` // 公众账号 ID
|
||
MchID string `xml:"mch_id"` // 商户号
|
||
DeviceInfo string `xml:"device_info,omitempty"` // 设备号
|
||
NonceStr string `xml:"nonce_str"` // 随机字符串
|
||
Sign string `xml:"sign"` // 签名
|
||
SignType string `xml:"sign_type,omitempty"` // 签名类型
|
||
Body string `xml:"body"` // 商品描述
|
||
Detail string `xml:"detail,omitempty"` // 商品详情
|
||
Attach string `xml:"attach,omitempty"` // 附加数据
|
||
OutTradeNo string `xml:"out_trade_no"` // 商户订单号
|
||
FeeType string `xml:"fee_type,omitempty"` // 标价币种
|
||
TotalFee string `xml:"total_fee"` // 标价金额
|
||
SpbillCreateIP string `xml:"spbill_create_ip"` // 终端 IP
|
||
TimeStart string `xml:"time_start,omitempty"` // 交易起始时间
|
||
TimeExpire string `xml:"time_expire,omitempty"` // 交易结束时间
|
||
GoodsTag string `xml:"goods_tag,omitempty"` // 订单优惠标记
|
||
NotifyURL string `xml:"notify_url"` // 通知地址
|
||
TradeType string `xml:"trade_type"` // 交易类型
|
||
ProductID string `xml:"product_id,omitempty"` // 商品 ID
|
||
LimitPay string `xml:"limit_pay,omitempty"` // 指定支付方式
|
||
OpenID string `xml:"openid,omitempty"` // 用户标识
|
||
SceneInfo string `xml:"scene_info,omitempty"` // 场景信息
|
||
|
||
XMLName struct{} `xml:"xml"`
|
||
}
|
||
|
||
func (req *payRequest) BridgePayRequest(p *Params, AppID, MchID, nonceStr, sign string) *payRequest {
|
||
request := payRequest{
|
||
AppID: AppID,
|
||
MchID: MchID,
|
||
NonceStr: nonceStr,
|
||
Sign: sign,
|
||
Body: p.Body,
|
||
OutTradeNo: p.OutTradeNo,
|
||
TotalFee: p.TotalFee,
|
||
SpbillCreateIP: p.CreateIP,
|
||
NotifyURL: p.NotifyURL,
|
||
TradeType: p.TradeType,
|
||
OpenID: p.OpenID,
|
||
SignType: p.SignType,
|
||
Detail: p.Detail,
|
||
Attach: p.Attach,
|
||
GoodsTag: p.GoodsTag,
|
||
}
|
||
return &request
|
||
}
|
||
|
||
// BridgeConfig get js bridge config
|
||
func (o *Order) BridgeConfig(p *Params) (cfg Config, err error) {
|
||
var (
|
||
buffer strings.Builder
|
||
timestamp = strconv.FormatInt(time.Now().Unix(), 10)
|
||
)
|
||
order, err := o.PrePayOrder(p)
|
||
if err != nil {
|
||
return
|
||
}
|
||
buffer.WriteString("appId=")
|
||
buffer.WriteString(order.AppID)
|
||
buffer.WriteString("&nonceStr=")
|
||
buffer.WriteString(order.NonceStr)
|
||
buffer.WriteString("&package=")
|
||
buffer.WriteString("prepay_id=" + order.PrePayID)
|
||
buffer.WriteString("&signType=")
|
||
buffer.WriteString(p.SignType)
|
||
buffer.WriteString("&timeStamp=")
|
||
buffer.WriteString(timestamp)
|
||
buffer.WriteString("&key=")
|
||
buffer.WriteString(o.Key)
|
||
|
||
sign, err := util.CalculateSign(buffer.String(), p.SignType, o.Key)
|
||
if err != nil {
|
||
return
|
||
}
|
||
// 签名
|
||
cfg.PaySign = sign
|
||
cfg.NonceStr = order.NonceStr
|
||
cfg.Timestamp = timestamp
|
||
cfg.PrePayID = order.PrePayID
|
||
cfg.SignType = p.SignType
|
||
cfg.Package = "prepay_id=" + order.PrePayID
|
||
return
|
||
}
|
||
|
||
// BridgeAppConfig get app bridge config
|
||
func (o *Order) BridgeAppConfig(p *Params) (cfg ConfigForApp, err error) {
|
||
var (
|
||
timestamp = strconv.FormatInt(util.GetCurrTS(), 10)
|
||
noncestr = util.RandomStr(32)
|
||
_package = "Sign=WXPay"
|
||
)
|
||
order, err := o.PrePayOrder(p)
|
||
if err != nil {
|
||
return
|
||
}
|
||
|
||
result := map[string]string{
|
||
"appid": order.AppID,
|
||
"partnerid": order.MchID,
|
||
"prepayid": order.PrePayID,
|
||
"package": _package,
|
||
"noncestr": noncestr,
|
||
"timestamp": timestamp,
|
||
}
|
||
// 签名
|
||
sign, err := util.ParamSign(result, o.Key)
|
||
if err != nil {
|
||
return
|
||
}
|
||
result["sign"] = sign
|
||
cfg = ConfigForApp{
|
||
AppID: result["appid"],
|
||
MchID: result["partnerid"],
|
||
PrePayID: result["prepayid"],
|
||
Package: result["package"],
|
||
NonceStr: result["noncestr"],
|
||
Timestamp: result["timestamp"],
|
||
Sign: result["sign"],
|
||
}
|
||
return
|
||
}
|
||
|
||
// PrePayOrder return data for invoke wechat payment
|
||
func (o *Order) PrePayOrder(p *Params) (payOrder PreOrder, err error) {
|
||
nonceStr := util.RandomStr(32)
|
||
|
||
// 通知地址
|
||
if len(p.NotifyURL) == 0 {
|
||
p.NotifyURL = o.NotifyURL // 默认使用 order.NotifyURL
|
||
}
|
||
|
||
// 签名类型
|
||
if p.SignType == "" {
|
||
p.SignType = util.SignTypeMD5
|
||
}
|
||
|
||
param := map[string]string{
|
||
"appid": o.AppID,
|
||
"body": p.Body,
|
||
"mch_id": o.MchID,
|
||
"nonce_str": nonceStr,
|
||
"out_trade_no": p.OutTradeNo,
|
||
"spbill_create_ip": p.CreateIP,
|
||
"total_fee": p.TotalFee,
|
||
"trade_type": p.TradeType,
|
||
"openid": p.OpenID,
|
||
"sign_type": p.SignType,
|
||
"detail": p.Detail,
|
||
"attach": p.Attach,
|
||
"goods_tag": p.GoodsTag,
|
||
"notify_url": p.NotifyURL,
|
||
}
|
||
|
||
if p.TimeExpire != "" {
|
||
// 如果有传入交易结束时间
|
||
param["time_expire"] = p.TimeExpire
|
||
}
|
||
|
||
sign, err := util.ParamSign(param, o.Key)
|
||
if err != nil {
|
||
return
|
||
}
|
||
request := new(payRequest).BridgePayRequest(p, o.AppID, o.MchID, nonceStr, sign)
|
||
if len(p.TimeExpire) > 0 {
|
||
// 如果有传入交易结束时间
|
||
request.TimeExpire = p.TimeExpire
|
||
}
|
||
rawRet, err := util.PostXML(payGateway, request)
|
||
if err != nil {
|
||
return
|
||
}
|
||
err = xml.Unmarshal(rawRet, &payOrder)
|
||
if err != nil {
|
||
return
|
||
}
|
||
if payOrder.ReturnCode == SUCCESS {
|
||
// pay success
|
||
if payOrder.ResultCode == SUCCESS {
|
||
err = nil
|
||
return
|
||
}
|
||
err = errors.New(payOrder.ErrCode + payOrder.ErrCodeDes)
|
||
return
|
||
}
|
||
err = errors.New("[msg : xmlUnmarshalError] [rawReturn : " + string(rawRet) + "] [sign : " + sign + "]")
|
||
return
|
||
}
|
||
|
||
// PrePayID will request wechat merchant api and request for a pre-payment order id
|
||
func (o *Order) PrePayID(p *Params) (prePayID string, err error) {
|
||
order, err := o.PrePayOrder(p)
|
||
if err != nil {
|
||
return
|
||
}
|
||
if order.PrePayID == "" {
|
||
err = errors.New("empty prepayid")
|
||
}
|
||
prePayID = order.PrePayID
|
||
return
|
||
}
|