pay.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. package pay
  2. import (
  3. "bytes"
  4. "crypto/hmac"
  5. "crypto/md5"
  6. "crypto/sha256"
  7. "encoding/hex"
  8. "encoding/xml"
  9. "errors"
  10. "hash"
  11. "sort"
  12. "strconv"
  13. "strings"
  14. "time"
  15. "github.com/silenceper/wechat/context"
  16. "github.com/silenceper/wechat/util"
  17. )
  18. var payGateway = "https://api.mch.weixin.qq.com/pay/unifiedorder"
  19. // Pay struct extends context
  20. type Pay struct {
  21. *context.Context
  22. }
  23. // Params was NEEDED when request unifiedorder
  24. // 传入的参数,用于生成 prepay_id 的必需参数
  25. type Params struct {
  26. TotalFee string
  27. CreateIP string
  28. Body string
  29. OutTradeNo string
  30. OpenID string
  31. TradeType string
  32. SignType string
  33. Detail string
  34. Attach string
  35. GoodsTag string
  36. NotifyURL string
  37. }
  38. // Config 是传出用于 js sdk 用的参数
  39. type Config struct {
  40. Timestamp string `json:"timestamp"`
  41. NonceStr string `json:"nonceStr"`
  42. PrePayID string `json:"prePayId"`
  43. SignType string `json:"signType"`
  44. Package string `json:"package"`
  45. PaySign string `json:"paySign"`
  46. }
  47. // PreOrder 是 unifie order 接口的返回
  48. type PreOrder struct {
  49. ReturnCode string `xml:"return_code"`
  50. ReturnMsg string `xml:"return_msg"`
  51. AppID string `xml:"appid,omitempty"`
  52. MchID string `xml:"mch_id,omitempty"`
  53. NonceStr string `xml:"nonce_str,omitempty"`
  54. Sign string `xml:"sign,omitempty"`
  55. ResultCode string `xml:"result_code,omitempty"`
  56. TradeType string `xml:"trade_type,omitempty"`
  57. PrePayID string `xml:"prepay_id,omitempty"`
  58. CodeURL string `xml:"code_url,omitempty"`
  59. ErrCode string `xml:"err_code,omitempty"`
  60. ErrCodeDes string `xml:"err_code_des,omitempty"`
  61. }
  62. // payRequest 接口请求参数
  63. type payRequest struct {
  64. AppID string `xml:"appid"`
  65. MchID string `xml:"mch_id"`
  66. DeviceInfo string `xml:"device_info,omitempty"`
  67. NonceStr string `xml:"nonce_str"`
  68. Sign string `xml:"sign"`
  69. SignType string `xml:"sign_type,omitempty"`
  70. Body string `xml:"body"`
  71. Detail string `xml:"detail,omitempty"`
  72. Attach string `xml:"attach,omitempty"` // 附加数据
  73. OutTradeNo string `xml:"out_trade_no"` // 商户订单号
  74. FeeType string `xml:"fee_type,omitempty"` // 标价币种
  75. TotalFee string `xml:"total_fee"` // 标价金额
  76. SpbillCreateIP string `xml:"spbill_create_ip"` // 终端IP
  77. TimeStart string `xml:"time_start,omitempty"` // 交易起始时间
  78. TimeExpire string `xml:"time_expire,omitempty"` // 交易结束时间
  79. GoodsTag string `xml:"goods_tag,omitempty"` // 订单优惠标记
  80. NotifyURL string `xml:"notify_url"` // 通知地址
  81. TradeType string `xml:"trade_type"` // 交易类型
  82. ProductID string `xml:"product_id,omitempty"` // 商品ID
  83. LimitPay string `xml:"limit_pay,omitempty"` //
  84. OpenID string `xml:"openid,omitempty"` // 用户标识
  85. SceneInfo string `xml:"scene_info,omitempty"` // 场景信息
  86. }
  87. // NewPay return an instance of Pay package
  88. func NewPay(ctx *context.Context) *Pay {
  89. pay := Pay{Context: ctx}
  90. return &pay
  91. }
  92. // BridgeConfig get js bridge config
  93. func (pcf *Pay) BridgeConfig(p *Params) (cfg Config, err error) {
  94. var (
  95. buffer strings.Builder
  96. h hash.Hash
  97. timestamp = strconv.FormatInt(time.Now().Unix(), 10)
  98. )
  99. order, err := pcf.PrePayOrder(p)
  100. if err != nil {
  101. return
  102. }
  103. if p.SignType == "" {
  104. p.SignType = "MD5"
  105. }
  106. buffer.WriteString("appId=")
  107. buffer.WriteString(order.AppID)
  108. buffer.WriteString("&nonceStr=")
  109. buffer.WriteString(order.NonceStr)
  110. buffer.WriteString("&package=")
  111. buffer.WriteString("prepay_id=" + order.PrePayID)
  112. buffer.WriteString("&signType=")
  113. buffer.WriteString(p.SignType)
  114. buffer.WriteString("&timeStamp=")
  115. buffer.WriteString(timestamp)
  116. buffer.WriteString("&key=")
  117. buffer.WriteString(pcf.PayKey)
  118. if p.SignType == "MD5" {
  119. h = md5.New()
  120. } else {
  121. h = hmac.New(sha256.New, []byte(pcf.PayKey))
  122. }
  123. h.Write([]byte(buffer.String()))
  124. // 签名
  125. cfg.PaySign = strings.ToUpper(hex.EncodeToString(h.Sum(nil)))
  126. cfg.NonceStr = order.NonceStr
  127. cfg.Timestamp = timestamp
  128. cfg.PrePayID = order.PrePayID
  129. cfg.SignType = p.SignType
  130. cfg.Package = "prepay_id=" + order.PrePayID
  131. return
  132. }
  133. // PrePayOrder return data for invoke wechat payment
  134. func (pcf *Pay) PrePayOrder(p *Params) (payOrder PreOrder, err error) {
  135. nonceStr := util.RandomStr(32)
  136. param := make(map[string]interface{})
  137. param["appid"] = pcf.AppID
  138. param["body"] = p.Body
  139. param["mch_id"] = pcf.PayMchID
  140. param["nonce_str"] = nonceStr
  141. param["out_trade_no"] = p.OutTradeNo
  142. param["spbill_create_ip"] = p.CreateIP
  143. param["total_fee"] = p.TotalFee
  144. param["trade_type"] = p.TradeType
  145. param["openid"] = p.OpenIDÒ
  146. param["detail"] = p.Detail
  147. param["attach"] = p.Attach
  148. param["goods_tag"] = p.GoodsTag
  149. param["notify_url"] = pcf.PayNotifyURL
  150. // 签名类型
  151. if p.SignType != "" {
  152. param["sign_type"] = p.SignType
  153. }
  154. // 通知地址
  155. if p.NotifyURL != "" {
  156. param["notify_url"] = p.NotifyURL
  157. }
  158. bizKey := "&key=" + pcf.PayKey
  159. str := orderParam(param, bizKey)
  160. sign := util.MD5Sum(str)
  161. request := payRequest{
  162. AppID: pcf.AppID,
  163. MchID: pcf.PayMchID,
  164. NonceStr: nonceStr,
  165. Sign: sign,
  166. Body: p.Body,
  167. OutTradeNo: p.OutTradeNo,
  168. TotalFee: p.TotalFee,
  169. SpbillCreateIP: p.CreateIP,
  170. NotifyURL: pcf.PayNotifyURL,
  171. TradeType: p.TradeType,
  172. OpenID: p.OpenID,
  173. }
  174. rawRet, err := util.PostXML(payGateway, request)
  175. if err != nil {
  176. return
  177. }
  178. err = xml.Unmarshal(rawRet, &payOrder)
  179. if err != nil {
  180. return
  181. }
  182. if payOrder.ReturnCode == "SUCCESS" {
  183. // pay success
  184. if payOrder.ResultCode == "SUCCESS" {
  185. err = nil
  186. return
  187. }
  188. err = errors.New(payOrder.ErrCode + payOrder.ErrCodeDes)
  189. return
  190. }
  191. err = errors.New("[msg : xmlUnmarshalError] [rawReturn : " + string(rawRet) + "] [params : " + str + "] [sign : " + sign + "]")
  192. return
  193. }
  194. // PrePayID will request wechat merchant api and request for a pre payment order id
  195. func (pcf *Pay) PrePayID(p *Params) (prePayID string, err error) {
  196. order, err := pcf.PrePayOrder(p)
  197. if err != nil {
  198. return
  199. }
  200. if order.PrePayID == "" {
  201. err = errors.New("empty prepayid")
  202. }
  203. prePayID = order.PrePayID
  204. return
  205. }
  206. // order params
  207. func orderParam(source interface{}, bizKey string) (returnStr string) {
  208. switch v := source.(type) {
  209. case map[string]string:
  210. keys := make([]string, 0, len(v))
  211. for k := range v {
  212. if k == "sign" {
  213. continue
  214. }
  215. keys = append(keys, k)
  216. }
  217. sort.Strings(keys)
  218. var buf bytes.Buffer
  219. for _, k := range keys {
  220. if v[k] == "" {
  221. continue
  222. }
  223. if buf.Len() > 0 {
  224. buf.WriteByte('&')
  225. }
  226. buf.WriteString(k)
  227. buf.WriteByte('=')
  228. buf.WriteString(v[k])
  229. }
  230. buf.WriteString(bizKey)
  231. returnStr = buf.String()
  232. case map[string]interface{}:
  233. keys := make([]string, 0, len(v))
  234. for k := range v {
  235. if k == "sign" {
  236. continue
  237. }
  238. keys = append(keys, k)
  239. }
  240. sort.Strings(keys)
  241. var buf bytes.Buffer
  242. for _, k := range keys {
  243. if v[k] == "" {
  244. continue
  245. }
  246. if buf.Len() > 0 {
  247. buf.WriteByte('&')
  248. }
  249. buf.WriteString(k)
  250. buf.WriteByte('=')
  251. switch vv := v[k].(type) {
  252. case string:
  253. buf.WriteString(vv)
  254. case int:
  255. buf.WriteString(strconv.FormatInt(int64(vv), 10))
  256. default:
  257. panic("params type not supported")
  258. }
  259. }
  260. buf.WriteString(bizKey)
  261. returnStr = buf.String()
  262. }
  263. return
  264. }