diff --git a/go.mod b/go.mod index f2705a5..839d7ea 100644 --- a/go.mod +++ b/go.mod @@ -20,10 +20,10 @@ require ( github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 // indirect github.com/oklog/run v1.0.0 // indirect github.com/pkg/errors v0.9.1 // indirect - golang.org/x/crypto v0.21.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/net v0.22.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/sys v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/grpc v1.64.0 // indirect google.golang.org/protobuf v1.34.2 // indirect diff --git a/go.sum b/go.sum index 0af23a1..3c7b3aa 100644 --- a/go.sum +++ b/go.sum @@ -46,16 +46,16 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= diff --git a/plugins/alipay_redpack/go.mod b/plugins/alipay_redpack/go.mod new file mode 100644 index 0000000..28ad171 --- /dev/null +++ b/plugins/alipay_redpack/go.mod @@ -0,0 +1,39 @@ +module plugins/alipay_redpack + +go 1.22.2 + +replace plugins/utils => ../../utils + +require ( + gitea.cdlsxd.cn/BaseSystem/plugin v0.0.0-20240808015117-a7d6a128aae4 + github.com/carlmjohnson/requests v0.23.4 + github.com/go-playground/validator/v10 v10.22.0 + github.com/hashicorp/go-plugin v1.6.1 + github.com/stretchr/testify v1.9.0 + plugins/utils v0.0.0-00010101000000-000000000000 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fatih/color v1.7.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/hashicorp/go-hclog v0.14.1 // indirect + github.com/hashicorp/yamux v0.1.1 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 // indirect + github.com/oklog/run v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/net v0.23.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/grpc v1.64.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/plugins/alipay_redpack/go.sum b/plugins/alipay_redpack/go.sum new file mode 100644 index 0000000..78c21a3 --- /dev/null +++ b/plugins/alipay_redpack/go.sum @@ -0,0 +1,73 @@ +gitea.cdlsxd.cn/BaseSystem/plugin v0.0.0-20240808015117-a7d6a128aae4 h1:0xL8Kufk3Ka75JpctzFHHyr2gJ8fvsFysOihPSyW3fo= +gitea.cdlsxd.cn/BaseSystem/plugin v0.0.0-20240808015117-a7d6a128aae4/go.mod h1:59zYRFcPur2HizJEIfttVOMY0AscollDYKX0yvPJS7k= +github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= +github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= +github.com/carlmjohnson/requests v0.23.4 h1:AxcvapfB9RPXLSyvAHk9YJoodQ43ZjzNHj6Ft3tQGdg= +github.com/carlmjohnson/requests v0.23.4/go.mod h1:Qzp6tW4DQyainPP+tGwiJTzwxvElTIKm0B191TgTtOA= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= +github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU= +github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-plugin v1.6.1 h1:P7MR2UP6gNKGPp+y7EZw2kOiq4IR9WiqLvp0XOsVdwI= +github.com/hashicorp/go-plugin v1.6.1/go.mod h1:XPHFku2tFo3o3QKFgSYo+cghcUhw1NA1hZyMK0PWAw0= +github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= +github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/plugins/alipay_redpack/internal/alipay_redpack.go b/plugins/alipay_redpack/internal/alipay_redpack.go new file mode 100644 index 0000000..d8e7c5f --- /dev/null +++ b/plugins/alipay_redpack/internal/alipay_redpack.go @@ -0,0 +1,94 @@ +package internal + +import ( + "context" + "fmt" + "gitea.cdlsxd.cn/BaseSystem/plugin/proto" + "github.com/carlmjohnson/requests" + "plugins/alipay_redpack/internal/po" +) + +// 插件通信信息,若不对应则会报错panic +const ( + Tag = "alipay_redpack" + Version = 1 + CookieKey = "alipay_redpack" + CookieValue = "alipay_redpack" +) + +const baseUri = "https://openapi.alipay.com/gateway.do" + +const ( + orderMethod = "alipay.fund.trans.uni.transfer" + queryMethod = "alipay.fund.trans.common.query" +) + +type AlipayCpnService struct{} + +func (s *AlipayCpnService) Order(ctx context.Context, request *proto.OrderRequest) (*proto.OrderResponse, error) { + c, err := transConfig(request.Config) + if err != nil { + return nil, err + } + poReq, err := orderReq(request.Order, request.Product) + if err != nil { + return nil, err + } + param, err := c.paramReq(poReq, orderMethod) + if err != nil { + return nil, err + } + uv, err := req(c, param) + if err != nil { + return nil, err + } + var response po.OrderResp + err = requests.URL(c.BaseUri).BodyForm(uv).ToJSON(&response).Fetch(ctx) + if err != nil { + return nil, fmt.Errorf("请求异常,msg:" + err.Error()) + } + + return orderResp(request, response), nil +} + +func (s *AlipayCpnService) Query(ctx context.Context, request *proto.QueryRequest) (*proto.QueryResponse, error) { + c, err := transConfig(request.Config) + if err != nil { + return nil, err + } + poReq, err := queryReq(request.Order) + if err != nil { + return nil, err + } + param, err := c.paramReq(poReq, queryMethod) + if err != nil { + return nil, err + } + uv, err := req(c, param) + if err != nil { + return nil, err + } + var response po.QueryResp + err = requests.URL(c.BaseUri).BodyForm(uv).ToJSON(&response).Fetch(ctx) + if err != nil { + return nil, fmt.Errorf("请求异常,msg:" + err.Error()) + } + return queryResp(request, response), nil +} + +func (s *AlipayCpnService) Notify(_ context.Context, request *proto.NotifyRequest) (*proto.NotifyResponse, error) { + c, err := transConfig(request.Config) + if err != nil { + return nil, err + } + n := notifyReq(request) + + b, err := Verify(n, c.Npk) + if err != nil { + return nil, err + } + if !b { + return nil, fmt.Errorf("验签失败") + } + return notifyResp(n), nil +} diff --git a/plugins/alipay_redpack/internal/alipay_redpack_test.go b/plugins/alipay_redpack/internal/alipay_redpack_test.go new file mode 100644 index 0000000..2c0862c --- /dev/null +++ b/plugins/alipay_redpack/internal/alipay_redpack_test.go @@ -0,0 +1,88 @@ +package internal + +import ( + "context" + "encoding/json" + "fmt" + "gitea.cdlsxd.cn/BaseSystem/plugin/proto" + "github.com/stretchr/testify/assert" + "testing" +) + +var server = &AlipayCpnService{} + +func config() []byte { + c := &Config{ + AppId: "2021004100663111", + Prk: "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDbA+YuMp4JUVj6rjzgwGKNXWkEMGX/rinqkfyBZ6B6p8EKz8zgA+ypiJLOixD3GyKnUnAzx4waNRZHfdEu+l57kJFtd/ipfwtJ28aTi7TqtqEpqD+UPY4ourt2CuyCFxWsonS6dczqtTvfAVArTdbGJYY+kNNVR3WiXgGUhkUu8N7vEowU00RUQGNdSVMUs4FX+HlU3RnEoRc/xUhPiaLf0Bm/g9wG96kwyg/TZvkNU7PpMVRdXeLrVORn0qThs3VA4dqondF+O12iC1TK4TKYGzFYczGAUsfuurtDyCc2GMoE+hH2FR8U7amQOVuYZFkutTdqaqukWpFQOr8wLeMzAgMBAAECggEAD715/3v3y6ejA3EeQvDQpGRANeLckcGMlaUkRpCSAf6oawSALuFZUt3T3zAzae7zUJ8mHTKMKR4DmeO68utfevXq3bkvj87nmslGvjfeKrgxYPMMjrTV0KuK6XLjiH3sOtn6FaR9s6iOwvovLs2LT/ZGbZyu84QNOjwTVP9JXZQkBgMItdKf+U3H2Cjp7U/qXBt8/9yzVFklp1g1883DAty0lzmT27dJimGVGaPQ8vNxo81+ZUEJAn6GUTk0K/GwJfhPTU8hh8G90n2LTyskoMjGxQe9lXfCcS9DmWawEQL4WTctPrDYlnS/cjCVMS0KXIFuxRNf6qaMYDeywC8BgQKBgQDyf40dvmw34Rrb46+NLayQ9W5CJI/dYeRajpCjoOomq5QYhbXzUCpVfbtByeGMsg3zN58NNsZGhl5SU0GdEdoOlxCk+2Hey2yQYF4ugQm/dTd68Jgqi3yujigAbNYa1ZhL9t3FqouPY1dGiaxl+DFYdSMIrsVFXh2NbrPyqTk5IQKBgQDnNaH7LCcIUqg9H8Tsls+8GLzP2HwF3hdll8asEsF3K3HX6/Zlp4VnnEcIkAxLRL/L0o5akXrmA18ZwfoSguTPXV9va3G2GgIiJHgcytmGtQVvbpFKuPnCXKz+avxnfO0flJqyYEuHr/40jsGbMkk0Kr52/n3ivXZbBUlT+tkt0wKBgB0qLgSnxEgsMJjFl3V5SsncWrhlwU+02Evz3X1wevjPpe4VFr7+ozjI+F5/MztCpt7bj6t9LPeKbYmlLb0ASqN6k6vj9+9ds97hWDJrnoqCRHvqt8JWKFauDi2O6WksyzZHqIB/dG14WyTGpg9VfEnRPLdsnZksKo26BLZol9NBAoGBAMz7oMNljqlzVtLyMo2q2zuhFuySusoc79NTL4FpE3rK2qCbA5V2YvDL/bIau7uTlRNodmrXZgU84fidIE9/Gsq5tp26vVK8Vj3c5Vxpf1dNcCct+MQtoMjvjzP0uBgsCrKf9lLEytHed1ozYnRsrbgBWWF4GTWH0cG6uxsoX5mfAoGAfYXKZdyRU+Su4y4EnDLMXd320ar7PaeuY8aZU7V6UQEsaOj6H3O8JMEOuBrOVEhAP2EkAC8ayargSXTSOkN97pg88agKDwA6jh4N6TKAK8XMft81YPPliVwZMsAUqihSKQBnKZ7ssHHLGWWWp5vfkyb7Y7dIkZcPzB0X3q/jL58=", + Npk: "", + MchCertPath: "/Users/lsxd/code/php/yxxt/market/config/alipaycash/appCertPublicKey_2021004100663111.crt", + RootCertPath: "/Users/lsxd/code/php/yxxt/market/config/alipaycash/alipayRootCert.crt", + } + marshal, _ := json.Marshal(c) + return marshal +} + +func TestOrder(t *testing.T) { + request := &proto.OrderRequest{ + Config: config(), + Order: &proto.OrderRequest_Order{ + OrderNo: "240403164049635931", + Account: "18666173766", + Extra: []byte(``), + }, + Product: &proto.OrderRequest_Product{ + ProductNo: "", + Price: 0.01, + Extra: []byte(`{"wishing":"祝福语"}`), + }, + } + t.Run("TestOrder", func(t *testing.T) { + got, err := server.Order(context.Background(), request) + if err != nil { + t.Errorf("Order() error = %v", err) + return + } + fmt.Printf("%+v", got) + assert.Equal(t, int(proto.Status_ING), int(got.Result.Status)) + }) +} + +func TestQuery(t *testing.T) { + request := &proto.QueryRequest{ + Config: config(), + Order: &proto.QueryRequest_Order{ + OrderNo: "lsxd202306071545141532", + TradeNo: "20230607110070000006320086925897", + Account: "", + Extra: []byte(``), + }, + } + t.Run("TestQuery", func(t *testing.T) { + got, err := server.Query(context.Background(), request) + if err != nil { + t.Errorf("Query() error = %v", err) + return + } + fmt.Printf("%+v \n", got) + assert.Equal(t, int(proto.Status_SUCCESS), int(got.Result.Status)) + }) +} + +func TestNotify(t *testing.T) { + in := &proto.NotifyRequest{ + Config: config(), + Queries: []byte(``), + Headers: []byte(``), + Body: []byte(``), + } + t.Run("TestNotify", func(t *testing.T) { + got, err := server.Notify(context.Background(), in) + if err != nil { + t.Errorf("Notify() error = %v", err) + return + } + fmt.Printf("TestNotify : %+v \n", got) + assert.Equal(t, int(proto.Status_SUCCESS), int(got.Result.Status)) + }) +} diff --git a/plugins/alipay_redpack/internal/po/notify.go b/plugins/alipay_redpack/internal/po/notify.go new file mode 100644 index 0000000..062a747 --- /dev/null +++ b/plugins/alipay_redpack/internal/po/notify.go @@ -0,0 +1,34 @@ +package po + +import "plugins/alipay_redpack/internal/vo" + +type NotifyBizContent struct { + OutBizNo string `json:"out_biz_no"` + ProductCode string `json:"product_code"` + BizScene string `json:"biz_scene"` + OriginInterface string `json:"origin_interface"` + OrderId string `json:"order_id"` + Status vo.Status `json:"status"` + ActionType string `json:"action_type"` + TransAmount string `json:"trans_amount"` + SettleSerialNo string `json:"settle_serial_no"` + SubOrderStatus string `json:"sub_order_status"` + SubOrderErrorCode string `json:"sub_order_error_code"` + SubOrderFailReason string `json:"sub_order_fail_reason"` + PayFundOrderId string `json:"pay_fund_order_id"` + PayDate string `json:"pay_date"` + RefundDate string `json:"refund_date"` + EntrustOrderId string `json:"entrust_order_id"` +} + +type Notify struct { + Charset string `json:"charset"` + UtcTimestamp string `json:"utc_timestamp"` + AppId string `json:"app_id"` + Version string `json:"version"` + SignType string `json:"sign_type"` + NotifyId string `json:"notify_id"` + MsgMethod string `json:"msg_method"` + Sign string `json:"sign"` + BizContent string `json:"biz_content"` +} diff --git a/plugins/alipay_redpack/internal/po/order.go b/plugins/alipay_redpack/internal/po/order.go new file mode 100644 index 0000000..fb34075 --- /dev/null +++ b/plugins/alipay_redpack/internal/po/order.go @@ -0,0 +1,69 @@ +package po + +import ( + "encoding/json" + "fmt" + "gitea.cdlsxd.cn/BaseSystem/plugin/proto" + "plugins/alipay_redpack/internal/vo" +) + +type PayeeInfo struct { + Identity string `validate:"required" json:"identity"` + IdentityType string `validate:"required" json:"identity_type"` +} + +type BusinessParams struct { + SubBizScene string `json:"sub_biz_scene"` +} + +func (b *BusinessParams) ToString() string { + s, _ := json.Marshal(b) + return string(s) +} + +type OrderReq struct { + OutBizNo string `validate:"required" json:"out_biz_no"` + TransAmount string `validate:"required" json:"trans_amount"` + ProductCode string `validate:"required" json:"product_code"` + BizScene string `validate:"required" json:"biz_scene"` + OrderTitle string `json:"order_title"` + Remark string `json:"remark"` + PayeeInfo *PayeeInfo `json:"payee_info"` + BusinessParams string `json:"business_params"` +} + +type OrderResponse struct { + Code vo.Code `json:"code"` + Msg string `json:"msg"` + SubCode string `json:"sub_code"` + SubMsg string `json:"sub_msg"` + + OutBizNo string `json:"out_biz_no"` + OrderId string `json:"order_id"` + PayFundOrderId string `json:"pay_fund_order_id"` + Status string `json:"status"` + TransDate int32 `json:"trans_date"` + SettleSerialNo int32 `json:"settle_serial_no"` +} + +type OrderResp struct { + Response OrderResponse `json:"alipay_fund_trans_uni_transfer_response"` + Sign string `json:"sign"` +} + +func (o *OrderResp) GetMsg() string { + if o.Response.Code.IsSuccess() { + return o.Response.Msg + } + return fmt.Sprintf( + "code:[%s],msg:[%s],subCode:[%s],subMsg[%s]", + o.Response.Code, o.Response.Msg, o.Response.SubCode, o.Response.SubMsg, + ) +} + +func (o *OrderResp) GetOrderStatus() proto.Status { + if o.Response.Code.IsSuccess() { + return proto.Status_ING + } + return proto.Status_FAIL +} diff --git a/plugins/alipay_redpack/internal/po/po.go b/plugins/alipay_redpack/internal/po/po.go new file mode 100644 index 0000000..54e5d5e --- /dev/null +++ b/plugins/alipay_redpack/internal/po/po.go @@ -0,0 +1,75 @@ +package po + +import ( + "encoding/json" + "fmt" + "github.com/go-playground/validator/v10" +) + +type Param struct { + AlipayRootCertSn string `json:"alipay_root_cert_sn" validate:"required"` + AppCertSn string `json:"app_cert_sn" validate:"required"` + AppId string `json:"app_id" validate:"required"` + Method string `json:"method" validate:"required"` + Format string `json:"format" validate:"required"` + Charset string `json:"charset" validate:"required"` + SignType string `json:"sign_type" validate:"required"` + Timestamp string `json:"timestamp" validate:"required"` + Version string `json:"version" validate:"required"` + BizContent string `json:"biz_content"` + Sign string `json:"sign"` +} + +type Req interface { + Validate() error + ToJson() []byte +} + +var _ Req = (*OrderReq)(nil) +var _ Req = (*QueryReq)(nil) +var _ Req = (*Notify)(nil) + +func (req *OrderReq) Validate() error { + err := validator.New().Struct(req) + if err != nil { + for _, err = range err.(validator.ValidationErrors) { + return fmt.Errorf("参数有误:" + err.Error()) + } + } + return nil +} + +func (req *OrderReq) ToJson() []byte { + b, _ := json.Marshal(req) + return b +} + +func (req *QueryReq) Validate() error { + err := validator.New().Struct(req) + if err != nil { + for _, err = range err.(validator.ValidationErrors) { + return fmt.Errorf("参数有误:" + err.Error()) + } + } + return nil +} + +func (req *QueryReq) ToJson() []byte { + b, _ := json.Marshal(req) + return b +} + +func (req *Notify) Validate() error { + err := validator.New().Struct(req) + if err != nil { + for _, err = range err.(validator.ValidationErrors) { + return fmt.Errorf("参数有误:" + err.Error()) + } + } + return nil +} + +func (req *Notify) ToJson() []byte { + b, _ := json.Marshal(req) + return b +} diff --git a/plugins/alipay_redpack/internal/po/query.go b/plugins/alipay_redpack/internal/po/query.go new file mode 100644 index 0000000..0c5d313 --- /dev/null +++ b/plugins/alipay_redpack/internal/po/query.go @@ -0,0 +1,39 @@ +package po + +import ( + "plugins/alipay_redpack/internal/vo" +) + +type QueryReq struct { + OutBizNo string `json:"out_biz_no"` + OrderId string `json:"order_id"` + ProductCode string `json:"product_code"` + BizScene string `json:"biz_scene"` +} + +type QueryResp struct { + Data QueryRespData `json:"alipay_fund_trans_common_query_response"` + Sign string `json:"sign"` +} + +type QueryRespData struct { + Code vo.Code `json:"code"` + Msg string `json:"msg"` + SubCode string `json:"subCode"` + SubMsg string `json:"subMsg"` + + OrderId string `json:"order_id"` + OutBizNo string `json:"out_biz_no"` + Status vo.Status `json:"status"` + PayFundOrderId string `json:"pay_fund_order_id"` + TransAmount string `json:"trans_amount"` + PayDate string `json:"pay_date"` + ArrivalTimeEnd string `json:"arrival_time_end"` + OrderFee string `json:"order_fee"` + ErrorCode string `json:"error_code"` + FailReason string `json:"fail_reason"` + SubOrderErrorCode string `json:"sub_order_error_code"` + SubOrderFailReason string `json:"sub_order_fail_reason"` + SubOrderStatus string `json:"sub_order_status"` + SettleSerialNo string `json:"settle_serial_no"` +} diff --git a/plugins/alipay_redpack/internal/transform.go b/plugins/alipay_redpack/internal/transform.go new file mode 100644 index 0000000..86ca936 --- /dev/null +++ b/plugins/alipay_redpack/internal/transform.go @@ -0,0 +1,147 @@ +package internal + +import ( + "encoding/json" + "fmt" + "gitea.cdlsxd.cn/BaseSystem/plugin/proto" + "html" + "plugins/alipay_redpack/internal/po" + "plugins/alipay_redpack/internal/vo" + "plugins/utils/alipay" + "time" +) + +type Config struct { + AppId string `json:"app_id"` + BaseUri string `json:"base_uri"` + Prk string `json:"prk"` // 私钥 + Npk string `json:"npk"` // 回调公钥 + + MchCertPath string `json:"mch_cert_path"` + RootCertPath string `json:"root_cert_path"` +} + +func transConfig(config []byte) (*Config, error) { + var c Config + err := json.Unmarshal(config, &c) + if err != nil { + return nil, err + } + if c.BaseUri == "" { + c.BaseUri = baseUri + } + return &c, nil +} + +func (c *Config) paramReq(req po.Req, method string) (*po.Param, error) { + if err := req.Validate(); err != nil { + return nil, err + } + cert, err := alipay.GetCert(c.MchCertPath, c.RootCertPath, c.AppId) + if err != nil { + return nil, err + } + return &po.Param{ + AlipayRootCertSn: cert.RootCertSN, + AppCertSn: cert.MchCertSN, + AppId: c.AppId, + Method: method, + Format: vo.Format, + Charset: vo.Charset, + SignType: vo.SignType, + Timestamp: time.Now().Format(time.DateTime), + Version: vo.Version, + BizContent: string(req.ToJson()), + Sign: "", + }, nil +} + +func orderReq(order *proto.OrderRequest_Order, product *proto.OrderRequest_Product) (*po.OrderReq, error) { + type Extra struct { + Wishing string `json:"wishing"` + } + var extra Extra + if product.Extra != nil { + err := json.Unmarshal(product.Extra, &extra) + if err != nil { + return nil, fmt.Errorf("product extra json unmarshal error: %v", err) + } + } + + payeeInfo := &po.PayeeInfo{ + Identity: order.Account, + IdentityType: "ALIPAY_USER_ID", + } + if isValidPhoneNumber(order.Account) { + payeeInfo.IdentityType = "ALIPAY_LOGON_ID" + } + businessParams := &po.BusinessParams{ + SubBizScene: "REDPACKET", + } + o := &po.OrderReq{ + OutBizNo: order.OrderNo, + TransAmount: fmt.Sprintf("%.2f", product.Price), + ProductCode: product.ProductNo, + BizScene: "DIRECT_TRANSFER", + OrderTitle: extra.Wishing, + Remark: extra.Wishing, + PayeeInfo: payeeInfo, + BusinessParams: businessParams.ToString(), + } + return o, nil +} + +func orderResp(request *proto.OrderRequest, resp po.OrderResp) *proto.OrderResponse { + data, _ := json.Marshal(resp) + return &proto.OrderResponse{ + Result: &proto.Result{ + Status: resp.GetOrderStatus(), + OrderNo: request.Order.OrderNo, + TradeNo: resp.Response.OrderId, + Message: resp.GetMsg(), + Data: data, + }, + } +} + +func queryReq(in *proto.QueryRequest_Order) (*po.QueryReq, error) { + return &po.QueryReq{ + OutBizNo: in.OrderNo, + OrderId: in.TradeNo, + ProductCode: "STD_RED_PACKET", + BizScene: "DIRECT_TRANSFER", + }, nil +} + +func queryResp(request *proto.QueryRequest, resp po.QueryResp) *proto.QueryResponse { + data, _ := json.Marshal(resp) + return &proto.QueryResponse{ + Result: &proto.Result{ + Status: resp.Data.Status.GetOrderStatus(), + OrderNo: request.Order.OrderNo, + TradeNo: request.Order.TradeNo, + Message: resp.Data.Msg, + Data: data, + }, + } +} + +func notifyReq(in *proto.NotifyRequest) *po.Notify { + var n *po.Notify + _ = json.Unmarshal(in.Queries, &n) + n.BizContent = html.UnescapeString(n.BizContent) + return n +} + +func notifyResp(n *po.Notify) *proto.NotifyResponse { + var b po.NotifyBizContent + _ = json.Unmarshal([]byte(n.BizContent), &b) + return &proto.NotifyResponse{ + Result: &proto.Result{ + Status: b.Status.GetOrderStatus(), + OrderNo: b.OutBizNo, + TradeNo: b.OrderId, + Message: b.Status.GetText(), + }, + } +} diff --git a/plugins/alipay_redpack/internal/util.go b/plugins/alipay_redpack/internal/util.go new file mode 100644 index 0000000..40f16b1 --- /dev/null +++ b/plugins/alipay_redpack/internal/util.go @@ -0,0 +1,119 @@ +package internal + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "errors" + "fmt" + "gitea.cdlsxd.cn/BaseSystem/plugin/utils" + "net/url" + "plugins/alipay_redpack/internal/po" + "regexp" + "strings" +) + +func isValidPhoneNumber(phoneNumber string) bool { + // 定义中国大陆手机号的正则表达式 + // 这个正则表达式匹配11位数字,以1开头,第二位为3、4、5、7、8中的一个,后面跟任意8位数字 + phoneRegex := `^1[34578]\d{9}$` + // 使用正则表达式进行匹配 + return regexp.MustCompile(phoneRegex).MatchString(phoneNumber) +} + +func req(config *Config, req *po.Param) (url.Values, error) { + var strToBeSigned strings.Builder + uv := url.Values{} + kvRows := utils.SortStructJsonTag(req) + for _, kv := range kvRows { + if kv.Key == "sign" { + continue + } + if kv.Value == "" { + continue + } + uv.Set(kv.Key, kv.Value) + strToBeSigned.WriteString(fmt.Sprintf("%s=%s&", kv.Key, kv.Value)) + } + s := strings.TrimRight(strToBeSigned.String(), "&") + sign, err := Sign(s, []byte(utils.NewPrivate().Build(config.Prk))) + if err != nil { + return nil, err + } + uv.Set("sign", sign) + + return uv, nil +} + +func Sign(data string, privateKeyPEM []byte) (string, error) { + block, _ := pem.Decode(privateKeyPEM) + if block == nil { + return "", errors.New("failed to parse PEM block containing the private key") + } + + privyKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + return "", fmt.Errorf("failed to parse DER encoded private key: %v", err) + } + + hashed := sha256.Sum256([]byte(data)) + signature, err := rsa.SignPKCS1v15(rand.Reader, privyKey.(*rsa.PrivateKey), crypto.SHA256, hashed[:]) + if err != nil { + return "", fmt.Errorf("failed to sign: %v", err) + } + + return base64.StdEncoding.EncodeToString(signature), nil +} + +func Verify(n *po.Notify, publicKeyPEM string) (bool, error) { + var strToBeSigned strings.Builder + uv := url.Values{} + kvRows := utils.SortStructJsonTag(n) + for _, kv := range kvRows { + if kv.Key == "sign" { + continue + } + if kv.Key == "sign_type" { + continue + } + uv.Set(kv.Key, kv.Value) + strToBeSigned.WriteString(fmt.Sprintf("%s=%s&", kv.Key, kv.Value)) + } + s := strings.TrimRight(strToBeSigned.String(), "&") + return check(s, n.Sign, []byte(utils.NewPublic().Build(publicKeyPEM))) +} + +func check(data, signature string, publicKeyPEM []byte) (bool, error) { + block, _ := pem.Decode(publicKeyPEM) + if block == nil { + return false, fmt.Errorf("failed to parse DER encoded public key") + } + + pubKey, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return false, fmt.Errorf("failed to parse DER encoded public key: %v", err) + } + + rsaPubKey, ok := pubKey.(*rsa.PublicKey) + if !ok { + return false, fmt.Errorf("failed to parse DER encoded public key: %v", err) + } + + hashed := sha256.Sum256([]byte(data)) + sig, err := base64.StdEncoding.DecodeString(signature) + if err != nil { + return false, fmt.Errorf("failed to decode signature: %v", err) + } + + err = rsa.VerifyPKCS1v15(rsaPubKey, crypto.SHA256, hashed[:], sig) + if err != nil { + fmt.Println("signature verification error:", err) + return false, nil + } + + return true, nil +} diff --git a/plugins/alipay_redpack/internal/vo/code.go b/plugins/alipay_redpack/internal/vo/code.go new file mode 100644 index 0000000..637749b --- /dev/null +++ b/plugins/alipay_redpack/internal/vo/code.go @@ -0,0 +1,9 @@ +package vo + +type Code string + +const CodeSuccess Code = "10000" + +func (c Code) IsSuccess() bool { + return c == CodeSuccess +} diff --git a/plugins/alipay_redpack/internal/vo/constant.go b/plugins/alipay_redpack/internal/vo/constant.go new file mode 100644 index 0000000..5603040 --- /dev/null +++ b/plugins/alipay_redpack/internal/vo/constant.go @@ -0,0 +1,8 @@ +package vo + +const ( + Version = "1.0" + Format = "json" + SignType = "RSA2" + Charset = "UTF-8" +) diff --git a/plugins/alipay_redpack/internal/vo/status.go b/plugins/alipay_redpack/internal/vo/status.go new file mode 100644 index 0000000..691bff5 --- /dev/null +++ b/plugins/alipay_redpack/internal/vo/status.go @@ -0,0 +1,44 @@ +package vo + +import "gitea.cdlsxd.cn/BaseSystem/plugin/proto" + +type Status string + +const ( + QueryStatusPending = "DEALING" + QueryStatusSuccess = "SUCCESS" + QueryStatusFailed = "FAIL" + QueryStatusRefund = "REFUND" +) + +var queryStatusTextMap = map[Status]string{ + QueryStatusPending: "处理中", + QueryStatusSuccess: "转账成功", + QueryStatusFailed: "失败", + QueryStatusRefund: "退票", +} + +var queryStatusMap = map[Status]proto.Status{ + QueryStatusPending: proto.Status_ING, + QueryStatusSuccess: proto.Status_SUCCESS, + QueryStatusFailed: proto.Status_FAIL, + QueryStatusRefund: proto.Status_FAIL, +} + +func (o Status) GetText() string { + msg, ok := queryStatusTextMap[o] + if !ok { + return "" + } + return msg +} + +func (o Status) GetOrderStatus() proto.Status { + if o == "" { + return proto.Status_INVALID + } + if resultStatus, ok := queryStatusMap[o]; ok { + return resultStatus + } + return proto.Status_FAIL +} diff --git a/plugins/alipay_redpack/internal/vo/voucher_status.go b/plugins/alipay_redpack/internal/vo/voucher_status.go new file mode 100644 index 0000000..12dec45 --- /dev/null +++ b/plugins/alipay_redpack/internal/vo/voucher_status.go @@ -0,0 +1,21 @@ +package vo + +type VoucherStatus string + +const ( + VoucherStatusEnabled VoucherStatus = "ENABLED" + VoucherStatusDisabled VoucherStatus = "DISABLED" +) + +var voucherStatusTextMap = map[VoucherStatus]string{ + VoucherStatusEnabled: "券状态:可用", + VoucherStatusDisabled: "券状态:不可用", +} + +func (o VoucherStatus) GetText() string { + msg, ok := voucherStatusTextMap[o] + if !ok { + return "" + } + return msg +} diff --git a/plugins/alipay_redpack/main.go b/plugins/alipay_redpack/main.go new file mode 100644 index 0000000..be3834f --- /dev/null +++ b/plugins/alipay_redpack/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "gitea.cdlsxd.cn/BaseSystem/plugin/shared" + "github.com/hashicorp/go-plugin" + "plugins/alipay_redpack/internal" +) + +func main() { + plugin.Serve(&plugin.ServeConfig{ + HandshakeConfig: shared.HandshakeConfig(internal.Version, internal.CookieKey, internal.CookieValue), + Plugins: shared.PluginSet(shared.NewPlugin(&internal.AlipayCpnService{}, internal.Tag)), + GRPCServer: plugin.DefaultGRPCServer, + }) +} diff --git a/utils/alipay/cert.go b/utils/alipay/cert.go new file mode 100644 index 0000000..da8e5c8 --- /dev/null +++ b/utils/alipay/cert.go @@ -0,0 +1,61 @@ +package alipay + +import ( + "fmt" + "sync" +) + +type CertConfig struct { + MchCertSN string + RootCertSN string +} + +type manager struct { + once sync.Once + mutex sync.RWMutex + CertConfigs map[string]*CertConfig +} + +var instance manager + +func getCertConfig(appId string) *CertConfig { + c, ok := instance.CertConfigs[appId] + if !ok { + return nil + } + return c +} + +func setCertConfig(appId string, certConfig *CertConfig) { + instance.mutex.Lock() + defer instance.mutex.Unlock() + instance.CertConfigs[appId] = certConfig +} + +func init() { + instance.CertConfigs = make(map[string]*CertConfig) +} + +func GetCert(mchCertPath, rootCertPath, appId string) (*CertConfig, error) { + if mchCertPath == "" || rootCertPath == "" || appId == "" { + return nil, fmt.Errorf("mchCertPath or rootCertPath or appId is empty") + } + c := getCertConfig(appId) + if c != nil { + return nil, fmt.Errorf("appId %s already exists", appId) + } + mchCertSN, err := GetMchCertSN(mchCertPath) + if err != nil { + return nil, fmt.Errorf("get mchCertSN error: %v", err) + } + rootCertSN, err := GetRootCertSN(rootCertPath) + if err != nil { + return nil, fmt.Errorf("get rootCertSN error: %v", err) + } + c = &CertConfig{ + MchCertSN: mchCertSN, + RootCertSN: rootCertSN, + } + setCertConfig(appId, c) + return c, nil +} diff --git a/utils/alipay/cert_test.go b/utils/alipay/cert_test.go new file mode 100644 index 0000000..4feacb3 --- /dev/null +++ b/utils/alipay/cert_test.go @@ -0,0 +1,43 @@ +package alipay + +import ( + "testing" +) + +func TestCertSN(t *testing.T) { + ms, err := GetMchCertSN("/Users/lsxd/code/php/yxxt/market/config/alipaycash/appCertPublicKey_2021004100663111.crt") + if err != nil { + t.Error(err) + } else { + //78a57140055b8e7853b1576bcf763361 + //78a57140055b8e7853b1576bcf763361 + t.Logf("merchantCertSN:%s", ms) + } + rs, err := GetRootCertSN("/Users/lsxd/code/php/yxxt/market/config/alipaycash/alipayRootCert.crt") + if err != nil { + t.Error(err) + } else { + //687b59193f3f462dd5336e5abf83c5d8_02941eef3187dddf3d3b83462e1dfcf6 + //687b59193f3f462dd5336e5abf83c5d8_02941eef3187dddf3d3b83462e1dfcf6 + t.Logf("alipayRootCertSN:%s", rs) + } +} + +func TestMchCertSN(t *testing.T) { + s, err := GetMchCertSN("/Users/lsxd/code/php/yxxt/market/config/alipaycash/appCertPublicKey_2021004100663111.crt") + if err != nil { + t.Error(err) + } else { + //78a57140055b8e7853b1576bcf763361 + t.Log(s) + } +} + +func TestRootCertSN(t *testing.T) { + s, err := GetRootCertSN("/Users/lsxd/code/php/yxxt/market/config/alipaycash/alipayRootCert.crt") + if err != nil { + t.Error(err) + } else { + t.Log(s) + } +} diff --git a/utils/alipay/common.go b/utils/alipay/common.go new file mode 100644 index 0000000..03161bf --- /dev/null +++ b/utils/alipay/common.go @@ -0,0 +1,38 @@ +package alipay + +import ( + "crypto/md5" + "crypto/x509" + "encoding/hex" + "encoding/pem" + "fmt" + "log" +) + +// md5Hash 计算 MD5 哈希值 +func md5Hash(s string) string { + h := md5.New() + h.Write([]byte(s)) + return hex.EncodeToString(h.Sum(nil)) +} + +// hex2dec 将字节数组转换为十六进制字符串 +func hex2dec(hex string) string { + var dec int + fmt.Sscanf(hex, "%x", &dec) + return fmt.Sprintf("%d", dec) +} + +func getCert(certData []byte) (*x509.Certificate, error) { + block, _ := pem.Decode(certData) + if block == nil { + log.Fatal("Failed to parse PEM block containing the cert") + } + + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, fmt.Errorf("failed to parse certificate: %v", err) + } + + return cert, nil +} diff --git a/utils/alipay/mch_cert.go b/utils/alipay/mch_cert.go new file mode 100644 index 0000000..280bc99 --- /dev/null +++ b/utils/alipay/mch_cert.go @@ -0,0 +1,22 @@ +package alipay + +import ( + "fmt" + "io/ioutil" + "log" +) + +// GetMchCertSN 提取证书序列号 +func GetMchCertSN(certPath string) (string, error) { + certData, err := ioutil.ReadFile(certPath) + if err != nil { + log.Fatalf("Failed to read the cert file: %s", err) + } + + cert, err := getCert(certData) + if err != nil { + return "", fmt.Errorf("failed to parse certificate: %v", err) + } + + return md5Hash(cert.Issuer.ToRDNSequence().String() + cert.SerialNumber.String()), nil +} diff --git a/utils/alipay/root_cert.go b/utils/alipay/root_cert.go new file mode 100644 index 0000000..3ce0457 --- /dev/null +++ b/utils/alipay/root_cert.go @@ -0,0 +1,38 @@ +package alipay + +import ( + "crypto/x509" + "fmt" + "io/ioutil" + "strings" +) + +// GetRootCertSN 计算证书的序列号并返回最终字符串 +func GetRootCertSN(certPath string) (string, error) { + certData, err := ioutil.ReadFile(certPath) + if err != nil { + return "", fmt.Errorf("failed to read certificate file: %v", err) + } + var sn string + blocks := strings.Split(string(certData), "-----END CERTIFICATE-----") + for _, blockStr := range blocks[:len(blocks)-1] { + cert, err := getCert([]byte(strings.TrimSpace(blockStr) + "\n-----END CERTIFICATE-----")) + if err != nil { + continue + } + serialNumber := cert.SerialNumber.String() + if strings.HasPrefix(serialNumber, "0x") { + serialNumber = hex2dec(serialNumber[2:]) + } + if cert.SignatureAlgorithm == x509.SHA1WithRSA || cert.SignatureAlgorithm == x509.SHA256WithRSA { + hash := md5Hash(cert.Issuer.ToRDNSequence().String() + serialNumber) + if sn == "" { + sn = hash + } else { + sn += "_" + hash + } + } + } + + return sn, nil +}