蛋蛋星球-制度模式
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

275 行
7.9 KiB

  1. package alipay
  2. import (
  3. "crypto/rsa"
  4. "encoding/base64"
  5. "encoding/json"
  6. "fmt"
  7. "time"
  8. "github.com/go-pay/crypto/aes"
  9. "github.com/go-pay/crypto/xpem"
  10. "github.com/go-pay/crypto/xrsa"
  11. "github.com/go-pay/gopay"
  12. "github.com/go-pay/gopay/pkg/xhttp"
  13. "github.com/go-pay/xlog"
  14. "github.com/go-pay/xtime"
  15. )
  16. type Client struct {
  17. AppId string
  18. AppCertSN string
  19. AliPayPublicCertSN string
  20. AliPayRootCertSN string
  21. ReturnUrl string
  22. NotifyUrl string
  23. Charset string
  24. SignType string
  25. AppAuthToken string
  26. IsProd bool
  27. aesKey string // biz_content 加密的 AES KEY
  28. ivKey []byte
  29. privateKey *rsa.PrivateKey
  30. aliPayPublicKey *rsa.PublicKey // 支付宝证书公钥内容 alipayPublicCert.crt
  31. autoSign bool
  32. DebugSwitch gopay.DebugSwitch
  33. logger xlog.XLogger
  34. location *time.Location
  35. hc *xhttp.Client
  36. }
  37. // 初始化支付宝客户端
  38. // 注意:如果使用支付宝公钥证书验签,请使用 client.SetCertSnByContent() 或 client.SetCertSnByPath() 设置 应用公钥证书、支付宝公钥证书、支付宝根证书
  39. // appid:应用ID
  40. // privateKey:应用私钥,支持PKCS1和PKCS8
  41. // isProd:是否是正式环境,沙箱环境请选择新版沙箱应用。
  42. func NewClient(appid, privateKey string, isProd bool) (client *Client, err error) {
  43. if appid == gopay.NULL || privateKey == gopay.NULL {
  44. return nil, gopay.MissAlipayInitParamErr
  45. }
  46. key := xrsa.FormatAlipayPrivateKey(privateKey)
  47. priKey, err := xpem.DecodePrivateKey([]byte(key))
  48. if err != nil {
  49. return nil, err
  50. }
  51. logger := xlog.NewLogger()
  52. logger.SetLevel(xlog.DebugLevel)
  53. client = &Client{
  54. AppId: appid,
  55. Charset: UTF8,
  56. SignType: RSA2,
  57. IsProd: isProd,
  58. privateKey: priKey,
  59. DebugSwitch: gopay.DebugOff,
  60. logger: logger,
  61. hc: xhttp.NewClient(),
  62. }
  63. return client, nil
  64. }
  65. // 开启请求完自动验签功能(默认不开启,推荐开启,只支持证书模式)
  66. // 注意:只支持证书模式
  67. // alipayPublicKeyContent:支付宝公钥证书文件内容[]byte
  68. func (a *Client) AutoVerifySign(alipayPublicKeyContent []byte) {
  69. pubKey, err := xpem.DecodePublicKey(alipayPublicKeyContent)
  70. if err != nil {
  71. a.logger.Errorf("AutoVerifySign(%s),err:%+v", alipayPublicKeyContent, err)
  72. }
  73. if pubKey != nil {
  74. a.aliPayPublicKey = pubKey
  75. a.autoSign = true
  76. }
  77. }
  78. // SetBodySize 设置http response body size(MB)
  79. func (a *Client) SetBodySize(sizeMB int) {
  80. if sizeMB > 0 {
  81. a.hc.SetBodySize(sizeMB)
  82. }
  83. }
  84. // SetHttpClient 设置自定义的xhttp.Client
  85. func (a *Client) SetHttpClient(client *xhttp.Client) {
  86. if client != nil {
  87. a.hc = client
  88. }
  89. }
  90. // SetLogger 设置自定义的logger
  91. func (a *Client) SetLogger(logger xlog.XLogger) {
  92. if logger != nil {
  93. a.logger = logger
  94. }
  95. }
  96. // SetAESKey 设置 biz_content 的AES加密key,设置此参数默认开启 biz_content 参数加密
  97. // 注意:目前不可用,设置后会报错
  98. func (a *Client) SetAESKey(aesKey string) {
  99. a.aesKey = aesKey
  100. a.ivKey = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
  101. }
  102. // Deprecated
  103. // 推荐使用 RequestParam()
  104. func (a *Client) GetRequestSignParam(bm gopay.BodyMap, method string) (string, error) {
  105. return a.RequestParam(bm, method)
  106. }
  107. // RequestParam 获取支付宝完整请求参数包含签名
  108. // 注意:biz_content 需要自行通过bm.SetBodyMap()设置,不设置则没有此参数
  109. func (a *Client) RequestParam(bm gopay.BodyMap, method string) (string, error) {
  110. var (
  111. bodyBs []byte
  112. err error
  113. sign string
  114. )
  115. if bm == nil {
  116. return "", gopay.BodyMapNilErr
  117. }
  118. // check if there is biz_content
  119. bz := bm.GetAny("biz_content")
  120. if bzBody, ok := bz.(gopay.BodyMap); ok {
  121. if bodyBs, err = json.Marshal(bzBody); err != nil {
  122. return "", fmt.Errorf("json.Marshal(%v):%w", bzBody, err)
  123. }
  124. bm.Set("biz_content", string(bodyBs))
  125. }
  126. bm.Set("method", method)
  127. // check public parameter
  128. a.checkPublicParam(bm)
  129. // check sign
  130. if bm.GetString("sign") == "" {
  131. sign, err = a.getRsaSign(bm, bm.GetString("sign_type"))
  132. if err != nil {
  133. return "", fmt.Errorf("GetRsaSign Error: %w", err)
  134. }
  135. bm.Set("sign", sign)
  136. }
  137. if a.DebugSwitch == gopay.DebugOn {
  138. a.logger.Debugf("Alipay_Request: %s", bm.JsonBody())
  139. }
  140. return bm.EncodeURLParams(), nil
  141. }
  142. // 公共参数处理
  143. func (a *Client) pubParamsHandle(bm gopay.BodyMap, method, bizContent string, authToken ...string) (param string, err error) {
  144. pubBody := make(gopay.BodyMap)
  145. pubBody.Set("app_id", a.AppId).
  146. Set("method", method).
  147. Set("format", "JSON").
  148. Set("charset", a.Charset).
  149. Set("sign_type", a.SignType).
  150. Set("version", "1.0").
  151. Set("timestamp", time.Now().Format(xtime.TimeLayout))
  152. if bm != nil {
  153. // version
  154. if version := bm.GetString("version"); version != gopay.NULL {
  155. pubBody.Set("version", version)
  156. }
  157. if a.AppCertSN != gopay.NULL {
  158. pubBody.Set("app_cert_sn", a.AppCertSN)
  159. }
  160. if a.AliPayRootCertSN != gopay.NULL {
  161. pubBody.Set("alipay_root_cert_sn", a.AliPayRootCertSN)
  162. }
  163. // return_url
  164. if a.ReturnUrl != gopay.NULL {
  165. pubBody.Set("return_url", a.ReturnUrl)
  166. }
  167. if returnUrl := bm.GetString("return_url"); returnUrl != gopay.NULL {
  168. pubBody.Set("return_url", returnUrl)
  169. }
  170. if a.location != nil {
  171. pubBody.Set("timestamp", time.Now().In(a.location).Format(xtime.TimeLayout))
  172. }
  173. // notify_url
  174. if a.NotifyUrl != gopay.NULL {
  175. pubBody.Set("notify_url", a.NotifyUrl)
  176. }
  177. if notifyUrl := bm.GetString("notify_url"); notifyUrl != gopay.NULL {
  178. pubBody.Set("notify_url", notifyUrl)
  179. }
  180. // default use app_auth_token
  181. if a.AppAuthToken != gopay.NULL {
  182. pubBody.Set("app_auth_token", a.AppAuthToken)
  183. }
  184. // if user set app_auth_token in body_map, use this
  185. if aat := bm.GetString("app_auth_token"); aat != gopay.NULL {
  186. pubBody.Set("app_auth_token", aat)
  187. }
  188. }
  189. if len(authToken) > 0 {
  190. pubBody.Set("auth_token", authToken[0])
  191. }
  192. if bizContent != gopay.NULL {
  193. if a.aesKey == gopay.NULL {
  194. pubBody.Set("biz_content", bizContent)
  195. } else {
  196. // AES Encrypt biz_content
  197. encryptBizContent, err := a.encryptBizContent(bizContent)
  198. if err != nil {
  199. return "", fmt.Errorf("EncryptBizContent Error: %w", err)
  200. }
  201. if a.DebugSwitch == gopay.DebugOn {
  202. a.logger.Debugf("Alipay_Origin_BizContent: %s", bizContent)
  203. a.logger.Debugf("Alipay_Encrypt_BizContent: %s", encryptBizContent)
  204. }
  205. pubBody.Set("biz_content", encryptBizContent)
  206. }
  207. }
  208. // sign
  209. sign, err := a.getRsaSign(pubBody, pubBody.GetString("sign_type"))
  210. if err != nil {
  211. return "", fmt.Errorf("GetRsaSign Error: %w", err)
  212. }
  213. pubBody.Set("sign", sign)
  214. if a.DebugSwitch == gopay.DebugOn {
  215. a.logger.Debugf("Alipay_Request: %s", pubBody.JsonBody())
  216. }
  217. param = pubBody.EncodeURLParams()
  218. return
  219. }
  220. // 公共参数检查
  221. func (a *Client) checkPublicParam(bm gopay.BodyMap) {
  222. bm.Set("format", "JSON").
  223. Set("charset", a.Charset).
  224. Set("sign_type", a.SignType).
  225. Set("version", "1.0").
  226. Set("timestamp", time.Now().Format(xtime.TimeLayout))
  227. if bm.GetString("app_id") == "" && a.AppId != gopay.NULL {
  228. bm.Set("app_id", a.AppId)
  229. }
  230. if bm.GetString("app_cert_sn") == "" && a.AppCertSN != gopay.NULL {
  231. bm.Set("app_cert_sn", a.AppCertSN)
  232. }
  233. if bm.GetString("alipay_root_cert_sn") == "" && a.AliPayRootCertSN != gopay.NULL {
  234. bm.Set("alipay_root_cert_sn", a.AliPayRootCertSN)
  235. }
  236. if bm.GetString("return_url") == "" && a.ReturnUrl != gopay.NULL {
  237. bm.Set("return_url", a.ReturnUrl)
  238. }
  239. if a.location != nil {
  240. bm.Set("timestamp", time.Now().In(a.location).Format(xtime.TimeLayout))
  241. }
  242. if bm.GetString("notify_url") == "" && a.NotifyUrl != gopay.NULL {
  243. bm.Set("notify_url", a.NotifyUrl)
  244. }
  245. if bm.GetString("app_auth_token") == "" && a.AppAuthToken != gopay.NULL {
  246. bm.Set("app_auth_token", a.AppAuthToken)
  247. }
  248. }
  249. func (a *Client) encryptBizContent(originData string) (string, error) {
  250. encryptData, err := aes.CBCEncrypt([]byte(originData), []byte(a.aesKey), a.ivKey)
  251. if err != nil {
  252. return "", err
  253. }
  254. return base64.StdEncoding.EncodeToString(encryptData), nil
  255. }