diff --git a/README.md b/README.md index 0c9fe57..ad037c8 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ - [ ] Limiter - [ ] Compress +transition 服务,查询后立即完成,提供是否访问接口参数,统一主动与回调调用 + callback 结果直接由 api 端提供,不通过前端转发 统一套餐创建逻辑 diff --git a/go.mod b/go.mod index 94f50c6..32e5bef 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( require ( filippo.io/edwards25519 v1.1.0 // indirect + github.com/alibabacloud-go/dysmsapi-20180501/v2 v2.0.6 // indirect github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect github.com/andybalholm/brotli v1.1.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect diff --git a/go.sum b/go.sum index ceaf3dd..01a0759 100644 --- a/go.sum +++ b/go.sum @@ -1,18 +1,56 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/agiledragon/gomonkey v2.0.2+incompatible/go.mod h1:2NGfXu1a80LLr2cmWXGBDaHEjb1idR6+FVlX5T3D9hw= +github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo= +github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= +github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5/go.mod h1:tWnyE9AjF8J8qqLk645oUmVUnFybApTQWklQmi5tY6g= +github.com/alibabacloud-go/darabonba-array v0.1.0/go.mod h1:BLKxr0brnggqOJPqT09DFJ8g3fsDshapUD3C3aOEFaI= +github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8= +github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10/go.mod h1:26a14FGhZVELuz2cc2AolvW4RHmIO3/HRwsdHhaIPDE= +github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ= +github.com/alibabacloud-go/darabonba-string v1.0.2/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA= +github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY= +github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc= +github.com/alibabacloud-go/debug v1.0.1/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc= +github.com/alibabacloud-go/dysmsapi-20180501/v2 v2.0.6 h1:XPck0S+7I8se2e8YnjA/D9OE6FzXwWo2UjlSziJs+z0= +github.com/alibabacloud-go/dysmsapi-20180501/v2 v2.0.6/go.mod h1:Ia7UzBNwNCeNR3TxGQAWi6yNttIFWGiR4HzIlA3Dasc= +github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE= +github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= +github.com/alibabacloud-go/openapi-util v0.1.1/go.mod h1:/UehBSE2cf1gYT43GV4E+RxTdLRzURImCYY0aRmlXpw= +github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg= +github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= +github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= +github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= +github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= +github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= +github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk= +github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= +github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4= +github.com/alibabacloud-go/tea-utils/v2 v2.0.6/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I= +github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8= github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE= github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/miniredis/v2 v2.34.0 h1:mBFWMaJSNL9RwdGRyEDoAAv8OQc5UlEhLDQggTglU/0= github.com/alicebob/miniredis/v2 v2.34.0/go.mod h1:kWShP4b58T1CW0Y5dViCd5ztzrDqRWqM3nksiyXk5s8= +github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw= +github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0= +github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM= +github.com/aliyun/credentials-go v1.3.10/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= @@ -20,6 +58,9 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= @@ -35,12 +76,30 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0kt github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -57,10 +116,15 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw= github.com/jxskiss/base62 v1.1.0/go.mod h1:HhWAlUXvxKThfOlZbcuFzsqwtF5TcqS9ru3y5GfjWAc= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lmittmann/tint v1.0.7 h1:D/0OqWZ0YOGZ6AyC+5Y2kD8PBEzBk6rFHVSfOqCkF9Y= github.com/lmittmann/tint v1.0.7/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= @@ -75,8 +139,15 @@ github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBW github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA= github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM= github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= @@ -93,16 +164,23 @@ github.com/smartwalle/ngx v1.0.9 h1:pUXDvWRZJIHVrCKA1uZ15YwNti+5P4GuJGbpJ4WvpMw= github.com/smartwalle/ngx v1.0.9/go.mod h1:mx/nz2Pk5j+RBs7t6u6k22MPiBG/8CtOMpCnALIG8Y0= github.com/smartwalle/nsign v1.0.9 h1:8poAgG7zBd8HkZy9RQDwasC6XZvJpDGQWSjzL2FZL6E= github.com/smartwalle/nsign v1.0.9/go.mod h1:eY6I4CJlyNdVMP+t6z1H6Jpd4m5/V+8xi44ufSTxXgc= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.59.0 h1:Qu0qYHfXvPk1mSLNqcFtEk6DpxgA26hy6bmydotDpRI= @@ -111,53 +189,133 @@ github.com/wechatpay-apiv3/wechatpay-go v0.2.20 h1:gS8oFn1bHGnyapR2Zb4aqTV6l4kJW github.com/wechatpay-apiv3/wechatpay-go v0.2.20/go.mod h1:A254AUBVB6R+EqQFo3yTgeh7HtyqRRtN2w9hQSOrd4Q= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -183,6 +341,8 @@ gorm.io/hints v1.1.2 h1:b5j0kwk5p4+3BtDtYqqfY+ATSxjj+6ptPgVveuynn9o= gorm.io/hints v1.1.2/go.mod h1:/ARdpUHAtyEMCh5NNi3tI7FsGh+Cj/MIUlvNxCNCFWg= gorm.io/plugin/dbresolver v1.5.3 h1:wFwINGZZmttuu9h7XpvbDHd8Lf9bb8GNzp/NpAMV2wU= gorm.io/plugin/dbresolver v1.5.3/go.mod h1:TSrVhaUg2DZAWP3PrHlDlITEJmNOkL0tFTjvTEsQ4XE= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE= modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= diff --git a/scripts/sql/init.sql b/scripts/sql/init.sql index 44b802a..b490c2c 100644 --- a/scripts/sql/init.sql +++ b/scripts/sql/init.sql @@ -706,6 +706,7 @@ create table trade ( payment decimal(12, 2) not null default 0, method int not null, status int not null default 0, + pay_url text, paid_at timestamp, cancel_at timestamp, created_at timestamp default current_timestamp, @@ -731,6 +732,7 @@ comment on column trade.amount is '订单总金额'; comment on column trade.payment is '支付金额'; comment on column trade.method is '支付方式:1-支付宝,2-微信'; comment on column trade.status is '订单状态:0-待支付,1-已支付,2-已取消,3-已退款'; +comment on column trade.pay_url is '支付链接'; comment on column trade.paid_at is '支付时间'; comment on column trade.cancel_at is '取消时间'; comment on column trade.created_at is '创建时间'; diff --git a/web/globals/wechat.go b/web/globals/wechat.go index fce51ba..159b75b 100644 --- a/web/globals/wechat.go +++ b/web/globals/wechat.go @@ -5,6 +5,8 @@ import ( "platform/pkg/env" "github.com/wechatpay-apiv3/wechatpay-go/core" + "github.com/wechatpay-apiv3/wechatpay-go/core/auth/verifiers" + "github.com/wechatpay-apiv3/wechatpay-go/core/notify" "github.com/wechatpay-apiv3/wechatpay-go/core/option" "github.com/wechatpay-apiv3/wechatpay-go/services/payments/native" "github.com/wechatpay-apiv3/wechatpay-go/utils" @@ -14,10 +16,12 @@ var WechatPay *WechatPayClient type WechatPayClient struct { Native *native.NativeApiService + Notify *notify.Handler } func InitWechatPay() { + // 加载 rsa 密钥文件 appPrivateKey, err := utils.LoadPrivateKey(env.WechatPayMchPrivateKeyPath) if err != nil { panic(err) @@ -28,6 +32,7 @@ func InitWechatPay() { panic(err) } + // 创建 WechatPay 客户端 client, err := core.NewClient(context.Background(), option.WithWechatPayPublicKeyAuthCipher( env.WechatPayMchId, @@ -41,7 +46,18 @@ func InitWechatPay() { panic(err) } + // 创建 WechatPay 通知处理器 + handler, err := notify.NewRSANotifyHandler(env.WechatPayApiCert, verifiers.NewSHA256WithRSAPubkeyVerifier( + env.WechatPayPublicKeyId, + *wechatPublicKey, + )) + if err != nil { + panic(err) + } + + // 创建 WechatPay 服务 WechatPay = &WechatPayClient{ Native: &native.NativeApiService{Client: client}, + Notify: handler, } } diff --git a/web/handlers/resource.go b/web/handlers/resource.go index d57f50b..3af167b 100644 --- a/web/handlers/resource.go +++ b/web/handlers/resource.go @@ -1,19 +1,14 @@ package handlers import ( - "platform/pkg/env" "platform/pkg/u" "platform/web/auth" "platform/web/common" - g "platform/web/globals" q "platform/web/queries" s "platform/web/services" - "strconv" "time" "github.com/gofiber/fiber/v2" - "github.com/smartwalle/alipay/v3" - "github.com/wechatpay-apiv3/wechatpay-go/services/payments/native" ) // region ListResourcePss @@ -143,7 +138,7 @@ func AllResource(c *fiber.Ctx) error { // endregion -// region CreateResourcePrepared +// region CompleteResource type CreateResourceReq struct { s.CreateResourceData @@ -172,34 +167,21 @@ func PrepareResourceByAlipay(c *fiber.Ctx) error { return err } - // 生成订单 - tradeNo, err := s.ID.GenSerial(c.Context()) - if err != nil { - return err - } - - // 调用外部接口 - alipayResp, err := g.Alipay.TradePagePay(alipay.TradePagePay{ - QRPayMode: "4", - Trade: alipay.Trade{ - OutTradeNo: tradeNo, - TotalAmount: strconv.FormatFloat(req.GetPrice(), 'f', 2, 64), - Subject: "购买套餐 - " + req.GetName(), - ProductCode: "FAST_INSTANT_TRADE_PAY", - TimeExpire: time.Now().Add(30 * time.Minute).Format("2006-01-02 15:04:05"), - }, - }) - if err != nil { - return err - } - // 保存交易信息 - err = s.Resource.PrepareResource(c.Context(), &req.CreateResourceData, authContext.Payload.Id, tradeNo, 1) + result, err := s.Resource.PrepareResource( + c.Context(), + &req.CreateResourceData, + authContext.Payload.Id, + s.TransactionMethodAlipay, + ) + if err != nil { + return err + } // 返回结果 return c.JSON(CreateResourceResp{ - TradeNo: tradeNo, - PayURL: alipayResp.String(), + TradeNo: result.TradeNo, + PayURL: result.PayURL, }) } @@ -217,35 +199,21 @@ func PrepareResourceByWechat(c *fiber.Ctx) error { return err } - // 生成订单号 - tradeNo, err := s.ID.GenSerial(c.Context()) - if err != nil { - return err - } - - // 调用外部接口 - wechatPayResp, _, err := g.WechatPay.Native.Prepay(c.Context(), native.PrepayRequest{ - Mchid: &env.WechatPayMchId, - Appid: &env.WechatPayAppId, - Description: u.P("购买套餐 - " + req.GetName()), - OutTradeNo: &tradeNo, - TimeExpire: u.P(time.Now().Add(30 * time.Minute)), - NotifyUrl: &env.WechatPayCallbackUrl, - Amount: &native.Amount{ - Total: u.P(int64(req.GetPrice() * 100)), - }, - }) - // 保存交易信息 - err = s.Resource.PrepareResource(c.Context(), &req.CreateResourceData, authContext.Payload.Id, tradeNo, 2) + result, err := s.Resource.PrepareResource( + c.Context(), + &req.CreateResourceData, + authContext.Payload.Id, + s.TransactionMethodWeChat, + ) if err != nil { return err } // 返回结果 return c.JSON(CreateResourceResp{ - TradeNo: tradeNo, - PayURL: *wechatPayResp.CodeUrl, + TradeNo: result.TradeNo, + PayURL: result.PayURL, }) } @@ -263,28 +231,16 @@ func CreateResourceByAlipay(c *fiber.Ctx) error { } // 验证支付结果 - alipayResp, err := g.Alipay.TradeQuery(c.Context(), alipay.TradeQuery{ - OutTradeNo: req.TradeNo, + result, err := s.Transaction.VerifyTransaction(c.Context(), &s.TransactionVerifyData{ + TradeNo: req.TradeNo, + Method: s.TransactionMethodAlipay, }) if err != nil { return err } - if alipayResp.TradeStatus != "TRADE_SUCCESS" { - return fiber.NewError(fiber.StatusBadRequest, "支付未完成,请确认后重试") - } - // 创建套餐 - payment, err := strconv.ParseFloat(alipayResp.ReceiptAmount, 64) - if err != nil { - return err - } - - paidAt, err := time.Parse("2006-01-02 15:04:05", alipayResp.SendPayDate) - if err != nil { - return err - } - - err = s.Resource.CreateResourcePrepared(c.Context(), req.TradeNo, alipayResp.TradeNo, payment, paidAt) + // 完成套餐创建 + err = s.Resource.CompleteResource(c.Context(), req.TradeNo, result) if err != nil { return err } @@ -306,27 +262,16 @@ func CreateResourceByWechat(c *fiber.Ctx) error { } // 验证支付结果 - wechatPayResp, _, err := g.WechatPay.Native.QueryOrderByOutTradeNo(c.Context(), native.QueryOrderByOutTradeNoRequest{ - OutTradeNo: &req.TradeNo, - Mchid: &env.WechatPayMchId, + result, err := s.Transaction.VerifyTransaction(c.Context(), &s.TransactionVerifyData{ + TradeNo: req.TradeNo, + Method: s.TransactionMethodWeChat, }) if err != nil { return err } - if *wechatPayResp.TradeState != "SUCCESS" { - return fiber.NewError(fiber.StatusBadRequest, "支付未完成,请确认后重试") - } - - // 创建套餐 - payment := float64(*wechatPayResp.Amount.PayerTotal) / 100 - - paidAt, err := time.Parse(time.RFC3339, *wechatPayResp.SuccessTime) - if err != nil { - return err - } - - err = s.Resource.CreateResourcePrepared(c.Context(), req.TradeNo, *wechatPayResp.OutTradeNo, payment, paidAt) + // 完成套餐创建 + err = s.Resource.CompleteResource(c.Context(), req.TradeNo, result) if err != nil { return err } @@ -349,7 +294,7 @@ func CreateResourceByBalance(c *fiber.Ctx) error { } // 创建套餐 - err = s.Resource.CreateResourceImmediately(&req.CreateResourceData, authCtx.Payload.Id) + err = s.Resource.CreateResource(&req.CreateResourceData, authCtx.Payload.Id) if err != nil { return err } diff --git a/web/handlers/trade.go b/web/handlers/trade.go index 2632e0c..80ad289 100644 --- a/web/handlers/trade.go +++ b/web/handlers/trade.go @@ -1,14 +1,19 @@ package handlers import ( + "fmt" + "log/slog" "net/http" g "platform/web/globals" + q "platform/web/queries" s "platform/web/services" "strconv" "time" "github.com/gofiber/fiber/v2" "github.com/smartwalle/alipay/v3" + "github.com/valyala/fasthttp/fasthttpadaptor" + "github.com/wechatpay-apiv3/wechatpay-go/services/payments" ) // region AlipayCallback @@ -16,73 +21,87 @@ import ( func AlipayCallback(c *fiber.Ctx) error { // 解析请求 - req := make(map[string][]string) - c.Context().QueryArgs().VisitAll(func(key, value []byte) { - req[string(key)] = append(req[string(key)], string(value)) - }) - c.Context().PostArgs().VisitAll(func(key, value []byte) { - req[string(key)] = append(req[string(key)], string(value)) - }) - - notification, err := g.Alipay.DecodeNotification(req) + httpRequest := new(http.Request) + if err := fasthttpadaptor.ConvertRequest(c.Context(), httpRequest, false); err != nil { + return err + } + if err := httpRequest.ParseForm(); err != nil { + return err + } + notification, err := g.Alipay.DecodeNotification(httpRequest.Form) if err != nil { return err } + slog.Debug("支付宝支付回调", "notification", fmt.Sprintf("%+v", notification)) + + // todo 退款通知 + if isRefund(notification) { + return act(c) + } + + // 查询交易信息 + trade, err := q.Q.Trade.Where(q.Trade.InnerNo.Eq(notification.OutTradeNo)).Take() + if err != nil { + // 跳过测试通知 + return act(c) + } switch notification.NotifyType { // 支付成功 case string(alipay.TradeStatusSuccess): - if isRefund(notification) { - break - } - + // 收集交易状态 payment, err := strconv.ParseFloat(notification.TotalAmount, 64) if err != nil { return err } - paidAt, err := time.Parse("2006-01-02 15:04:05", notification.GmtPayment) if err != nil { return err } + verified := &s.TransactionVerifyResult{ + TransId: notification.TradeNo, + Payment: payment, + Time: paidAt, + } + switch trade.Type { - err = s.Resource.CreateResourcePrepared( - c.Context(), - notification.OutTradeNo, - notification.TradeNo, - payment, - paidAt, - ) - if err != nil { - return err + // 余额充值 + case 0: + err := s.User.RechargeConfirm(c.Context(), notification.OutTradeNo, verified) + if err != nil { + return err + } + + // 购买产品 + case 1: + err = s.Resource.CompleteResource(c.Context(), notification.OutTradeNo, verified) + if err != nil { + return err + } } // 支付关闭 case string(alipay.TradeStatusClosed): + switch trade.Type { - if isRefund(notification) { - break + // 购买产品 + case 1: + + cancelAt, err := time.Parse("2006-01-02 15:04:05", notification.GmtClose) + if err != nil { + return err + } + + err = s.Resource.CancelResource(c.Context(), notification.OutTradeNo, cancelAt, s.TransactionMethodAlipay) + if err != nil { + return err + } } - - cancelAt, err := time.Parse("2006-01-02 15:04:05", notification.GmtClose) - if err != nil { - return err - } - - err = s.Resource.CancelResource(c.Context(), notification.OutTradeNo, cancelAt) - if err != nil { - return err - } - - default: } - g.Alipay.ACKNotification(AdapterWriter{ - c: c, - }) - return nil + return act(c) } type AdapterWriter struct { @@ -105,12 +124,72 @@ func isRefund(notification *alipay.Notification) bool { return notification.OutBizNo != "" || notification.RefundFee != "" || notification.GmtRefund != "" } +func act(c *fiber.Ctx) error { + g.Alipay.ACKNotification(AdapterWriter{c: c}) + return nil +} + // endregion // region WechatPayCallback func WechatPayCallback(c *fiber.Ctx) error { + // 解析请求参数 + req := new(http.Request) + if err := fasthttpadaptor.ConvertRequest(c.Context(), req, false); err != nil { + return err + } + + content := new(payments.Transaction) + _, err := g.WechatPay.Notify.ParseNotifyRequest(c.Context(), req, content) + if err != nil { + return err + } + + slog.Debug("微信支付回调", "content", fmt.Sprintf("%+v", content)) + + // 查询交易信息 + trade, err := q.Q.Trade.Where(q.Trade.InnerNo.Eq(*content.OutTradeNo)).Take() + if err != nil { + // 跳过测试通知 + return nil + } + switch *content.TradeState { + + // 支付成功 + case "SUCCESS": + + // 收集交易状态 + payment := float64(*content.Amount.PayerTotal) / 100 + paidAt, err := time.Parse(time.RFC3339, *content.SuccessTime) + if err != nil { + return err + } + verified := &s.TransactionVerifyResult{ + TransId: *content.TransactionId, + Payment: payment, + Time: paidAt, + } + + switch { + + // 余额充值 + case trade.Type == 0: + err := s.User.RechargeConfirm(c.Context(), *content.OutTradeNo, verified) + if err != nil { + return err + } + + // 购买产品 + case trade.Type == 1: + err = s.Resource.CompleteResource(c.Context(), *content.OutTradeNo, verified) + if err != nil { + return err + } + } + } + return nil } diff --git a/web/handlers/user.go b/web/handlers/user.go index 75fd5da..87609f9 100644 --- a/web/handlers/user.go +++ b/web/handlers/user.go @@ -2,8 +2,11 @@ package handlers import ( "errors" + "platform/web/auth" q "platform/web/queries" - "platform/web/services" + s "platform/web/services" + "strconv" + "time" "github.com/gofiber/fiber/v2" "gorm.io/gorm" @@ -25,7 +28,7 @@ func GetUserByToken(c *fiber.Ctx) error { } // 查询会话信息 - session, err := services.Session.Find(c.Context(), req.Token) + session, err := s.Session.Find(c.Context(), req.Token) if err != nil { return err } @@ -47,3 +50,160 @@ func GetUserByToken(c *fiber.Ctx) error { } // endregion + +// region recharge + +type RechargePrepareReq struct { + Amount float64 `json:"amount" validate:"required,min=0.01"` +} + +type RechargePrepareResp struct { + TradeNo string `json:"trade_no"` + PayURL string `json:"pay_url"` +} + +type RechargeConfirmReq struct { + TradeNo string `json:"trade_no" validate:"required"` +} + +type RechargeConfirmResp struct { + Status string `json:"status"` +} + +// RechargePrepareAlipay 通过支付宝充值 +func RechargePrepareAlipay(c *fiber.Ctx) error { + // 检查权限 + authContext, err := auth.Protect(c, []s.PayloadType{s.PayloadUser}, []string{}) + if err != nil { + return err + } + + // 解析请求参数 + req := new(RechargePrepareReq) + if err := c.BodyParser(req); err != nil { + return err + } + + // 保存交易信息 + var result *s.TransactionPrepareResult + err = q.Q.Transaction(func(tx *q.Query) error { + result, err = s.Transaction.PrepareTransaction(c.Context(), tx, authContext.Payload.Id, &s.TransactionPrepareData{ + Subject: "账户充值 - " + strconv.FormatFloat(req.Amount, 'f', 2, 64) + "元", + Amount: req.Amount, + ExpireAt: time.Now().Add(30 * time.Minute), + Type: s.TransactionTypeRecharge, + Method: s.TransactionMethodAlipay, + }) + return err + }) + if err != nil { + return err + } + + // 返回结果 + return c.JSON(RechargePrepareResp{ + TradeNo: result.TradeNo, + PayURL: result.PayURL, + }) +} + +func RechargeConfirmAlipay(c *fiber.Ctx) error { + // 检查权限 + _, err := auth.Protect(c, []s.PayloadType{s.PayloadUser}, []string{}) + if err != nil { + return err + } + + // 解析请求参数 + req := new(RechargeConfirmReq) + if err := c.BodyParser(req); err != nil { + return err + } + + // 验证支付结果 + result, err := s.Transaction.VerifyTransaction(c.Context(), &s.TransactionVerifyData{ + TradeNo: req.TradeNo, + Method: s.TransactionMethodAlipay, + }) + if err != nil { + return err + } + + // 更新数据库 + err = s.User.RechargeConfirm(c.Context(), req.TradeNo, result) + if err != nil { + return err + } + + return c.JSON(fiber.Map{"status": "success"}) +} + +func RechargePrepareWechat(c *fiber.Ctx) error { + // 检查权限 + authContext, err := auth.Protect(c, []s.PayloadType{s.PayloadUser}, []string{}) + if err != nil { + return err + } + + // 解析请求参数 + req := new(RechargePrepareReq) + if err := c.BodyParser(req); err != nil { + return err + } + + // 保存交易信息 + var result *s.TransactionPrepareResult + err = q.Q.Transaction(func(tx *q.Query) error { + result, err = s.Transaction.PrepareTransaction(c.Context(), tx, authContext.Payload.Id, &s.TransactionPrepareData{ + Subject: "账户充值 - " + strconv.FormatFloat(req.Amount, 'f', 2, 64) + "元", + Amount: req.Amount, + ExpireAt: time.Now().Add(30 * time.Minute), + Type: s.TransactionTypeRecharge, + Method: s.TransactionMethodWeChat, + }) + return err + }) + if err != nil { + return err + } + + // 返回结果 + return c.JSON(RechargePrepareResp{ + TradeNo: result.TradeNo, + PayURL: result.PayURL, + }) +} + +func RechargeConfirmWechat(c *fiber.Ctx) error { + // 检查权限 + _, err := auth.Protect(c, []s.PayloadType{s.PayloadUser}, []string{}) + if err != nil { + return err + } + + // 解析请求参数 + req := new(struct { + TradeNo string `json:"trade_no" validate:"required"` + }) + if err := c.BodyParser(req); err != nil { + return err + } + + // 验证支付结果 + result, err := s.Transaction.VerifyTransaction(c.Context(), &s.TransactionVerifyData{ + TradeNo: req.TradeNo, + }) + if err != nil { + return err + } + + // 更新数据库 + err = s.User.RechargeConfirm(c.Context(), req.TradeNo, result) + if err != nil { + return err + } + + return c.JSON(fiber.Map{"status": "success"}) +} + +// endregion diff --git a/web/models/trade.gen.go b/web/models/trade.gen.go index 3ec8b31..22f9764 100644 --- a/web/models/trade.gen.go +++ b/web/models/trade.gen.go @@ -30,6 +30,7 @@ type Trade struct { Type int32 `gorm:"column:type;not null" json:"type"` CancelAt common.LocalDateTime `gorm:"column:cancel_at" json:"cancel_at"` PaidAt common.LocalDateTime `gorm:"column:paid_at" json:"paid_at"` + PayURL string `gorm:"column:pay_url" json:"pay_url"` } // TableName Trade's table name diff --git a/web/queries/trade.gen.go b/web/queries/trade.gen.go index 76aba67..e6f5319 100644 --- a/web/queries/trade.gen.go +++ b/web/queries/trade.gen.go @@ -43,6 +43,7 @@ func newTrade(db *gorm.DB, opts ...gen.DOOption) trade { _trade.Type = field.NewInt32(tableName, "type") _trade.CancelAt = field.NewField(tableName, "cancel_at") _trade.PaidAt = field.NewField(tableName, "paid_at") + _trade.PayURL = field.NewString(tableName, "pay_url") _trade.fillFieldMap() @@ -69,6 +70,7 @@ type trade struct { Type field.Int32 CancelAt field.Field PaidAt field.Field + PayURL field.String fieldMap map[string]field.Expr } @@ -101,6 +103,7 @@ func (t *trade) updateTableName(table string) *trade { t.Type = field.NewInt32(table, "type") t.CancelAt = field.NewField(table, "cancel_at") t.PaidAt = field.NewField(table, "paid_at") + t.PayURL = field.NewString(table, "pay_url") t.fillFieldMap() @@ -117,7 +120,7 @@ func (t *trade) GetFieldByName(fieldName string) (field.OrderExpr, bool) { } func (t *trade) fillFieldMap() { - t.fieldMap = make(map[string]field.Expr, 16) + t.fieldMap = make(map[string]field.Expr, 17) t.fieldMap["id"] = t.ID t.fieldMap["user_id"] = t.UserID t.fieldMap["inner_no"] = t.InnerNo @@ -134,6 +137,7 @@ func (t *trade) fillFieldMap() { t.fieldMap["type"] = t.Type t.fieldMap["cancel_at"] = t.CancelAt t.fieldMap["paid_at"] = t.PaidAt + t.fieldMap["pay_url"] = t.PayURL } func (t trade) clone(db *gorm.DB) trade { diff --git a/web/router.go b/web/router.go index 1f063ca..da39368 100644 --- a/web/router.go +++ b/web/router.go @@ -43,6 +43,10 @@ func ApplyRouters(app *fiber.App) { user.Post("/get/token", handlers.GetUserByToken) user.Post("/identify", handlers.Identify) user.Post("/identify/callback", handlers.IdentifyCallback) + user.Post("/recharge/prepare/alipay", handlers.RechargePrepareAlipay) + user.Post("/recharge/confirm/alipay", handlers.RechargeConfirmAlipay) + user.Post("/recharge/prepare/wechat", handlers.RechargePrepareWechat) + user.Post("/recharge/confirm/wechat", handlers.RechargeConfirmWechat) // 账单 bill := api.Group("/bill") diff --git a/web/services/resource.go b/web/services/resource.go index 56b2332..66d2cc6 100644 --- a/web/services/resource.go +++ b/web/services/resource.go @@ -18,56 +18,37 @@ var Resource = &resourceService{} type resourceService struct{} -func (s *resourceService) PrepareResource(ctx context.Context, data *CreateResourceData, uid int32, tradeNo string, method int32) error { +func (s *resourceService) PrepareResource(ctx context.Context, data *CreateResourceData, uid int32, method TransactionMethod) (*TransactionPrepareResult, error) { amount := data.GetPrice() // 保存到数据库 + var result *TransactionPrepareResult err := q.Q.Transaction(func(q *q.Query) error { - // 创建交易订单 - var trade = m.Trade{ - UserID: uid, - InnerNo: tradeNo, - Subject: "购买套餐 - " + data.GetName(), - Method: method, - Type: 1, - Status: 0, - Amount: amount, - } - err := q.Trade.Create(&trade) - if err != nil { - return err - } + var err error - // 保存用户帐单 - bill := m.Bill{ - UserID: uid, - TradeID: trade.ID, - BillNo: ID.GenReadable("bil"), - Info: "购买套餐 - " + data.GetName(), - Type: 1, - Amount: -amount, - } - err = q.Bill. - Omit(q.Bill.ResourceID, q.Bill.RefundID). - Create(&bill) + // 生成交易订单 + result, err = Transaction.PrepareTransaction(ctx, q, uid, &TransactionPrepareData{ + Subject: "购买套餐 - " + data.GetName(), + Amount: amount, + ExpireAt: time.Now().Add(30 * time.Minute), + Type: TransactionTypePurchase, + Method: method, + }) if err != nil { return err } // 保存请求缓存 - cache := &CreateResourceCache{ + reqStr, err := json.Marshal(&CreateResourceCache{ CreateResourceData: *data, Uid: uid, - TradeId: trade.ID, - BillId: bill.ID, - } - - reqStr, err := json.Marshal(cache) + TradeId: result.Trade.ID, + BillId: result.Bill.ID, + }) if err != nil { return err } - - err = rds.Client.Set(ctx, tradeNo, reqStr, 30*time.Minute).Err() + err = rds.Client.Set(ctx, result.TradeNo, reqStr, 30*time.Minute).Err() if err != nil { return err } @@ -75,20 +56,19 @@ func (s *resourceService) PrepareResource(ctx context.Context, data *CreateResou return nil }) if err != nil { - return err + return nil, err } - return nil + return result, nil } -func (s *resourceService) CreateResourcePrepared(ctx context.Context, tradeNo string, outerTradeNo string, payment float64, at time.Time) error { +func (s *resourceService) CompleteResource(ctx context.Context, tradeNo string, rs *TransactionVerifyResult) error { // 获取请求缓存 reqStr, err := rds.Client.Get(ctx, tradeNo).Result() if err != nil { return err } - cache := new(CreateResourceCache) if err := json.Unmarshal([]byte(reqStr), cache); err != nil { return err @@ -103,21 +83,7 @@ func (s *resourceService) CreateResourcePrepared(ctx context.Context, tradeNo st return err } - // 更新订单状态 - _, err = q.Trade.Debug(). - Select(q.Trade.OuterNo, q.Trade.Payment, q.Trade.Status, q.Trade.PaidAt). - Updates(&m.Trade{ - ID: cache.TradeId, - OuterNo: outerTradeNo, - Payment: payment, - Status: 1, - PaidAt: common.LocalDateTime(at), - }) - if err != nil { - return err - } - - // 更新账单状态 + // 更新账单 _, err = q.Bill.Debug(). Select(q.Bill.ResourceID). Updates(&m.Bill{ @@ -128,6 +94,15 @@ func (s *resourceService) CreateResourcePrepared(ctx context.Context, tradeNo st return err } + // 完成交易 + _, err = Transaction.CompleteTransaction(ctx, q, &TransactionCompleteData{ + TradeNo: tradeNo, + TransactionVerifyResult: *rs, + }) + if err != nil { + return err + } + // 删除缓存 err = rds.Client.Del(ctx, tradeNo).Err() if err != nil { @@ -143,10 +118,24 @@ func (s *resourceService) CreateResourcePrepared(ctx context.Context, tradeNo st return nil } -func (s *resourceService) CreateResourceImmediately(data *CreateResourceData, uid int32) error { +func (s *resourceService) CreateResource(data *CreateResourceData, uid int32) error { // 保存交易信息 err := q.Q.Transaction(func(q *q.Query) error { + amount := data.GetPrice() + + // 检查用户 + user, err := q.User. + Where(q.User.ID.Eq(uid)). + Take() + if err != nil { + return err + } + + // 检查余额 + if user.Balance < amount { + return fiber.NewError(fiber.StatusBadRequest, "余额不足") + } // 保存套餐 resource, err := createResource(data, uid) @@ -170,6 +159,14 @@ func (s *resourceService) CreateResourceImmediately(data *CreateResourceData, ui return err } + // 更新用户余额 + _, err = q.User. + Where(q.User.ID.Eq(uid)). + Update(q.User.Balance, user.Balance-amount) + if err != nil { + return err + } + return nil }) if err != nil { @@ -221,27 +218,10 @@ type CreateResourceCache struct { } func createResource(data *CreateResourceData, uid int32) (*m.Resource, error) { - amount := data.GetPrice() - - // 检查用户 - user, err := q.User. - Where( - q.User.ID.Eq(uid), - q.User.Status.Eq(1), - ). - Take() - if err != nil { - return nil, err - } - - // 检查余额 - if user.Balance < amount { - return nil, fiber.NewError(fiber.StatusBadRequest, "余额不足") - } // 创建套餐 - resource := &m.Resource{ - UserID: user.ID, + resource := m.Resource{ + UserID: uid, ResourceNo: ID.GenReadable("res"), Active: true, Type: 1, @@ -253,38 +233,29 @@ func createResource(data *CreateResourceData, uid int32) (*m.Resource, error) { DailyLimit: data.DailyLimit, }, } - err = q.Resource.Create(resource) + err := q.Resource.Create(&resource) if err != nil { return nil, err } - // 更新用户余额 - user.Balance -= amount - _, err = q.User. - Where(q.User.ID.Eq(uid)). - Update(q.User.Balance, user.Balance) - if err != nil { - return nil, err - } - - return resource, nil + return &resource, nil } -func (s *resourceService) CancelResource(ctx context.Context, tradeNo string, at time.Time) error { - // 获取请求缓存 +func (s *resourceService) CancelResource(ctx context.Context, tradeNo string, at time.Time, method TransactionMethod) error { + // 删除请求缓存 _, err := rds.Client.Del(ctx, tradeNo).Result() if err != nil { return err } + // 取消交易 + err = Transaction.RevokeTransaction(ctx, tradeNo, method) + if err != nil { + return err + } + // 更新订单状态 - _, err = q.Trade. - Where(q.Trade.InnerNo.Eq(tradeNo)). - Select(q.Trade.Status, q.Trade.CancelAt). - Updates(m.Trade{ - Status: 2, - CancelAt: common.LocalDateTime(at), - }) + err = Transaction.FinishTransaction(ctx, q.Q, tradeNo, at) if err != nil { return err } diff --git a/web/services/transaction.go b/web/services/transaction.go new file mode 100644 index 0000000..658133f --- /dev/null +++ b/web/services/transaction.go @@ -0,0 +1,324 @@ +package services + +import ( + "context" + "errors" + "io" + "log/slog" + "net/http" + "platform/pkg/env" + "platform/pkg/u" + "platform/web/common" + g "platform/web/globals" + m "platform/web/models" + q "platform/web/queries" + "strconv" + "time" + + "github.com/smartwalle/alipay/v3" + "github.com/wechatpay-apiv3/wechatpay-go/services/payments/native" +) + +var Transaction = &transactionService{} + +type transactionService struct { +} + +func (s *transactionService) PrepareTransaction(ctx context.Context, q *q.Query, uid int32, data *TransactionPrepareData) (*TransactionPrepareResult, error) { + var subject = data.Subject + var amount = data.Amount + var expire = data.ExpireAt + var tType = data.Type + var method = data.Method + + // 生成订单号 + tradeNo, err := ID.GenSerial(ctx) + if err != nil { + return nil, err + } + + // 创建支付订单 + var payUrl string + switch method { + + // 调用支付宝支付接口 + case TransactionMethodAlipay: + resp, err := g.Alipay.TradePagePay(alipay.TradePagePay{ + QRPayMode: "4", + Trade: alipay.Trade{ + ProductCode: "FAST_INSTANT_TRADE_PAY", + OutTradeNo: tradeNo, + Subject: subject, + TotalAmount: strconv.FormatFloat(amount, 'f', 2, 64), + TimeExpire: expire.Format("2006-01-02 15:04:05"), + }, + }) + if err != nil { + return nil, err + } + payUrl = resp.String() + + // 调用微信支付接口 + case TransactionMethodWeChat: + resp, _, err := g.WechatPay.Native.Prepay(ctx, native.PrepayRequest{ + Appid: &env.WechatPayAppId, + Mchid: &env.WechatPayMchId, + OutTradeNo: &tradeNo, + Description: &subject, + TimeExpire: &expire, + NotifyUrl: &env.WechatPayCallbackUrl, + Amount: &native.Amount{ + Total: u.P(int64(amount * 100)), + }, + }) + if err != nil { + return nil, err + } + payUrl = *resp.CodeUrl + + // 不支持的支付方式 + default: + return nil, errors.New("不支持的支付方式") + } + + // 保存交易订单 + var trade = m.Trade{ + UserID: uid, + InnerNo: tradeNo, + Subject: subject, + Method: int32(method), + Type: int32(tType), + Amount: amount, + Status: 0, // 0-待支付 + PayURL: payUrl, + } + err = q.Trade.Create(&trade) + if err != nil { + return nil, err + } + + // 保存用户帐单 + var bill = m.Bill{ + BillNo: ID.GenReadable("bil"), + UserID: uid, + TradeID: trade.ID, + Info: subject, + Type: int32(tType), + Amount: amount, + } + err = q.Bill. + Omit(q.Bill.ResourceID, q.Bill.RefundID). + Create(&bill) + if err != nil { + return nil, err + } + + return &TransactionPrepareResult{ + TradeNo: tradeNo, + PayURL: payUrl, + Bill: &bill, + Trade: &trade, + }, nil +} + +func (s *transactionService) VerifyTransaction(ctx context.Context, data *TransactionVerifyData) (*TransactionVerifyResult, error) { + var tradeNo = data.TradeNo + var method = data.Method + + // 检查交易号是否存在 + var transId string + var paidAt time.Time + var payment float64 + switch method { + + // 检查支付宝交易 + case TransactionMethodAlipay: + resp, err := g.Alipay.TradeQuery(ctx, alipay.TradeQuery{ + OutTradeNo: tradeNo, + }) + if err != nil { + return nil, err + } + if resp.Code != alipay.CodeSuccess { + slog.Warn("支付宝交易取消失败", "code", resp.Code, "sub_code", resp.SubCode, "msg", resp.Msg) + return nil, errors.New("交易查询失败") + } + if resp.TradeStatus != alipay.TradeStatusSuccess { + return nil, errors.New("交易未完成") + } + + transId = resp.TradeNo + payment, err = strconv.ParseFloat(resp.TotalAmount, 64) + if err != nil { + return nil, err + } + paidAt, err = time.Parse("2006-01-02 15:04:05", resp.SendPayDate) + if err != nil { + return nil, err + } + + // 检查微信交易 + case TransactionMethodWeChat: + resp, _, err := g.WechatPay.Native.QueryOrderByOutTradeNo(ctx, native.QueryOrderByOutTradeNoRequest{ + OutTradeNo: &tradeNo, + Mchid: &env.WechatPayMchId, + }) + if err != nil { + return nil, err + } + if *resp.TradeState != "SUCCESS" { + return nil, errors.New("交易未完成") + } + + transId = *resp.TransactionId + payment = float64(*resp.Amount.PayerTotal) / 100 + paidAt, err = time.Parse(time.RFC3339, *resp.SuccessTime) + if err != nil { + return nil, err + } + + // 不支持的支付方式 + default: + return nil, errors.New("不支持的支付方式") + } + + return &TransactionVerifyResult{ + TransId: transId, + Payment: payment, + Time: paidAt, + }, nil +} + +func (s *transactionService) CompleteTransaction(ctx context.Context, q *q.Query, data *TransactionCompleteData) (*TransactionCompleteResult, error) { + var transId = data.TransId + var tradeNo = data.TradeNo + var payment = data.Payment + var paidAt = data.Time + + // 获取交易信息 + trade, err := q.Trade.WithContext(ctx). + Where(q.Trade.InnerNo.Eq(tradeNo)). + First() + if err != nil { + return nil, err + } + + // 检查交易状态 + if trade.Status != 0 { + return nil, nil + } + + // 更新交易状态 + trade.Status = 1 + trade.OuterNo = transId + trade.Payment = payment + trade.PaidAt = common.LocalDateTime(paidAt) + trade.PayURL = "" + _, err = q.Trade.WithContext(ctx).Updates(trade) + if err != nil { + return nil, err + } + + return &TransactionCompleteResult{ + Trade: trade, + }, nil +} + +func (s *transactionService) RevokeTransaction(ctx context.Context, tradeNo string, method TransactionMethod) error { + + switch method { + + case TransactionMethodAlipay: + resp, err := g.Alipay.TradeCancel(ctx, alipay.TradeCancel{ + OutTradeNo: tradeNo, + }) + if err != nil { + return err + } + if resp.Code != alipay.CodeSuccess { + slog.Warn("支付宝交易取消失败", "code", resp.Code, "sub_code", resp.SubCode, "msg", resp.Msg) + return errors.New("交易取消失败") + } + + case TransactionMethodWeChat: + resp, err := g.WechatPay.Native.CloseOrder(ctx, native.CloseOrderRequest{ + Mchid: &env.WechatPayMchId, + OutTradeNo: &tradeNo, + }) + if err != nil { + return err + } + if resp.Response.StatusCode != http.StatusNoContent { + body, _ := io.ReadAll(resp.Response.Body) + slog.Warn("微信交易取消失败", "code", resp.Response.StatusCode, "body", string(body)) + return errors.New("交易取消失败") + } + } + + return nil +} + +func (s *transactionService) FinishTransaction(ctx context.Context, q *q.Query, tradeNo string, time time.Time) error { + _, err := q.Trade. + Where(q.Trade.InnerNo.Eq(tradeNo)). + Select(q.Trade.Status, q.Trade.CancelAt, q.Trade.PayURL). + Updates(m.Trade{ + Status: 2, + CancelAt: common.LocalDateTime(time), + PayURL: "", + }) + if err != nil { + return err + } + + return nil +} + +type TransactionType int32 + +const ( + TransactionTypeRecharge TransactionType = iota + TransactionTypePurchase +) + +type TransactionMethod int32 + +const ( + TransactionMethodAlipay TransactionMethod = iota + TransactionMethodWeChat +) + +type TransactionPrepareData struct { + Subject string + Amount float64 + ExpireAt time.Time + Type TransactionType + Method TransactionMethod +} + +type TransactionPrepareResult struct { + TradeNo string + PayURL string + Bill *m.Bill + Trade *m.Trade +} + +type TransactionVerifyData struct { + TradeNo string + Method TransactionMethod +} + +type TransactionVerifyResult struct { + TransId string + Payment float64 + Time time.Time +} + +type TransactionCompleteData struct { + TradeNo string + TransactionVerifyResult +} + +type TransactionCompleteResult struct { + Trade *m.Trade +} diff --git a/web/services/user.go b/web/services/user.go new file mode 100644 index 0000000..62f4aa5 --- /dev/null +++ b/web/services/user.go @@ -0,0 +1,46 @@ +package services + +import ( + "context" + q "platform/web/queries" +) + +var User = &userService{} + +type userService struct{} + +func (s *userService) RechargeConfirm(ctx context.Context, tradeNo string, verified *TransactionVerifyResult) error { + + err := q.Q.Transaction(func(tx *q.Query) error { + + // 更新交易状态 + result, err := Transaction.CompleteTransaction(ctx, tx, &TransactionCompleteData{ + TradeNo: tradeNo, + TransactionVerifyResult: *verified, + }) + if err != nil { + return err + } + + // 更新用户余额 + user, err := tx.User. + Where(tx.User.ID.Eq(result.Trade.UserID)).Take() + if err != nil { + return err + } + + _, err = tx.User. + Where(tx.User.ID.Eq(user.ID)). + Update(tx.User.Balance, user.Balance+result.Trade.Amount) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return err + } + + return nil +}