pay.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  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. buffer.WriteString("appId=")
  104. buffer.WriteString(order.AppID)
  105. buffer.WriteString("&nonceStr=")
  106. buffer.WriteString(order.NonceStr)
  107. buffer.WriteString("&package=")
  108. buffer.WriteString("prepay_id=" + order.PrePayID)
  109. buffer.WriteString("&signType=")
  110. buffer.WriteString(p.SignType)
  111. buffer.WriteString("&timeStamp=")
  112. buffer.WriteString(timestamp)
  113. buffer.WriteString("&key=")
  114. buffer.WriteString(pcf.PayKey)
  115. if p.SignType == "MD5" {
  116. h = md5.New()
  117. } else {
  118. h = hmac.New(sha256.New, []byte(pcf.PayKey))
  119. }
  120. h.Write([]byte(buffer.String()))
  121. // 签名
  122. cfg.PaySign = strings.ToUpper(hex.EncodeToString(h.Sum(nil)))
  123. cfg.NonceStr = order.NonceStr
  124. cfg.Timestamp = timestamp
  125. cfg.PrePayID = order.PrePayID
  126. cfg.SignType = p.SignType
  127. cfg.Package = "prepay_id=" + order.PrePayID
  128. return
  129. }
  130. // PrePayOrder return data for invoke wechat payment
  131. func (pcf *Pay) PrePayOrder(p *Params) (payOrder PreOrder, err error) {
  132. nonceStr := util.RandomStr(32)
  133. notifyURL := pcf.PayNotifyURL
  134. // 签名类型
  135. if p.SignType == "" {
  136. p.SignType = "MD5"
  137. }
  138. // 通知地址
  139. if p.NotifyURL != "" {
  140. notifyURL = p.NotifyURL
  141. }
  142. param := make(map[string]interface{})
  143. param["appid"] = pcf.AppID
  144. param["body"] = p.Body
  145. param["mch_id"] = pcf.PayMchID
  146. param["nonce_str"] = nonceStr
  147. param["out_trade_no"] = p.OutTradeNo
  148. param["spbill_create_ip"] = p.CreateIP
  149. param["total_fee"] = p.TotalFee
  150. param["trade_type"] = p.TradeType
  151. param["openid"] = p.OpenID
  152. param["sign_type"] = p.SignType
  153. param["detail"] = p.Detail
  154. param["attach"] = p.Attach
  155. param["goods_tag"] = p.GoodsTag
  156. param["notify_url"] = notifyURL
  157. bizKey := "&key=" + pcf.PayKey
  158. str := orderParam(param, bizKey)
  159. sign := util.MD5Sum(str)
  160. request := payRequest{
  161. AppID: pcf.AppID,
  162. MchID: pcf.PayMchID,
  163. NonceStr: nonceStr,
  164. Sign: sign,
  165. Body: p.Body,
  166. OutTradeNo: p.OutTradeNo,
  167. TotalFee: p.TotalFee,
  168. SpbillCreateIP: p.CreateIP,
  169. NotifyURL: notifyURL,
  170. TradeType: p.TradeType,
  171. OpenID: p.OpenID,
  172. SignType: p.SignType,
  173. Detail: p.Detail,
  174. Attach: p.Attach,
  175. GoodsTag: p.GoodsTag,
  176. }
  177. rawRet, err := util.PostXML(payGateway, request)
  178. if err != nil {
  179. return
  180. }
  181. err = xml.Unmarshal(rawRet, &payOrder)
  182. if err != nil {
  183. return
  184. }
  185. if payOrder.ReturnCode == "SUCCESS" {
  186. // pay success
  187. if payOrder.ResultCode == "SUCCESS" {
  188. err = nil
  189. return
  190. }
  191. err = errors.New(payOrder.ErrCode + payOrder.ErrCodeDes)
  192. return
  193. }
  194. err = errors.New("[msg : xmlUnmarshalError] [rawReturn : " + string(rawRet) + "] [params : " + str + "] [sign : " + sign + "]")
  195. return
  196. }
  197. // PrePayID will request wechat merchant api and request for a pre payment order id
  198. func (pcf *Pay) PrePayID(p *Params) (prePayID string, err error) {
  199. order, err := pcf.PrePayOrder(p)
  200. if err != nil {
  201. return
  202. }
  203. if order.PrePayID == "" {
  204. err = errors.New("empty prepayid")
  205. }
  206. prePayID = order.PrePayID
  207. return
  208. }
  209. // order params
  210. func orderParam(source interface{}, bizKey string) (returnStr string) {
  211. switch v := source.(type) {
  212. case map[string]string:
  213. keys := make([]string, 0, len(v))
  214. for k := range v {
  215. if k == "sign" {
  216. continue
  217. }
  218. keys = append(keys, k)
  219. }
  220. sort.Strings(keys)
  221. var buf bytes.Buffer
  222. for _, k := range keys {
  223. if v[k] == "" {
  224. continue
  225. }
  226. if buf.Len() > 0 {
  227. buf.WriteByte('&')
  228. }
  229. buf.WriteString(k)
  230. buf.WriteByte('=')
  231. buf.WriteString(v[k])
  232. }
  233. buf.WriteString(bizKey)
  234. returnStr = buf.String()
  235. case map[string]interface{}:
  236. keys := make([]string, 0, len(v))
  237. for k := range v {
  238. if k == "sign" {
  239. continue
  240. }
  241. keys = append(keys, k)
  242. }
  243. sort.Strings(keys)
  244. var buf bytes.Buffer
  245. for _, k := range keys {
  246. if v[k] == "" {
  247. continue
  248. }
  249. if buf.Len() > 0 {
  250. buf.WriteByte('&')
  251. }
  252. buf.WriteString(k)
  253. buf.WriteByte('=')
  254. switch vv := v[k].(type) {
  255. case string:
  256. buf.WriteString(vv)
  257. case int:
  258. buf.WriteString(strconv.FormatInt(int64(vv), 10))
  259. default:
  260. panic("params type not supported")
  261. }
  262. }
  263. buf.WriteString(bizKey)
  264. returnStr = buf.String()
  265. }
  266. return
  267. }