simple.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. package pay
  2. import (
  3. "bytes"
  4. "crypto/md5"
  5. "encoding/hex"
  6. "fmt"
  7. "hash"
  8. "sort"
  9. "time"
  10. "unicode/utf8"
  11. "github.com/yaotian/gowechat/util"
  12. )
  13. //Order 下单
  14. //https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
  15. type Order struct {
  16. OpenID string //trade_type=JSAPI时(即公众号支付),此参数必传,此参数为微信用户在商户对应appid下的唯一标识
  17. Body string //String(128)
  18. OutTradeNum string //String(32) 20150806125346 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。
  19. TotalFee int //分为单位
  20. IP string
  21. NotifyURL string //异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数
  22. TradeType string //JSAPI,NATIVE,APP
  23. ProductID string //trade_type=NATIVE时(即扫码支付),此参数必传
  24. }
  25. //GetPrepayMap 支付页面需要的信息,map格式
  26. func (c *Pay) GetPrepayMap(order Order) (result map[string]string, err error) {
  27. err = c.checkOrder(order)
  28. if err != nil {
  29. return
  30. }
  31. var prepayID string
  32. prepayID, err = c.getPrepayID(order)
  33. if err != nil {
  34. return
  35. }
  36. nocestr := util.RandomStr(8)
  37. timestamp := fmt.Sprint(time.Now().Unix())
  38. result = make(map[string]string)
  39. result["appId"] = c.AppID
  40. result["timeStamp"] = timestamp
  41. result["nonceStr"] = nocestr
  42. result["package"] = "prepay_id=" + prepayID
  43. result["signType"] = "MD5"
  44. sign := sign(result, c.MchAPIKey, nil)
  45. result["paySign"] = sign
  46. return
  47. }
  48. // 调用 UnifiedOrder 获得 prepayID
  49. func (c *Pay) getPrepayID(order Order) (prepayID string, err error) {
  50. input := c.createUnifiedOrderMap(order)
  51. var result map[string]string
  52. if result, err = c.UnifiedOrder(input); err == nil { //有prepay_id
  53. prepayID := result["prepay_id"]
  54. if prepayID != "" {
  55. return prepayID, nil
  56. }
  57. err = fmt.Errorf("prepayID is empty")
  58. }
  59. return
  60. }
  61. func (c *Pay) createUnifiedOrderMap(order Order) (input map[string]string) {
  62. input = make(map[string]string)
  63. input["appid"] = c.AppID //设置微信分配的公众账号ID
  64. input["mch_id"] = c.MchID //设置微信支付分配的商户号
  65. input["nonce_str"] = util.RandomStr(5) //设置随机字符串,不长于32位。推荐随机数生成算法
  66. input["body"] = order.Body //获取商品或支付单简要描述的值
  67. input["out_trade_no"] = order.OutTradeNum //设置商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号
  68. input["total_fee"] = util.ToStr(order.TotalFee) //设置订单总金额,只能为整数,详见支付金额
  69. input["spbill_create_ip"] = order.IP //设置APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。
  70. input["notify_url"] = order.NotifyURL //设置接收微信支付异步通知回调地址
  71. input["trade_type"] = order.TradeType
  72. //设置取值如下:JSAPI,NATIVE,APP,详细说明见参数规定
  73. if order.ProductID != "" {
  74. input["product_id"] = order.ProductID //这个
  75. }
  76. input["openid"] = order.OpenID //设置trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识。下单前需要调用【网页授权获取用户信息】接口获取到用户的Openid
  77. //sign
  78. sign := sign(input, c.MchAPIKey, nil)
  79. input["sign"] = sign
  80. return
  81. }
  82. // 微信支付签名.
  83. // parameters: 待签名的参数集合
  84. // apiKey: API密钥
  85. // fn: func() hash.Hash, 如果 fn == nil 则默认用 md5.New
  86. func sign(parameters map[string]string, apiKey string, fn func() hash.Hash) string {
  87. ks := make([]string, 0, len(parameters))
  88. for k := range parameters {
  89. if k == "sign" {
  90. continue
  91. }
  92. ks = append(ks, k)
  93. }
  94. sort.Strings(ks)
  95. if fn == nil {
  96. fn = md5.New
  97. }
  98. h := fn()
  99. buf := make([]byte, 256)
  100. for _, k := range ks {
  101. v := parameters[k]
  102. if v == "" {
  103. continue
  104. }
  105. buf = buf[:0]
  106. buf = append(buf, k...)
  107. buf = append(buf, '=')
  108. buf = append(buf, v...)
  109. buf = append(buf, '&')
  110. h.Write(buf)
  111. }
  112. buf = buf[:0]
  113. buf = append(buf, "key="...)
  114. buf = append(buf, apiKey...)
  115. h.Write(buf)
  116. signature := make([]byte, h.Size()*2)
  117. hex.Encode(signature, h.Sum(nil))
  118. return string(bytes.ToUpper(signature))
  119. }
  120. func (c *Pay) checkOrder(order Order) (err error) {
  121. tradeType := order.TradeType
  122. if tradeType != "JSAPI" && tradeType != "APP" && tradeType != "NATIVE" {
  123. return fmt.Errorf("tradeType is invalid")
  124. }
  125. if tradeType == "NATIVE" {
  126. if order.ProductID == "" {
  127. err = fmt.Errorf("Native TradeType need ProductID")
  128. return
  129. }
  130. }
  131. if tradeType == "JSAPI" {
  132. if order.OpenID == "" {
  133. err = fmt.Errorf("OpenID can not be empty when pay mode is JSAPI")
  134. return
  135. }
  136. }
  137. if utf8.RuneCountInString(order.Body) > 128 || order.Body == "" {
  138. err = fmt.Errorf("Body is invalid. Size can not exceed 128.")
  139. return
  140. }
  141. if utf8.RuneCountInString(order.OutTradeNum) > 32 || order.OutTradeNum == "" {
  142. err = fmt.Errorf("OutTradeNum is invalid. Size can not exceed 128.")
  143. return
  144. }
  145. if order.TotalFee <= 0 {
  146. err = fmt.Errorf("Order TotalFee is invalid.")
  147. return
  148. }
  149. if order.IP == "" {
  150. err = fmt.Errorf("Order IP is invalid.")
  151. return
  152. }
  153. if order.NotifyURL == "" {
  154. err = fmt.Errorf("Notify URL is invalid.")
  155. return
  156. }
  157. return
  158. }