notify.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. // Copyright 2021 Tencent Inc. All rights reserved.
  2. // Package notify 微信支付 API v3 Go SDK 商户通知处理库
  3. package notify
  4. import (
  5. "bytes"
  6. "context"
  7. "crypto/aes"
  8. "crypto/cipher"
  9. "encoding/base64"
  10. "encoding/json"
  11. "fmt"
  12. "io/ioutil"
  13. "net/http"
  14. "github.com/wechatpay-apiv3/wechatpay-go/core/auth"
  15. "github.com/wechatpay-apiv3/wechatpay-go/core/auth/validators"
  16. )
  17. const rsaSignatureType = "WECHATPAY2-SHA256-RSA2048"
  18. const defaultSignatureType = rsaSignatureType
  19. const aeadAesGcmAlgorithm = "AEAD_AES_256_GCM"
  20. // Handler 通知处理器,使用前先设置验签和解密的算法套件
  21. type Handler struct {
  22. cipherSuites map[string]CipherSuite
  23. }
  24. // CipherSuite 算法套件,包括验签和解密
  25. type CipherSuite struct {
  26. signatureType string
  27. validator validators.WechatPayNotifyValidator
  28. aeadAlgorithm string
  29. aead cipher.AEAD
  30. }
  31. // NewEmptyHandler 创建一个不包含算法套件的空通知处理器
  32. func NewEmptyHandler() *Handler {
  33. h := &Handler{
  34. cipherSuites: map[string]CipherSuite{},
  35. }
  36. return h
  37. }
  38. // AddCipherSuite 添加一个算法套件
  39. func (h *Handler) AddCipherSuite(cipherSuite CipherSuite) *Handler {
  40. h.cipherSuites[cipherSuite.signatureType] = cipherSuite
  41. return h
  42. }
  43. // AddRSAWithAESGCM 添加一个 RSA + AES-GCM 的算法套件
  44. func (h *Handler) AddRSAWithAESGCM(verifier auth.Verifier, aesgcm cipher.AEAD) *Handler {
  45. v := CipherSuite{
  46. signatureType: rsaSignatureType,
  47. validator: *validators.NewWechatPayNotifyValidator(verifier),
  48. aeadAlgorithm: aeadAesGcmAlgorithm,
  49. aead: aesgcm,
  50. }
  51. return h.AddCipherSuite(v)
  52. }
  53. // ParseNotifyRequest 从 HTTP 请求(http.Request) 中解析 微信支付通知(notify.Request)
  54. func (h *Handler) ParseNotifyRequest(
  55. ctx context.Context,
  56. request *http.Request,
  57. content interface{},
  58. ) (*Request, error) {
  59. signType := request.Header.Get("Wechatpay-Signature-Type")
  60. if signType == "" {
  61. signType = defaultSignatureType
  62. }
  63. suite, ok := h.cipherSuites[signType]
  64. if !ok {
  65. return nil, fmt.Errorf("unsupported Wechatpay-Signature-Type: %s", signType)
  66. }
  67. if err := suite.validator.Validate(ctx, request); err != nil {
  68. return nil, fmt.Errorf("invalid notification, err: %v, request: %+v",
  69. err, request)
  70. }
  71. body, err := getRequestBody(request)
  72. if err != nil {
  73. return nil, err
  74. }
  75. return processBody(suite, body, content)
  76. }
  77. func processBody(suite CipherSuite, body []byte, content interface{}) (*Request, error) {
  78. ret := new(Request)
  79. if err := json.Unmarshal(body, ret); err != nil {
  80. return nil, fmt.Errorf("parse request body error: %v", err)
  81. }
  82. if ret.Resource.Algorithm != suite.aeadAlgorithm {
  83. return nil, fmt.Errorf(
  84. "possible invalid notification, resource.algorithm %s is not the configured algorithm %s",
  85. ret.Resource.Algorithm,
  86. suite.aeadAlgorithm)
  87. }
  88. plaintext, err := doAEADOpen(
  89. suite.aead,
  90. ret.Resource.Nonce,
  91. ret.Resource.Ciphertext,
  92. ret.Resource.AssociatedData,
  93. )
  94. if err != nil {
  95. return ret, fmt.Errorf("%s decrypt error: %v", ret.Resource.Algorithm, err)
  96. }
  97. ret.Resource.Plaintext = plaintext
  98. if err = json.Unmarshal([]byte(plaintext), &content); err != nil {
  99. return ret, fmt.Errorf("unmarshal plaintext to content failed: %v", err)
  100. }
  101. return ret, nil
  102. }
  103. func doAEADOpen(c cipher.AEAD, nonce, ciphertext, additionalData string) (string, error) {
  104. data, err := base64.StdEncoding.DecodeString(ciphertext)
  105. if err != nil {
  106. return "", err
  107. }
  108. plaintext, err := c.Open(
  109. nil,
  110. []byte(nonce),
  111. data,
  112. []byte(additionalData),
  113. )
  114. if err != nil {
  115. return "", err
  116. }
  117. return string(plaintext), nil
  118. }
  119. func getRequestBody(request *http.Request) ([]byte, error) {
  120. body, err := ioutil.ReadAll(request.Body)
  121. if err != nil {
  122. return nil, fmt.Errorf("read request body err: %v", err)
  123. }
  124. _ = request.Body.Close()
  125. request.Body = ioutil.NopCloser(bytes.NewBuffer(body))
  126. return body, nil
  127. }
  128. // NewRSANotifyHandler 创建一个 RSA 的通知处理器,它包含 AES-GCM 解密能力
  129. func NewRSANotifyHandler(apiV3Key string, verifier auth.Verifier) (*Handler, error) {
  130. c, err := aes.NewCipher([]byte(apiV3Key))
  131. if err != nil {
  132. return nil, err
  133. }
  134. aesgcm, err := cipher.NewGCM(c)
  135. if err != nil {
  136. return nil, err
  137. }
  138. return NewEmptyHandler().AddRSAWithAESGCM(verifier, aesgcm), nil
  139. }
  140. // NewNotifyHandler 创建通知处理器
  141. // Deprecated: Use NewRSANotifyHandler instead
  142. func NewNotifyHandler(apiV3Key string, verifier auth.Verifier) *Handler {
  143. h, _ := NewRSANotifyHandler(apiV3Key, verifier)
  144. return h
  145. }