完善商福通支付接口,修复证书加载问题;数据库扩展支付平台字段并更新支付信息保存逻辑;日志中间件异步记录日志

This commit is contained in:
2025-06-17 10:53:05 +08:00
parent 692106ae5c
commit a4e5fc2af5
19 changed files with 444 additions and 421 deletions

View File

@@ -10,6 +10,8 @@
### 长期 ### 长期
异步日志记录,统一收集
模型字段修改,特定枚举字段使用自定义类型代替通用 int32 模型字段修改,特定枚举字段使用自定义类型代替通用 int32
更新接口可以传输更结构化的数据,直接区分不同类型以加快更新速度 更新接口可以传输更结构化的数据,直接区分不同类型以加快更新速度

4
go.mod
View File

@@ -8,7 +8,6 @@ require (
github.com/go-playground/locales v0.14.1 github.com/go-playground/locales v0.14.1
github.com/go-playground/universal-translator v0.18.1 github.com/go-playground/universal-translator v0.18.1
github.com/go-playground/validator/v10 v10.26.0 github.com/go-playground/validator/v10 v10.26.0
github.com/go-redsync/redsync v1.4.2
github.com/go-redsync/redsync/v4 v4.13.0 github.com/go-redsync/redsync/v4 v4.13.0
github.com/gofiber/fiber/v2 v2.52.6 github.com/gofiber/fiber/v2 v2.52.6
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
@@ -43,7 +42,6 @@ require (
github.com/clbanning/mxj/v2 v2.7.0 // indirect github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/go-redis/redis v6.15.9+incompatible // indirect
github.com/go-sql-driver/mysql v1.9.1 // indirect github.com/go-sql-driver/mysql v1.9.1 // indirect
github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect
github.com/gomodule/redigo v2.0.0+incompatible // indirect github.com/gomodule/redigo v2.0.0+incompatible // indirect
@@ -65,8 +63,6 @@ require (
github.com/mattn/go-sqlite3 v1.14.24 // indirect github.com/mattn/go-sqlite3 v1.14.24 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/onsi/gomega v1.37.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/smartwalle/ncrypto v1.0.4 // indirect github.com/smartwalle/ncrypto v1.0.4 // indirect

37
go.sum
View File

@@ -77,9 +77,6 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
@@ -96,14 +93,11 @@ github.com/go-redis/redis/v7 v7.4.1 h1:PASvf36gyUpr2zdOUS/9Zqc80GbM+9BDyiJSJDDOr
github.com/go-redis/redis/v7 v7.4.1/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= github.com/go-redis/redis/v7 v7.4.1/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-redsync/redsync v1.4.2 h1:KADEZ2rlaHMZWnlkthQCxfGP+8ZWwJLiSjOYN3mntKA=
github.com/go-redsync/redsync v1.4.2/go.mod h1:my8/M5YL986u2jBMtZTLkBIgBsKNNSixJWzWwISH6Uw=
github.com/go-redsync/redsync/v4 v4.13.0 h1:49X6GJfnbLGaIpBBREM/zA4uIMDXKAh1NDkvQ1EkZKA= github.com/go-redsync/redsync/v4 v4.13.0 h1:49X6GJfnbLGaIpBBREM/zA4uIMDXKAh1NDkvQ1EkZKA=
github.com/go-redsync/redsync/v4 v4.13.0/go.mod h1:HMW4Q224GZQz6x1Xc7040Yfgacukdzu7ifTDAKiyErQ= github.com/go-redsync/redsync/v4 v4.13.0/go.mod h1:HMW4Q224GZQz6x1Xc7040Yfgacukdzu7ifTDAKiyErQ=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtSb/iI= github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtSb/iI=
github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI= github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
@@ -140,12 +134,10 @@ github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORR
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hibiken/asynq v0.25.1 h1:phj028N0nm15n8O2ims+IvJ2gz4k2auvermngh9JhTw= github.com/hibiken/asynq v0.25.1 h1:phj028N0nm15n8O2ims+IvJ2gz4k2auvermngh9JhTw=
github.com/hibiken/asynq v0.25.1/go.mod h1:pazWNOLBu0FEynQRBvHA26qdIKRSmfdIfUm4HdsLmXg= github.com/hibiken/asynq v0.25.1/go.mod h1:pazWNOLBu0FEynQRBvHA26qdIKRSmfdIfUm4HdsLmXg=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= 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/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
@@ -200,17 +192,6 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -267,7 +248,6 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= 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.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
@@ -292,7 +272,6 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 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/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.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.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.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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@@ -302,16 +281,13 @@ 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/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-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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/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-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-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-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-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-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-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/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-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/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-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.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.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
@@ -332,7 +308,6 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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-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-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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.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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
@@ -341,17 +316,12 @@ golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/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-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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/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-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-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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/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-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-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.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -401,7 +371,6 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 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-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.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
@@ -411,7 +380,6 @@ 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-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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 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/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-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
@@ -430,16 +398,11 @@ google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwl
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

8
pkg/env/env.go vendored
View File

@@ -352,6 +352,7 @@ func loadAliyun() {
var ( var (
SftPayEnable = false SftPayEnable = false
SftPayAppId string SftPayAppId string
SftPayRouteId string
SftPayAppPrivateKey string SftPayAppPrivateKey string
SftPayPublicKey string SftPayPublicKey string
) )
@@ -375,6 +376,11 @@ func loadSftPay() {
SftPayAppId = value SftPayAppId = value
} }
value = os.Getenv("SFTPAY_ROUTE_ID")
if value != "" {
SftPayRouteId = value
}
value = os.Getenv("SFTPAY_APP_PRIVATE_KEY") value = os.Getenv("SFTPAY_APP_PRIVATE_KEY")
if value == "" { if value == "" {
panic("环境变量 SFTPAY_APP_PRIVATE_KEY 的值不能为空") panic("环境变量 SFTPAY_APP_PRIVATE_KEY 的值不能为空")
@@ -442,5 +448,5 @@ func Init() {
loadAlipay() loadAlipay()
loadWechatPay() loadWechatPay()
loadAliyun() loadAliyun()
// loadSftPay() loadSftPay()
} }

View File

@@ -805,7 +805,8 @@ create table trade (
amount decimal(12, 2) not null default 0, amount decimal(12, 2) not null default 0,
payment decimal(12, 2) not null default 0, payment decimal(12, 2) not null default 0,
method int not null, method int not null,
acquirer int not null, platform int not null,
acquirer int ,
status int not null default 0, status int not null default 0,
pay_url text, pay_url text,
paid_at timestamp, paid_at timestamp,
@@ -832,6 +833,7 @@ comment on column trade.remark is '订单备注';
comment on column trade.amount is '订单总金额'; comment on column trade.amount is '订单总金额';
comment on column trade.payment is '支付金额'; comment on column trade.payment is '支付金额';
comment on column trade.method is '支付方式1-支付宝2-微信3-商福通渠道支付宝4-商福通渠道微信'; comment on column trade.method is '支付方式1-支付宝2-微信3-商福通渠道支付宝4-商福通渠道微信';
comment on column trade.platform is '支付平台1-电脑网站2-手机网站';
comment on column trade.acquirer is '收单机构1-支付宝2-微信3-银联'; comment on column trade.acquirer is '收单机构1-支付宝2-微信3-银联';
comment on column trade.status is '订单状态0-待支付1-已支付2-已取消3-已退款'; comment on column trade.status is '订单状态0-待支付1-已支付2-已取消3-已退款';
comment on column trade.pay_url is '支付链接'; comment on column trade.pay_url is '支付链接';

View File

@@ -49,7 +49,7 @@ type PageResp struct {
// region error // region error
type BizErr struct { type Err struct {
msg string msg string
err error err error
@@ -58,18 +58,18 @@ type BizErr struct {
errFunc string errFunc string
} }
func (e *BizErr) Error() string { func (e *Err) Error() string {
if e.err != nil { if e.err != nil {
return e.msg + "" + e.err.Error() return e.msg + "" + e.err.Error()
} }
return e.msg return e.msg
} }
func (e *BizErr) Unwrap() error { func (e *Err) Unwrap() error {
return e.err return e.err
} }
func (e *BizErr) Source() *slog.Source { func (e *Err) Source() *slog.Source {
return &slog.Source{ return &slog.Source{
Function: e.errFunc, Function: e.errFunc,
File: e.errFile, File: e.errFile,
@@ -77,25 +77,37 @@ func (e *BizErr) Source() *slog.Source {
} }
} }
func NewBizErr(msg string, err ...error) (biz *BizErr) { func newErr(msg string, err ...error) Err {
biz = &BizErr{ o := Err{
msg: msg, msg: msg,
} }
if len(err) > 0 { if len(err) > 0 {
biz.err = err[0] o.err = err[0]
} }
if env.RunMode == env.RunModeDev { if env.RunMode == env.RunModeDev {
pc, file, line, ok := runtime.Caller(1) pc, file, line, ok := runtime.Caller(2)
if ok { if ok {
biz.errFile = file o.errFile = file
biz.errLine = line o.errLine = line
biz.errFunc = runtime.FuncForPC(pc).Name() o.errFunc = runtime.FuncForPC(pc).Name()
} }
} }
return biz return o
}
type BizErr struct{ Err }
func NewBizErr(msg string, err ...error) (biz *BizErr) {
return &BizErr{newErr(msg, err...)}
}
type ServErr struct{ Err }
func NewServErr(msg string, err ...error) *ServErr {
return &ServErr{newErr(msg, err...)}
} }
// endregion // endregion

View File

@@ -17,6 +17,13 @@ const (
MethodSftWeChat // 商福通渠道指定微信 MethodSftWeChat // 商福通渠道指定微信
) )
type Platform int32
const (
PlatformDesktop Platform = iota + 1 // 桌面网站
PlatformMobile // 手机网站
)
type Acquirer int32 type Acquirer int32
const ( const (

View File

@@ -46,7 +46,7 @@ func ErrorHandler(c *fiber.Ctx, err error) error {
// 所有未手动声明的错误类型 // 所有未手动声明的错误类型
default: default:
slog.Debug("未处理的异常", slog.String("type", reflect.TypeOf(err).Name()), slog.String("error", err.Error())) slog.Warn("未处理的异常", slog.String("type", reflect.TypeOf(err).Name()), slog.String("error", err.Error()))
} }
c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)

View File

@@ -6,7 +6,7 @@ import (
var Asynq *asynq.Client var Asynq *asynq.Client
func InitAsynq() { func initAsynq() {
var client = asynq.NewClientFromRedisClient(Redis) var client = asynq.NewClientFromRedisClient(Redis)
Asynq = client Asynq = client
} }

View File

@@ -9,5 +9,6 @@ func Init() {
initRedis() initRedis()
initOrm() initOrm()
initProxy() initProxy()
InitAsynq() initAsynq()
initSft()
} }

View File

@@ -6,13 +6,15 @@ import (
"crypto/rsa" "crypto/rsa"
"crypto/sha256" "crypto/sha256"
"crypto/x509" "crypto/x509"
"encoding/base64"
"encoding/json" "encoding/json"
"encoding/pem"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"net/http/httputil"
"platform/pkg/env" "platform/pkg/env"
"platform/pkg/u" "platform/pkg/u"
"platform/web/core"
"strings" "strings"
"time" "time"
) )
@@ -21,29 +23,31 @@ var SFTPay SftClient
type SftClient struct { type SftClient struct {
appid string appid string
routeId string
privateKey *rsa.PrivateKey privateKey *rsa.PrivateKey
publicKey *rsa.PublicKey publicKey *rsa.PublicKey
} }
func init() { func initSft() {
if !env.SftPayEnable { if !env.SftPayEnable {
return panic("商福通支付未启用,请检查环境变量 SFTPAY_ENABLE")
} }
SFTPay = SftClient{ SFTPay = SftClient{
appid: env.SftPayAppId, appid: env.SftPayAppId,
routeId: env.SftPayRouteId,
} }
// 加载私钥 // 加载私钥
block, _ := pem.Decode([]byte(env.SftPayAppPrivateKey)) private, err := base64.StdEncoding.DecodeString(env.SftPayAppPrivateKey)
if block == nil || block.Type != "RSA PRIVATE KEY" { if err != nil {
panic("加载商福通私钥失败") panic("解析商福通私钥失败: " + err.Error())
} }
var privateKey *rsa.PrivateKey var privateKey *rsa.PrivateKey
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) privateKey, err = x509.ParsePKCS1PrivateKey(private)
if err != nil { if err != nil {
pkcs8, err := x509.ParsePKCS8PrivateKey(block.Bytes) pkcs8, err := x509.ParsePKCS8PrivateKey(private)
if err != nil { if err != nil {
panic("解析商福通私钥失败: " + err.Error()) panic("解析商福通私钥失败: " + err.Error())
} }
@@ -57,13 +61,13 @@ func init() {
SFTPay.privateKey = privateKey SFTPay.privateKey = privateKey
// 加载公钥 // 加载公钥
block, _ = pem.Decode([]byte(env.SftPayPublicKey)) public, err := base64.StdEncoding.DecodeString(env.SftPayPublicKey)
if block == nil || block.Type != "PUBLIC KEY" { if err != nil {
panic("加载商福通公钥失败") panic("解析商福通公钥失败: " + err.Error())
} }
var publicKey *rsa.PublicKey var publicKey *rsa.PublicKey
pkix, err := x509.ParsePKIXPublicKey(block.Bytes) pkix, err := x509.ParsePKIXPublicKey(public)
if err != nil { if err != nil {
panic("解析商福通公钥失败: " + err.Error()) panic("解析商福通公钥失败: " + err.Error())
} }
@@ -78,11 +82,13 @@ func init() {
func (s *SftClient) PaymentScanPay(req *PaymentScanPayReq) (*PaymentScanPayResp, error) { func (s *SftClient) PaymentScanPay(req *PaymentScanPayReq) (*PaymentScanPayResp, error) {
const url = "https://pay.rscygroup.com/api/open/payment/scanpay" const url = "https://pay.rscygroup.com/api/open/payment/scanpay"
req.RouteNo = u.P(s.routeId)
return call[PaymentScanPayResp](s, url, req) return call[PaymentScanPayResp](s, url, req)
} }
func (s *SftClient) PaymentH5Pay(req *PaymentH5PayReq) (*PaymentH5PayResp, error) { func (s *SftClient) PaymentH5Pay(req *PaymentH5PayReq) (*PaymentH5PayResp, error) {
const url = "https://pay.rscygroup.com/api/open/payment/h5pay" const url = "https://pay.rscygroup.com/api/open/payment/h5pay"
req.RouteNo = u.P(s.routeId)
return call[PaymentH5PayResp](s, url, req) return call[PaymentH5PayResp](s, url, req)
} }
@@ -91,6 +97,11 @@ func (s *SftClient) OrderClose(req *OrderCloseReq) (*OrderCloseResp, error) {
return call[OrderCloseResp](s, url, req) return call[OrderCloseResp](s, url, req)
} }
func (s *SftClient) QueryTrade(req *QueryTradeReq) (*QueryTradeResp, error) {
const url = "https://pay.rscygroup.com/api/open/query/trade"
return call[QueryTradeResp](s, url, req)
}
type PaymentScanPayReq struct { type PaymentScanPayReq struct {
Subject string `json:"subject"` Subject string `json:"subject"`
Body string `json:"body"` Body string `json:"body"`
@@ -132,6 +143,11 @@ type PaymentH5PayReq struct {
LimitPay *int `json:"limitPay"` LimitPay *int `json:"limitPay"`
} }
type QueryTradeReq struct {
PayOrderId *string `json:"payOrderId"`
MchOrderNo *string `json:"mchOrderNo"`
}
type OrderCloseReq struct { type OrderCloseReq struct {
MchOrderNo *string `json:"mchOrderNo"` MchOrderNo *string `json:"mchOrderNo"`
PayOrderId *string `json:"payOrderId"` PayOrderId *string `json:"payOrderId"`
@@ -197,6 +213,29 @@ type PaymentH5PayResp struct {
SettlementType *string `json:"settlementType"` SettlementType *string `json:"settlementType"`
} }
type QueryTradeResp struct {
Amount int64 `json:"amount"`
ChannelSendNo *string `json:"channelSendNo"`
IfCode *string `json:"ifCode"`
MercNo string `json:"mercNo"`
MchOrderNo string `json:"mchOrderNo"`
PayOrderId *string `json:"payOrderId"`
PayType string `json:"payType"`
ChannelTradeNo *string `json:"channelTradeNo"`
State SftTradeState `json:"state"`
RefundAmt *int64 `json:"refundAmt"`
RefundState int32 `json:"refundState"`
DrType *string `json:"drType"`
ExtParam *string `json:"extParam"`
PayTime *string `json:"payTime"`
Subject string `json:"subject"`
TradeFee *int64 `json:"tradeFee"`
CashFee *int64 `json:"cashFee"`
StoreId *string `json:"storeId"`
UserId *string `json:"userId"`
SettlementType *string `json:"settlementType"`
}
type OrderCloseResp struct { type OrderCloseResp struct {
MchOrderNo string `json:"mchOrderNo"` MchOrderNo string `json:"mchOrderNo"`
PayOrderId string `json:"payOrderId"` PayOrderId string `json:"payOrderId"`
@@ -224,19 +263,30 @@ func call[T any](s *SftClient, url string, req any) (*T, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("创建请求失败:%w", err) return nil, fmt.Errorf("创建请求失败:%w", err)
} }
request.Header.Set("Content-Type", "application/json")
reqDump, err := httputil.DumpRequest(request, true)
if err != nil {
return nil, fmt.Errorf("请求内容转储失败:%w", err)
}
println(string(reqDump) + "\n\n")
response, err := http.DefaultClient.Do(request) response, err := http.DefaultClient.Do(request)
if err != nil { if err != nil {
return nil, fmt.Errorf("请求失败:%w", err) return nil, fmt.Errorf("请求失败:%w", err)
} }
respDump, err := httputil.DumpResponse(response, true)
if err != nil {
return nil, fmt.Errorf("响应内容转储失败:%w", err)
}
println(string(respDump) + "\n\n")
if response.StatusCode != http.StatusOK { if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("请求响应失败:%d", response.StatusCode) return nil, fmt.Errorf("请求响应失败:%d", response.StatusCode)
} }
defer func(Body io.ReadCloser) { defer func(body io.ReadCloser) {
err := Body.Close() _ = body.Close()
if err != nil {
}
}(response.Body) }(response.Body)
body, err := io.ReadAll(response.Body) body, err := io.ReadAll(response.Body)
@@ -248,15 +298,9 @@ func call[T any](s *SftClient, url string, req any) (*T, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("解密响应内容失败:%w", err) return nil, fmt.Errorf("解密响应内容失败:%w", err)
} }
if decode.Code != "000000" {
return nil, fmt.Errorf("请求业务响应失败:%s", u.Z(decode.Msg))
}
if decode.BizData == nil {
return nil, nil
}
var resp = new(T) var resp = new(T)
err = json.Unmarshal([]byte(*decode.BizData), resp) err = json.Unmarshal([]byte(decode), resp)
if err != nil { if err != nil {
return nil, fmt.Errorf("响应正文解析失败:%w", err) return nil, fmt.Errorf("响应正文解析失败:%w", err)
} }
@@ -290,24 +334,34 @@ func (s *SftClient) sign(msg any) (*request, error) {
return &body, nil return &body, nil
} }
func (s *SftClient) verify(str []byte) (*response, error) { func (s *SftClient) verify(str []byte) (string, error) {
var resp = new(response) var resp = new(response)
err := json.Unmarshal(str, resp) err := json.Unmarshal(str, resp)
if err != nil { if err != nil {
return nil, fmt.Errorf("解析响应正文失败:%w", err) return "", fmt.Errorf("解析响应正文失败:%w", err)
} }
if resp.Sign != nil || resp.SignType != nil || resp.BizData != nil { if resp.Code != "000000" {
return "", fmt.Errorf("请求业务响应失败:%s", u.Z(resp.Msg))
hashed := sha256.Sum256([]byte(resp.String()))
err := rsa.VerifyPKCS1v15(s.publicKey, crypto.SHA256, hashed[:], []byte(*resp.Sign))
if err != nil {
return nil, fmt.Errorf("验签失败:%w", err)
}
} }
return resp, err if resp.Sign == nil {
return "", core.NewServErr("响应数据签名为空")
}
ser, err := resp.String()
if err != nil {
return "", fmt.Errorf("格式化响应内容失败:%w", err)
}
hashed := sha256.Sum256([]byte(ser))
err = rsa.VerifyPKCS1v15(s.publicKey, crypto.SHA256, hashed[:], []byte(*resp.Sign))
if err != nil {
return "", fmt.Errorf("验签失败:%w", err)
}
return *resp.BizData, nil
} }
type request struct { type request struct {
@@ -336,11 +390,17 @@ type response struct {
Timestamp string `json:"timestamp"` Timestamp string `json:"timestamp"`
} }
func (r response) String() string { func (r response) String() (string, error) {
if r.BizData == nil || r.Msg == nil || r.SignType == nil {
return "", core.NewServErr(fmt.Sprintf(
"上游数据返回有空值BizData %vMsg %v, SignType %v",
r.BizData == nil, r.Msg == nil, r.SignType == nil,
))
}
return fmt.Sprintf( return fmt.Sprintf(
"bizData=%s&code=%s&msg=%s&signType=%s&timestamp=%s", "bizData=%s&code=%s&msg=%s&signType=%s&timestamp=%s",
u.Z(r.BizData), r.Code, u.Z(r.Msg), u.Z(r.SignType), r.Timestamp, u.Z(r.BizData), r.Code, u.Z(r.Msg), u.Z(r.SignType), r.Timestamp,
) ), nil
} }
type SftPayType string type SftPayType string
@@ -350,3 +410,16 @@ const (
SftWeChat SftPayType = "WECHAT" SftWeChat SftPayType = "WECHAT"
SftUnionPay SftPayType = "UNIONPAY" SftUnionPay SftPayType = "UNIONPAY"
) )
type SftTradeState string
const (
SftInit SftTradeState = "INIT"
SftTradeAWAIT SftTradeState = "TRADE_WAIT"
SftTradeSuccess SftTradeState = "TRADE_SUCCESS"
SftTradeFail SftTradeState = "TRADE_FAIL"
SftTradeCancel SftTradeState = "TRADE_CANCEL"
SftTradeRefund SftTradeState = "TRADE_REFUND"
SftRefundIng SftTradeState = "REFUND_ING"
SftTradeClosed SftTradeState = "TRADE_CLOSED"
)

View File

@@ -293,7 +293,7 @@ func PrepareCreateResource(c *fiber.Ctx) error {
} }
// 准备创建套餐 // 准备创建套餐
result, err := s.Resource.PrepareResource(authCtx.Payload.Id, time.Now(), req.Method, &req.CreateResourceSerializer) result, err := s.Resource.PrepareResource(authCtx.Payload.Id, time.Now(), &req.CreateResourceSerializer)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -1,8 +1,10 @@
package handlers package handlers
import ( import (
"fmt"
"github.com/shopspring/decimal" "github.com/shopspring/decimal"
"platform/web/auth" "platform/web/auth"
"platform/web/core"
trade2 "platform/web/domains/trade" trade2 "platform/web/domains/trade"
m "platform/web/models" m "platform/web/models"
q "platform/web/queries" q "platform/web/queries"
@@ -143,7 +145,9 @@ func UpdatePassword(c *fiber.Ctx) error {
// region /recharge // region /recharge
type RechargePrepareReq struct { type RechargePrepareReq struct {
Amount string `json:"amount" validate:"required,numeric"` Amount string `json:"amount" validate:"required,numeric"`
Platform trade2.Platform `json:"platform" validate:"required"`
Method trade2.Method `json:"method" validate:"required"`
} }
type RechargePrepareResp struct { type RechargePrepareResp struct {
@@ -159,7 +163,6 @@ type RechargeConfirmResp struct {
Status string `json:"status"` Status string `json:"status"`
} }
// RechargePrepareAlipay 通过支付宝充值
func RechargePrepareAlipay(c *fiber.Ctx) error { func RechargePrepareAlipay(c *fiber.Ctx) error {
// 检查权限 // 检查权限
authContext, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{}) authContext, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{})
@@ -181,12 +184,13 @@ func RechargePrepareAlipay(c *fiber.Ctx) error {
} }
var result *s.TradeCreateResult var result *s.TradeCreateResult
err = q.Q.Transaction(func(tx *q.Query) error { err = q.Q.Transaction(func(tx *q.Query) error {
result, err = s.Trade.SendCreateTradeByQrcode(tx, authContext.Payload.Id, now, &s.TradeCreateData{ result, err = s.Trade.CreateTrade(tx, authContext.Payload.Id, now, &s.TradeCreateData{
Subject: "账户充值 - " + amount.StringFixed(2) + "元", Subject: "账户充值 - " + amount.StringFixed(2) + "元",
Amount: amount, Amount: amount,
ExpireAt: time.Now().Add(30 * time.Minute), ExpireAt: time.Now().Add(30 * time.Minute),
Type: trade2.TypeRecharge, Type: trade2.TypeRecharge,
Method: trade2.MethodAlipay, Method: trade2.MethodAlipay,
Platform: req.Platform,
}) })
return err return err
}) })
@@ -215,7 +219,7 @@ func RechargeConfirmAlipay(c *fiber.Ctx) error {
} }
// 验证支付结果 // 验证支付结果
result, err := s.Trade.VerifyCreateTrade(&s.TradeVerifyData{ result, err := s.Trade.VerifyTrade(&s.TradeVerifyData{
TradeNo: req.TradeNo, TradeNo: req.TradeNo,
Method: trade2.MethodAlipay, Method: trade2.MethodAlipay,
}) })
@@ -253,12 +257,13 @@ func RechargePrepareWechat(c *fiber.Ctx) error {
} }
var result *s.TradeCreateResult var result *s.TradeCreateResult
err = q.Q.Transaction(func(tx *q.Query) error { err = q.Q.Transaction(func(tx *q.Query) error {
result, err = s.Trade.SendCreateTradeByQrcode(tx, authContext.Payload.Id, now, &s.TradeCreateData{ result, err = s.Trade.CreateTrade(tx, authContext.Payload.Id, now, &s.TradeCreateData{
Subject: "账户充值 - " + amount.StringFixed(2) + "元", Subject: "账户充值 - " + amount.StringFixed(2) + "元",
Amount: amount, Amount: amount,
ExpireAt: now.Add(30 * time.Minute), ExpireAt: now.Add(30 * time.Minute),
Type: trade2.TypeRecharge, Type: trade2.TypeRecharge,
Method: trade2.MethodWeChat, Method: trade2.MethodWeChat,
Platform: req.Platform,
}) })
return err return err
}) })
@@ -289,7 +294,82 @@ func RechargeConfirmWechat(c *fiber.Ctx) error {
} }
// 验证支付结果 // 验证支付结果
result, err := s.Trade.VerifyCreateTrade(&s.TradeVerifyData{ result, err := s.Trade.VerifyTrade(&s.TradeVerifyData{
TradeNo: req.TradeNo,
Method: trade2.MethodWeChat,
})
if err != nil {
return err
}
// 更新数据库
err = s.User.RechargeConfirm(req.TradeNo, result)
if err != nil {
return err
}
return c.JSON(fiber.Map{"status": "success"})
}
func RechargePrepare(c *fiber.Ctx) error {
// 检查权限
authContext, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{})
if err != nil {
return err
}
// 解析请求参数
req := new(RechargePrepareReq)
if err := c.BodyParser(req); err != nil {
return err
}
// 保存交易信息
var now = time.Now()
amount, err := decimal.NewFromString(req.Amount)
if err != nil {
return core.NewBizErr(fmt.Sprintf("金额格式错误: %s", err.Error()))
}
var result *s.TradeCreateResult
err = q.Q.Transaction(func(tx *q.Query) error {
result, err = s.Trade.CreateTrade(tx, authContext.Payload.Id, now, &s.TradeCreateData{
Subject: "账户充值 - " + amount.StringFixed(2) + "元",
Amount: amount,
ExpireAt: now.Add(30 * time.Minute),
Type: trade2.TypeRecharge,
Method: req.Method,
Platform: req.Platform,
})
return err
})
if err != nil {
return err
}
// 返回结果
return c.JSON(RechargePrepareResp{
TradeNo: result.TradeNo,
PayURL: result.PayURL,
})
}
func RechargeConfirm(c *fiber.Ctx) error {
// 检查权限
_, err := auth.Protect(c, []auth.PayloadType{auth.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.Trade.VerifyTrade(&s.TradeVerifyData{
TradeNo: req.TradeNo, TradeNo: req.TradeNo,
Method: trade2.MethodWeChat, Method: trade2.MethodWeChat,
}) })

View File

@@ -24,7 +24,7 @@ type Trade struct {
Remark *string `gorm:"column:remark;type:character varying(255);comment:订单备注" json:"remark"` // 订单备注 Remark *string `gorm:"column:remark;type:character varying(255);comment:订单备注" json:"remark"` // 订单备注
Amount decimal.Decimal `gorm:"column:amount;type:numeric(12,2);not null;comment:订单总金额" json:"amount"` // 订单总金额 Amount decimal.Decimal `gorm:"column:amount;type:numeric(12,2);not null;comment:订单总金额" json:"amount"` // 订单总金额
Payment decimal.Decimal `gorm:"column:payment;type:numeric(12,2);not null;comment:支付金额" json:"payment"` // 支付金额 Payment decimal.Decimal `gorm:"column:payment;type:numeric(12,2);not null;comment:支付金额" json:"payment"` // 支付金额
Method int32 `gorm:"column:method;type:integer;not null;comment:支付方式1-支付宝2-微信3-商福通" json:"method"` // 支付方式1-支付宝2-微信3-商福通 Method int32 `gorm:"column:method;type:integer;not null;comment:支付方式1-支付宝2-微信3-商福通渠道支付宝4-商福通渠道微信" json:"method"` // 支付方式1-支付宝2-微信3-商福通渠道支付宝4-商福通渠道微信
Status int32 `gorm:"column:status;type:integer;not null;comment:订单状态0-待支付1-已支付2-已取消3-已退款" json:"status"` // 订单状态0-待支付1-已支付2-已取消3-已退款 Status int32 `gorm:"column:status;type:integer;not null;comment:订单状态0-待支付1-已支付2-已取消3-已退款" json:"status"` // 订单状态0-待支付1-已支付2-已取消3-已退款
PayURL *string `gorm:"column:pay_url;type:text;comment:支付链接" json:"pay_url"` // 支付链接 PayURL *string `gorm:"column:pay_url;type:text;comment:支付链接" json:"pay_url"` // 支付链接
PaidAt *orm.LocalDateTime `gorm:"column:paid_at;type:timestamp without time zone;comment:支付时间" json:"paid_at"` // 支付时间 PaidAt *orm.LocalDateTime `gorm:"column:paid_at;type:timestamp without time zone;comment:支付时间" json:"paid_at"` // 支付时间
@@ -33,6 +33,7 @@ type Trade struct {
UpdatedAt *orm.LocalDateTime `gorm:"column:updated_at;type:timestamp without time zone;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间 UpdatedAt *orm.LocalDateTime `gorm:"column:updated_at;type:timestamp without time zone;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp without time zone;comment:删除时间" json:"deleted_at"` // 删除时间 DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp without time zone;comment:删除时间" json:"deleted_at"` // 删除时间
Acquirer int32 `gorm:"column:acquirer;type:integer;not null;comment:收单机构1-支付宝2-微信3-银联" json:"acquirer"` // 收单机构1-支付宝2-微信3-银联 Acquirer int32 `gorm:"column:acquirer;type:integer;not null;comment:收单机构1-支付宝2-微信3-银联" json:"acquirer"` // 收单机构1-支付宝2-微信3-银联
Platform int32 `gorm:"column:platform;type:integer;not null;comment:支付平台1-电脑网站2-手机网站" json:"platform"` // 支付平台1-电脑网站2-手机网站
} }
// TableName Trade's table name // TableName Trade's table name

View File

@@ -45,6 +45,7 @@ func newTrade(db *gorm.DB, opts ...gen.DOOption) trade {
_trade.UpdatedAt = field.NewField(tableName, "updated_at") _trade.UpdatedAt = field.NewField(tableName, "updated_at")
_trade.DeletedAt = field.NewField(tableName, "deleted_at") _trade.DeletedAt = field.NewField(tableName, "deleted_at")
_trade.Acquirer = field.NewInt32(tableName, "acquirer") _trade.Acquirer = field.NewInt32(tableName, "acquirer")
_trade.Platform = field.NewInt32(tableName, "platform")
_trade.fillFieldMap() _trade.fillFieldMap()
@@ -64,7 +65,7 @@ type trade struct {
Remark field.String // 订单备注 Remark field.String // 订单备注
Amount field.Field // 订单总金额 Amount field.Field // 订单总金额
Payment field.Field // 支付金额 Payment field.Field // 支付金额
Method field.Int32 // 支付方式1-支付宝2-微信3-商福通 Method field.Int32 // 支付方式1-支付宝2-微信3-商福通渠道支付宝4-商福通渠道微信
Status field.Int32 // 订单状态0-待支付1-已支付2-已取消3-已退款 Status field.Int32 // 订单状态0-待支付1-已支付2-已取消3-已退款
PayURL field.String // 支付链接 PayURL field.String // 支付链接
PaidAt field.Field // 支付时间 PaidAt field.Field // 支付时间
@@ -73,6 +74,7 @@ type trade struct {
UpdatedAt field.Field // 更新时间 UpdatedAt field.Field // 更新时间
DeletedAt field.Field // 删除时间 DeletedAt field.Field // 删除时间
Acquirer field.Int32 // 收单机构1-支付宝2-微信3-银联 Acquirer field.Int32 // 收单机构1-支付宝2-微信3-银联
Platform field.Int32 // 支付平台1-电脑网站2-手机网站
fieldMap map[string]field.Expr fieldMap map[string]field.Expr
} }
@@ -107,6 +109,7 @@ func (t *trade) updateTableName(table string) *trade {
t.UpdatedAt = field.NewField(table, "updated_at") t.UpdatedAt = field.NewField(table, "updated_at")
t.DeletedAt = field.NewField(table, "deleted_at") t.DeletedAt = field.NewField(table, "deleted_at")
t.Acquirer = field.NewInt32(table, "acquirer") t.Acquirer = field.NewInt32(table, "acquirer")
t.Platform = field.NewInt32(table, "platform")
t.fillFieldMap() t.fillFieldMap()
@@ -123,7 +126,7 @@ func (t *trade) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
} }
func (t *trade) fillFieldMap() { func (t *trade) fillFieldMap() {
t.fieldMap = make(map[string]field.Expr, 18) t.fieldMap = make(map[string]field.Expr, 19)
t.fieldMap["id"] = t.ID t.fieldMap["id"] = t.ID
t.fieldMap["user_id"] = t.UserID t.fieldMap["user_id"] = t.UserID
t.fieldMap["inner_no"] = t.InnerNo t.fieldMap["inner_no"] = t.InnerNo
@@ -142,6 +145,7 @@ func (t *trade) fillFieldMap() {
t.fieldMap["updated_at"] = t.UpdatedAt t.fieldMap["updated_at"] = t.UpdatedAt
t.fieldMap["deleted_at"] = t.DeletedAt t.fieldMap["deleted_at"] = t.DeletedAt
t.fieldMap["acquirer"] = t.Acquirer t.fieldMap["acquirer"] = t.Acquirer
t.fieldMap["platform"] = t.Platform
} }
func (t trade) clone(db *gorm.DB) trade { func (t trade) clone(db *gorm.DB) trade {

View File

@@ -28,6 +28,8 @@ func ApplyRouters(app *fiber.App) {
user.Post("/recharge/confirm/alipay", handlers.RechargeConfirmAlipay) user.Post("/recharge/confirm/alipay", handlers.RechargeConfirmAlipay)
user.Post("/recharge/prepare/wechat", handlers.RechargePrepareWechat) user.Post("/recharge/prepare/wechat", handlers.RechargePrepareWechat)
user.Post("/recharge/confirm/wechat", handlers.RechargeConfirmWechat) user.Post("/recharge/confirm/wechat", handlers.RechargeConfirmWechat)
user.Post("/recharge/prepare", handlers.RechargePrepare)
user.Post("/recharge/confirm", handlers.RechargeConfirm)
// 白名单 // 白名单
whitelist := api.Group("/whitelist") whitelist := api.Group("/whitelist")

View File

@@ -87,7 +87,7 @@ func (s *resourceService) CreateResource(uid int32, now time.Time, ser *CreateRe
return nil return nil
} }
func (s *resourceService) PrepareResource(uid int32, now time.Time, method trade2.Method, ser *CreateResourceSerializer) (*TradeCreateResult, error) { func (s *resourceService) PrepareResource(uid int32, now time.Time, ser *CreateResourceSerializer) (*TradeCreateResult, error) {
data, err := ser.ToData() data, err := ser.ToData()
if err != nil { if err != nil {
@@ -97,18 +97,22 @@ func (s *resourceService) PrepareResource(uid int32, now time.Time, method trade
name := data.GetName() name := data.GetName()
amount := data.GetPrice() amount := data.GetPrice()
method := ser.PaymentMethod
platform := ser.PaymentPlatform
// 保存到数据库 // 保存到数据库
var result *TradeCreateResult var result *TradeCreateResult
err = q.Q.Transaction(func(q *q.Query) error { err = q.Q.Transaction(func(q *q.Query) error {
var err error var err error
// 生成交易订单 // 生成交易订单
result, err = Trade.SendCreateTradeByQrcode(q, uid, now, &TradeCreateData{ result, err = Trade.CreateTrade(q, uid, now, &TradeCreateData{
Subject: "购买套餐 - " + name, Subject: "购买套餐 - " + name,
Amount: amount, Amount: amount,
ExpireAt: time.Now().Add(30 * time.Minute), ExpireAt: time.Now().Add(30 * time.Minute),
Type: trade2.TypeRecharge, Type: trade2.TypeRecharge,
Method: method, Method: method,
Platform: platform,
}) })
if err != nil { if err != nil {
return err return err
@@ -124,7 +128,6 @@ func (s *resourceService) PrepareResource(uid int32, now time.Time, method trade
Uid: uid, Uid: uid,
TradeId: result.Trade.ID, TradeId: result.Trade.ID,
BillId: result.Bill.ID, BillId: result.Bill.ID,
Method: method,
CreateResourceSerializer: resourceSerializer, CreateResourceSerializer: resourceSerializer,
}, 30*time.Minute).Err() }, 30*time.Minute).Err()
if err != nil { if err != nil {
@@ -158,9 +161,9 @@ func (s *resourceService) CompleteResource(tradeNo string, now time.Time, opResu
rs = opResult[0] rs = opResult[0]
} else { } else {
var err error var err error
rs, err = Trade.VerifyCreateTrade(&TradeVerifyData{ rs, err = Trade.VerifyTrade(&TradeVerifyData{
TradeNo: tradeNo, TradeNo: tradeNo,
Method: cache.Method, Method: cache.PaymentMethod,
}) })
if err != nil { if err != nil {
return err return err
@@ -229,7 +232,7 @@ func (s *resourceService) CancelResource(tradeNo string, now time.Time, opRevoke
// 取消交易 // 取消交易
if len(opRevoked) <= 0 { if len(opRevoked) <= 0 {
err = Trade.CancelTrade(tradeNo, cache.Method) err = Trade.CancelTrade(tradeNo, cache.PaymentMethod)
if err != nil { if err != nil {
return err return err
} }
@@ -403,9 +406,11 @@ func (data *CreateLongResourceData) GetPrice() decimal.Decimal {
} }
type CreateResourceSerializer struct { type CreateResourceSerializer struct {
Type resource2.Type `json:"type" validate:"required"` Type resource2.Type `json:"type" validate:"required"`
Short *CreateShortResourceData `json:"short,omitempty"` Short *CreateShortResourceData `json:"short,omitempty"`
Long *CreateLongResourceData `json:"long,omitempty"` Long *CreateLongResourceData `json:"long,omitempty"`
PaymentMethod trade2.Method `json:"payment_method" validate:"required"`
PaymentPlatform trade2.Platform `json:"payment_platform" validate:"required"`
} }
func (s *CreateResourceSerializer) ToData() (CreateResourceData, error) { func (s *CreateResourceSerializer) ToData() (CreateResourceData, error) {
@@ -433,10 +438,9 @@ func (s *CreateResourceSerializer) ByData(data CreateResourceData) error {
} }
type CreateResourceCache struct { type CreateResourceCache struct {
Uid int32 `json:"uid"` Uid int32 `json:"uid"`
TradeId int32 `json:"trade_id"` TradeId int32 `json:"trade_id"`
BillId int32 `json:"bill_id"` BillId int32 `json:"bill_id"`
Method trade2.Method `json:"method"`
*CreateResourceSerializer *CreateResourceSerializer
} }

View File

@@ -9,6 +9,7 @@ import (
"net/http" "net/http"
"platform/pkg/env" "platform/pkg/env"
"platform/pkg/u" "platform/pkg/u"
"platform/web/core"
bill2 "platform/web/domains/bill" bill2 "platform/web/domains/bill"
coupon2 "platform/web/domains/coupon" coupon2 "platform/web/domains/coupon"
trade2 "platform/web/domains/trade" trade2 "platform/web/domains/trade"
@@ -29,11 +30,12 @@ var Trade = &tradeService{}
type tradeService struct { type tradeService struct {
} }
func (s *tradeService) SendCreateTradeByQrcode(q *q.Query, uid int32, now time.Time, data *TradeCreateData) (*TradeCreateResult, error) { func (s *tradeService) CreateTrade(q *q.Query, uid int32, now time.Time, data *TradeCreateData) (*TradeCreateResult, error) {
var subject = data.Subject var subject = data.Subject
var expire = data.ExpireAt var expire = data.ExpireAt
var tType = data.Type var tType = data.Type
var method = data.Method var method = data.Method
var platform = data.Platform
var amount = data.Amount var amount = data.Amount
// 实际支付金额,只在创建真实订单时使用 // 实际支付金额,只在创建真实订单时使用
@@ -103,11 +105,10 @@ func (s *tradeService) SendCreateTradeByQrcode(q *q.Query, uid int32, now time.T
// 创建支付订单 // 创建支付订单
var payUrl string var payUrl string
var acquirer trade2.Acquirer switch {
switch method {
// 调用支付宝支付接口 // 支付宝 + 电脑网站
case trade2.MethodAlipay: case method == trade2.MethodAlipay && platform == trade2.PlatformDesktop:
resp, err := g.Alipay.TradePagePay(alipay.TradePagePay{ resp, err := g.Alipay.TradePagePay(alipay.TradePagePay{
QRPayMode: "4", QRPayMode: "4",
QRCodeWidth: "196", // 二维码宽度需要-4支付宝页面布局有问题 QRCodeWidth: "196", // 二维码宽度需要-4支付宝页面布局有问题
@@ -123,10 +124,9 @@ func (s *tradeService) SendCreateTradeByQrcode(q *q.Query, uid int32, now time.T
return nil, err return nil, err
} }
payUrl = resp.String() payUrl = resp.String()
acquirer = trade2.AcquirerAlipay
// 调用微信支付接口 // 微信 + 电脑网站
case trade2.MethodWeChat: case method == trade2.MethodWeChat && platform == trade2.PlatformDesktop:
resp, _, err := g.WechatPay.Native.Prepay(context.Background(), native.PrepayRequest{ resp, _, err := g.WechatPay.Native.Prepay(context.Background(), native.PrepayRequest{
Appid: &env.WechatPayAppId, Appid: &env.WechatPayAppId,
Mchid: &env.WechatPayMchId, Mchid: &env.WechatPayMchId,
@@ -142,10 +142,9 @@ func (s *tradeService) SendCreateTradeByQrcode(q *q.Query, uid int32, now time.T
return nil, err return nil, err
} }
payUrl = *resp.CodeUrl payUrl = *resp.CodeUrl
acquirer = trade2.AcquirerWeChat
// 调用商福通接口 // 商福通 + 电脑网站
case trade2.MethodSft: case method == trade2.MethodSft && platform == trade2.PlatformDesktop:
resp, err := g.SFTPay.PaymentScanPay(&g.PaymentScanPayReq{ resp, err := g.SFTPay.PaymentScanPay(&g.PaymentScanPayReq{
MchOrderNo: tradeNo, MchOrderNo: tradeNo,
Subject: subject, Subject: subject,
@@ -159,155 +158,9 @@ func (s *tradeService) SendCreateTradeByQrcode(q *q.Query, uid int32, now time.T
return nil, err return nil, err
} }
payUrl = u.Z(u.Z(resp.PayInfo).QrCodeUrl) payUrl = u.Z(u.Z(resp.PayInfo).QrCodeUrl)
if payUrl == "" {
return nil, errors.New("支付接口未返回正确的二维码地址")
}
switch resp.PayType {
case g.SftAlipay:
acquirer = trade2.AcquirerAlipay
case g.SftWeChat:
acquirer = trade2.AcquirerWeChat
case g.SftUnionPay:
acquirer = trade2.AcquirerUnionPay
}
// 不支持的支付方式 // 商福通 + 手机网站
default: case (method == trade2.MethodSftAlipay || method == trade2.MethodSftWeChat) && platform == trade2.PlatformMobile:
return nil, ErrTransactionNotSupported
}
// 保存交易订单
var billType bill2.Type
switch tType {
case trade2.TypeRecharge:
billType = bill2.TypeRecharge
case trade2.TypePurchase:
billType = bill2.TypeConsume
}
var trade = m.Trade{
UserID: uid,
InnerNo: tradeNo,
Subject: subject,
Method: int32(method),
Type: int32(tType),
Amount: amount,
PayURL: &payUrl,
Acquirer: int32(acquirer),
}
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(billType),
Amount: amount,
}
err = q.Bill.
Omit(q.Bill.ResourceID, q.Bill.RefundID).
Create(&bill)
if err != nil {
return nil, err
}
// 提交异步任务更新订单状态
_, err = g.Asynq.Enqueue(tasks.NewUpdateTrade(tradeNo, method))
if err != nil {
return nil, err
}
return &TradeCreateResult{
TradeNo: tradeNo,
PayURL: payUrl,
Bill: &bill,
Trade: &trade,
}, nil
}
func (s *tradeService) SendCreateTradeByRedirect(q *q.Query, uid int32, now time.Time, data *TradeCreateData) (*TradeCreateResult, error) {
var subject = data.Subject
var expire = data.ExpireAt
var tType = data.Type
var method = data.Method
var amount = data.Amount
// 实际支付金额,只在创建真实订单时使用
var amountReal = data.Amount
if env.RunMode == "debug" {
amountReal = decimal.NewFromFloat(0.01)
}
// 附加优惠券
if data.CouponCode != "" {
coupon, err := q.Coupon.
Where(
q.Coupon.Code.Eq(data.CouponCode),
q.Coupon.Status.Eq(int32(coupon2.StatusUnused)),
).
Take()
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("优惠券不存在或已失效")
}
return nil, err
}
var expireAt = time.Time(u.Z(coupon.ExpireAt))
if !expireAt.IsZero() && expireAt.Before(now) {
_, err = q.Coupon.
Where(q.Coupon.ID.Eq(coupon.ID)).
Update(q.Coupon.Status, coupon2.StatusExpired)
if err != nil {
return nil, err
}
return nil, errors.New("优惠券已过期")
}
if amount.Cmp(coupon.MinAmount) < 0 {
return nil, errors.New("订单金额未达到使用优惠券的条件")
}
if coupon.UserID != nil {
switch *coupon.UserID {
// 指定用户的优惠券
case uid:
amount = amount.Sub(coupon.Amount)
if expireAt.IsZero() {
_, err = q.Coupon.
Where(q.Coupon.ID.Eq(coupon.ID)).
Update(q.Coupon.Status, int32(coupon2.StatusUsed))
if err != nil {
return nil, err
}
}
// 该优惠券不属于当前用户
default:
return nil, errors.New("优惠券不属于当前用户")
}
} else {
// 公开优惠券
amount = amount.Sub(coupon.Amount)
}
}
// 生成订单号
tradeNo, err := ID.GenSerial(context.Background())
if err != nil {
return nil, err
}
// 创建支付订单
var payUrl string
var acquirer trade2.Acquirer
switch method {
// 调用商福通接口
case trade2.MethodSftAlipay, trade2.MethodSftWeChat:
var payType g.SftPayType var payType g.SftPayType
if method == trade2.MethodSftAlipay { if method == trade2.MethodSftAlipay {
payType = g.SftAlipay payType = g.SftAlipay
@@ -328,24 +181,31 @@ func (s *tradeService) SendCreateTradeByRedirect(q *q.Query, uid int32, now time
return nil, err return nil, err
} }
payUrl = u.Z(u.Z(resp.PayInfo).PayUrl) payUrl = u.Z(u.Z(resp.PayInfo).PayUrl)
if payUrl == "" {
return nil, errors.New("支付接口未返回正确的二维码地址")
}
switch resp.PayType {
case g.SftAlipay:
acquirer = trade2.AcquirerAlipay
case g.SftWeChat:
acquirer = trade2.AcquirerWeChat
case g.SftUnionPay:
acquirer = trade2.AcquirerUnionPay
}
// 不支持的支付方式 // 不支持的支付方式
default: default:
slog.Warn(ErrTransactionNotSupported.Error(), "method", method, "platform", platform)
return nil, ErrTransactionNotSupported return nil, ErrTransactionNotSupported
} }
// 保存交易订单 // 保存交易订单
var trade = m.Trade{
UserID: uid,
InnerNo: tradeNo,
Subject: subject,
Type: int32(tType),
Method: int32(method),
Platform: int32(platform),
Amount: amount,
PayURL: &payUrl,
}
err = q.Trade.Create(&trade)
if err != nil {
return nil, err
}
// 保存用户帐单
var billType bill2.Type var billType bill2.Type
switch tType { switch tType {
case trade2.TypeRecharge: case trade2.TypeRecharge:
@@ -354,22 +214,6 @@ func (s *tradeService) SendCreateTradeByRedirect(q *q.Query, uid int32, now time
billType = bill2.TypeConsume billType = bill2.TypeConsume
} }
var trade = m.Trade{
UserID: uid,
InnerNo: tradeNo,
Subject: subject,
Method: int32(method),
Type: int32(tType),
Amount: amount,
PayURL: &payUrl,
Acquirer: int32(acquirer),
}
err = q.Trade.Create(&trade)
if err != nil {
return nil, err
}
// 保存用户帐单
var bill = m.Bill{ var bill = m.Bill{
BillNo: ID.GenReadable("bil"), BillNo: ID.GenReadable("bil"),
UserID: uid, UserID: uid,
@@ -378,6 +222,7 @@ func (s *tradeService) SendCreateTradeByRedirect(q *q.Query, uid int32, now time
Type: int32(billType), Type: int32(billType),
Amount: amount, Amount: amount,
} }
err = q.Bill. err = q.Bill.
Omit(q.Bill.ResourceID, q.Bill.RefundID). Omit(q.Bill.ResourceID, q.Bill.RefundID).
Create(&bill) Create(&bill)
@@ -398,78 +243,12 @@ func (s *tradeService) SendCreateTradeByRedirect(q *q.Query, uid int32, now time
Trade: &trade, Trade: &trade,
}, nil }, nil
} }
func (s *tradeService) VerifyCreateTrade(data *TradeVerifyData) (*TradeSuccessResult, error) {
var tradeNo = data.TradeNo
var method = data.Method
// 检查交易号是否存在
var transId string
var paidAt time.Time
var payment decimal.Decimal
switch method {
// 检查支付宝交易
case trade2.MethodAlipay:
resp, err := g.Alipay.TradeQuery(context.Background(), 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, ErrTransactionNotPaid
}
transId = resp.TradeNo
payment, err = decimal.NewFromString(resp.TotalAmount)
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 trade2.MethodWeChat:
resp, _, err := g.WechatPay.Native.QueryOrderByOutTradeNo(context.Background(), native.QueryOrderByOutTradeNoRequest{
OutTradeNo: &tradeNo,
Mchid: &env.WechatPayMchId,
})
if err != nil {
return nil, err
}
if *resp.TradeState != "SUCCESS" {
return nil, ErrTransactionNotPaid
}
transId = *resp.TransactionId
payment = decimal.NewFromInt(*resp.Amount.PayerTotal).Div(decimal.NewFromInt(100))
paidAt, err = time.Parse(time.RFC3339, *resp.SuccessTime)
if err != nil {
return nil, err
}
// 不支持的支付方式
default:
return nil, ErrTransactionNotSupported
}
return &TradeSuccessResult{
TransId: transId,
Payment: payment,
Time: paidAt,
}, nil
}
func (s *tradeService) OnTradeCreated(q *q.Query, data *OnTradeCreateData) (*m.Trade, error) { func (s *tradeService) OnTradeCreated(q *q.Query, data *OnTradeCreateData) (*m.Trade, error) {
var transId = data.TransId var transId = data.TransId
var tradeNo = data.TradeNo var tradeNo = data.TradeNo
var payment = data.Payment var payment = data.Payment
var paidAt = data.Time var paidAt = data.Time
var acquirer = data.Acquirer
// 获取交易信息 // 获取交易信息
trade, err := q.Trade. trade, err := q.Trade.
@@ -491,6 +270,7 @@ func (s *tradeService) OnTradeCreated(q *q.Query, data *OnTradeCreateData) (*m.T
trade.Status = int32(trade2.StatusSuccess) trade.Status = int32(trade2.StatusSuccess)
trade.OuterNo = &transId trade.OuterNo = &transId
trade.Payment = payment trade.Payment = payment
trade.Acquirer = int32(acquirer)
trade.PaidAt = u.P(orm.LocalDateTime(paidAt)) trade.PaidAt = u.P(orm.LocalDateTime(paidAt))
trade.PayURL = u.P("") trade.PayURL = u.P("")
_, err = q.Trade.Updates(trade) _, err = q.Trade.Updates(trade)
@@ -574,12 +354,106 @@ func (s *tradeService) OnTradeRefunded(q *q.Query, tradeNo string, now time.Time
panic("todo") panic("todo")
} }
func (s *tradeService) VerifyTrade(data *TradeVerifyData) (*TradeSuccessResult, error) {
var tradeNo = data.TradeNo
var method = data.Method
// 检查交易号是否存在
var transId string
var paidAt time.Time
var payment decimal.Decimal
switch method {
// 检查支付宝交易
case trade2.MethodAlipay:
resp, err := g.Alipay.TradeQuery(context.Background(), 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, ErrTransactionNotPaid
}
transId = resp.TradeNo
payment, err = decimal.NewFromString(resp.TotalAmount)
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 trade2.MethodWeChat:
resp, _, err := g.WechatPay.Native.QueryOrderByOutTradeNo(context.Background(), native.QueryOrderByOutTradeNoRequest{
OutTradeNo: &tradeNo,
Mchid: &env.WechatPayMchId,
})
if err != nil {
return nil, err
}
if *resp.TradeState != "SUCCESS" {
return nil, ErrTransactionNotPaid
}
transId = *resp.TransactionId
payment = decimal.NewFromInt(*resp.Amount.PayerTotal).Div(decimal.NewFromInt(100))
paidAt, err = time.Parse(time.RFC3339, *resp.SuccessTime)
if err != nil {
return nil, err
}
// 检查商福通交易
case trade2.MethodSft, trade2.MethodSftAlipay, trade2.MethodSftWeChat:
resp, err := g.SFTPay.QueryTrade(&g.QueryTradeReq{
MchOrderNo: &tradeNo,
})
if err != nil {
return nil, err
}
if resp.State != g.SftTradeSuccess {
return nil, ErrTransactionNotPaid
}
if resp.PayOrderId == nil {
return nil, errors.New("商福通交易号不存在")
}
if resp.PayTime == nil {
return nil, errors.New("商福通交易时间不存在")
}
transId = *resp.PayOrderId
payment = decimal.NewFromInt(resp.Amount).Div(decimal.NewFromInt(100))
paidAt, err = time.Parse("2006-01-02 15:04:05", *resp.PayTime)
if err != nil {
return nil, err
}
// 不支持的支付方式
default:
return nil, ErrTransactionNotSupported
}
return &TradeSuccessResult{
TransId: transId,
Payment: payment,
Time: paidAt,
}, nil
}
type TradeCreateData struct { type TradeCreateData struct {
Subject string Subject string
Amount decimal.Decimal Amount decimal.Decimal
ExpireAt time.Time ExpireAt time.Time
Type trade2.Type Type trade2.Type
Method trade2.Method Method trade2.Method
Platform trade2.Platform
CouponCode string CouponCode string
} }
@@ -596,9 +470,10 @@ type TradeVerifyData struct {
} }
type TradeSuccessResult struct { type TradeSuccessResult struct {
TransId string TransId string
Payment decimal.Decimal Payment decimal.Decimal
Time time.Time Time time.Time
Acquirer trade2.Acquirer
} }
type OnTradeCreateData struct { type OnTradeCreateData struct {
@@ -606,13 +481,6 @@ type OnTradeCreateData struct {
TradeSuccessResult TradeSuccessResult
} }
type TradePlatform int
const (
TradePlatformDesktop TradePlatform = iota + 1 // 桌面端
TradePlatformMobile // 移动端
)
type TradeErr string type TradeErr string
func (e TradeErr) Error() string { func (e TradeErr) Error() string {
@@ -620,6 +488,6 @@ func (e TradeErr) Error() string {
} }
var ( var (
ErrTransactionNotPaid = TradeErr("交易未支付") ErrTransactionNotPaid = core.NewBizErr("交易未支付")
ErrTransactionNotSupported = TradeErr("不支持的支付方式") ErrTransactionNotSupported = core.NewBizErr("不支持的支付方式")
) )

View File

@@ -126,50 +126,52 @@ func newLogger() fiber.Handler {
return false return false
}, },
Done: func(c *fiber.Ctx, logBytes []byte) { Done: func(c *fiber.Ctx, logBytes []byte) {
var logStr = strings.TrimPrefix(string(logBytes), "🚀") go func(ip, ua, method, path string, status int, logBytes []byte) {
var logVars = strings.Split(logStr, "|") var logStr = strings.TrimPrefix(string(logBytes), "🚀")
var logVars = strings.Split(logStr, "|")
var reqTimeStr = strings.TrimSpace(logVars[0]) var reqTimeStr = strings.TrimSpace(logVars[0])
reqTime, err := time.ParseInLocation("2006-01-02 15:04:05", reqTimeStr, time.Local) reqTime, err := time.ParseInLocation("2006-01-02 15:04:05", reqTimeStr, time.Local)
if err != nil { if err != nil {
slog.Error("时间解析错误", slog.Any("err", err)) slog.Error("时间解析错误", slog.Any("err", err))
return return
} }
var authInfo = strings.Split(strings.TrimSpace(logVars[1]), " ") var authInfo = strings.Split(strings.TrimSpace(logVars[1]), " ")
var authType = auth.PayloadTypeFromStr(strings.TrimSpace(authInfo[0])) var authType = auth.PayloadTypeFromStr(strings.TrimSpace(authInfo[0]))
authID, err := strconv.Atoi(strings.TrimSpace(authInfo[1])) authID, err := strconv.Atoi(strings.TrimSpace(authInfo[1]))
if err != nil { if err != nil {
slog.Error("负载ID解析错误", slog.Any("err", err)) slog.Error("负载ID解析错误", slog.Any("err", err))
return return
} }
var latency = strings.TrimSpace(logVars[4]) var latency = strings.TrimSpace(logVars[4])
var errStr = strings.TrimSpace(logVars[5]) var errStr = strings.TrimSpace(logVars[5])
var item = &m.LogsRequest{ var item = &m.LogsRequest{
IP: c.IP(), IP: ip,
Ua: u.P(c.Get("User-Agent")), Ua: u.P(ua),
Method: c.Method(), Method: method,
Path: c.Path(), Path: path,
Latency: &latency, Latency: &latency,
Status: int32(c.Response().StatusCode()), Status: int32(status),
Error: &errStr, Error: &errStr,
Time: u.P(orm.LocalDateTime(reqTime)), Time: u.P(orm.LocalDateTime(reqTime)),
} }
if authType != auth.PayloadNone { if authType != auth.PayloadNone {
item.Identity = u.P(int32(authType)) item.Identity = u.P(int32(authType))
} }
if authID != 0 { if authID != 0 {
item.Visitor = u.P(int32(authID)) item.Visitor = u.P(int32(authID))
} }
err = q.LogsRequest.Create(item) err = q.LogsRequest.Create(item)
if err != nil { if err != nil {
slog.Error("日志记录错误", slog.Any("err", err)) slog.Error("日志记录错误", slog.Any("err", err))
return return
} }
}(c.IP(), c.Get("User-Agent"), c.Method(), c.Path(), c.Response().StatusCode(), logBytes)
}, },
}) })
} }