| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246 |
- package pay
- import (
- "bytes"
- "encoding/json"
- "fmt"
- "time"
- "unicode/utf8"
- "github.com/yaotian/gowechat/mch/base"
- "github.com/yaotian/gowechat/util"
- )
- //OrderInput 下单
- //官网文档 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
- type OrderInput struct {
- OpenID string //trade_type=JSAPI时(即公众号支付),此参数必传,此参数为微信用户在商户对应appid下的唯一标识
- Body string //String(128)
- OutTradeNum string //String(32) 20150806125346 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。
- TotalFee int //分为单位
- IP string
- NotifyURL string //异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数
- ProductID string //trade_type=NATIVE时(即扫码支付),此参数必传
- tradeType string //JSAPI,NATIVE,APP
- }
- //SetTradeType 设置TradeType
- func (c *OrderInput) setTradeType(tradeType string) {
- c.tradeType = tradeType
- }
- //WxPayInfo 统一下单后,返回的信息,这些信息是前端jssdk支付时需要的配置
- type WxPayInfo struct {
- AppID string `json:"appId"`
- TimeStamp string `json:"timeStamp"`
- NonceStr string `json:"nonceStr"`
- Package string `json:"package"`
- SignType string `json:"signType"`
- PaySign string `json:"paySign"`
- resultMap map[string]string
- }
- //ToJSON WeixinJSBridge json content
- func (c *WxPayInfo) ToJSON() (str string) {
- js, err := json.Marshal(c)
- if err == nil {
- return string(js)
- }
- return
- }
- //ToMap result map[string]string
- func (c *WxPayInfo) ToMap() (m map[string]string) {
- return c.resultMap
- }
- /*GetJsAPIConfig 前端JsAPI支付时,需要提交的信息
- */
- func (c *Pay) GetJsAPIConfig(order OrderInput) (config *WxPayInfo, err error) {
- order.setTradeType("JSAPI")
- err = c.checkOrder(order)
- if err != nil {
- return
- }
- var prepayID string
- prepayID, err = c.getPrepayID(order)
- if err != nil {
- return
- }
- nocestr := util.RandomStr(8)
- timestamp := fmt.Sprint(time.Now().Unix())
- result := make(map[string]string)
- result["appId"] = c.AppID
- result["timeStamp"] = timestamp
- result["nonceStr"] = nocestr
- result["package"] = "prepay_id=" + prepayID
- result["signType"] = "MD5"
- sign := base.Sign(result, c.MchAPIKey, nil)
- result["paySign"] = sign
- config = new(WxPayInfo)
- config.NonceStr = util.RandomStr(8)
- config.TimeStamp = fmt.Sprint(time.Now().Unix())
- config.AppID = c.AppID
- config.Package = "prepay_id=" + prepayID
- config.SignType = "MD5"
- config.PaySign = sign
- config.resultMap = result
- return
- }
- //GetNativePayQrcodePicURL native支付时二维码图片的url
- func (c *Pay) GetNativePayQrcodePicURL(order OrderInput) (qrcodeURL string, err error) {
- order.setTradeType("NATIVE")
- input := c.createUnifiedOrderMap(order)
- var result map[string]string
- if result, err = c.UnifiedOrder(input); err == nil { //有prepay_id
- qrcodeURL = result["code_url"]
- if len(qrcodeURL) == 0 {
- err = fmt.Errorf("native pay Qrcode url is empty")
- }
- }
- return
- }
- // 调用 UnifiedOrder 获得 prepayID
- func (c *Pay) getPrepayID(order OrderInput) (prepayID string, err error) {
- input := c.createUnifiedOrderMap(order)
- var result map[string]string
- if result, err = c.UnifiedOrder(input); err == nil { //有prepay_id
- prepayID := result["prepay_id"]
- if prepayID != "" {
- return prepayID, nil
- }
- err = fmt.Errorf("prepayID is empty")
- }
- return
- }
- func (c *Pay) createUnifiedOrderMap(order OrderInput) (input map[string]string) {
- input = make(map[string]string)
- input["appid"] = c.AppID //设置微信分配的公众账号ID
- input["mch_id"] = c.MchID //设置微信支付分配的商户号
- input["nonce_str"] = util.RandomStr(5) //设置随机字符串,不长于32位。推荐随机数生成算法
- input["body"] = order.Body //获取商品或支付单简要描述的值
- input["out_trade_no"] = order.OutTradeNum //设置商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号
- input["total_fee"] = util.ToStr(order.TotalFee) //设置订单总金额,只能为整数,详见支付金额
- input["spbill_create_ip"] = order.IP //设置APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。
- input["notify_url"] = order.NotifyURL //设置接收微信支付异步通知回调地址
- input["trade_type"] = order.tradeType
- //设置取值如下:JSAPI,NATIVE,APP,详细说明见参数规定
- if order.ProductID != "" {
- input["product_id"] = order.ProductID //这个
- }
- input["openid"] = order.OpenID //设置trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识。下单前需要调用【网页授权获取用户信息】接口获取到用户的Openid
- //sign
- sign := base.Sign(input, c.MchAPIKey, nil)
- input["sign"] = sign
- return
- }
- func (c *Pay) checkOrder(order OrderInput) (err error) {
- tradeType := order.tradeType
- if tradeType != "JSAPI" && tradeType != "APP" && tradeType != "NATIVE" {
- return fmt.Errorf("tradeType is invalid")
- }
- if tradeType == "NATIVE" {
- if order.ProductID == "" {
- err = fmt.Errorf("Native TradeType need ProductID")
- return
- }
- }
- if tradeType == "JSAPI" {
- if order.OpenID == "" {
- err = fmt.Errorf("OpenID can not be empty when pay mode is JSAPI")
- return
- }
- }
- if utf8.RuneCountInString(order.Body) > 128 || order.Body == "" {
- err = fmt.Errorf("Body is invalid. Size can not exceed 128.")
- return
- }
- if utf8.RuneCountInString(order.OutTradeNum) > 32 || order.OutTradeNum == "" {
- err = fmt.Errorf("OutTradeNum is invalid. Size can not exceed 128.")
- return
- }
- if order.TotalFee <= 0 {
- err = fmt.Errorf("Order TotalFee is invalid.")
- return
- }
- if order.IP == "" {
- err = fmt.Errorf("Order IP is invalid.")
- return
- }
- if order.NotifyURL == "" {
- err = fmt.Errorf("Notify URL is invalid.")
- return
- }
- return
- }
- //CheckPayNotifyData 检查pay notify url收到的消息,是否是返回成功
- func (c *Pay) CheckPayNotifyData(data []byte) (isSuccess bool, err error) {
- msg, err := base.ParseXMLToMap(bytes.NewReader(data))
- if err != nil {
- return
- }
- ReturnCode, ok := msg["return_code"]
- if ReturnCode == base.ReturnCodeSuccess || !ok {
- haveAppId := msg["appid"]
- if haveAppId != c.AppID {
- err = fmt.Errorf("get appid is not same as mine. AppID from response is %s. My server AppID is %s,", haveAppId, c.AppID)
- return
- }
- haveMchId := msg["mch_id"]
- if haveMchId != c.MchID {
- err = fmt.Errorf("get Mch id is not same as mine. MchID from response is %s. My server MchID is %s,", haveMchId, c.MchID)
- return
- }
- signature1, ok := msg["sign"]
- if !ok {
- err = fmt.Errorf("no sign got")
- return
- }
- signature2 := base.Sign(msg, c.MchAPIKey, nil)
- if signature1 != signature2 {
- err = fmt.Errorf("Sign is not same. sige_got is %s, sign_mine is %s", signature1, signature2)
- return
- }
- outTradeNum, ok := msg["out_trade_no"]
- if !ok || outTradeNum == "" {
- err = fmt.Errorf("no out_trade_no")
- return
- }
- result_code, ok := msg["result_code"]
- if !ok {
- err = fmt.Errorf("no result_code")
- return
- }
- if result_code == base.ResultCodeSuccess {
- isSuccess = true
- }
- }
- return
- }
|