插件-支付宝红包转账,签名,证书加载,加密处理

This commit is contained in:
李子铭 2024-08-20 18:17:33 +08:00
parent b78c50e72e
commit b40c2ad29e
22 changed files with 1085 additions and 9 deletions

6
go.mod
View File

@ -20,10 +20,10 @@ require (
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 // indirect github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 // indirect
github.com/oklog/run v1.0.0 // indirect github.com/oklog/run v1.0.0 // indirect
github.com/pkg/errors v0.9.1 // 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/net v0.22.0 // indirect
golang.org/x/sys v0.18.0 // indirect golang.org/x/sys v0.23.0 // indirect
golang.org/x/text v0.14.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/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
google.golang.org/grpc v1.64.0 // indirect google.golang.org/grpc v1.64.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect google.golang.org/protobuf v1.34.2 // indirect

12
go.sum
View File

@ -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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 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 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 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-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-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= 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 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=

View File

@ -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
)

View File

@ -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=

View File

@ -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
}

View File

@ -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))
})
}

View File

@ -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"`
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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(),
},
}
}

View File

@ -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
}

View File

@ -0,0 +1,9 @@
package vo
type Code string
const CodeSuccess Code = "10000"
func (c Code) IsSuccess() bool {
return c == CodeSuccess
}

View File

@ -0,0 +1,8 @@
package vo
const (
Version = "1.0"
Format = "json"
SignType = "RSA2"
Charset = "UTF-8"
)

View File

@ -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
}

View File

@ -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
}

View File

@ -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,
})
}

61
utils/alipay/cert.go Normal file
View File

@ -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
}

43
utils/alipay/cert_test.go Normal file
View File

@ -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)
}
}

38
utils/alipay/common.go Normal file
View File

@ -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
}

22
utils/alipay/mch_cert.go Normal file
View File

@ -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
}

38
utils/alipay/root_cert.go Normal file
View File

@ -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
}