alipay_service_api.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. package gopay
  2. import (
  3. "crypto"
  4. "crypto/aes"
  5. "crypto/cipher"
  6. "crypto/md5"
  7. "crypto/rsa"
  8. "crypto/x509"
  9. "encoding/base64"
  10. "encoding/hex"
  11. "encoding/json"
  12. "encoding/pem"
  13. "errors"
  14. "fmt"
  15. "hash"
  16. "io/ioutil"
  17. "net/http"
  18. "reflect"
  19. "strings"
  20. "time"
  21. "github.com/tjfoc/gmsm/sm2"
  22. )
  23. //解析支付宝支付完成后的Notify信息
  24. func ParseAliPayNotifyResult(req *http.Request) (notifyReq *AliPayNotifyRequest, err error) {
  25. notifyReq = new(AliPayNotifyRequest)
  26. notifyReq.NotifyTime = req.FormValue("notify_time")
  27. notifyReq.NotifyType = req.FormValue("notify_type")
  28. notifyReq.NotifyId = req.FormValue("notify_id")
  29. notifyReq.AppId = req.FormValue("app_id")
  30. notifyReq.Charset = req.FormValue("charset")
  31. notifyReq.Version = req.FormValue("version")
  32. notifyReq.SignType = req.FormValue("sign_type")
  33. notifyReq.Sign = req.FormValue("sign")
  34. notifyReq.AuthAppId = req.FormValue("auth_app_id")
  35. notifyReq.TradeNo = req.FormValue("trade_no")
  36. notifyReq.OutTradeNo = req.FormValue("out_trade_no")
  37. notifyReq.OutBizNo = req.FormValue("out_biz_no")
  38. notifyReq.BuyerId = req.FormValue("buyer_id")
  39. notifyReq.BuyerLogonId = req.FormValue("buyer_logon_id")
  40. notifyReq.SellerId = req.FormValue("seller_id")
  41. notifyReq.SellerEmail = req.FormValue("seller_email")
  42. notifyReq.TradeStatus = req.FormValue("trade_status")
  43. notifyReq.TotalAmount = req.FormValue("total_amount")
  44. notifyReq.ReceiptAmount = req.FormValue("receipt_amount")
  45. notifyReq.InvoiceAmount = req.FormValue("invoice_amount")
  46. notifyReq.BuyerPayAmount = req.FormValue("buyer_pay_amount")
  47. notifyReq.PointAmount = req.FormValue("point_amount")
  48. notifyReq.RefundFee = req.FormValue("refund_fee")
  49. notifyReq.Subject = req.FormValue("subject")
  50. notifyReq.Body = req.FormValue("body")
  51. notifyReq.GmtCreate = req.FormValue("gmt_create")
  52. notifyReq.GmtPayment = req.FormValue("gmt_payment")
  53. notifyReq.GmtRefund = req.FormValue("gmt_refund")
  54. notifyReq.GmtClose = req.FormValue("gmt_close")
  55. billList := req.FormValue("fund_bill_list")
  56. if billList != null {
  57. bills := make([]fundBillListInfo, 0)
  58. if err = json.Unmarshal([]byte(billList), &bills); err != nil {
  59. return nil, fmt.Errorf("xml.Unmarshal:%v", err.Error())
  60. }
  61. notifyReq.FundBillList = bills
  62. } else {
  63. notifyReq.FundBillList = nil
  64. }
  65. notifyReq.PassbackParams = req.FormValue("passback_params")
  66. detailList := req.FormValue("voucher_detail_list")
  67. if detailList != null {
  68. details := make([]voucherDetailListInfo, 0)
  69. if err = json.Unmarshal([]byte(detailList), &details); err != nil {
  70. return nil, fmt.Errorf("xml.Unmarshal:%v", err.Error())
  71. }
  72. notifyReq.VoucherDetailList = details
  73. } else {
  74. notifyReq.VoucherDetailList = nil
  75. }
  76. return
  77. }
  78. //支付通知的签名验证和参数签名后的Sign(Deprecated)
  79. // aliPayPublicKey:支付宝公钥
  80. // notifyReq:利用 gopay.ParseAliPayNotifyResult() 得到的结构体
  81. // 返回参数ok:是否验证通过
  82. // 返回参数err:错误信息
  83. func VerifyAliPayResultSign(aliPayPublicKey string, notifyReq *AliPayNotifyRequest) (ok bool, err error) {
  84. body := make(BodyMap)
  85. body.Set("notify_time", notifyReq.NotifyTime)
  86. body.Set("notify_type", notifyReq.NotifyType)
  87. body.Set("notify_id", notifyReq.NotifyId)
  88. body.Set("app_id", notifyReq.AppId)
  89. body.Set("charset", notifyReq.Charset)
  90. body.Set("version", notifyReq.Version)
  91. //body.Set("sign", notifyReq.Sign) //验签时去掉
  92. //body.Set("sign_type", notifyReq.SignType) //验签时去掉
  93. body.Set("auth_app_id", notifyReq.AuthAppId)
  94. body.Set("trade_no", notifyReq.TradeNo)
  95. body.Set("out_trade_no", notifyReq.OutTradeNo)
  96. body.Set("out_biz_no", notifyReq.OutBizNo)
  97. body.Set("buyer_id", notifyReq.BuyerId)
  98. body.Set("buyer_logon_id", notifyReq.BuyerLogonId)
  99. body.Set("seller_id", notifyReq.SellerId)
  100. body.Set("seller_email", notifyReq.SellerEmail)
  101. body.Set("trade_status", notifyReq.TradeStatus)
  102. body.Set("total_amount", notifyReq.TotalAmount)
  103. body.Set("receipt_amount", notifyReq.ReceiptAmount)
  104. body.Set("invoice_amount", notifyReq.InvoiceAmount)
  105. body.Set("buyer_pay_amount", notifyReq.BuyerPayAmount)
  106. body.Set("point_amount", notifyReq.PointAmount)
  107. body.Set("refund_fee", notifyReq.RefundFee)
  108. body.Set("subject", notifyReq.Subject)
  109. body.Set("body", notifyReq.Body)
  110. body.Set("gmt_create", notifyReq.GmtCreate)
  111. body.Set("gmt_payment", notifyReq.GmtPayment)
  112. body.Set("gmt_refund", notifyReq.GmtRefund)
  113. body.Set("gmt_close", notifyReq.GmtClose)
  114. body.Set("fund_bill_list", jsonToString(notifyReq.FundBillList))
  115. body.Set("passback_params", notifyReq.PassbackParams)
  116. body.Set("voucher_detail_list", jsonToString(notifyReq.VoucherDetailList))
  117. newBody := make(BodyMap)
  118. for k, v := range body {
  119. if v != null {
  120. newBody.Set(k, v)
  121. }
  122. }
  123. pKey := FormatAliPayPublicKey(aliPayPublicKey)
  124. signData := newBody.EncodeAliPaySignParams()
  125. if err = verifyAliPaySign(signData, notifyReq.Sign, notifyReq.SignType, pKey); err != nil {
  126. return false, err
  127. }
  128. return true, nil
  129. }
  130. /*
  131. Q:使用公钥证书签名方式下,为什么开放平台网关的响应报文需要携带支付宝公钥证书SN(alipay_cert_sn)?
  132. **
  133. A:开发者上传自己的应用公钥证书后,开放平台会为开发者应用自动签发支付宝公钥证书供开发者下载,用来对开放平台网关响应报文做验签。
  134. 但是支付宝公钥证书可能因证书到期或者变更CA签发机构等原因,可能会重新签发证书。在重新签发前,开放平台会在门户上提前提醒开发者支付宝应用公钥证书变更时间。
  135. 但为避免开发者因未能及时感知支付宝公钥证书变更而导致验签失败,开放平台提供了一种支付宝公钥证书无感知升级机制,具体流程如下:
  136. 1)开放平台网关在响应报文中会多返回支付宝公钥证书SN
  137. 2)开放平台网关提供根据SN下载对应支付宝公钥证书的API接口
  138. 3)开发者在验签过程中,先比较本地使用的支付宝公钥证书SN与开放平台网关响应中SN是否一致。若不一致,可调用支付宝公钥证书下载接口下载对应SN的支付宝公钥证书。
  139. 4)对下载的支付宝公钥证书执行证书链校验,若校验通过,则用该证书验签。
  140. 基于该机制可实现支付宝公钥证书变更时开发者无感知,当前开放平台提供的SDK已基于该机制实现对应功能。若开发者未通过SDK接入,须自行实现该功能。
  141. */
  142. //支付宝同步返回验签或异步通知验签
  143. // 注意:APP支付,手机网站支付,电脑网站支付 暂不支持同步返回验签
  144. // aliPayPublicKey:支付宝公钥
  145. // bean: 同步返回验签时,此参数为 aliRsp.SignData ;异步通知验签时,此参数为异步通知解析的结构体 notifyReq
  146. // syncSign:同步返回验签时,此参数必传,即:aliRsp.Sign ;异步通知验签时,不传此参数,否则会出错。
  147. // 返回参数ok:是否验签通过
  148. // 返回参数err:错误信息
  149. // 验签文档:https://docs.open.alipay.com/200/106120
  150. func VerifyAliPaySign(aliPayPublicKey string, bean interface{}, syncSign ...string) (ok bool, err error) {
  151. if bean == nil {
  152. return false, errors.New("bean is nil")
  153. }
  154. var (
  155. bodySign string
  156. bodySignType string
  157. pKey string
  158. signData string
  159. bm BodyMap
  160. bs []byte
  161. )
  162. if len(syncSign) > 0 {
  163. bodySign = syncSign[0]
  164. bodySignType = "RSA2"
  165. signData = bean.(string)
  166. goto Verify
  167. }
  168. if bs, err = json.Marshal(bean); err != nil {
  169. return false, fmt.Errorf("json.Marshal:%v", err.Error())
  170. }
  171. bm = make(BodyMap)
  172. if err = json.Unmarshal(bs, &bm); err != nil {
  173. return false, fmt.Errorf("json.Unmarshal:%v", err.Error())
  174. }
  175. bodySign = bm.Get("sign")
  176. bodySignType = bm.Get("sign_type")
  177. bm.Remove("sign")
  178. bm.Remove("sign_type")
  179. signData = bm.EncodeAliPaySignParams()
  180. Verify:
  181. pKey = FormatAliPayPublicKey(aliPayPublicKey)
  182. if err = verifyAliPaySign(signData, bodySign, bodySignType, pKey); err != nil {
  183. return false, err
  184. }
  185. return true, nil
  186. }
  187. func verifyAliPaySign(signData, sign, signType, aliPayPublicKey string) (err error) {
  188. var (
  189. h hash.Hash
  190. hashs crypto.Hash
  191. block *pem.Block
  192. pubKey interface{}
  193. publicKey *rsa.PublicKey
  194. ok bool
  195. )
  196. signBytes, _ := base64.StdEncoding.DecodeString(sign)
  197. if block, _ = pem.Decode([]byte(aliPayPublicKey)); block == nil {
  198. return errors.New("支付宝公钥Decode错误")
  199. }
  200. if pubKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil {
  201. return fmt.Errorf("x509.ParsePKIXPublicKey:%v", err.Error())
  202. }
  203. if publicKey, ok = pubKey.(*rsa.PublicKey); !ok {
  204. return errors.New("支付宝公钥转换错误")
  205. }
  206. switch signType {
  207. case "RSA":
  208. hashs = crypto.SHA1
  209. case "RSA2":
  210. hashs = crypto.SHA256
  211. default:
  212. hashs = crypto.SHA256
  213. }
  214. h = hashs.New()
  215. h.Write([]byte(signData))
  216. return rsa.VerifyPKCS1v15(publicKey, hashs, h.Sum(nil), signBytes)
  217. }
  218. func jsonToString(v interface{}) (str string) {
  219. if v == nil {
  220. return null
  221. }
  222. var (
  223. bs []byte
  224. err error
  225. )
  226. if bs, err = json.Marshal(v); err != nil {
  227. return null
  228. }
  229. if str = string(bs); str == null {
  230. return null
  231. }
  232. return
  233. }
  234. //格式化 普通应用秘钥
  235. func FormatPrivateKey(privateKey string) (pKey string) {
  236. var buffer strings.Builder
  237. buffer.WriteString("-----BEGIN RSA PRIVATE KEY-----\n")
  238. rawLen := 64
  239. keyLen := len(privateKey)
  240. raws := keyLen / rawLen
  241. temp := keyLen % rawLen
  242. if temp > 0 {
  243. raws++
  244. }
  245. start := 0
  246. end := start + rawLen
  247. for i := 0; i < raws; i++ {
  248. if i == raws-1 {
  249. buffer.WriteString(privateKey[start:])
  250. } else {
  251. buffer.WriteString(privateKey[start:end])
  252. }
  253. buffer.WriteByte('\n')
  254. start += rawLen
  255. end = start + rawLen
  256. }
  257. buffer.WriteString("-----END RSA PRIVATE KEY-----\n")
  258. pKey = buffer.String()
  259. return
  260. }
  261. //格式化 普通支付宝公钥
  262. func FormatAliPayPublicKey(publicKey string) (pKey string) {
  263. var buffer strings.Builder
  264. buffer.WriteString("-----BEGIN PUBLIC KEY-----\n")
  265. rawLen := 64
  266. keyLen := len(publicKey)
  267. raws := keyLen / rawLen
  268. temp := keyLen % rawLen
  269. if temp > 0 {
  270. raws++
  271. }
  272. start := 0
  273. end := start + rawLen
  274. for i := 0; i < raws; i++ {
  275. if i == raws-1 {
  276. buffer.WriteString(publicKey[start:])
  277. } else {
  278. buffer.WriteString(publicKey[start:end])
  279. }
  280. buffer.WriteByte('\n')
  281. start += rawLen
  282. end = start + rawLen
  283. }
  284. buffer.WriteString("-----END PUBLIC KEY-----\n")
  285. pKey = buffer.String()
  286. return
  287. }
  288. //获取证书序列号SN
  289. // certPath:X.509证书文件路径(appCertPublicKey.crt、alipayRootCert.crt、alipayCertPublicKey_RSA2)
  290. // 返回 sn:证书序列号(app_cert_sn、alipay_root_cert_sn、alipay_cert_sn)
  291. // 返回 err:error 信息
  292. func GetCertSN(certPath string) (sn string, err error) {
  293. var (
  294. certData []byte
  295. block *pem.Block
  296. certs []*x509.Certificate
  297. sm2Certs []*sm2.Certificate
  298. name, serialNumber string
  299. h hash.Hash
  300. )
  301. if certData, err = ioutil.ReadFile(certPath); err != nil {
  302. return null, fmt.Errorf("ioutil.ReadFile:%v", err.Error())
  303. }
  304. if block, _ = pem.Decode(certData); block == nil {
  305. return null, errors.New("pem.Decode:pem Decode error,block is null")
  306. }
  307. if certs, err = x509.ParseCertificates(block.Bytes); err != nil {
  308. if sm2Certs, err = sm2.ParseCertificates(block.Bytes); err != nil {
  309. return null, fmt.Errorf("sm2.ParseCertificates:%v", err.Error())
  310. }
  311. name = sm2Certs[0].Issuer.String()
  312. serialNumber = sm2Certs[0].SerialNumber.String()
  313. goto Sign
  314. }
  315. if certs == nil {
  316. return null, fmt.Errorf("x509.ParseCertificates:certs is null")
  317. }
  318. name = certs[0].Issuer.String()
  319. serialNumber = certs[0].SerialNumber.String()
  320. Sign:
  321. h = md5.New()
  322. h.Write([]byte(name))
  323. h.Write([]byte(serialNumber))
  324. sn = hex.EncodeToString(h.Sum(nil))
  325. return sn, nil
  326. }
  327. //解密支付宝开放数据到 结构体
  328. // encryptedData:包括敏感数据在内的完整用户信息的加密数据
  329. // secretKey:AES密钥,支付宝管理平台配置
  330. // beanPtr:需要解析到的结构体指针
  331. // 文档:https://docs.alipay.com/mini/introduce/aes
  332. // 文档:https://docs.open.alipay.com/common/104567
  333. func DecryptAliPayOpenDataToStruct(encryptedData, secretKey string, beanPtr interface{}) (err error) {
  334. beanValue := reflect.ValueOf(beanPtr)
  335. if beanValue.Kind() != reflect.Ptr {
  336. return errors.New("传入参数类型必须是以指针形式")
  337. }
  338. if beanValue.Elem().Kind() != reflect.Struct {
  339. return errors.New("传入interface{}必须是结构体")
  340. }
  341. var (
  342. block cipher.Block
  343. blockMode cipher.BlockMode
  344. originData []byte
  345. )
  346. aesKey, _ := base64.StdEncoding.DecodeString(secretKey)
  347. ivKey := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
  348. secretData, _ := base64.StdEncoding.DecodeString(encryptedData)
  349. if block, err = aes.NewCipher(aesKey); err != nil {
  350. return fmt.Errorf("aes.NewCipher:%v", err.Error())
  351. }
  352. if len(secretData)%len(aesKey) != 0 {
  353. return errors.New("encryptedData is error")
  354. }
  355. blockMode = cipher.NewCBCDecrypter(block, ivKey)
  356. originData = make([]byte, len(secretData))
  357. blockMode.CryptBlocks(originData, secretData)
  358. if len(originData) > 0 {
  359. originData = PKCS5UnPadding(originData)
  360. }
  361. if err = json.Unmarshal(originData, beanPtr); err != nil {
  362. return fmt.Errorf("json.Unmarshal:%v", err.Error())
  363. }
  364. return nil
  365. }
  366. //解密支付宝开放数据到 BodyMap
  367. // encryptedData:包括敏感数据在内的完整用户信息的加密数据
  368. // secretKey:AES密钥,支付宝管理平台配置
  369. // 文档:https://docs.alipay.com/mini/introduce/aes
  370. // 文档:https://docs.open.alipay.com/common/104567
  371. func DecryptAliPayOpenDataToBodyMap(encryptedData, secretKey string) (bm BodyMap, err error) {
  372. var (
  373. aesKey, originData []byte
  374. ivKey = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
  375. block cipher.Block
  376. blockMode cipher.BlockMode
  377. )
  378. aesKey, _ = base64.StdEncoding.DecodeString(secretKey)
  379. secretData, _ := base64.StdEncoding.DecodeString(encryptedData)
  380. if block, err = aes.NewCipher(aesKey); err != nil {
  381. return nil, fmt.Errorf("aes.NewCipher:%v", err.Error())
  382. }
  383. if len(secretData)%len(aesKey) != 0 {
  384. return nil, errors.New("encryptedData is error")
  385. }
  386. blockMode = cipher.NewCBCDecrypter(block, ivKey)
  387. originData = make([]byte, len(secretData))
  388. blockMode.CryptBlocks(originData, secretData)
  389. if len(originData) > 0 {
  390. originData = PKCS5UnPadding(originData)
  391. }
  392. bm = make(BodyMap)
  393. if err = json.Unmarshal(originData, &bm); err != nil {
  394. return nil, fmt.Errorf("json.Unmarshal:%v", err.Error())
  395. }
  396. return
  397. }
  398. //换取授权访问令牌(默认使用utf-8,RSA2)
  399. // appId:应用ID
  400. // PrivateKey:应用私钥
  401. // grantType:值为 authorization_code 时,代表用code换取;值为 refresh_token 时,代表用refresh_token换取,传空默认code换取
  402. // codeOrToken:支付宝授权码或refresh_token
  403. // 文档:https://docs.open.alipay.com/api_9/alipay.system.oauth.token
  404. func AliPaySystemOauthToken(appId, privateKey, grantType, codeOrToken string) (rsp *AliPaySystemOauthTokenResponse, err error) {
  405. var bs []byte
  406. bm := make(BodyMap)
  407. if "authorization_code" == grantType {
  408. bm.Set("grant_type", "authorization_code")
  409. bm.Set("code", codeOrToken)
  410. } else if "refresh_token" == grantType {
  411. bm.Set("grant_type", "refresh_token")
  412. bm.Set("refresh_token", codeOrToken)
  413. } else {
  414. bm.Set("grant_type", "authorization_code")
  415. bm.Set("code", codeOrToken)
  416. }
  417. if bs, err = aliPaySystemOauthToken(appId, privateKey, bm, "alipay.system.oauth.token", true); err != nil {
  418. return
  419. }
  420. rsp = new(AliPaySystemOauthTokenResponse)
  421. if err = json.Unmarshal(bs, rsp); err != nil {
  422. return nil, fmt.Errorf("json.Unmarshal:%v", err.Error())
  423. }
  424. if rsp.AliPaySystemOauthTokenResponse.AccessToken == "" {
  425. return nil, errors.New("access_token is null")
  426. }
  427. return
  428. }
  429. //向支付宝发送请求
  430. func aliPaySystemOauthToken(appId, privateKey string, body BodyMap, method string, isProd bool) (bytes []byte, err error) {
  431. body.Set("app_id", appId)
  432. body.Set("method", method)
  433. body.Set("format", "JSON")
  434. body.Set("charset", "utf-8")
  435. body.Set("sign_type", "RSA2")
  436. body.Set("timestamp", time.Now().Format(TimeLayout))
  437. body.Set("version", "1.0")
  438. var (
  439. sign, url string
  440. errs []error
  441. )
  442. pKey := FormatPrivateKey(privateKey)
  443. if sign, err = getRsaSign(body, "RSA2", pKey); err != nil {
  444. return
  445. }
  446. body.Set("sign", sign)
  447. agent := HttpAgent()
  448. if !isProd {
  449. url = zfbSandboxBaseUrlUtf8
  450. } else {
  451. url = zfbBaseUrlUtf8
  452. }
  453. if _, bytes, errs = agent.Post(url).Type("form-data").SendString(FormatAliPayURLParam(body)).EndBytes(); len(errs) > 0 {
  454. return nil, errs[0]
  455. }
  456. return
  457. }