client.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. package qq
  2. import (
  3. "crypto/tls"
  4. "crypto/x509"
  5. "encoding/xml"
  6. "errors"
  7. "fmt"
  8. "strings"
  9. "sync"
  10. "github.com/iGoogle-ink/gopay"
  11. )
  12. type Client struct {
  13. MchId string
  14. ApiKey string
  15. IsProd bool
  16. certificate tls.Certificate
  17. certPool *x509.CertPool
  18. mu sync.RWMutex
  19. }
  20. // 初始化QQ客户端(正式环境)
  21. // mchId:商户ID
  22. // ApiKey:API秘钥值
  23. func NewClient(mchId, apiKey string) (client *Client) {
  24. if mchId != gopay.NULL && apiKey != gopay.NULL {
  25. return &Client{
  26. MchId: mchId,
  27. ApiKey: apiKey,
  28. }
  29. }
  30. return nil
  31. }
  32. // 提交付款码支付
  33. // 文档地址:https://qpay.qq.com/buss/wiki/1/1122
  34. func (q *Client) MicroPay(bm gopay.BodyMap) (qqRsp *MicroPayResponse, err error) {
  35. err = bm.CheckEmptyError("nonce_str", "body", "out_trade_no", "total_fee", "spbill_create_ip", "device_info", "auth_code")
  36. if err != nil {
  37. return nil, err
  38. }
  39. bm.Set("trade_type", TradeType_MicroPay)
  40. bs, err := q.doQQ(bm, microPay, nil)
  41. if err != nil {
  42. return nil, err
  43. }
  44. qqRsp = new(MicroPayResponse)
  45. if err = xml.Unmarshal(bs, qqRsp); err != nil {
  46. return nil, fmt.Errorf("xml.Unmarshal(%s):%w", string(bs), err)
  47. }
  48. return qqRsp, nil
  49. }
  50. // 撤销订单
  51. // 文档地址:https://qpay.qq.com/buss/wiki/1/1125
  52. func (q *Client) Reverse(bm gopay.BodyMap) (qqRsp *ReverseResponse, err error) {
  53. err = bm.CheckEmptyError("sub_mch_id", "nonce_str", "out_trade_no", "op_user_id", "op_user_passwd")
  54. if err != nil {
  55. return nil, err
  56. }
  57. bs, err := q.doQQ(bm, reverse, nil)
  58. if err != nil {
  59. return nil, err
  60. }
  61. qqRsp = new(ReverseResponse)
  62. if err = xml.Unmarshal(bs, qqRsp); err != nil {
  63. return nil, fmt.Errorf("xml.Unmarshal(%s):%w", string(bs), err)
  64. }
  65. return qqRsp, nil
  66. }
  67. // 统一下单
  68. // 文档地址:https://qpay.qq.com/buss/wiki/38/1203
  69. func (q *Client) UnifiedOrder(bm gopay.BodyMap) (qqRsp *UnifiedOrderResponse, err error) {
  70. err = bm.CheckEmptyError("nonce_str", "body", "out_trade_no", "total_fee", "spbill_create_ip", "trade_type", "notify_url")
  71. if err != nil {
  72. return nil, err
  73. }
  74. bs, err := q.doQQ(bm, unifiedOrder, nil)
  75. if err != nil {
  76. return nil, err
  77. }
  78. qqRsp = new(UnifiedOrderResponse)
  79. if err = xml.Unmarshal(bs, qqRsp); err != nil {
  80. return nil, fmt.Errorf("xml.Unmarshal(%s):%w", string(bs), err)
  81. }
  82. return qqRsp, nil
  83. }
  84. // 订单查询
  85. // 文档地址:https://qpay.qq.com/buss/wiki/38/1205
  86. func (q *Client) OrderQuery(bm gopay.BodyMap) (qqRsp *OrderQueryResponse, err error) {
  87. err = bm.CheckEmptyError("nonce_str")
  88. if err != nil {
  89. return nil, err
  90. }
  91. if bm.Get("out_trade_no") == gopay.NULL && bm.Get("transaction_id") == gopay.NULL {
  92. return nil, errors.New("out_trade_no and transaction_id are not allowed to be null at the same time")
  93. }
  94. bs, err := q.doQQ(bm, orderQuery, nil)
  95. if err != nil {
  96. return nil, err
  97. }
  98. qqRsp = new(OrderQueryResponse)
  99. if err = xml.Unmarshal(bs, qqRsp); err != nil {
  100. return nil, fmt.Errorf("xml.Unmarshal(%s):%w", string(bs), err)
  101. }
  102. return qqRsp, nil
  103. }
  104. // 关闭订单
  105. // 文档地址:https://qpay.qq.com/buss/wiki/38/1206
  106. func (q *Client) CloseOrder(bm gopay.BodyMap) (qqRsp *CloseOrderResponse, err error) {
  107. err = bm.CheckEmptyError("nonce_str", "out_trade_no")
  108. if err != nil {
  109. return nil, err
  110. }
  111. bs, err := q.doQQ(bm, orderClose, nil)
  112. if err != nil {
  113. return nil, err
  114. }
  115. qqRsp = new(CloseOrderResponse)
  116. if err = xml.Unmarshal(bs, qqRsp); err != nil {
  117. return nil, fmt.Errorf("xml.Unmarshal(%s):%w", string(bs), err)
  118. }
  119. return qqRsp, nil
  120. }
  121. // 申请退款
  122. // 注意:如已使用client.AddCertFilePath()添加过证书,参数certFilePath、keyFilePath、pkcs12FilePath全传空字符串 "",否则,3证书Path均不可空
  123. // 文档地址:https://qpay.qq.com/buss/wiki/38/1207
  124. func (q *Client) Refund(bm gopay.BodyMap, certFilePath, keyFilePath, pkcs12FilePath interface{}) (qqRsp *RefundResponse, err error) {
  125. if err = checkCertFilePath(certFilePath, keyFilePath, pkcs12FilePath); err != nil {
  126. return nil, err
  127. }
  128. err = bm.CheckEmptyError("nonce_str", "out_refund_no", "refund_fee", "op_user_id", "op_user_passwd")
  129. if err != nil {
  130. return nil, err
  131. }
  132. if bm.Get("out_trade_no") == gopay.NULL && bm.Get("transaction_id") == gopay.NULL {
  133. return nil, errors.New("out_trade_no and transaction_id are not allowed to be null at the same time")
  134. }
  135. tlsConfig, err := q.addCertConfig(certFilePath, keyFilePath, pkcs12FilePath)
  136. if err != nil {
  137. return nil, err
  138. }
  139. bs, err := q.doQQ(bm, refund, tlsConfig)
  140. if err != nil {
  141. return nil, err
  142. }
  143. qqRsp = new(RefundResponse)
  144. if err = xml.Unmarshal(bs, qqRsp); err != nil {
  145. return nil, fmt.Errorf("xml.Unmarshal(%s):%w", string(bs), err)
  146. }
  147. return qqRsp, nil
  148. }
  149. // 退款查询
  150. // 文档地址:https://qpay.qq.com/buss/wiki/38/1208
  151. func (q *Client) RefundQuery(bm gopay.BodyMap) (qqRsp *RefundQueryResponse, err error) {
  152. err = bm.CheckEmptyError("nonce_str")
  153. if err != nil {
  154. return nil, err
  155. }
  156. if bm.Get("refund_id") == gopay.NULL && bm.Get("out_refund_no") == gopay.NULL && bm.Get("transaction_id") == gopay.NULL && bm.Get("out_trade_no") == gopay.NULL {
  157. return nil, errors.New("refund_id, out_refund_no, out_trade_no, transaction_id are not allowed to be null at the same time")
  158. }
  159. bs, err := q.doQQ(bm, refundQuery, nil)
  160. if err != nil {
  161. return nil, err
  162. }
  163. qqRsp = new(RefundQueryResponse)
  164. if err = xml.Unmarshal(bs, qqRsp); err != nil {
  165. return nil, fmt.Errorf("xml.Unmarshal(%s):%w", string(bs), err)
  166. }
  167. return qqRsp, nil
  168. }
  169. // 交易账单
  170. // 文档地址:https://qpay.qq.com/buss/wiki/38/1209
  171. func (q *Client) StatementDown(bm gopay.BodyMap) (qqRsp string, err error) {
  172. err = bm.CheckEmptyError("nonce_str", "bill_date", "bill_type")
  173. if err != nil {
  174. return gopay.NULL, err
  175. }
  176. billType := bm.Get("bill_type")
  177. if billType != "ALL" && billType != "SUCCESS" && billType != "REFUND" && billType != "RECHAR" {
  178. return gopay.NULL, errors.New("bill_type error, please reference: https://qpay.qq.com/buss/wiki/38/1209")
  179. }
  180. bs, err := q.doQQ(bm, statementDown, nil)
  181. if err != nil {
  182. return gopay.NULL, err
  183. }
  184. return string(bs), nil
  185. }
  186. // 资金账单
  187. // 文档地址:https://qpay.qq.com/buss/wiki/38/3089
  188. func (q *Client) AccRoll(bm gopay.BodyMap) (qqRsp string, err error) {
  189. err = bm.CheckEmptyError("nonce_str", "bill_date", "acc_type")
  190. if err != nil {
  191. return gopay.NULL, err
  192. }
  193. accType := bm.Get("acc_type")
  194. if accType != "CASH" && accType != "MARKETING" {
  195. return gopay.NULL, errors.New("acc_type error, please reference: https://qpay.qq.com/buss/wiki/38/3089")
  196. }
  197. bs, err := q.doQQ(bm, accRoll, nil)
  198. if err != nil {
  199. return gopay.NULL, err
  200. }
  201. return string(bs), nil
  202. }
  203. // 向QQ发送请求
  204. func (q *Client) doQQ(bm gopay.BodyMap, url string, tlsConfig *tls.Config) (bs []byte, err error) {
  205. bm.Set("mch_id", q.MchId)
  206. if bm.Get("fee_type") == gopay.NULL {
  207. bm.Set("fee_type", "CNY")
  208. }
  209. if bm.Get("sign") == gopay.NULL {
  210. var sign string
  211. sign = getReleaseSign(q.ApiKey, bm.Get("sign_type"), bm)
  212. bm.Set("sign", sign)
  213. }
  214. httpClient := gopay.NewHttpClient()
  215. if tlsConfig != nil {
  216. httpClient.SetTLSConfig(tlsConfig)
  217. }
  218. res, bs, errs := httpClient.Type(gopay.TypeXML).Post(url).SendString(generateXml(bm)).EndBytes()
  219. if len(errs) > 0 {
  220. return nil, errs[0]
  221. }
  222. if res.StatusCode != 200 {
  223. return nil, fmt.Errorf("HTTP Request Error, StatusCode = %d", res.StatusCode)
  224. }
  225. if strings.Contains(string(bs), "HTML") {
  226. return nil, errors.New(string(bs))
  227. }
  228. return bs, nil
  229. }