client.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  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 string) (qqRsp *RefundResponse, err error) {
  125. err = bm.CheckEmptyError("nonce_str", "out_refund_no", "refund_fee", "op_user_id", "op_user_passwd")
  126. if err != nil {
  127. return nil, err
  128. }
  129. if bm.Get("out_trade_no") == gopay.NULL && bm.Get("transaction_id") == gopay.NULL {
  130. return nil, errors.New("out_trade_no and transaction_id are not allowed to be null at the same time")
  131. }
  132. tlsConfig, err := q.addCertConfig(certFilePath, keyFilePath, pkcs12FilePath)
  133. if err != nil {
  134. return nil, err
  135. }
  136. bs, err := q.doQQ(bm, refund, tlsConfig)
  137. if err != nil {
  138. return nil, err
  139. }
  140. qqRsp = new(RefundResponse)
  141. if err = xml.Unmarshal(bs, qqRsp); err != nil {
  142. return nil, fmt.Errorf("xml.Unmarshal(%s):%w", string(bs), err)
  143. }
  144. return qqRsp, nil
  145. }
  146. // 退款查询
  147. // 文档地址:https://qpay.qq.com/buss/wiki/38/1208
  148. func (q *Client) RefundQuery(bm gopay.BodyMap) (qqRsp *RefundQueryResponse, err error) {
  149. err = bm.CheckEmptyError("nonce_str")
  150. if err != nil {
  151. return nil, err
  152. }
  153. 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 {
  154. return nil, errors.New("refund_id, out_refund_no, out_trade_no, transaction_id are not allowed to be null at the same time")
  155. }
  156. bs, err := q.doQQ(bm, refundQuery, nil)
  157. if err != nil {
  158. return nil, err
  159. }
  160. qqRsp = new(RefundQueryResponse)
  161. if err = xml.Unmarshal(bs, qqRsp); err != nil {
  162. return nil, fmt.Errorf("xml.Unmarshal(%s):%w", string(bs), err)
  163. }
  164. return qqRsp, nil
  165. }
  166. // 交易账单
  167. // 文档地址:https://qpay.qq.com/buss/wiki/38/1209
  168. func (q *Client) StatementDown(bm gopay.BodyMap) (qqRsp string, err error) {
  169. err = bm.CheckEmptyError("nonce_str", "bill_date", "bill_type")
  170. if err != nil {
  171. return gopay.NULL, err
  172. }
  173. billType := bm.Get("bill_type")
  174. if billType != "ALL" && billType != "SUCCESS" && billType != "REFUND" && billType != "RECHAR" {
  175. return gopay.NULL, errors.New("bill_type error, please reference: https://qpay.qq.com/buss/wiki/38/1209")
  176. }
  177. bs, err := q.doQQ(bm, statementDown, nil)
  178. if err != nil {
  179. return gopay.NULL, err
  180. }
  181. return string(bs), nil
  182. }
  183. // 资金账单
  184. // 文档地址:https://qpay.qq.com/buss/wiki/38/3089
  185. func (q *Client) AccRoll(bm gopay.BodyMap) (qqRsp string, err error) {
  186. err = bm.CheckEmptyError("nonce_str", "bill_date", "acc_type")
  187. if err != nil {
  188. return gopay.NULL, err
  189. }
  190. accType := bm.Get("acc_type")
  191. if accType != "CASH" && accType != "MARKETING" {
  192. return gopay.NULL, errors.New("acc_type error, please reference: https://qpay.qq.com/buss/wiki/38/3089")
  193. }
  194. bs, err := q.doQQ(bm, accRoll, nil)
  195. if err != nil {
  196. return gopay.NULL, err
  197. }
  198. return string(bs), nil
  199. }
  200. // 向QQ发送请求
  201. func (q *Client) doQQ(bm gopay.BodyMap, url string, tlsConfig *tls.Config) (bs []byte, err error) {
  202. bm.Set("mch_id", q.MchId)
  203. if bm.Get("fee_type") == gopay.NULL {
  204. bm.Set("fee_type", "CNY")
  205. }
  206. if bm.Get("sign") == gopay.NULL {
  207. var sign string
  208. sign = getReleaseSign(q.ApiKey, bm.Get("sign_type"), bm)
  209. bm.Set("sign", sign)
  210. }
  211. httpClient := gopay.NewHttpClient()
  212. if tlsConfig != nil {
  213. httpClient.SetTLSConfig(tlsConfig)
  214. }
  215. res, bs, errs := httpClient.Type(gopay.TypeXML).Post(url).SendString(generateXml(bm)).EndBytes()
  216. if len(errs) > 0 {
  217. return nil, errs[0]
  218. }
  219. if res.StatusCode != 200 {
  220. return nil, fmt.Errorf("HTTP Request Error, StatusCode = %d", res.StatusCode)
  221. }
  222. if strings.Contains(string(bs), "HTML") {
  223. return nil, errors.New(string(bs))
  224. }
  225. return bs, nil
  226. }