Compare commits

...

127 Commits

Author SHA1 Message Date
陈俊宏 f24a725cef Merge branch 'dev/dev1.0' into feature_413_cjh 2024-08-09 17:59:41 +08:00
陈俊宏 bdfd4c6b9c 支付服务 2024-08-09 17:59:19 +08:00
wolter 595726bc60 Merge remote-tracking branch 'origin/dev/dev1.0' into dev/dev1.0 2024-08-09 17:51:58 +08:00
wolter 413f312ef6 实际支付金额fix 2024-08-09 17:51:44 +08:00
Rzy c13634ed08 <feat>退款 2024-08-09 17:49:41 +08:00
Rzy a54212d38c 解决冲突 2024-08-09 17:26:50 +08:00
Rzy bb9ff190c3 <feat>退款 2024-08-09 17:24:03 +08:00
wolter 78380d3a70 前台,订单查询fix 2024-08-09 17:14:30 +08:00
wolter 9a413afa51 后台,订单日志列表增加查询条件 2024-08-09 16:40:21 +08:00
Rzy 7ed9cb048d Merge remote-tracking branch 'origin/dev/dev1.0' into dev/dev1.0 2024-08-09 16:12:31 +08:00
Rzy d76344573d <feat>rsa加密长度限制改善 2024-08-09 16:12:13 +08:00
wolter b0be00741f 后台,订单日志列表 2024-08-09 16:05:34 +08:00
wolter 966aba1dc4 调整api接口返回加密 2024-08-09 15:23:08 +08:00
陈俊宏 a6e8412654 Merge branch 'feature_413_cjh' into dev/dev1.0 2024-08-09 11:42:14 +08:00
陈俊宏 583aee88e2 支付服务 2024-08-09 11:41:08 +08:00
Rzy 92733acffa <feat>订单重复创建问题 2024-08-09 11:20:33 +08:00
Rzy 1630304f3d <feat>解除pay和paycheck之间的强关联性 2024-08-09 10:43:00 +08:00
Rzy 50ef2fd13a <feat>区分是否需要记录日志 2024-08-09 10:08:30 +08:00
wolter b3d8acba93 前台,调整回调通知地址 2024-08-08 18:32:56 +08:00
wolter 8c05e8ff06 后台,应用通知地址 2024-08-08 18:16:53 +08:00
wolter 5e0627a0f3 增加下游回调日志记录 2024-08-08 17:55:26 +08:00
陈俊宏 f6bdfdf84c Merge remote-tracking branch 'origin/dev/dev1.0' into dev/dev1.0 2024-08-08 17:25:58 +08:00
陈俊宏 6357d6706d 记录third表的日志信息 2024-08-08 17:25:42 +08:00
Rzy 9b7f904108 回调判断 2024-08-08 17:00:22 +08:00
Rzy 0755b14aa2 <feat>去掉删除判断 2024-08-08 16:37:23 +08:00
Rzy 1f7d46c110 <feat>去掉删除判断 2024-08-08 16:20:36 +08:00
Rzy 7f20bf6925 <feat>增加解密失败报错 2024-08-08 15:46:54 +08:00
wolter 39e64c8a22 后台,rsa生成密钥调整,关闭订单fix 2024-08-08 15:44:02 +08:00
陈俊宏 e5f50423ad Merge branch 'dev/dev1.0' into feature_413_cjh 2024-08-08 15:06:28 +08:00
陈俊宏 222522dc76 记录third表 2024-08-08 14:59:15 +08:00
Rzy 9c725163c8 Merge branch 'feature/rzy/api_1.0' into dev/dev1.0 2024-08-08 14:53:39 +08:00
Rzy ee912a204d <feat>退款 2024-08-08 14:52:30 +08:00
wolter 627e1c852c 后台,密钥对和api接口对应 2024-08-08 14:19:07 +08:00
wolter 342a247c67 后台,查询支付和退款回调,订单查询fix 2024-08-08 11:34:09 +08:00
陈俊宏 c9c7bdbc16 支付宝回调签名 2024-08-08 11:24:56 +08:00
Rzy aa43040aa2 <feat>调整代码结构 2024-08-08 11:15:50 +08:00
Rzy cb14c35aa9 <feat>调整代码结构 2024-08-08 10:48:25 +08:00
Rzy ebea5c52e0 <fix>处理bug 2024-08-08 09:52:05 +08:00
Rzy 40dfffe31a <fix>bug修改 2024-08-08 09:49:27 +08:00
陈俊宏 58f8cedb85 Merge branch 'dev/dev1.0' into feature_413_cjh 2024-08-08 09:43:02 +08:00
陈俊宏 d6bf4dd952 回调log记录 2024-08-08 09:42:46 +08:00
Rzy 7ed1ca131b Merge branch 'feature/rzy/api_1.0' into dev/dev1.0 2024-08-08 09:30:39 +08:00
Rzy f225fc0f8c <feat>完善支付 2024-08-08 09:30:02 +08:00
陈俊宏 30b83b3645 Merge branch 'feature_413_cjh' into dev/dev1.0 2024-08-08 09:20:49 +08:00
陈俊宏 62d050c02f 回调log 2024-08-08 09:20:25 +08:00
wolter 3061987dae Merge branch 'refs/heads/feature/zxp' into dev/dev1.0 2024-08-08 09:18:49 +08:00
wolter 3d5e33fed0 Merge remote-tracking branch 'refs/remotes/origin/feature/rzy/api_1.0' into dev/dev1.0
# Conflicts:
#	app/constants/errorcode/error_code.go
#	app/services/thirdpay/notify/notify.go
2024-08-08 09:14:08 +08:00
Rzy f85d6cc410 <feat>订单回调 2024-08-07 18:49:43 +08:00
wolter bf1bc4b1a2 后台,密钥对生成 2024-08-07 17:14:03 +08:00
Rzy d169ca7366 Merge branch 'dev/dev1.0' into feature/rzy/api_1.0 2024-08-07 11:51:34 +08:00
Rzy 0834171ea3 <feat>订单回调 2024-08-07 11:12:06 +08:00
wolter 514104a391 定时查询退款单 2024-08-07 10:49:06 +08:00
陈俊宏 ad56b5f0e5 Merge branch 'dev/dev1.0' into feature_413_cjh 2024-08-07 10:32:37 +08:00
陈俊宏 82ef285e7d 订单查询 2024-08-07 10:32:15 +08:00
陈俊宏 ca42c43bd2 回调 触发下游回调 2024-08-07 10:22:40 +08:00
陈俊宏 6ab982e874 Merge remote-tracking branch 'origin/feature/rzy/api_1.0' into feature_413_cjh 2024-08-07 10:16:13 +08:00
wolter c79e316233 Merge remote-tracking branch 'refs/remotes/origin/feature/rzy/api_1.0' into feature/zxp 2024-08-07 10:03:42 +08:00
Rzy c8a188e362 回调修改 2024-08-07 10:01:38 +08:00
陈俊宏 75d68a3032 Merge remote-tracking branch 'origin/feature/rzy/api_1.0' into feature_413_cjh 2024-08-07 09:00:49 +08:00
wolter e261a39cf5 Merge remote-tracking branch 'refs/remotes/origin/feature/rzy/api_1.0' into feature/zxp 2024-08-07 08:48:28 +08:00
Rzy 7a887833ad 回调 2024-08-06 18:46:05 +08:00
陈俊宏 68b9e16864 支付回调处理 2024-08-06 18:00:49 +08:00
wolter b19576089c 订单查询 2024-08-06 17:48:17 +08:00
Rzy 4642ae73fe Merge branch 'dev/dev1.0' into feature/rzy/api_1.0
# Conflicts:
#	app/http/entities/backend/order.go
2024-08-06 17:13:26 +08:00
Rzy 6cfc7c0c06 <feat>解决冲突 2024-08-06 17:12:18 +08:00
Rzy fa6b385209 Merge branch 'dev/dev1.0' into feature/rzy/api_1.0
# Conflicts:
#	app/constants/common/common.go
#	app/constants/errorcode/error_code.go
#	app/http/entities/backend/order.go
2024-08-06 17:02:25 +08:00
陈俊宏 2d4d674145 Merge branch 'dev/dev1.0' into feature_413_cjh 2024-08-06 16:56:26 +08:00
陈俊宏 d979b7a966 支付宝证书 2024-08-06 16:56:00 +08:00
陈俊宏 38f3e04352 支付宝证书 2024-08-06 16:55:18 +08:00
Rzy 943c3401c6 <feat>支付接口 2024-08-06 16:53:59 +08:00
陈俊宏 0d38872fa6 Merge branch 'feature_413_cjh' into dev/dev1.0 2024-08-06 15:42:22 +08:00
陈俊宏 2927161585 记录操作日志 2024-08-06 15:41:57 +08:00
陈俊宏 e9d3dbece0 Merge remote-tracking branch 'origin/dev/dev1.0' into dev/dev1.0 2024-08-06 15:12:23 +08:00
wolter 3ed083bff6 订单增加字段:用户支付金额 2024-08-06 15:12:05 +08:00
陈俊宏 7ee666e459 order_third_pay_log 更新数据表结构 2024-08-06 15:11:51 +08:00
陈俊宏 d488dddefd 微信发起支付 将订单号转化为字符串 2024-08-06 14:32:50 +08:00
陈俊宏 daf5e2134f 测试阿里 2024-08-06 14:07:56 +08:00
陈俊宏 6adcce1480 回调接口 2024-08-06 11:19:13 +08:00
陈俊宏 74d1472a43 支付配置 2024-08-06 11:09:39 +08:00
陈俊宏 e8576d67d9 支付配置 2024-08-06 10:35:30 +08:00
陈俊宏 9f052435cc 支付配置 2024-08-06 10:22:03 +08:00
Rzy 548491c1f1 <feat>支付部分 2024-08-05 18:45:01 +08:00
wolter 50382f2c0a 后台,定时任务查询订单状态和关闭订单 2024-08-05 17:51:57 +08:00
Rzy 71ccc46f23 Merge remote-tracking branch 'origin/feature_413_cjh' into feature/rzy/api_1.0 2024-08-05 17:07:35 +08:00
Rzy 79723bd945 支付准备 2024-08-05 17:07:11 +08:00
wolter 0ac4feb07a 后台,订单字段调整fix 2024-08-05 17:01:58 +08:00
陈俊宏 a7cf191437 Merge branch 'dev/dev1.0' into feature_413_cjh 2024-08-05 16:42:44 +08:00
陈俊宏 0f747c2578 支付回调 2024-08-05 16:42:25 +08:00
Rzy 7dc1f9ef08 Merge branch 'dev/dev1.0' into feature/rzy/api_1.0 2024-08-05 16:23:48 +08:00
Rzy 50924acbc7 Merge branch 'dev/dev1.0' of https://gitea.cdlsxd.cn/BaseSystem/PaymentCenter into dev/dev1.0
# Conflicts:
#	app/models/appmodel/app.go
2024-08-05 16:22:09 +08:00
Rzy 3e7df62375 <feat>网页支付 2024-08-05 16:20:01 +08:00
wolter 65621d439b 后台,更新请求日志和回调日志data 2024-08-05 15:42:20 +08:00
陈俊宏 39fb6d4d93 支付回调 2024-08-05 15:30:02 +08:00
wolter 5326e9c8d9 更新models 2024-08-05 14:47:20 +08:00
wolter 490e2fa15f 后台,添加定时任务 2024-08-05 14:40:57 +08:00
Rzy b1a1a4cd50 <feat>api表单验证 2024-08-05 14:05:46 +08:00
Rzy 8ead5e11e2 加密解密 2024-08-05 10:59:02 +08:00
Rzy 4d28483087 Merge branch 'dev/dev1.0' into feature/rzy/api_1.0
# Conflicts:
#	app/constants/errorcode/error_code.go
2024-08-05 10:56:30 +08:00
Rzy 13dce2d1c3 加密解密 2024-08-05 10:55:35 +08:00
Rzy aa1481b923 合并代码 2024-08-02 18:38:51 +08:00
Rzy b781a61f21 Merge branch 'feature/rzy/api_1.0' into dev/dev1.0
# Conflicts:
#	app/constants/common/common.go
#	app/constants/errorcode/error_code.go
#	app/data/app.go
#	app/http/controllers/base.go
#	app/http/requestmapping/front.go
#	app/http/routes/route.go
#	app/services/app.go
#	app/utils/util.go
#	go.mod
#	go.sum
2024-08-02 18:34:37 +08:00
Rzy 6e6e01a823 中间件 2024-08-02 18:25:28 +08:00
陈俊宏 fb5c0e458b 回调查询 2024-08-02 18:14:04 +08:00
陈俊宏 b8e32f407a Merge branch 'dev/dev1.0' into feature_413_cjh
# Conflicts:
#	app/constants/common/common.go
2024-08-02 17:34:07 +08:00
wolter f1bb1ad53b fix 2024-08-02 17:32:49 +08:00
wolter b8da95788b 更新支付方式请求参数 2024-08-02 17:23:38 +08:00
wolter 88b95cb67a 后台 2024-08-02 17:21:18 +08:00
陈俊宏 4842aa47a3 必要位置添加日志 2024-08-02 16:53:48 +08:00
陈俊宏 a79b18d279 支付退款+退款单查询+订单关闭 2024-08-02 16:37:37 +08:00
陈俊宏 f1ee9b4196 支付退款+退款单查询+订单关闭 2024-08-02 16:37:07 +08:00
wolter bf4e24619d 修改后台路由,添加auth校验 2024-08-02 14:32:44 +08:00
wolter 92c1a3c230 添加dockerfile 2024-08-02 14:02:13 +08:00
wolter f8f0ac2913 应用和支付方式修改不能修改商户id 2024-08-02 13:54:25 +08:00
陈俊宏 4d4338f178 支付调起+订单查询 2024-08-02 13:49:28 +08:00
陈俊宏 c1e354e4ab 支付调起+订单查询 2024-08-02 13:47:52 +08:00
Rzy 28bd78ab7f Merge branch 'dev/dev1.0' into feature/rzy/api_1.0 2024-08-02 11:44:09 +08:00
Rzy a056e2b26a api中间件 2024-08-02 11:41:54 +08:00
wolter 7a3823a2a7 更新订单和订单日志 2024-08-02 10:41:57 +08:00
Rzy 753b466605 Merge branch 'dev/dev1.0' into feature/rzy/api_1.0 2024-08-02 09:42:03 +08:00
陈俊宏 0b844afa5f Merge branch 'dev/dev1.0' into feature_413_cjh 2024-08-02 09:23:06 +08:00
wolter 5a60a92f4b 调整字段白名单和下游通知地址由app管理,更新应用模块 2024-08-02 09:18:49 +08:00
Rzy dd3255e220 <feat>新增rsa,sm2,sm4加解密以及测试用例 2024-08-01 16:40:54 +08:00
Rzy 4be80f228e <fix>(解决冲突):之后会另起分支 2024-08-01 14:53:22 +08:00
陈俊宏 1a292ff5d3 Merge branch 'dev/dev1.0' into feature_413_cjh
# Conflicts:
#	app/constants/common/common.go
#	app/data/app.go
#	app/data/order_log.go
#	app/data/pay_channel.go
2024-08-01 14:48:39 +08:00
陈俊宏 d343257e23 支付提交一波 2024-08-01 14:46:56 +08:00
wolter 95a050d64e 商户模块 2024-08-01 14:43:39 +08:00
wolter 689b0f80db 增加data层,更新model数据表 2024-07-31 18:38:17 +08:00
99 changed files with 8073 additions and 719 deletions

43
Dockerfile Normal file
View File

@ -0,0 +1,43 @@
## 使用官方Go镜像作为构建环境
#FROM golang:1.21.0 AS builder
#
## 设置工作目录
#WORKDIR /app
#
## 复制项目源码
#COPY . .
#
## 复制go模块依赖文件
#COPY go.mod go.sum ./
#
## 安装go模块依赖
#RUN go env -w GOPROXY=https://goproxy.cn,direct
#RUN go mod tidy
#
## 编译Go应用程序生成静态链接的二进制文件
#RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o server .
# 创建最终镜像用于运行编译后的Go程序
FROM alpine
RUN echo 'http://mirrors.ustc.edu.cn/alpine/v3.5/main' > /etc/apk/repositories \
&& echo 'http://mirrors.ustc.edu.cn/alpine/v3.5/community' >>/etc/apk/repositories \
&& apk update && apk add tzdata \
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone \
ENV server = "admin"
# 设置工作目录
WORKDIR /app
# 将编译好的二进制文件从构建阶段复制到运行阶段
COPY /server .
#COPY --from=builder /app/server .
COPY .env .
ENV TZ=Asia/Shanghai
# 设置容器启动时运行的命令
ENTRYPOINT ["/app/server", "-a", "$server"]

28
Makefile Normal file
View File

@ -0,0 +1,28 @@
GOHOSTOS:=$(shell go env GOHOSTOS)
GOPATH:=$(shell go env GOPATH)
VERSION=$(shell git describe --tags --always)
ifeq ($(GOHOSTOS), windows)
#the `find.exe` is different from `find` in bash/shell.
#to see https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/find.
#changed to use git-bash.exe to run find cli or other cli friendly, caused of every developer has a Git.
#Git_Bash= $(subst cmd\,bin\bash.exe,$(dir $(shell where git)))
Git_Bash=$(subst \,/,$(subst cmd\,bin\bash.exe,$(dir $(shell where git))))
INTERNAL_PROTO_FILES=$(shell $(Git_Bash) -c "find internal -name *.proto")
API_PROTO_FILES=$(shell $(Git_Bash) -c "find api -name *.proto")
else
INTERNAL_PROTO_FILES=$(shell find internal -name *.proto)
API_PROTO_FILES=$(shell find api -name *.proto)
endif
.PHONY: build
build:
sh build/shell/build.sh
.PHONY: api
api:
sh build/shell/build.sh & build/bin/snow -a api

View File

@ -1,9 +1,317 @@
package console
import (
"PaymentCenter/app/constants/common"
"PaymentCenter/app/constants/errorcode"
"PaymentCenter/app/data"
"PaymentCenter/app/http/entities"
"PaymentCenter/app/http/entities/backend"
"PaymentCenter/app/models/ordersmodel"
"PaymentCenter/app/services/thirdpay/thirdpay_notify"
"PaymentCenter/app/third/paymentService"
"PaymentCenter/app/third/paymentService/payCommon"
"PaymentCenter/app/utils"
"PaymentCenter/config"
"context"
"encoding/json"
"fmt"
"github.com/qit-team/snow-core/command"
"strconv"
"sync"
"time"
"xorm.io/builder"
)
func RegisterCommand(c *command.Command) {
c.AddFunc("test", test)
c.AddFunc("closeOrder", closeOrder)
c.AddFunc("queryOrder", queryOrder)
}
// 关闭长时间支付中的订单
func closeOrder() {
var now = time.Now().Format(time.DateTime)
utils.Log(nil, "关闭订单", now)
var ctx = context.Background()
orderIds := make([]int64, 0)
// 查询未支付的订单
repo := data.NewOrderRepo(ordersmodel.GetInstance().GetDb())
// 拼接条件
cond := builder.NewCond()
cond = cond.And(builder.Eq{"status": common.ORDER_STATUS_PAYING}, builder.Lt{"orders.create_time": time.Now().Add(-time.Second * time.Duration(config.GetConf().CronConfig.CloseOrderTime))})
// 查询订单
order := make([]ordersmodel.OrdersLeftPayChannelList, 0)
err := repo.OrdersLeftPayChannelList(cond, entities.PageRequest{}, &order)
if err != nil {
utils.Log(nil, "关闭订单,查询未支付订单失败", err)
} else if len(order) > 0 {
for i := range order {
orderInfo := order[i]
// 往上游发送关闭订单请求
req := paymentService.OrderCloseRequest{OrderId: orderInfo.Id}
switch utils.PayType(orderInfo.ChannelType) {
case common.PAY_CHANNLE_TYPE_WECHAT:
wx := backend.WechatPayChannel{}
_ = json.Unmarshal([]byte(orderInfo.ExtJson), &wx)
req.PayChannel = payCommon.PAY_CHANNLE_TYPE_WECHAT
req.Wx = paymentService.WxPay{
AppId: orderInfo.AppId,
MchId: wx.MchId,
SerialNo: wx.SerialNo,
ApiV3Key: wx.ApiV3Key,
PrivateKey: wx.PrivateKey,
}
case common.PAY_CHANNLE_TYPE_ZFB:
var ali backend.AliPayChannel
req.PayChannel = payCommon.PAY_CHANNLE_TYPE_ZFB
_ = json.Unmarshal([]byte(orderInfo.ExtJson), &ali)
req.Ali = paymentService.AliPay{
AppId: orderInfo.AppId,
PrivateKey: ali.PrivateKey,
AppPublicCert: ali.AppPublicCert,
AlipayRootCert: ali.AlipayRootCert,
AlipayPublicCert: ali.AlipayPublicCert,
}
default:
utils.Log(nil, "关闭订单,支付渠道不支持", orderInfo.ChannelType)
continue
}
// 发起关闭订单请求
response := paymentService.OrderClose(ctx, req)
// 成功
if response.Code != payCommon.PAY_SUCCESS_CODE {
utils.Log(nil, "关闭订单,上游失败", response, orderInfo.Id)
}
orderIds = append(orderIds, orderInfo.Id)
// 回调通知下游
notifyResult := thirdpay_notify.NewOrderNotifyWithHandle(orderInfo.Id, common.ORDER_STATUS_CLOSE, 0, "长时间未支付关闭订单")
//utils.Log(nil, "主动查询订单支付状态,回调下游", notifyResult)
if notifyResult.ErrCode != errorcode.Success {
utils.Log(nil, "关闭订单,回调下游失败", fmt.Sprintf("%#v", notifyResult))
}
}
// 修改订单状态为关闭
cond = builder.NewCond()
cond = cond.And(builder.In("id", orderIds))
_, err = repo.OrderUpdate(&ordersmodel.Orders{Status: common.ORDER_STATUS_CLOSE}, cond, "status")
if err != nil {
utils.Log(nil, "关闭订单,修改订单状态失败", err)
return
}
}
utils.Log(nil, "关闭订单,修改订单状态成功", "总数量="+strconv.Itoa(len(order)), "成功数量="+strconv.Itoa(len(orderIds)))
}
// 主动查询订单支付状态
func queryOrder() {
var now = time.Now().Format(time.DateTime)
utils.Log(nil, "主动查询订单支付状态", now)
ctx := context.Background()
// 查询未支付的订单
repo := data.NewOrderRepo(ordersmodel.GetInstance().GetDb())
// 拼接条件
cond := builder.NewCond()
cond = cond.And(builder.Eq{"status": common.ORDER_STATUS_PAYING}, builder.Gt{"orders.create_time": time.Now().Add(-time.Second * time.Duration(config.GetConf().CronConfig.QueryOrderTime))})
cond = cond.And(builder.Eq{"order_type": common.ORDER_TYPE_PAY})
order := make([]ordersmodel.OrdersLeftPayChannelList, 0)
err := repo.OrdersLeftPayChannelList(cond, entities.PageRequest{}, &order)
if err != nil {
utils.Log(nil, "主动查询订单支付状态,查询未付中订单失败", err)
return
} else if len(order) > 0 {
ch := make(chan struct{}, config.GetConf().CronConfig.ConcurrentNumber)
wg := sync.WaitGroup{}
for index := range order {
ch <- struct{}{}
wg.Add(1)
orderInfo := order[index]
// 发起查询上游支付
go func(orderInfo ordersmodel.OrdersLeftPayChannelList) {
defer func() {
<-ch
wg.Done()
}()
query := paymentService.PayOrderQueryRequest{
OrderId: orderInfo.Id,
}
switch utils.PayType(orderInfo.ChannelType) {
case common.PAY_CHANNLE_TYPE_WECHAT:
wx := backend.WechatPayChannel{}
_ = json.Unmarshal([]byte(orderInfo.ExtJson), &wx)
query.PayChannel = payCommon.PAY_CHANNLE_TYPE_WECHAT
query.Wx = paymentService.WxPay{
AppId: orderInfo.AppId,
MchId: wx.MchId,
SerialNo: wx.SerialNo,
ApiV3Key: wx.ApiV3Key,
PrivateKey: wx.PrivateKey,
}
case common.PAY_CHANNLE_TYPE_ZFB:
ali := backend.AliPayChannel{}
query.PayChannel = payCommon.PAY_CHANNLE_TYPE_ZFB
_ = json.Unmarshal([]byte(orderInfo.ExtJson), &ali)
query.Ali = paymentService.AliPay{
AppId: orderInfo.AppId,
PrivateKey: ali.PrivateKey,
AppPublicCert: ali.AppPublicCert,
AlipayRootCert: ali.AlipayRootCert,
AlipayPublicCert: ali.AlipayPublicCert,
}
default:
utils.Log(nil, "查询订单,支付渠道不支持", orderInfo.ChannelType)
return
}
// 发起查询
result := paymentService.PayOrderQuery(ctx, query)
utils.Log(nil, "主动查询订单支付状态,上游返回数据", result)
// 查询成功,校验状态
var status int
if result.Code == payCommon.PAY_SUCCESS_CODE {
var msg string
switch result.Result.TradeState {
case "SUCCESS":
// 成功
status = common.ORDER_STATUS_PAYED
msg = "支付成功"
case "REFUND":
// 退款 订单支付完成才能退款,所以支付单的状态是支付完成
status = common.ORDER_STATUS_PAYED
msg = "支付成功"
case "NOTPAY":
// 未支付
return
case "CLOSED":
// 关闭
status = common.ORDER_STATUS_CLOSE
msg = "订单关闭"
}
// 回调通知下游 todo
notifyResult := thirdpay_notify.NewOrderNotifyWithHandle(orderInfo.Id, status, int(result.Result.PayerTotal), msg)
//utils.Log(nil, "主动查询订单支付状态,回调下游", notifyResult)
if notifyResult.ErrCode != errorcode.Success {
utils.Log(nil, "主动查询订单支付状态,回调下游失败", fmt.Sprintf("%+v", notifyResult))
}
}
}(orderInfo)
}
wg.Wait()
}
}
// 主动查询退款订单状态
func queryRefundOrder() {
var now = time.Now().Format(time.DateTime)
utils.Log(nil, "主动查询退款订单状态", now)
ctx := context.Background()
// 查询退款中的订单
repo := data.NewOrderRepo(ordersmodel.GetInstance().GetDb())
// 拼接条件
cond := builder.NewCond()
cond = cond.And(builder.Eq{"status": common.ORDER_STATUS_PAYING}, builder.Gt{"orders.create_time": time.Now().Add(-time.Second * time.Duration(config.GetConf().CronConfig.QueryOrderTime))})
cond = cond.And(builder.Eq{"order_type": common.ORDER_TYPE_REFUND})
order := make([]ordersmodel.OrdersLeftPayChannelList, 0)
err := repo.OrdersLeftPayChannelList(cond, entities.PageRequest{}, &order)
if err != nil {
utils.Log(nil, "主动查询退款订单状态,查询退款中订单失败", err)
return
} else if len(order) > 0 {
ch := make(chan struct{}, config.GetConf().CronConfig.ConcurrentNumber)
wg := sync.WaitGroup{}
for index := range order {
ch <- struct{}{}
wg.Add(1)
orderInfo := order[index]
// 发起查询上游
go func(orderInfo ordersmodel.OrdersLeftPayChannelList) {
defer func() {
<-ch
wg.Done()
}()
query := paymentService.OrderRefundQueryRequest{
RefundOrderId: orderInfo.Id,
OrderId: orderInfo.RefundOrderId,
}
switch utils.PayType(orderInfo.ChannelType) {
case common.PAY_CHANNLE_TYPE_WECHAT:
wx := backend.WechatPayChannel{}
_ = json.Unmarshal([]byte(orderInfo.ExtJson), &wx)
query.PayChannel = payCommon.PAY_CHANNLE_TYPE_WECHAT
query.Wx = paymentService.WxPay{
AppId: orderInfo.AppId,
MchId: wx.MchId,
SerialNo: wx.SerialNo,
ApiV3Key: wx.ApiV3Key,
PrivateKey: wx.PrivateKey,
}
case common.PAY_CHANNLE_TYPE_ZFB:
ali := backend.AliPayChannel{}
query.PayChannel = payCommon.PAY_CHANNLE_TYPE_ZFB
_ = json.Unmarshal([]byte(orderInfo.ExtJson), &ali)
query.Ali = paymentService.AliPay{
AppId: orderInfo.AppId,
PrivateKey: ali.PrivateKey,
AppPublicCert: ali.AppPublicCert,
AlipayRootCert: ali.AlipayRootCert,
AlipayPublicCert: ali.AlipayPublicCert,
}
default:
utils.Log(nil, "查询订单,支付渠道不支持", orderInfo.ChannelType)
return
}
// 发起查询
result := paymentService.OrderRefundQuery(ctx, query)
utils.Log(nil, "主动查询退款订单状态,上游返回数据", result)
// 查询成功,校验状态
var status int
if result.Code == payCommon.PAY_SUCCESS_CODE {
// 退款状态 0未申请1退款中2退款成功3退款失败
var msg string
switch result.Result.RefundStatus {
case 0:
// 未申请
utils.Log(nil, "主动查询退款订单状态,未申请", status)
return
case 1:
// 退款中
status = common.ORDER_STATUS_PAYING
return
case 2:
// 退款成功
status = common.ORDER_STATUS_PAYED
msg = "退款成功"
case 3:
// 退款失败
status = common.ORDER_STATUS_FAILED
msg = "退款失败"
}
// 回调通知下游 todo
notifyResult := thirdpay_notify.NewOrderNotifyWithHandle(orderInfo.Id, status, int(result.Result.RefundFee), msg)
//utils.Log(nil, "主动查询退款订单状态,回调下游", notifyResult)
if notifyResult.ErrCode != errorcode.Success {
utils.Log(nil, "查询退款订单状态,回调下游失败", notifyResult)
}
}
}(orderInfo)
}
wg.Wait()
}
}
// 回调下游
func callbackOrder() {
var now = time.Now().Format(time.DateTime)
utils.Log(nil, "回调下游", now)
// 查询回调失败的订单
// 发起回调
}

View File

@ -1,6 +1,8 @@
package console
import (
"PaymentCenter/config"
"fmt"
"github.com/robfig/cron"
)
@ -9,7 +11,11 @@ import (
* @wiki https://godoc.org/github.com/robfig/cron
*/
func RegisterSchedule(c *cron.Cron) {
fmt.Println(config.GetConf().CronConfig)
//c.AddFunc("0 30 * * * *", test)
//c.AddFunc("@hourly", test)
c.AddFunc("@every 10s", test)
//c.AddFunc("@every 10s", test)
c.AddFunc("@every 60s", closeOrder)
c.AddFunc("@every 10s", queryOrder)
c.AddFunc("@every 60s", queryRefundOrder)
}

View File

@ -3,5 +3,73 @@ package common
const (
TOKEN_PRE = "player_token_"
TOKEN_Admin = "Admin_token_"
ADMIN_V1 = "/admin/api/v1"
ADMIN_V1 = "/pay/admin/api/v1"
FRONT_V1 = "/pay/front/api/v1"
// 支付渠道枚举,1微信JSAPI2微信H53微信app4微信Native5微信小程序6支付宝网页&移动应用7支付宝小程序8支付宝JSAPI
PAY_CHANNEL_UNKNOWN = 0
PAY_CHANNEL_WECHAT_JSAPI = 1
PAY_CHANNEL_WECHAT_H5 = 2
PAY_CHANNEL_WECHAT_APP = 3
PAY_CHANNEL_WECHAT_NATIVE = 4
PAY_CHANNEL_WECHAT_MINI = 5
PAY_CHANNEL_ALIPAY_WEB = 6
PAY_CHANNEL_ALIPAY_MINI = 7
PAY_CHANNEL_ALIPAY_JSAPI = 8
// 统一登陆信息
ADMIN_USER_ID = "User-Id"
ADMIN_USER_NAME = "User-Name"
ADMIN_USER_INCLUDEUSERS = "Include-Users"
// '订单状态: 1待支付、2支付中、3支付成功、4支付失败、5订单关闭
ORDER_STATUS_WAITPAY = 1
ORDER_STATUS_PAYING = 2
ORDER_STATUS_PAYED = 3
ORDER_STATUS_FAILED = 4
ORDER_STATUS_CLOSE = 5
// 订单类型1支付2退款
ORDER_TYPE_PAY = 1
ORDER_TYPE_REFUND = 2
STATUS_ENABLE = 1
STATUS_DISABLED = 2
PAY_CHANNLE_TYPE_WECHAT = 1 // 支付类型: 微信
PAY_CHANNLE_TYPE_ZFB = 2 // 支付类型:支付宝
THIRD_ORDER_TYPE_PAY = 1 // 发起支付
THIRD_ORDER_TYPE_ORDER_QUERY = 2 // 查询订单
THIRD_ORDER_TYPE_REFUND = 3 // 发起退款
THIRD_ORDER_TYPE_REFUND_QUERY = 4 // 退款查询
THIRD_ORDER_TYPE_CLOSE = 5 // 关闭订单
THIRD_ORDER_TYPE_CALL_BACK = 6 // 支付回调
THIRD_ORDER_LOG_STATUS_COMMON = 0 // 第三方日志状态 未知
THIRD_ORDER_LOG_STATUS_FAIL = 1 // 第三方日志状态 失败
THIRD_ORDER_LOG_STATUS_SUCCESS = 2 // 第三方日志状态 成功
)
var PayChannelList = map[int]string{
PAY_CHANNEL_WECHAT_JSAPI: "微信JSAPI",
PAY_CHANNEL_WECHAT_H5: "微信H5",
PAY_CHANNEL_WECHAT_APP: "微信app",
PAY_CHANNEL_WECHAT_NATIVE: "微信Native",
PAY_CHANNEL_WECHAT_MINI: "微信小程序",
PAY_CHANNEL_ALIPAY_WEB: "支付宝网页&移动应用",
PAY_CHANNEL_ALIPAY_MINI: "支付宝小程序",
PAY_CHANNEL_ALIPAY_JSAPI: "支付宝JSAPI",
}
var OrderTypeList = map[int]string{
ORDER_TYPE_PAY: "付款",
ORDER_TYPE_REFUND: "退款",
}
var OrderStatusMap = map[int]string{
ORDER_STATUS_WAITPAY: "等待执行",
ORDER_STATUS_PAYING: "执行中",
ORDER_STATUS_PAYED: "执行成功",
ORDER_STATUS_FAILED: "执行失败",
ORDER_STATUS_CLOSE: "订单关闭",
}

View File

@ -19,8 +19,67 @@ const (
//系统错误
SystemError = 500
//请求超时
RequestTimeOut = 600
//未登录
NotLogin = 1000
// 商户
MerchantNotFound = 1100
// app
AppNotFound = 1200
AppDisabled = 1201
AppIpNotAllow = 1202
AppRsaDecryptKeyNotFound = 1203
AppDecryptDataDiscrepancy = 1204
AppNotifyUrlNotFound = 1205
AppRsaDecryptFail = 1210
AppRsaEncryptKeyNotFound = 1211
AppRsaEncryptFail = 1212
AppSM2DecryptKeyNotFound = 1220
AppSM2DecryptFail = 1221
AppSM2EncryptKeyNotFound = 1222
AppSM2EncryptFail = 1223
AppSM4DecryptKeyNotFound = 1230
AppSM4DecryptFail = 1231
AppSM4EncryptKeyNotFound = 1232
AppSM4EncryptFail = 1233
AppAesEncryptFail = 1234
AppDeEncryptFail = 1250
// 加密方式不存在
EncryptTypeNotFound = 1241
//渠道
PayChannelNotFound = 1300
PayChannelNotBuild = 1301
PayChannelExtJsonError = 1302
//订单
OrdersNotFound = 1401
OrdersExist = 1402
OrderTypeNotFount = 1403
OrderIsDelete = 1405
OrderStatusErr = 1406
OrderClosed = 1407
OrderFailed = 1408
OrderPayed = 1409
RefundOrderNotFound = 1410
OrderStatusRefundNotSupport = 1411
//请求日志
RequestLogErrors = 1500
RequestLogNotFound = 1501
RequestResponseValid = 1502
//回调
NotifySendFail = 1600
//订单结果
PrePayFail = 1701
PreRefundFail = 1702
)
var MsgEN = map[int]string{
@ -33,11 +92,61 @@ var MsgEN = map[int]string{
}
var MsgZH = map[int]string{
Success: "请求成功",
ParamError: "参数错误",
NotFound: "数据不存在",
NotAuth: "未经授权",
NotLogin: "未登录",
Success: "请求成功",
ParamError: "参数错误",
NotFound: "数据不存在",
NotAuth: "未经授权",
NotLogin: "未登录",
RequestTimeOut: "请求超时",
MerchantNotFound: "商户不存在",
AppNotFound: "app_id未找到",
AppDisabled: "app通道关闭",
AppIpNotAllow: "ip不在白名单内",
AppDecryptDataDiscrepancy: "解密数据不一致",
SystemError: "系统错误",
AppNotifyUrlNotFound: "未设置回调地址",
AppRsaDecryptKeyNotFound: "密匙缺失无法进行Rsa解密",
AppRsaDecryptFail: "Rsa解密失败",
AppRsaEncryptKeyNotFound: "密匙缺失无法进行Rsa加密",
AppRsaEncryptFail: "Rsa加密失败",
AppSM2DecryptKeyNotFound: "密匙缺失无法进行sm2解密",
AppSM2DecryptFail: "sm2解密失败",
AppSM2EncryptKeyNotFound: "密匙缺失无法进行sm2加密",
AppSM2EncryptFail: "sm2加密失败",
AppSM4DecryptKeyNotFound: "密匙缺失无法进行sm4解密",
AppSM4DecryptFail: "sm4解密失败",
AppSM4EncryptKeyNotFound: "密匙缺失无法进行sm4加密",
AppSM4EncryptFail: "sm4加密失败",
AppAesEncryptFail: "aes 加密失败",
EncryptTypeNotFound: "加密方式不存在",
AppDeEncryptFail: "未知原因导致解密失败请检查加密数据是和app加密配置",
PayChannelNotFound: "支付方式不存在",
PayChannelNotBuild: "支付方式尚未开通",
PayChannelExtJsonError: "支付方式扩展参数错误",
RequestLogErrors: "请求日志错误",
RequestLogNotFound: "未找到日志信息",
RequestResponseValid: "上游返回格式无效",
OrdersNotFound: "未找到订单",
OrdersExist: "订单已存在",
OrderTypeNotFount: "未知的支付方式",
OrderIsDelete: "订单已删除",
OrderStatusErr: "订单状态错误",
OrderClosed: "订单已关闭,无法继续支付",
OrderFailed: "订单支付失败,请重新发起",
OrderPayed: "订单已支付成功,请勿重复支付",
NotifySendFail: "回调发送失败",
RefundOrderNotFound: "退款订单未找到",
OrderStatusRefundNotSupport: "订单状态不支持退款",
PrePayFail: "预支付失败",
}
var MsgMap map[string]map[int]string = map[string]map[int]string{"en": MsgZH}

View File

@ -0,0 +1,7 @@
package pojo
const (
RSA int32 = iota + 1
SM2
SM4
)

0
app/data/.gitkeep Normal file
View File

48
app/data/app.go Normal file
View File

@ -0,0 +1,48 @@
package data
import (
"PaymentCenter/app/http/entities"
"PaymentCenter/app/models/appmodel"
"database/sql"
"xorm.io/builder"
"xorm.io/xorm"
)
type AppRepo struct {
repo xorm.Interface
}
func NewAppRepo(repo xorm.Interface) *AppRepo {
return &AppRepo{
repo: repo,
}
}
func (m *AppRepo) AppList(conn builder.Cond, pageFilter entities.PageRequest, appList *[]appmodel.App) (int64, error) {
repo := m.repo.Where(conn)
if pageFilter.Page > 0 {
repo = repo.Limit(pageFilter.PageSize, pageFilter.PageSize*(pageFilter.Page-1))
}
return repo.Desc("create_time").FindAndCount(appList)
}
func (m *AppRepo) AppInsertOne(app *appmodel.App) (int64, error) {
return m.repo.InsertOne(app)
}
func (m *AppRepo) AppDelete(app *appmodel.App, conn builder.Cond) (int64, error) {
return m.repo.Where(conn).Delete(app)
}
// columns 参数为要更新的字段
func (m *AppRepo) AppUpdate(app *appmodel.App, conn builder.Cond, columns ...string) (int64, error) {
return m.repo.Where(conn).MustCols(columns...).Update(app)
}
func (m *AppRepo) AppFindOne(app *appmodel.App, conn builder.Cond, columns ...string) (*appmodel.App, error) {
has, err := m.repo.Where(conn).Get(app)
if !has {
return nil, sql.ErrNoRows
}
return app, err
}

1
app/data/merchant.go Normal file
View File

@ -0,0 +1 @@
package data

52
app/data/merchat.go Normal file
View File

@ -0,0 +1,52 @@
package data
import (
"PaymentCenter/app/http/entities"
"PaymentCenter/app/models/merchantmodel"
"database/sql"
"xorm.io/builder"
"xorm.io/xorm"
)
type MerchantRepo struct {
repo xorm.Interface
}
func NewMerchantRepo(repo xorm.Interface) *MerchantRepo {
return &MerchantRepo{
repo: repo,
}
}
func (m *MerchantRepo) MerchantList(conn builder.Cond, pageFilter entities.PageRequest, merchantList *[]merchantmodel.Merchant) (int64, error) {
repo := m.repo.Where(conn)
if pageFilter.Page > 0 {
repo = repo.Limit(pageFilter.PageSize, pageFilter.PageSize*(pageFilter.Page-1))
}
return repo.Desc("create_time").FindAndCount(merchantList)
}
func (m *MerchantRepo) MerchantInsertOne(merchant *merchantmodel.Merchant) (int64, error) {
return m.repo.InsertOne(merchant)
}
func (m *MerchantRepo) MerchantDelete(merchant *merchantmodel.Merchant, conn builder.Cond) (int64, error) {
return m.repo.Where(conn).Delete(merchant)
}
// columns 参数为要更新的字段
func (m *MerchantRepo) MerchantUpdate(merchant *merchantmodel.Merchant, conn builder.Cond, columns ...string) (int64, error) {
return m.repo.Where(conn).MustCols(columns...).Update(merchant)
}
func (m *MerchantRepo) MerchantDetail(merchant *merchantmodel.Merchant, conn builder.Cond) (bool, error) {
return m.repo.Where(conn).Get(merchant)
}
func (m *MerchantRepo) MerchantFindOne(merchant *merchantmodel.Merchant, conn builder.Cond, columns ...string) (*merchantmodel.Merchant, error) {
has, err := m.repo.Where(conn).Get(merchant)
if !has {
return nil, sql.ErrNoRows
}
return merchant, err
}

View File

@ -0,0 +1,39 @@
package data
import (
"PaymentCenter/app/http/entities"
"PaymentCenter/app/models/ordercallbacklogmodel"
"xorm.io/builder"
"xorm.io/xorm"
)
type OrderCallbackLogRepo struct {
repo xorm.Interface
}
func NewOrderCallbackLogRepo(repo xorm.Interface) *OrderCallbackLogRepo {
return &OrderCallbackLogRepo{
repo: repo,
}
}
func (m *OrderCallbackLogRepo) OrderCallbackLogList(conn builder.Cond, pageFilter entities.PageRequest, orderLogList *[]ordercallbacklogmodel.OrderCallbackLog) (int64, error) {
repo := m.repo.Where(conn)
if pageFilter.Page > 0 {
repo = repo.Limit(pageFilter.PageSize, pageFilter.PageSize*(pageFilter.Page-1))
}
return repo.Desc("create_time").FindAndCount(orderLogList)
}
func (m *OrderCallbackLogRepo) OrderCallbackLogInsertOne(orderLog *ordercallbacklogmodel.OrderCallbackLog) (int64, error) {
return m.repo.InsertOne(orderLog)
}
func (m *OrderCallbackLogRepo) OrderCallbackLogDelete(orderLog *ordercallbacklogmodel.OrderCallbackLog, conn builder.Cond) (int64, error) {
return m.repo.Where(conn).Delete(orderLog)
}
// columns 参数为要更新的字段
func (m *OrderCallbackLogRepo) OrderCallbackLogUpdate(orderLog *ordercallbacklogmodel.OrderCallbackLog, conn builder.Cond, columns ...string) (int64, error) {
return m.repo.Where(conn).MustCols(columns...).Update(orderLog)
}

View File

@ -0,0 +1,49 @@
package data
import (
"PaymentCenter/app/http/entities"
"PaymentCenter/app/models/orderrequestlogmodel"
"xorm.io/builder"
"xorm.io/xorm"
)
type OrderRequestLogRepo struct {
repo xorm.Interface
}
func NewOrderRequestLogRepo(repo xorm.Interface) *OrderRequestLogRepo {
return &OrderRequestLogRepo{
repo: repo,
}
}
func (m *OrderRequestLogRepo) OrderRequestLogList(conn builder.Cond, pageFilter entities.PageRequest, orderLogList *[]orderrequestlogmodel.OrderRequestLog) (int64, error) {
repo := m.repo.Where(conn)
if pageFilter.Page > 0 {
repo = repo.Limit(pageFilter.PageSize, pageFilter.PageSize*(pageFilter.Page-1))
}
return repo.Desc("create_time").FindAndCount(orderLogList)
}
func (m *OrderRequestLogRepo) OrderRequestLogInsertOne(orderLog *orderrequestlogmodel.OrderRequestLog) (int64, error) {
return m.repo.InsertOne(orderLog)
}
func (m *OrderRequestLogRepo) OrderRequestLogDelete(orderLog *orderrequestlogmodel.OrderRequestLog, conn builder.Cond) (int64, error) {
return m.repo.Where(conn).Delete(orderLog)
}
// columns 参数为要更新的字段
func (m *OrderRequestLogRepo) OrderRequestLogUpdate(orderLog *orderrequestlogmodel.OrderRequestLog, conn builder.Cond, columns ...string) (int64, error) {
return m.repo.Where(conn).MustCols(columns...).Update(orderLog)
}
// 后台查询订单日志列表
func (m *OrderRequestLogRepo) OrderRequestLogBackendList(conn builder.Cond, pageFilter entities.PageRequest, orderLogList *[]orderrequestlogmodel.OrderRequestLog) (int64, error) {
repo := m.repo.Where(conn)
if pageFilter.Page > 0 {
repo = repo.Limit(pageFilter.PageSize, pageFilter.PageSize*(pageFilter.Page-1))
}
repo.Join("left", "orders", "orders.app_id = order_request_log.app_id and orders.out_trade_no = order_request_log.out_trade_no")
return repo.Desc("order_request_log.create_time").FindAndCount(orderLogList)
}

View File

@ -0,0 +1,39 @@
package data
import (
"PaymentCenter/app/http/entities"
"PaymentCenter/app/models/orderthirdpaylogmodel"
"xorm.io/builder"
"xorm.io/xorm"
)
type OrderThirdPayLogRepo struct {
repo xorm.Interface
}
func NewOrderThirdPayLogRepo(repo xorm.Interface) *OrderThirdPayLogRepo {
return &OrderThirdPayLogRepo{
repo: repo,
}
}
func (m *OrderThirdPayLogRepo) OrderThirdPayLogList(conn builder.Cond, pageFilter entities.PageRequest, orderLogList *[]orderthirdpaylogmodel.OrderThirdPayLog) (int64, error) {
repo := m.repo.Where(conn)
if pageFilter.Page > 0 {
repo = repo.Limit(pageFilter.PageSize, pageFilter.PageSize*(pageFilter.Page-1))
}
return repo.Desc("create_time").FindAndCount(orderLogList)
}
func (m *OrderThirdPayLogRepo) OrderThirdPayLogInsertOne(orderLog *orderthirdpaylogmodel.OrderThirdPayLog) (int64, error) {
return m.repo.InsertOne(orderLog)
}
func (m *OrderThirdPayLogRepo) OrderThirdPayLogDelete(orderLog *orderthirdpaylogmodel.OrderThirdPayLog, conn builder.Cond) (int64, error) {
return m.repo.Where(conn).Delete(orderLog)
}
// columns 参数为要更新的字段
func (m *OrderThirdPayLogRepo) OrderThirdPayLogUpdate(orderLog *orderthirdpaylogmodel.OrderThirdPayLog, conn builder.Cond, columns ...string) (int64, error) {
return m.repo.Where(conn).MustCols(columns...).Update(orderLog)
}

70
app/data/orders.go Normal file
View File

@ -0,0 +1,70 @@
package data
import (
"PaymentCenter/app/http/entities"
"PaymentCenter/app/models/ordersmodel"
"database/sql"
"xorm.io/builder"
"xorm.io/xorm"
)
type OrderRepo struct {
repo xorm.Interface
}
func NewOrderRepo(repo xorm.Interface) *OrderRepo {
return &OrderRepo{
repo: repo,
}
}
func (m *OrderRepo) OrderList(conn builder.Cond, pageFilter entities.PageRequest, orderList *[]ordersmodel.Orders) (int64, error) {
repo := m.repo.Where(conn)
if pageFilter.Page > 0 {
repo = repo.Limit(pageFilter.PageSize, pageFilter.PageSize*(pageFilter.Page-1))
}
return repo.Desc("create_time").FindAndCount(orderList)
}
func (m *OrderRepo) OrderInsertOne(order *ordersmodel.Orders) (int64, error) {
return m.repo.InsertOne(order)
}
func (m *OrderRepo) OrderDelete(order *ordersmodel.Orders, conn builder.Cond) (int64, error) {
return m.repo.Where(conn).Delete(order)
}
// columns 参数为要更新的字段
func (m *OrderRepo) OrderUpdate(order *ordersmodel.Orders, conn builder.Cond, columns ...string) (int64, error) {
return m.repo.Where(conn).MustCols(columns...).Update(order)
}
func (m *OrderRepo) OrdersBackendList(conn builder.Cond, pageFilter entities.PageRequest, orderList *[]ordersmodel.OrdersBackendList) (int64, error) {
repo := m.repo.Select(`orders.*, merchant.name as merchant_name, app.app_name, pay_channel.pay_name`).
Where(conn)
if pageFilter.Page > 0 {
repo = repo.Limit(pageFilter.PageSize, pageFilter.PageSize*(pageFilter.Page-1))
}
repo = repo.Join("left", "app", "app.id = orders.app_id").
Join("left", "merchant", "merchant.id = orders.merchant_id").
Join("left", "pay_channel", "pay_channel.id = orders.pay_channel_id")
return repo.Desc("orders.create_time").FindAndCount(orderList)
}
func (m *OrderRepo) OrdersLeftPayChannelList(conn builder.Cond, pageFilter entities.PageRequest, orderList *[]ordersmodel.OrdersLeftPayChannelList) error {
repo := m.repo.Select(`orders.*,pay_channel.channel_type,pay_channel.app_id ,pay_channel.ext_json`).
Where(conn)
if pageFilter.Page > 0 {
repo = repo.Limit(pageFilter.PageSize, pageFilter.PageSize*(pageFilter.Page-1))
}
repo = repo.Join("left", "pay_channel", "pay_channel.id = orders.pay_channel_id")
return repo.Find(orderList)
}
func (m *OrderRepo) OrderFindOne(order *ordersmodel.Orders, conn builder.Cond, columns ...string) (*ordersmodel.Orders, error) {
has, err := m.repo.Where(conn).Get(order)
if !has {
return nil, sql.ErrNoRows
}
return order, err
}

52
app/data/pay_channel.go Normal file
View File

@ -0,0 +1,52 @@
package data
import (
"PaymentCenter/app/http/entities"
"PaymentCenter/app/models/paychannelmodel"
"database/sql"
"xorm.io/builder"
"xorm.io/xorm"
)
type PayChannelRepo struct {
repo xorm.Interface
}
func NewPayChannelRepo(repo xorm.Interface) *PayChannelRepo {
return &PayChannelRepo{
repo: repo,
}
}
func (m *PayChannelRepo) PayChannelList(conn builder.Cond, pageFilter entities.PageRequest, payChannelList *[]paychannelmodel.PayChannel) (int64, error) {
repo := m.repo.Where(conn)
if pageFilter.Page > 0 {
repo = repo.Limit(pageFilter.PageSize, pageFilter.PageSize*(pageFilter.Page-1))
}
return repo.Desc("create_time").FindAndCount(payChannelList)
}
func (m *PayChannelRepo) PayChannelInsertOne(payChannel *paychannelmodel.PayChannel) (int64, error) {
return m.repo.InsertOne(payChannel)
}
func (m *PayChannelRepo) PayChannelDelete(payChannel *paychannelmodel.PayChannel, conn builder.Cond) (int64, error) {
return m.repo.Where(conn).Delete(payChannel)
}
// columns 参数为要更新的字段,即使为空
func (m *PayChannelRepo) PayChannelUpdate(payChannel *paychannelmodel.PayChannel, conn builder.Cond, columns ...string) (int64, error) {
return m.repo.Where(conn).MustCols(columns...).Update(payChannel)
}
func (m *PayChannelRepo) PayChannelGet(payChannel *paychannelmodel.PayChannel, conn builder.Cond) (bool, error) {
return m.repo.Where(conn).Get(payChannel)
}
func (m *PayChannelRepo) PayChannelFindOne(payChannel *paychannelmodel.PayChannel, conn builder.Cond, columns ...string) (*paychannelmodel.PayChannel, error) {
has, err := m.repo.Where(conn).Get(payChannel)
if !has {
return nil, sql.ErrNoRows
}
return payChannel, err
}

130
app/http/controllers/api.go Normal file
View File

@ -0,0 +1,130 @@
package controllers
import (
"PaymentCenter/app/constants/common"
"PaymentCenter/app/constants/errorcode"
"PaymentCenter/app/http/entities/front"
"PaymentCenter/app/models/orderrequestlogmodel"
"PaymentCenter/app/services"
"PaymentCenter/app/services/thirdpay/api"
"PaymentCenter/app/utils"
"PaymentCenter/config"
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
func ApiRes(c *gin.Context, data interface{}, code int, msg ...string) {
var logId int64
var responseData interface{}
var message string
var appCheckInfo *services.AppCheck
originData := "{}"
appInfo := GetAppCheckInfo(c)
if appInfo != nil {
appCheckInfo = appInfo.(*services.AppCheck)
}
// 空数据
if utils.IsNil(data) {
data = struct{}{}
}
// 获取错误信息
if len(msg) > 0 {
message = msg[0]
} else {
message = errorcode.GetMsg(code, "")
}
// 获取日志ID
log, exists := GetApiLogId(c)
if exists {
logId = log.(int64)
}
// 加密数据
if code == errorcode.Success {
if apiRsp, ok := data.(front.ApiResponse); ok {
apiRsp.Order, code = api.EnCrypt(appCheckInfo.App, apiRsp.Order)
if code != errorcode.Success {
message = errorcode.GetMsg(code, "")
}
responseData = apiRsp
} else {
responseData = data
}
}
if code == errorcode.Success {
ApiSuccess(c, responseData, logId, message)
b, _ := json.Marshal(data)
originData = string(b)
} else {
ApiError(c, code, logId, message)
originData = fmt.Sprintf("{\"code\":%d,\"message\":\"%s\"}", code, message)
}
// 记录日志
if logId > 0 {
services.RequestLogUpdate(&orderrequestlogmodel.OrderRequestLog{
Id: logId,
AppId: appCheckInfo.AppId,
OutTradeNo: GetOutTradeNo(c),
MerchantResponse: originData,
Status: common.STATUS_DISABLED,
})
}
}
func ApiSuccess(c *gin.Context, data interface{}, log_id int64, messageSlice ...string) {
var message string
if len(messageSlice) > 0 {
message = messageSlice[0]
}
if message == "" {
message = errorcode.GetMsg(errorcode.Success, c.GetHeader("local"))
}
if config.GetConf().Env == "production" {
c.String(http.StatusOK, EncriptJson(gin.H{
"code": errorcode.Success,
"message": message,
"data": data,
"trace_id": log_id,
}))
} else {
c.JSON(http.StatusOK, gin.H{
"code": errorcode.Success,
"message": message,
"data": data,
"trace_id": log_id,
})
}
c.Abort()
}
/**
* 失败时返回
*/
func ApiError(c *gin.Context, code int, log_id int64, msg ...string) {
message := ""
if len(msg) > 0 {
message = msg[0]
} else {
message = errorcode.GetMsg(code, "")
}
if config.GetConf().Env == "production" {
c.String(http.StatusOK, EncriptJson(gin.H{
"code": code,
"message": message,
"data": make(map[string]string),
"trace_id": log_id,
}))
} else {
c.JSON(http.StatusOK, gin.H{
"code": code,
"message": message,
"data": make(map[string]string),
"trace_id": log_id,
})
}
c.Abort()
}

View File

@ -0,0 +1,93 @@
package backend
import (
"PaymentCenter/app/constants/errorcode"
"PaymentCenter/app/constants/pojo"
"PaymentCenter/app/http/controllers"
"PaymentCenter/app/http/entities"
"PaymentCenter/app/http/entities/backend"
"PaymentCenter/app/models/appmodel"
"PaymentCenter/app/services"
"PaymentCenter/app/utils/encrypt/rsa"
"PaymentCenter/app/utils/encrypt/sm2"
"PaymentCenter/app/utils/encrypt/sm4"
"github.com/ahmetb/go-linq/v3"
"github.com/gin-gonic/gin"
)
func AppList(c *gin.Context) {
req, _ := controllers.GetRequest(c).(*backend.AppListRequest)
req.SetDefault()
payChannelList, total, code := services.AppList(*req)
result := make([]backend.AppResponse, 0, len(payChannelList))
linq.From(payChannelList).SelectT(func(in appmodel.App) (out backend.AppResponse) {
out.ResponseFromDb(in)
return
}).ToSlice(&result)
data := entities.PageRsp{
Total: total,
Data: result,
}
controllers.HandCodeRes(c, data, code)
}
func AppCreate(c *gin.Context) {
req, _ := controllers.GetRequest(c).(*backend.AppCreateRequest)
payChannel, err := req.RequestToDb()
if err != nil {
controllers.Error(c, errorcode.ParamError, err.Error())
return
}
code := services.AppCreate(&payChannel)
data := backend.AppResponse{}
data.ResponseFromDb(payChannel)
controllers.HandCodeRes(c, data, code)
}
func AppUpdate(c *gin.Context) {
req, _ := controllers.GetRequest(c).(*backend.AppUpdateRequest)
payChannel, err := req.RequestToDb()
if err != nil {
controllers.Error(c, errorcode.ParamError, err.Error())
return
}
code := services.AppUpdate(&payChannel)
data := backend.AppResponse{}
data.ResponseFromDb(payChannel)
controllers.HandCodeRes(c, data, code)
}
func AppDelete(c *gin.Context) {
req, _ := controllers.GetRequest(c).(*entities.IdRequest)
code := services.AppDelete(*req)
controllers.HandCodeRes(c, nil, code)
}
func GenerateDecrypt(c *gin.Context) {
req, _ := controllers.GetRequest(c).(*backend.GenerateDecryptKeyRequest)
var publicKey, privateKey string
var err error
switch req.KeyType {
case pojo.SM2:
publicKey, privateKey, err = sm2.GenerateSM2Key()
case pojo.RSA:
publicKey, privateKey, err = rsa.GenerateKey()
case pojo.SM4:
privateKey, publicKey = sm4.GenerateKey()
default:
controllers.HandCodeRes(c, "", errorcode.EncryptTypeNotFound)
return
}
if err != nil {
controllers.Error(c, errorcode.SystemError, err.Error())
return
}
controllers.HandCodeRes(c, map[string]string{
"publicKey": publicKey,
"privateKey": privateKey,
}, errorcode.Success)
}

View File

@ -0,0 +1,55 @@
package backend
import (
"PaymentCenter/app/http/controllers"
"PaymentCenter/app/http/entities"
"PaymentCenter/app/http/entities/backend"
"PaymentCenter/app/models/merchantmodel"
"PaymentCenter/app/services"
"github.com/ahmetb/go-linq/v3"
"github.com/gin-gonic/gin"
)
func MerchantList(c *gin.Context) {
req, _ := controllers.GetRequest(c).(*backend.MerchantListRequest)
req.SetDefault()
req.GroupInfo = controllers.GetAdminUserIncludeUsers(c)
merchantList, total, code := services.MerchantList(*req)
result := make([]backend.MerchantResponse, 0, len(merchantList))
linq.From(merchantList).SelectT(func(in merchantmodel.Merchant) (out backend.MerchantResponse) {
out.ResponseFromDb(in)
return
}).ToSlice(&result)
data := entities.PageRsp{
Total: total,
Data: result,
}
controllers.HandCodeRes(c, data, code)
}
func MerchantCreate(c *gin.Context) {
req, _ := controllers.GetRequest(c).(*backend.MerchantCreateRequest)
merchant := req.RequestToDb()
code := services.MerchantCreate(&merchant)
data := backend.MerchantResponse{}
data.ResponseFromDb(merchant)
controllers.HandCodeRes(c, data, code)
}
func MerchantUpdate(c *gin.Context) {
req, _ := controllers.GetRequest(c).(*backend.MerchantUpdateRequest)
merchant := req.RequestToDb()
code := services.MerchantUpdate(&merchant)
data := backend.MerchantResponse{}
data.ResponseFromDb(merchant)
controllers.HandCodeRes(c, data, code)
}
func MerchantDelete(c *gin.Context) {
req, _ := controllers.GetRequest(c).(*entities.IdRequest)
code := services.MerchantDelete(*req)
controllers.HandCodeRes(c, nil, code)
}

View File

@ -0,0 +1,87 @@
package backend
import (
"PaymentCenter/app/constants/errorcode"
"PaymentCenter/app/http/controllers"
"PaymentCenter/app/http/entities"
"PaymentCenter/app/http/entities/backend"
"PaymentCenter/app/models/ordercallbacklogmodel"
"PaymentCenter/app/models/orderrequestlogmodel"
"PaymentCenter/app/models/ordersmodel"
"PaymentCenter/app/models/orderthirdpaylogmodel"
"PaymentCenter/app/services"
"github.com/ahmetb/go-linq/v3"
"github.com/gin-gonic/gin"
)
func OrderList(c *gin.Context) {
req, _ := controllers.GetRequest(c).(*backend.OrderListRequest)
filter, err := req.ValidateRequest()
if err != nil {
controllers.Error(c, errorcode.ParamError, err.Error())
return
}
orderList, total, code := services.OrderList(filter)
result := make([]backend.OrdersResponse, 0, len(orderList))
linq.From(orderList).SelectT(func(in ordersmodel.OrdersBackendList) (out backend.OrdersResponse) {
out.ResponseFromDb(in)
return
}).ToSlice(&result)
data := entities.PageRsp{
Total: total,
Data: result,
}
controllers.HandCodeRes(c, data, code)
}
func OrderRequestLogsList(c *gin.Context) {
req, _ := controllers.GetRequest(c).(*backend.OrderLogsListRequest)
req.SetDefault()
requestLog, total, code := services.OrderRequestLogsList(*req)
requestLogList := make([]backend.OrderRequestLogResponse, 0, len(requestLog))
linq.From(requestLog).SelectT(func(in orderrequestlogmodel.OrderRequestLog) (out backend.OrderRequestLogResponse) {
out.ResponseFromDb(in)
return
}).ToSlice(&requestLogList)
controllers.HandCodeRes(c, entities.PageRsp{
Total: total,
Data: requestLogList,
}, code)
}
func OrderThirdLogsList(c *gin.Context) {
req, _ := controllers.GetRequest(c).(*backend.OrderLogsListRequest)
req.SetDefault()
thirdLog, total, code := services.OrderThirdLogsList(*req)
thirdLogList := make([]backend.OrderThirdLogResponse, 0, len(thirdLog))
linq.From(thirdLog).SelectT(func(in orderthirdpaylogmodel.OrderThirdPayLog) (out backend.OrderThirdLogResponse) {
out.ResponseFromDb(in)
return
}).ToSlice(&thirdLogList)
controllers.HandCodeRes(c, entities.PageRsp{
Total: total,
Data: thirdLogList,
}, code)
}
func OrderCallbackLogsList(c *gin.Context) {
req, _ := controllers.GetRequest(c).(*backend.OrderLogsListRequest)
req.SetDefault()
callbackLog, total, code := services.OrderCallbackLogsList(*req)
callbackLogList := make([]backend.OrderCallbackLogResponse, 0, len(callbackLog))
linq.From(callbackLog).SelectT(func(in ordercallbacklogmodel.OrderCallbackLog) (out backend.OrderCallbackLogResponse) {
out.ResponseFromDb(in)
return
}).ToSlice(&callbackLogList)
controllers.HandCodeRes(c, entities.PageRsp{
Total: total,
Data: callbackLogList,
}, code)
}

View File

@ -0,0 +1,63 @@
package backend
import (
"PaymentCenter/app/constants/errorcode"
"PaymentCenter/app/http/controllers"
"PaymentCenter/app/http/entities"
"PaymentCenter/app/http/entities/backend"
"PaymentCenter/app/models/paychannelmodel"
"PaymentCenter/app/services"
"github.com/ahmetb/go-linq/v3"
"github.com/gin-gonic/gin"
)
func PayChannelList(c *gin.Context) {
req, _ := controllers.GetRequest(c).(*backend.PayChannelListRequest)
req.SetDefault()
payChannelList, total, code := services.PayChannelList(*req)
result := make([]backend.PayChannelResponse, 0, len(payChannelList))
linq.From(payChannelList).SelectT(func(in paychannelmodel.PayChannel) (out backend.PayChannelResponse) {
out.ResponseFromDb(in)
return
}).ToSlice(&result)
data := entities.PageRsp{
Total: total,
Data: result,
}
controllers.HandCodeRes(c, data, code)
}
func PayChannelCreate(c *gin.Context) {
req, _ := controllers.GetRequest(c).(*backend.PayChannelCreateRequest)
payChannel, err := req.RequestToDb()
if err != nil {
controllers.Error(c, errorcode.ParamError, err.Error())
return
}
code := services.PayChannelCreate(&payChannel)
data := backend.PayChannelResponse{}
data.ResponseFromDb(payChannel)
controllers.HandCodeRes(c, data, code)
}
func PayChannelUpdate(c *gin.Context) {
req, _ := controllers.GetRequest(c).(*backend.PayChannelUpdateRequest)
payChannel, err := req.RequestToDb()
if err != nil {
controllers.Error(c, errorcode.ParamError, err.Error())
return
}
code := services.PayChannelUpdate(&payChannel)
data := backend.PayChannelResponse{}
data.ResponseFromDb(payChannel)
controllers.HandCodeRes(c, data, code)
}
func PayChannelDelete(c *gin.Context) {
req, _ := controllers.GetRequest(c).(*entities.IdRequest)
code := services.PayChannelDelete(*req)
controllers.HandCodeRes(c, nil, code)
}

View File

@ -1,6 +1,7 @@
package controllers
import (
"PaymentCenter/app/constants/common"
"PaymentCenter/app/utils"
"PaymentCenter/config"
"bytes"
@ -8,6 +9,7 @@ import (
"encoding/base64"
"encoding/json"
"errors"
"github.com/bytedance/sonic"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/qit-team/snow-core/redis"
@ -15,6 +17,9 @@ import (
zh_translations "gopkg.in/go-playground/validator.v9/translations/zh"
"io/ioutil"
"net/http"
"reflect"
"regexp"
"strconv"
"PaymentCenter/app/constants/errorcode"
@ -24,7 +29,11 @@ import (
/**
* 成功时返回
*/
func Success(c *gin.Context, data interface{}, message string) {
func Success(c *gin.Context, data interface{}, messageSlice ...string) {
var message string
if len(messageSlice) > 0 {
message = messageSlice[0]
}
if message == "" {
message = errorcode.GetMsg(errorcode.Success, c.GetHeader("local"))
}
@ -103,17 +112,33 @@ func GenRequest(c *gin.Context, request interface{}) (msgs []string, err error)
if c.Request.Method == "GET" || c.Request.Method == "DELETE" {
err = c.ShouldBindQuery(request)
} else {
err = c.ShouldBindJSON(request)
err = c.ShouldBind(request)
}
var req, _ = sonic.Marshal(request)
utils.Log(c, c.FullPath(), "请求参数", string(req))
if err == nil {
validate := validator.New()
_ = validate.RegisterValidation("phoneValidation", phoneValidation)
zh_ch := zh.New()
uni := ut.New(zh_ch)
trans, _ := uni.GetTranslator("zh")
_ = validate.RegisterTranslation("phoneValidation", trans, func(ut ut.Translator) error {
return ut.Add("phoneValidation", "手机号不合法", true) // 添加翻译
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("phoneValidation", fe.Field()) // 获取翻译
return t
},
)
//注册一个函数获取struct tag里自定义的label作为字段名
validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
name := fld.Tag.Get("label")
return name
})
//验证器注册翻译器
zh_translations.RegisterDefaultTranslations(validate, trans)
_ = zh_translations.RegisterDefaultTranslations(validate, trans)
errValidate := validate.Struct(request)
if errValidate != nil {
@ -127,6 +152,34 @@ func GenRequest(c *gin.Context, request interface{}) (msgs []string, err error)
return
}
func ValidApiData(dataByte []byte, validStruct interface{}) (msgs []string, err error) {
validate := validator.New()
err = json.Unmarshal(dataByte, validStruct)
if err != nil {
err = errors.New(errorcode.GetMsg(errorcode.ParamError, ""))
}
zh_ch := zh.New()
uni := ut.New(zh_ch)
trans, _ := uni.GetTranslator("zh")
//注册一个函数获取struct tag里自定义的label作为字段名
validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
name := fld.Tag.Get("label")
return name
})
//验证器注册翻译器
_ = zh_translations.RegisterDefaultTranslations(validate, trans)
errValidate := validate.Struct(validStruct)
if errValidate != nil {
// 如果有错误,打印出来
for _, v := range errValidate.(validator.ValidationErrors) {
msgs = append(msgs, v.Translate(trans))
}
err = errors.New(errorcode.GetMsg(errorcode.ParamError, ""))
return
}
return
}
// 重复读取body
func ReadBody(c *gin.Context) (body []byte, err error) {
body, err = ioutil.ReadAll(c.Request.Body)
@ -142,6 +195,28 @@ func GetRequest(c *gin.Context) interface{} {
return request
}
func GetAppCheckInfo(c *gin.Context) interface{} {
request, exists := c.Get("appCheckInfo")
if !exists {
return nil
}
return request
}
// 获取OutTradeNo
func GetOutTradeNo(c *gin.Context) string {
outTradeNo, _ := c.Get("OutTradeNo")
if outTradeNo == nil {
return ""
}
return outTradeNo.(string)
}
func GetApiLogId(c *gin.Context) (interface{}, bool) {
request, exists := c.Get("log")
return request, exists
}
func HandRes(c *gin.Context, data interface{}, err error) {
if err == nil {
Success(c, data, "")
@ -175,3 +250,40 @@ func Frequence(key string) bool {
return true
}
}
// 自定义验证器
func phoneValidation(fl validator.FieldLevel) bool {
phone := fl.Field().String()
if phone == "" {
return true
}
// 使用正则表达式验证手机号
phoneRegex := `^1[3-9]\d{9}$`
reg := regexp.MustCompile(phoneRegex)
return reg.MatchString(phone)
}
// 管理后台获取用户id
func GetAdminId(c *gin.Context) int {
userIdStr, _ := c.Get(common.ADMIN_USER_ID)
if userIdStr != nil {
var userId, _ = strconv.Atoi(userIdStr.(string))
return userId
}
return 0
}
// 获取对应数据权限中拥有用户数据ID
func GetAdminUserIncludeUsers(c *gin.Context) string {
name, _ := c.Get(common.ADMIN_USER_INCLUDEUSERS)
if name != nil {
var temp, _ = name.(string)
return temp
}
return ""
}
func ErrWithCode(c *gin.Context, code int) {
Error(c, code, errorcode.GetMsg(code, c.GetHeader("local")))
}

View File

@ -0,0 +1,82 @@
package front
import (
"PaymentCenter/app/constants/errorcode"
"PaymentCenter/app/http/controllers"
"PaymentCenter/app/http/entities/front"
"PaymentCenter/app/models/ordersmodel"
"PaymentCenter/app/services"
"PaymentCenter/app/services/thirdpay"
"github.com/gin-gonic/gin"
"xorm.io/builder"
)
func PayUrl(c *gin.Context) {
var res front.ApiResponse
req := controllers.GetRequest(c).(*front.PayReqs)
c.Set("OutTradeNo", req.OutTradeNo)
appCheckInfo := controllers.GetAppCheckInfo(c).(*services.AppCheck)
check := thirdpay.ThirdPayInfoCheck(c.Request.Context(), req, appCheckInfo, c.ClientIP())
if check.CheckCode != errorcode.Success {
controllers.ApiRes(c, nil, check.CheckCode)
return
}
check.CheckOrderPay()
if check.CheckCode != errorcode.Success {
controllers.ApiRes(c, nil, check.CheckCode)
return
}
pay := thirdpay.ThirdPayUrl(check)
if pay.PayCode != errorcode.Success {
controllers.ApiRes(c, nil, pay.PayCode)
return
}
res.Order = thirdpay.NewOrdersResp(pay.Order)
res.Url = pay.Url
controllers.ApiRes(c, res, pay.PayCode)
return
}
func Refund(c *gin.Context) {
req := controllers.GetRequest(c).(*front.RefundReqs)
c.Set("OutTradeNo", req.OutTradeNo)
appCheckInfo := controllers.GetAppCheckInfo(c).(*services.AppCheck)
refund := thirdpay.ThirdPayRefund(c.Request.Context(), req, appCheckInfo, c.ClientIP())
if refund.PayCode != errorcode.Success {
controllers.ApiRes(c, nil, refund.PayCode)
}
data := thirdpay.NewOrdersResp(refund.Order)
var res front.ApiResponse
res.Order = data
controllers.ApiRes(c, res, refund.PayCode)
return
}
// 查询订单
func QueryOrder(c *gin.Context) {
req := controllers.GetRequest(c).(*front.QueryReqs)
c.Set("OutTradeNo", req.OutTradeNo)
appCheckInfo := controllers.GetAppCheckInfo(c).(*services.AppCheck)
// 查询订单表
order := ordersmodel.Orders{
OutTradeNo: req.OutTradeNo,
AppId: req.AppId,
MerchantId: appCheckInfo.App.MerchantId,
}
cond := builder.NewCond()
cond = cond.And(builder.Eq{"out_trade_no": order.OutTradeNo}, builder.Eq{"app_id": order.AppId})
_, code := services.OrderFindOne(&order, cond)
if code != errorcode.Success {
controllers.ApiRes(c, nil, code)
return
}
data := thirdpay.NewOrdersResp(&order)
var res front.ApiResponse
res.Order = data
controllers.ApiRes(c, res, errorcode.Success)
}

View File

@ -0,0 +1,137 @@
package front
import (
"PaymentCenter/app/constants/common"
"PaymentCenter/app/constants/errorcode"
"PaymentCenter/app/models/paychannelmodel"
"PaymentCenter/app/services"
"PaymentCenter/app/third/paymentService"
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/alipay"
"github.com/go-pay/gopay/wechat/v3"
"github.com/qit-team/snow-core/log/logger"
"strconv"
"net/http"
)
// WxCallback 微信支付回调
func WxCallback(c *gin.Context) {
logger.Info(c, "WxCallback-回调数据", c.Request)
payChannelId := c.Param("payChannelId")
logger.Info(c, "WxCallback-回调数据payChannelId", payChannelId)
if payChannelId == "" {
c.String(http.StatusBadRequest, "%s", "fail")
return
}
// 查询应用下的支付配置
var payChannelModel paychannelmodel.PayChannel
payChannelIdInt, _ := strconv.Atoi(payChannelId)
payChannelModel.Id = int64(payChannelIdInt)
code := services.PayChannelGet(&payChannelModel)
if code == errorcode.PayChannelNotFound {
logger.Error(c, "AliCallback-回调数据未获取到支付配置404")
c.String(http.StatusBadRequest, "%s", "fail")
return
}
if payChannelModel.ChannelType != common.PAY_CHANNEL_WECHAT_H5 {
logger.Error(c, "WxCallback-回调数据解析支付配置错误,查询的数据不是当前渠道")
c.String(http.StatusBadRequest, "%s", "fail")
return
}
var wxConfig paymentService.WxPay
err := json.Unmarshal([]byte(payChannelModel.ExtJson), &wxConfig)
if err != nil {
logger.Error(c, "WxCallback-回调数据解析支付配置错误", fmt.Sprintf("错误原因:%s", err.Error()))
c.String(http.StatusBadRequest, "%s", "fail")
return
}
if wxConfig.ApiV3Key == "" || wxConfig.MchId == "" || wxConfig.PrivateKey == "" || wxConfig.SerialNo == "" {
logger.Error(c, "WxCallback-回调数据解析支付配置错误,解析出来的信息为空")
c.String(http.StatusBadRequest, "%s", "fail")
return
}
wxConfig.AppId = payChannelModel.AppId
notifyReq, err := wechat.V3ParseNotify(c.Request)
if err != nil {
logger.Error(c, "WxCallback-回调数据验签失败", err.Error())
c.String(http.StatusBadRequest, "%s", "fail")
return
}
notifyReqJson, _ := json.Marshal(notifyReq)
logger.Info(c, "WxCallback-解析微信回调请求的参数到 V3NotifyReq 结构体", string(notifyReqJson))
err = paymentService.WxPayCallBack(notifyReq, wxConfig)
if err != nil {
logger.Error(c, "WxCallback-回调执行失败,失败原因:", err.Error())
c.String(http.StatusBadRequest, "%s", "fail")
return
}
c.JSON(http.StatusOK, &wechat.V3NotifyRsp{Code: gopay.SUCCESS, Message: "成功"})
return
}
// AliCallback 支付宝支付回调
func AliCallback(c *gin.Context) {
logger.Info(c, "AliCallback-回调数据", c.Request)
payChannelId := c.Param("payChannelId")
logger.Info(c, "AliCallback-回调数据APPID", payChannelId)
if payChannelId == "" {
c.String(http.StatusBadRequest, "%s", "fail")
return
}
// 查询应用下的支付配置
var payChannelModel paychannelmodel.PayChannel
payChannelIdInt, _ := strconv.Atoi(payChannelId)
payChannelModel.Id = int64(payChannelIdInt)
code := services.PayChannelGet(&payChannelModel)
if code == errorcode.PayChannelNotFound {
logger.Error(c, "AliCallback-回调数据未获取到支付配置404")
c.String(http.StatusBadRequest, "%s", "fail")
return
}
if payChannelModel.ChannelType != common.PAY_CHANNEL_ALIPAY_WEB {
logger.Error(c, "AliCallback-回调数据解析支付配置错误,查询的数据不是当前渠道")
c.String(http.StatusBadRequest, "%s", "fail")
return
}
var aliConfig paymentService.AliPay
err := json.Unmarshal([]byte(payChannelModel.ExtJson), &aliConfig)
if err != nil {
logger.Error(c, "AliCallback-回调数据解析支付配置错误", fmt.Sprintf("错误原因:%s", err.Error()))
c.String(http.StatusBadRequest, "%s", "fail")
return
}
if aliConfig.AlipayPublicCert == "" || aliConfig.PrivateKey == "" || aliConfig.AppPublicCert == "" || aliConfig.AlipayRootCert == "" {
logger.Error(c, "AliCallback-回调数据解析支付配置错误,解析出来的信息为空")
c.String(http.StatusBadRequest, "%s", "fail")
return
}
aliConfig.AppId = payChannelModel.AppId
notifyReq, err := alipay.ParseNotifyToBodyMap(c.Request) // c.Request 是 gin 框架的写法
if err != nil {
c.String(http.StatusBadRequest, "%s", "fail")
return
}
notifyReqJson, _ := json.Marshal(notifyReq)
logger.Info(c, "AliCallback-解析支付宝支付异步通知的参数到BodyMap", string(notifyReqJson))
err = paymentService.ALiCallBack(notifyReq, aliConfig)
if err != nil {
logger.Error(c, "AliCallback-回调执行失败,失败原因:", err.Error())
c.String(http.StatusBadRequest, "%s", "fail")
return
}
c.String(http.StatusOK, "%s", "success")
return
}

View File

@ -1,12 +0,0 @@
package domains
type Filter struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
}
type ProductFilter struct {
Page int `json:"page" form:"page"`
PageSize int `json:"page_size" form:"page_size"`
ProductName string `json:"product_name" form:"product_name"`
}

View File

@ -1,23 +0,0 @@
package domains
import "time"
type Product struct {
Id int
ProductName string
ProductType int
PacketRule int
ProductKind int
Amount string
CouponType int
IsSuperposition int
TemplateId int
Status int
IsNeedBill int
CreateTime time.Time
SupplierProductId int64
SupplierProductName string
Creator string
UpdateTime time.Time
}

View File

@ -0,0 +1,115 @@
package backend
import (
"PaymentCenter/app/http/entities"
"PaymentCenter/app/models/appmodel"
"errors"
"strings"
)
type AppListRequest struct {
Id int64 `json:"id" form:"id"`
MerchantId int64 `json:"merchant_id" form:"merchant_id" validate:"required" label:"商户ID"`
entities.PageRequest
}
type AppResponse struct {
Id int64 `json:"id"`
MerchantId int64 `json:"merchant_id"`
AppName string `json:"app_name"`
AppRemark string `json:"app_remark"`
Status int32 `json:"status"`
KeyType int32 `json:"key_type"`
PublicKey string `json:"public_key"`
PrivateKey string `json:"private_key"`
MerchantPublicKey string `json:"merchant_public_key"`
CreateTime string `json:"create_time"`
WhiteIp string `json:"white_ip"`
NotifyUrl string `json:"notify_url"`
}
func (a *AppResponse) ResponseFromDb(db appmodel.App) {
a.Id = db.Id
a.MerchantId = db.MerchantId
a.AppName = db.AppName
a.AppRemark = db.AppRemark
a.Status = db.Status
a.KeyType = db.KeyType
a.PublicKey = db.PublicKey
a.PrivateKey = db.PrivateKey
a.MerchantPublicKey = db.MerchantPublicKey
a.CreateTime = db.CreateTime.Format("2006-01-02 15:04:05")
a.WhiteIp = db.WhiteIp
a.NotifyUrl = db.NotifyUrl
}
type AppCreateRequest struct {
MerchantId int64 `json:"merchant_id" validate:"required" label:"商户ID"`
AppName string `json:"app_name" validate:"required" label:"应用名称"`
AppRemark string `json:"app_remark" label:"应用备注"`
Status int32 `json:"status" validate:"oneof=0 1 2" label:"应用状态"`
KeyType int32 `json:"key_type" validate:"required" label:"应用密钥类型"`
PublicKey string `json:"public_key" validate:"required" label:"应用公钥"`
PrivateKey string `json:"private_key" validate:"required" label:"应用私钥"`
MerchantPublicKey string `json:"merchant_public_key" label:"商户公钥"`
WhiteIp string `json:"white_ip"`
NotifyUrl string `json:"notify_url" validate:"required" label:"通知地址"`
}
func (a *AppCreateRequest) RequestToDb() (db appmodel.App, err error) {
// 判断通知地址url是否合法
if a.NotifyUrl == "" || strings.HasPrefix(a.NotifyUrl, "http://") || strings.HasPrefix(a.NotifyUrl, "https://") {
db.MerchantId = a.MerchantId
db.AppName = a.AppName
db.AppRemark = a.AppRemark
db.Status = a.Status
db.KeyType = a.KeyType
db.PublicKey = a.PublicKey
db.PrivateKey = a.PrivateKey
db.MerchantPublicKey = a.MerchantPublicKey
db.WhiteIp = a.WhiteIp
db.NotifyUrl = a.NotifyUrl
} else {
err = errors.New("通知地址格式不正确")
return
}
return
}
type AppUpdateRequest struct {
Id int64 `json:"id" validate:"required" label:"应用ID"`
AppName string `json:"app_name" label:"应用名称"`
AppRemark string `json:"app_remark" label:"应用备注"`
Status int32 `json:"status" label:"应用状态"`
KeyType int32 `json:"key_type" label:"应用密钥类型"`
PublicKey string `json:"public_key" label:"应用公钥"`
PrivateKey string `json:"private_key" label:"应用私钥"`
MerchantPublicKey string `json:"merchant_public_key" label:"商户公钥"`
WhiteIp string `json:"white_ip"`
NotifyUrl string `json:"notify_url"`
}
func (a *AppUpdateRequest) RequestToDb() (db appmodel.App, err error) {
// 判断通知地址url是否合法
if a.NotifyUrl == "" || strings.HasPrefix(a.NotifyUrl, "http://") || strings.HasPrefix(a.NotifyUrl, "https://") {
db.Id = a.Id
db.AppName = a.AppName
db.AppRemark = a.AppRemark
db.Status = a.Status
db.KeyType = a.KeyType
db.PublicKey = a.PublicKey
db.PrivateKey = a.PrivateKey
db.MerchantPublicKey = a.MerchantPublicKey
db.WhiteIp = a.WhiteIp
db.NotifyUrl = a.NotifyUrl
} else {
err = errors.New("通知地址格式不正确")
return
}
return
}
type GenerateDecryptKeyRequest struct {
KeyType int32 `json:"key_type" form:"key_type" label:"密钥类型"`
}

View File

@ -0,0 +1,66 @@
package backend
import (
"PaymentCenter/app/http/entities"
"PaymentCenter/app/models/merchantmodel"
)
type MerchantListRequest struct {
entities.PageRequest
Name string `form:"name"`
Contact string `form:"contact"`
Phone string `form:"phone"`
GroupInfo string
}
type MerchantResponse struct {
Id int64 `json:"id"`
Name string `json:"name"`
Contact string `json:"contact"`
Phone string `json:"phone"`
Remark string `json:"remark"`
CreateTime string `json:"create_time"`
}
func (m *MerchantResponse) ResponseFromDb(db merchantmodel.Merchant) {
m.Id = db.Id
m.Name = db.Name
m.Contact = db.Contact
m.Phone = db.Phone
m.Remark = db.Remark
m.CreateTime = db.CreateTime.Format("2006-01-02 15:04:05")
}
type MerchantCreateRequest struct {
Name string `json:"name" validate:"required" label:"商户名称"`
Contact string `json:"contact" validate:"required" label:"联系人"`
Phone string `json:"phone" validate:"required,phoneValidation" label:"联系电话"`
Remark string `json:"remark" label:"备注"`
Creator int
}
func (m *MerchantCreateRequest) RequestToDb() (db merchantmodel.Merchant) {
db.Name = m.Name
db.Contact = m.Contact
db.Phone = m.Phone
db.Remark = m.Remark
db.Creator = m.Creator
return db
}
type MerchantUpdateRequest struct {
Id int64 `json:"id" validate:"required" label:"商户ID"`
Name string `json:"name"`
Contact string `json:"contact"`
Phone string `json:"phone" validate:"phoneValidation" label:"联系电话"`
Remark string `json:"remark"`
}
func (m *MerchantUpdateRequest) RequestToDb() (db merchantmodel.Merchant) {
db.Id = m.Id
db.Name = m.Name
db.Contact = m.Contact
db.Phone = m.Phone
db.Remark = m.Remark
return db
}

View File

@ -0,0 +1,173 @@
package backend
import (
"PaymentCenter/app/http/entities"
"PaymentCenter/app/models/ordercallbacklogmodel"
"PaymentCenter/app/models/orderrequestlogmodel"
"PaymentCenter/app/models/ordersmodel"
"PaymentCenter/app/models/orderthirdpaylogmodel"
"PaymentCenter/app/utils"
"github.com/pkg/errors"
"time"
)
type OrderListRequest struct {
Id int64 `json:"id" form:"id"`
MerchantId int64 `json:"merchant_id" form:"merchant_id"`
PayChannelId int64 `json:"pay_channel_id" form:"pay_channel_id"`
AppId int64 `json:"app_id" form:"app_id"`
OutTradeNo string `json:"out_trade_no" form:"out_trade_no"`
Status int `json:"status" form:"status"`
OrderType int `json:"order_type" form:"order_type"`
StartTime string `json:"start_time" form:"start_time"`
EndTime string `json:"end_time" form:"end_time"`
entities.PageRequest
}
type OrderList struct {
Id int64 `json:"id"`
MerchantId int64 `json:"merchant_id"`
PayChannelId int64 `json:"pay_channel_id"`
AppId int64 `json:"app_id"`
OutTradeNo string `json:"out_trade_no"`
Status int `json:"status"`
OrderType int `json:"order_type"`
StartTime time.Time `json:"start_time"`
EndTime time.Time `json:"end_time"`
entities.PageRequest
}
func (o *OrderListRequest) ValidateRequest() (r OrderList, err error) {
if o.StartTime != "" {
r.StartTime, err = utils.StrToTimeShanghai(o.StartTime)
if err != nil {
err = errors.Wrap(err, "时间格式错误")
return
}
}
if o.EndTime != "" {
r.EndTime, err = utils.StrToTimeShanghai(o.EndTime)
if err != nil {
err = errors.Wrap(err, "时间格式错误")
return
}
}
r.Id = o.Id
r.MerchantId = o.MerchantId
r.PayChannelId = o.PayChannelId
r.AppId = o.AppId
r.OutTradeNo = o.OutTradeNo
r.Status = o.Status
r.OrderType = o.OrderType
r.PageRequest = o.PageRequest
r.SetDefault()
return
}
type OrdersResponse struct {
Id int64 `json:"id"`
MerchantId int64 `json:"merchant_id"`
PayChannelId int64 `json:"pay_channel_id"`
AppId int64 `json:"app_id"`
OutTradeNo string `json:"out_trade_no"`
Status int `json:"status"`
OrderType int `json:"order_type"`
Amount int `json:"amount"`
PayerTotal int `json:"payer_total"`
ExtJson string `json:"ext_json"`
CreateTime string `json:"create_time"`
UpdateTime string `json:"update_time"`
MerchantName string `json:"merchant_name"`
PayName string `json:"pay_name"`
AppName string `json:"app_name"`
}
func (o *OrdersResponse) ResponseFromDb(db ordersmodel.OrdersBackendList) {
o.Id = db.Id
o.MerchantId = db.MerchantId
o.PayChannelId = db.PayChannelId
o.AppId = db.AppId
o.OutTradeNo = db.OutTradeNo
o.Status = db.Status
o.OrderType = db.OrderType
o.Amount = db.Amount
o.PayerTotal = db.PayerTotal
o.ExtJson = db.ExtJson
o.CreateTime = db.CreateTime.Format("2006-01-02 15:04:05")
o.UpdateTime = db.UpdateTime.Format("2006-01-02 15:04:05")
o.MerchantName = db.MerchantName
o.PayName = db.PayName
o.AppName = db.AppName
}
type OrderLogsListRequest struct {
Id int64 `json:"id" form:"id"`
OrderId int64 `json:"order_id" form:"order_id"`
AppId int64 `json:"app_id" form:"app_id"`
OutTradeNo string `json:"out_trade_no" form:"out_trade_no"`
entities.PageRequest
}
type OrderRequestLogResponse struct {
Id int64 `json:"id"`
AppId int64 `json:"app_id"`
OutTradeNo string `json:"out_trade_no"`
IpAddress string `json:"ip_address"`
Url string `json:"url"`
MerchantRequest string `json:"merchant_request"`
MerchantResponse string `json:"merchant_response"`
CreateTime string `json:"create_time"`
Status int `json:"status"`
}
func (o *OrderRequestLogResponse) ResponseFromDb(db orderrequestlogmodel.OrderRequestLog) {
o.Id = db.Id
o.AppId = db.AppId
o.OutTradeNo = db.OutTradeNo
o.IpAddress = db.IpAddress
o.Url = db.URL
o.Status = db.Status
o.MerchantRequest = db.MerchantRequest
o.MerchantResponse = db.MerchantResponse
o.CreateTime = db.CreateTime.Format("2006-01-02 15:04:05")
}
type OrderThirdLogResponse struct {
Id int64 `json:"id"`
OrderId int64 `json:"order_id"`
CreateTime string `json:"create_time"`
PayCallback string `json:"pay_callback"`
Status int `json:"status"`
PayParam string `json:"pay_param"`
MerchantCallback string `json:"merchant_callback"`
}
func (o *OrderThirdLogResponse) ResponseFromDb(db orderthirdpaylogmodel.OrderThirdPayLog) {
o.Id = db.Id
o.OrderId = db.OrderId
o.PayCallback = db.PayCallback
o.Status = db.Status
o.PayParam = db.PayParam
o.MerchantCallback = db.MerchantCallback
o.CreateTime = db.CreateTime.Format("2006-01-02 15:04:05")
}
type OrderCallbackLogResponse struct {
Id int64 `json:"id"`
OrderId int64 `json:"order_id"`
MerchantRequest string `json:"merchant_request"`
Status int `json:"status"`
MerchantResponse string `json:"merchant_response"`
CreateTime string `json:"create_time"`
}
func (o *OrderCallbackLogResponse) ResponseFromDb(db ordercallbacklogmodel.OrderCallbackLog) {
o.Id = db.Id
o.OrderId = db.OrderId
o.MerchantRequest = db.MerchantRequest
o.Status = db.Status
o.MerchantResponse = db.MerchantResponse
o.CreateTime = db.CreateTime.Format("2006-01-02 15:04:05")
}

View File

@ -0,0 +1,135 @@
package backend
import (
"PaymentCenter/app/constants/common"
"PaymentCenter/app/http/entities"
"PaymentCenter/app/models/paychannelmodel"
"PaymentCenter/app/utils"
"encoding/json"
"github.com/pkg/errors"
)
type PayChannelResponse struct {
Id int64 `json:"id"`
PayName string `json:"pay_name"`
MerchantId int64 `json:"merchant_id"`
ChannelType int `json:"channel_type"`
AppId string `json:"app_id"`
ExpireTime string `json:"expire_time"`
CreateTime string `json:"create_time"`
AliPayChannel *AliPayChannel `json:"ali_pay_channel,omitempty"`
WechatPayChannel *WechatPayChannel `json:"wechat_pay_channel,omitempty"`
}
func (p *PayChannelResponse) ResponseFromDb(db paychannelmodel.PayChannel) {
p.Id = db.Id
p.PayName = db.PayName
p.MerchantId = db.MerchantId
p.ChannelType = db.ChannelType
p.AppId = db.AppId
if db.ExpireTime.IsZero() {
p.ExpireTime = ""
} else {
p.ExpireTime = db.ExpireTime.Format("2006-01-02 15:04:05")
}
if db.CreateTime.IsZero() {
p.CreateTime = ""
} else {
p.CreateTime = db.CreateTime.Format("2006-01-02 15:04:05")
}
switch db.ChannelType {
case common.PAY_CHANNEL_WECHAT_H5, common.PAY_CHANNEL_WECHAT_JSAPI, common.PAY_CHANNEL_WECHAT_NATIVE, common.PAY_CHANNEL_WECHAT_APP, common.PAY_CHANNEL_WECHAT_MINI:
_ = json.Unmarshal([]byte(db.ExtJson), &p.WechatPayChannel)
case common.PAY_CHANNEL_ALIPAY_JSAPI, common.PAY_CHANNEL_ALIPAY_WEB, common.PAY_CHANNEL_ALIPAY_MINI:
_ = json.Unmarshal([]byte(db.ExtJson), &p.AliPayChannel)
}
}
type PayChannelCreateRequest struct {
PayName string `json:"pay_name" validate:"required" label:"支付渠道名称"`
MerchantId int64 `json:"merchant_id" validate:"required" label:"商户ID"`
ChannelType int `json:"channel_type" validate:"required" label:"支付渠道"` //支付渠道枚举,1微信JSAPI2微信H53微信app4微信Native5微信小程序6支付宝网页&移动应用7支付宝小程序8支付宝JSAPI
AppId string `json:"app_id" validate:"required" label:"应用appId"`
ExpireTime string `json:"expire_time"`
AliPayChannel AliPayChannel `json:"ali_pay_channel,omitempty"`
WechatPayChannel WechatPayChannel `json:"wechat_pay_channel,omitempty"`
}
type WechatPayChannel struct {
MchId string `json:"mch_id"` // 商户ID 或者服务商模式的 sp_mchid
SerialNo string `json:"serial_no"` // 商户证书的证书序列号
ApiV3Key string `json:"api_v_3_key"` // apiV3Key商户平台获取
PrivateKey string `json:"private_key"` // 私钥 apiclient_key.pem 读取后的内容
}
type AliPayChannel struct {
PrivateKey string `json:"private_key"` // 应用私钥
AppPublicCert string `json:"app_public_cert"` // 应用公钥
AlipayRootCert string `json:"alipay_root_cert"` // 支付宝根证书
AlipayPublicCert string `json:"alipay_public_cert"` // 支付宝公钥
}
func (p *PayChannelCreateRequest) RequestToDb() (db paychannelmodel.PayChannel, err error) {
db.PayName = p.PayName
db.MerchantId = p.MerchantId
db.ChannelType = p.ChannelType
db.AppId = p.AppId
if p.ExpireTime != "" {
db.ExpireTime, err = utils.StrToTimeShanghai(p.ExpireTime)
if err != nil {
err = errors.Wrap(err, "时间格式错误")
}
}
switch p.ChannelType {
case common.PAY_CHANNEL_WECHAT_H5, common.PAY_CHANNEL_WECHAT_JSAPI, common.PAY_CHANNEL_WECHAT_NATIVE, common.PAY_CHANNEL_WECHAT_APP, common.PAY_CHANNEL_WECHAT_MINI:
b, _ := json.Marshal(p.WechatPayChannel)
db.ExtJson = string(b)
case common.PAY_CHANNEL_ALIPAY_JSAPI, common.PAY_CHANNEL_ALIPAY_WEB, common.PAY_CHANNEL_ALIPAY_MINI:
b, _ := json.Marshal(p.AliPayChannel)
db.ExtJson = string(b)
default:
err = errors.New("支付渠道类型错误")
}
return
}
type PayChannelListRequest struct {
Id int64 `json:"id" form:"id" `
MerchantId int64 `json:"merchant_id" form:"merchant_id" validate:"required" label:"商户ID"`
entities.PageRequest
}
type PayChannelUpdateRequest struct {
Id int64 `json:"id" validate:"required"`
PayName string `json:"pay_name" validate:"required" label:"支付渠道名称"`
ChannelType int `json:"channel_type" validate:"required" label:"支付渠道"` //支付渠道枚举,1微信JSAPI2微信H53微信app4微信Native5微信小程序6支付宝网页&移动应用7支付宝小程序8支付宝JSAPI
AppId string `json:"app_id" validate:"required" label:"应用appId"`
ExpireTime string `json:"expire_time"`
AliPayChannel AliPayChannel `json:"ali_pay_channel,omitempty"`
WechatPayChannel WechatPayChannel `json:"wechat_pay_channel,omitempty"`
}
func (p PayChannelUpdateRequest) RequestToDb() (db paychannelmodel.PayChannel, err error) {
db.Id = p.Id
db.PayName = p.PayName
db.ChannelType = p.ChannelType
db.AppId = p.AppId
if p.ExpireTime != "" {
db.ExpireTime, err = utils.StrToTimeShanghai(p.ExpireTime)
if err != nil {
err = errors.Wrap(err, "时间格式错误")
}
}
switch p.ChannelType {
case common.PAY_CHANNEL_WECHAT_H5, common.PAY_CHANNEL_WECHAT_JSAPI, common.PAY_CHANNEL_WECHAT_NATIVE, common.PAY_CHANNEL_WECHAT_APP, common.PAY_CHANNEL_WECHAT_MINI:
b, _ := json.Marshal(p.WechatPayChannel)
db.ExtJson = string(b)
case common.PAY_CHANNEL_ALIPAY_JSAPI, common.PAY_CHANNEL_ALIPAY_WEB, common.PAY_CHANNEL_ALIPAY_MINI:
b, _ := json.Marshal(p.AliPayChannel)
db.ExtJson = string(b)
default:
err = errors.New("支付渠道类型错误")
}
return
}

View File

@ -1,15 +1,24 @@
package entities
type IdRequest struct {
Id int64 `json:"id"`
Id int64 `json:"id" form:"id" valid:"Required"`
}
type PageRequest struct {
Page int64 `json:"page"`
PageSize int64 `json:"pageSize"`
Page int `json:"page" form:"page"`
PageSize int `json:"page_size" form:"page_size"`
}
func (p *PageRequest) SetDefault() {
if p.Page == 0 {
p.Page = 1
}
if p.PageSize == 0 {
p.PageSize = 10
}
}
type PageRsp struct {
Total int64 `json:"total"`
Data []interface{} `json:"data"`
Total int64 `json:"total"`
Data interface{} `json:"data"`
}

View File

@ -0,0 +1,49 @@
package front
type ApiCommonBody struct {
AppId int64 `json:"app_id" validate:"required"`
Timestamp int64 `json:"timestamp" validate:"required"`
}
type RequestBody struct {
AppId int64 `json:"app_id" validate:"required"`
Timestamp int64 `json:"timestamp" validate:"required"`
Data string `json:"data" validate:"required"`
Key string `json:"key" validate:"max=32"`
}
type PayCommonReqBody struct {
ApiCommonBody
PayChannelId int64 `json:"pay_channel_id" validate:"required" label:"支付渠道"`
Amount int `json:"amount" validate:"required" label:"支付金额,单位分"`
ExtJson string `json:"ext_json" label:"扩展参数"`
Desc string `json:"desc" validate:"max=100" label:"商品描述"`
}
type PayReqs struct {
PayCommonReqBody
OutTradeNo string `json:"out_trade_no" validate:"required" label:"外侧商户订单号"`
}
type RefundReqs struct {
PayCommonReqBody
RefundOutTradeNo string `json:"refund_out_trade_no" label:"需要退款的外侧商户订单号"`
RefundOrderId string `json:"refundOrder_id" label:"需要退款的平台订单号"`
OutTradeNo string `json:"out_trade_no" validate:"required" label:"外侧商户订单号"`
}
type PayUrlResp struct {
Order string `json:"order"`
Url string `json:"url"`
}
type QueryReqs struct {
ApiCommonBody
OutTradeNo string `json:"out_trade_no" validate:"required" label:"外侧商户订单号"`
}
// api 接口返回数据, 统一返回结构, order数据会进行加密
type ApiResponse struct {
Order interface{} `json:"order"`
Url string `json:"url,omitempty"`
}

View File

@ -0,0 +1 @@
package front

View File

@ -4,12 +4,14 @@ import (
"PaymentCenter/app/constants/common"
"PaymentCenter/app/constants/errorcode"
"PaymentCenter/app/http/controllers"
"PaymentCenter/app/http/entities/front"
"PaymentCenter/app/http/requestmapping"
"PaymentCenter/app/services"
"PaymentCenter/app/services/thirdpay/api"
"PaymentCenter/app/utils"
"context"
"errors"
"PaymentCenter/config"
"encoding/json"
"github.com/gin-gonic/gin"
"github.com/qit-team/snow-core/redis"
"strings"
)
@ -59,20 +61,40 @@ func Cors() gin.HandlerFunc {
func AdminAuth() gin.HandlerFunc {
return func(c *gin.Context) {
var token = c.GetHeader("token")
//将token放入redis
var playerId, err = redis.GetRedis().Get(context.Background(), utils.GetRealKey(common.TOKEN_Admin+token)).Result()
if rs, errRedis := redis.GetRedis().SIsMember(context.Background(), "disabled_uids", playerId).Result(); errRedis == nil && rs {
err = errors.New(errorcode.GetMsg(errorcode.NotFound, ""))
redis.GetRedis().SRem(context.Background(), "disabled_uids", playerId)
}
if err == nil {
c.Set("playerId", playerId)
c.Next()
return
} else {
controllers.HandCodeRes(c, nil, errorcode.Forbidden)
clientIp := c.ClientIP()
utils.Log(c, "请求地址clientIp", clientIp, config.GetConf().AdminGate)
if config.GetConf().Debug == false && !utils.SliceInStr(clientIp, config.GetConf().AdminGate) {
c.Abort()
controllers.HandCodeRes(c, nil, errorcode.Forbidden)
return
}
var userName = c.GetHeader("User-Name")
if userName != "" {
c.Set(common.ADMIN_USER_NAME, userName)
}
var IncludeUsers = c.GetHeader("Include-Users")
if IncludeUsers != "" {
c.Set(common.ADMIN_USER_INCLUDEUSERS, IncludeUsers)
}
var adminId = c.GetHeader("User-Id")
// 测试环境直接放行
if config.GetConf().Debug == true {
c.Set(common.ADMIN_USER_ID, adminId)
c.Next()
} else {
utils.Log(c, "请求header信息", "adminId="+adminId, "IncludeUsers="+IncludeUsers)
// 正式环境校验
if adminId != "" {
c.Set(common.ADMIN_USER_ID, adminId)
c.Next()
} else {
c.Abort()
controllers.HandCodeRes(c, nil, errorcode.NotAuth)
return
}
}
}
}
@ -81,25 +103,98 @@ func ValidateRequest() gin.HandlerFunc {
return func(c *gin.Context) {
var path = c.FullPath()
var handler func() interface{}
if strings.Index(path, "admin") >= 0 {
handler = requestmapping.BackendRequestMap[path]
} else {
handler = requestmapping.FrontRequestMap[path]
handler = requestmapping.FrontRequestMapBeforeDecrypt[path]
}
if handler == nil {
utils.Log(c, "path", path)
utils.Log(c, "path", path, "未找到handler")
controllers.HandCodeRes(c, nil, errorcode.NotFound)
} else {
v := handler()
msg, err := controllers.GenRequest(c, v)
if err != nil {
utils.Log(c, "path", path)
controllers.Error(c, errorcode.ParamError, msg...)
} else {
c.Set("request", v)
c.Next()
}
return
}
v := handler()
msg, err := controllers.GenRequest(c, v)
if err != nil {
utils.Log(c, "参数错误", "path=", path, "err=", err.Error(), "msg=", msg)
controllers.Error(c, errorcode.ParamError, msg...)
c.Abort()
}
c.Set("request", v)
c.Next()
}
}
func ValidatePayRequest() gin.HandlerFunc {
return func(c *gin.Context) {
var (
path = c.FullPath()
code int
log_id int64
)
var handler func() (interface{}, bool)
requestData, err := utils.SonicApiDataToStruct(controllers.GetRequest(c), &front.RequestBody{})
if err != nil {
controllers.ApiRes(c, nil, errorcode.ParamError)
return
}
requestDataStruct := requestData.(*front.RequestBody)
//判断时间
//now := time.Now().UnixNano() / 1000000
//if requestDataStruct.Timestamp > now || (config.GetConf().TimeOut != 0 && (now-requestDataStruct.Timestamp) > config.GetConf().TimeOut) {
// controllers.ApiRes(c, nil, errorcode.RequestTimeOut)
// return
//}
//获取app信息
appCheck := services.GetAppCheck(requestDataStruct.AppId, c.ClientIP())
//存入请求记录
if appCheck.Code != errorcode.Success {
controllers.ApiRes(c, nil, appCheck.Code)
return
}
//解密
dataByte, errCode := api.DeCrypt(appCheck.App, requestDataStruct.Data, requestDataStruct.Key)
if errCode != errorcode.Success {
controllers.ApiRes(c, nil, errCode)
return
}
//检查解密后的数据是否与请求一致
reCheck := appCheck.ReCheckAfterDecrypt(dataByte, requestDataStruct)
if !reCheck {
controllers.ApiRes(c, nil, appCheck.GetCode())
return
}
//表单验证
handler = requestmapping.FrontRequestMap[path]
v, isSaveLog := handler()
if isSaveLog {
//记录请求日志
log_id, code = services.AddRequestLog(dataByte, c.ClientIP(), path)
if code != errorcode.Success {
controllers.ApiRes(c, nil, errCode)
}
}
c.Set("log", log_id)
msg, err := controllers.ValidApiData(dataByte, v)
if err != nil {
utils.Log(c, "参数错误", "path=", path, "err=", err.Error(), "msg=", msg)
controllers.ApiRes(c, nil, errorcode.ParamError, msg...)
c.Abort()
}
err = json.Unmarshal(dataByte, &v)
if err != nil {
controllers.ApiRes(c, nil, errorcode.Forbidden)
return
}
c.Set("request", v)
c.Set("appCheckInfo", appCheck)
c.Next()
}
}

View File

@ -1,8 +1,32 @@
package requestmapping
import (
"PaymentCenter/app/constants/common"
"PaymentCenter/app/http/entities"
"PaymentCenter/app/http/entities/backend"
)
var BackendRequestMap = map[string]func() interface{}{
//common.ADMIN_V1 + "/product/create": func() interface{} {
// return new(backend.ProductCreateRequest)
//},
// 商户
common.ADMIN_V1 + "/merchant/create": func() interface{} { return new(backend.MerchantCreateRequest) },
common.ADMIN_V1 + "/merchant/update": func() interface{} { return new(backend.MerchantUpdateRequest) },
common.ADMIN_V1 + "/merchant/list": func() interface{} { return new(backend.MerchantListRequest) },
common.ADMIN_V1 + "/merchant/delete": func() interface{} { return new(entities.IdRequest) },
// 支付方式
common.ADMIN_V1 + "/paychannel/create": func() interface{} { return new(backend.PayChannelCreateRequest) },
common.ADMIN_V1 + "/paychannel/update": func() interface{} { return new(backend.PayChannelUpdateRequest) },
common.ADMIN_V1 + "/paychannel/list": func() interface{} { return new(backend.PayChannelListRequest) },
common.ADMIN_V1 + "/paychannel/delete": func() interface{} { return new(entities.IdRequest) },
// 应用
common.ADMIN_V1 + "/app/create": func() interface{} { return new(backend.AppCreateRequest) },
common.ADMIN_V1 + "/app/update": func() interface{} { return new(backend.AppUpdateRequest) },
common.ADMIN_V1 + "/app/list": func() interface{} { return new(backend.AppListRequest) },
common.ADMIN_V1 + "/app/delete": func() interface{} { return new(entities.IdRequest) },
common.ADMIN_V1 + "/app/decrypt": func() interface{} { return new(backend.GenerateDecryptKeyRequest) },
// 订单
common.ADMIN_V1 + "/order/list": func() interface{} { return new(backend.OrderListRequest) },
common.ADMIN_V1 + "/order/log/request": func() interface{} { return new(backend.OrderLogsListRequest) },
common.ADMIN_V1 + "/order/log/callback": func() interface{} { return new(backend.OrderLogsListRequest) },
common.ADMIN_V1 + "/order/log/third": func() interface{} { return new(backend.OrderLogsListRequest) },
}

View File

@ -1,7 +1,19 @@
package requestmapping
var FrontRequestMap = map[string]func() interface{}{
//"/v1/login": func() interface{} {
// return new(front.LoginRequest)
//},
import (
"PaymentCenter/app/constants/common"
"PaymentCenter/app/http/entities/front"
)
var FrontRequestMap = map[string]func() (validForm interface{}, isSaveLog bool){
common.FRONT_V1 + "/pay/url": func() (interface{}, bool) { return new(front.PayReqs), true },
common.FRONT_V1 + "/pay/refund": func() (interface{}, bool) { return new(front.RefundReqs), true },
common.FRONT_V1 + "/pay/query": func() (interface{}, bool) { return new(front.QueryReqs), false },
}
var FrontRequestMapBeforeDecrypt = map[string]func() interface{}{
common.FRONT_V1 + "/pay/url": func() interface{} { return new(front.RequestBody) },
common.FRONT_V1 + "/pay/refund": func() interface{} { return new(front.RequestBody) },
common.FRONT_V1 + "/pay/query": func() interface{} { return new(front.RequestBody) },
}

View File

@ -2,6 +2,7 @@ package routes
import (
"PaymentCenter/app/http/controllers"
"PaymentCenter/app/http/controllers/backend"
"PaymentCenter/app/http/middlewares"
"PaymentCenter/app/http/trace"
"PaymentCenter/app/utils"
@ -22,9 +23,36 @@ func RegisterAdminRoute(router *gin.Engine) {
}
}
//v1 := router.Group("/admin/api/v1")
//{
//
//}
v1 := router.Group("/pay/admin/api/v1", middlewares.AdminAuth(), middlewares.ValidateRequest())
{
// 商户管理
merchant := v1.Group("/merchant")
merchant.GET("/list", backend.MerchantList) // 商户列表
merchant.POST("/create", backend.MerchantCreate) // 商户创建
merchant.PUT("/update", backend.MerchantUpdate) // 商户更新
merchant.DELETE("/delete", backend.MerchantDelete) // 商户删除
// 支付方式
pay := v1.Group("/paychannel")
pay.GET("/list", backend.PayChannelList) // 支付方式列表
pay.POST("/create", backend.PayChannelCreate) // 支付方式创建
pay.PUT("/update", backend.PayChannelUpdate) // 支付方式更新
pay.DELETE("/delete", backend.PayChannelDelete) // 支付方式删除
// 应用管理
app := v1.Group("/app")
app.GET("/list", backend.AppList) // 应用列表
app.POST("/create", backend.AppCreate) // 应用创建
app.PUT("/update", backend.AppUpdate) // 应用更新
app.DELETE("/delete", backend.AppDelete) // 应用删除
app.GET("/decrypt", backend.GenerateDecrypt) // 生成密钥对
// 订单
order := v1.Group("/order")
order.GET("/list", backend.OrderList) // 订单列表
order.GET("/log/request", backend.OrderRequestLogsList) // 请求日志列表
order.GET("/log/callback", backend.OrderCallbackLogsList) // 回调日志列表
order.GET("/log/third", backend.OrderThirdLogsList) // 三方日志列表
}
}

View File

@ -4,17 +4,19 @@ package routes
* 配置路由
*/
import (
"PaymentCenter/app/constants/common"
"PaymentCenter/app/http/controllers"
"PaymentCenter/app/http/controllers/front"
"PaymentCenter/app/http/middlewares"
"PaymentCenter/app/http/trace"
"PaymentCenter/app/utils/metric"
"PaymentCenter/config"
ginSwagger "github.com/swaggo/gin-swagger"
"github.com/swaggo/gin-swagger/swaggerFiles"
"github.com/gin-gonic/gin"
"github.com/qit-team/snow-core/http/middleware"
"github.com/qit-team/snow-core/log/logger"
"github.com/swaggo/gin-swagger"
"github.com/swaggo/gin-swagger/swaggerFiles"
)
// api路由配置
@ -43,17 +45,25 @@ func RegisterRoute(router *gin.Engine) {
router.Use(middlewares.Cors())
router.NoRoute(controllers.Error404)
//api版本
//v1 := router.Group("/v1", middlewares.ValidateRequest())
//{
//
//}
//router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
v1 := router.Group(common.FRONT_V1)
{
//回调处理
notify := v1.Group("/notify")
{
notify.POST("/wx/:payChannelId", front.WxCallback)
notify.POST("/ali/:payChannelId", front.AliCallback)
}
pay := v1.Group("/pay", middlewares.ValidateRequest(), middlewares.ValidatePayRequest())
{
pay.POST("/url", front.PayUrl)
pay.POST("/query", front.QueryOrder) //查询订单
pay.POST("/refund", front.Refund)
}
}
router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
//router.GET("/hello", controllers.HelloHandler)
//router.GET("/create", controllers.HelloCreateHandler)
//router.GET("/update", controllers.UpdateHandler)
//router.GET("/delete", controllers.DeleteHandler)
//router.GET("/query", controllers.QueryHandler)
}

View File

@ -0,0 +1,49 @@
package appmodel
import (
"github.com/qit-team/snow-core/db"
"sync"
"time"
)
var (
once sync.Once
m *AppModel
)
// 实体
type App struct {
Id int64
MerchantId int64 `xorm:"'merchant_id' bigint(20)"`
AppName string `xorm:"'app_name' varchar(20)"`
AppRemark string `xorm:"'app_remark' varchar(200)"`
Status int32 `xorm:"'status' tinyint(2)"`
KeyType int32 `xorm:"'key_type' tinyint(2)"`
PublicKey string `xorm:"'public_key' varchar(1024)"`
PrivateKey string `xorm:"'private_key' TEXT"`
MerchantPublicKey string `xorm:"'merchant_public_key' varchar(1024)"`
CreateTime time.Time `xorm:"'create_time' datetime created"`
UpdateTime time.Time `xorm:"'update_time' timestamp updated"`
DeleteTime time.Time `xorm:"'delete_time' timestamp deleted"`
NotifyUrl string `xorm:"'notify_url' varchar(128)"`
WhiteIp string `xorm:"'white_ip' varchar(255)"`
}
// 表名
func (m *App) TableName() string {
return "app"
}
// 私有化防止被外部new
type AppModel struct {
db.Model //组合基础Model集成基础Model的属性和方法
}
// 单例模式
func GetInstance() *AppModel {
once.Do(func() {
m = new(AppModel)
//m.DiName = "" //设置数据库实例连接默认db.SingletonMain
})
return m
}

View File

@ -0,0 +1,44 @@
package merchantmodel
import (
"github.com/qit-team/snow-core/db"
"sync"
"time"
)
var (
once sync.Once
m *MerchantModel
)
// 实体
type Merchant struct {
Id int64
Name string `xorm:"'name' varchar(128)"`
Contact string `xorm:"'contact' varchar(128)"`
Phone string `xorm:"'phone' varchar(11)"`
Remark string `xorm:"'remark' varchar(1024)"`
CreateTime time.Time `xorm:"'create_time' datetime created"`
UpdateTime time.Time `xorm:"'update_time' timestamp updated"`
DeleteTime time.Time `xorm:"'delete_time' timestamp deleted"`
Creator int `xorm:"'creator' int(11)"`
}
// 表名
func (m *Merchant) TableName() string {
return "merchant"
}
// 私有化防止被外部new
type MerchantModel struct {
db.Model //组合基础Model集成基础Model的属性和方法
}
// 单例模式
func GetInstance() *MerchantModel {
once.Do(func() {
m = new(MerchantModel)
//m.DiName = "" //设置数据库实例连接默认db.SingletonMain
})
return m
}

View File

@ -0,0 +1,41 @@
package ordercallbacklogmodel
import (
"github.com/qit-team/snow-core/db"
"sync"
"time"
)
var (
once sync.Once
m *OrderCallbackLogModel
)
// 实体
type OrderCallbackLog struct {
Id int64 `xorm:"'id' bigint(20) pk autoincr"`
OrderId int64 `xorm:"'order_id' bigint(20)"`
MerchantRequest string `xorm:"'merchant_request' JSON"`
Status int `xorm:"'status' int(11)"`
MerchantResponse string `xorm:"'merchant_response' JSON"`
CreateTime time.Time `xorm:"'create_time' datetime created"`
}
// 表名
func (m *OrderCallbackLog) TableName() string {
return "order_callback_log"
}
// 私有化防止被外部new
type OrderCallbackLogModel struct {
db.Model //组合基础Model集成基础Model的属性和方法
}
// 单例模式
func GetInstance() *OrderCallbackLogModel {
once.Do(func() {
m = new(OrderCallbackLogModel)
//m.DiName = "" //设置数据库实例连接默认db.SingletonMain
})
return m
}

View File

@ -0,0 +1,45 @@
package orderrequestlogmodel
import (
"github.com/qit-team/snow-core/db"
"sync"
"time"
)
var (
once sync.Once
m *OrderRequestLogModel
)
// 实体
type OrderRequestLog struct {
Id int64
AppId int64 `xorm:"'app_id' bigint(20)"`
OutTradeNo string `xorm:"'out_trade_no' varchar(50)"`
IpAddress string `xorm:"'ip_address' varchar(16)"`
URL string `xorm:"'url' varchar(100)"`
MerchantRequest string `xorm:"'merchant_request' JSON"`
MerchantResponse string `xorm:"'merchant_response' JSON"`
CreateTime time.Time `xorm:"'create_time' datetime created"`
UpdateTime time.Time `xorm:"'update_time' timestamp updated"`
Status int `xorm:"'status' TINYINT"`
}
// 表名
func (m *OrderRequestLog) TableName() string {
return "order_request_log"
}
// 私有化防止被外部new
type OrderRequestLogModel struct {
db.Model //组合基础Model集成基础Model的属性和方法
}
// 单例模式
func GetInstance() *OrderRequestLogModel {
once.Do(func() {
m = new(OrderRequestLogModel)
//m.DiName = "" //设置数据库实例连接默认db.SingletonMain
})
return m
}

View File

@ -0,0 +1,63 @@
package ordersmodel
import (
"github.com/qit-team/snow-core/db"
"sync"
"time"
)
var (
once sync.Once
m *OrdersModel
)
// 实体
type Orders struct {
Id int64
MerchantId int64 `xorm:"'merchant_id' bigint(20)"`
PayChannelId int64 `xorm:"'pay_channel_id' bigint(20)"`
AppId int64 `xorm:"'app_id' bigint(20)"`
OutTradeNo string `xorm:"'out_trade_no' varchar(50)"`
OrderType int `xorm:"'order_type' TINYINT"`
RefundOrderId int64 `xorm:"'refund_order_id' bigint(20)"`
Amount int `xorm:"'amount' int(11)"`
PayerTotal int `xorm:"'payer_total' int(11)"`
ExtJson string `xorm:"'ext_json' varchar(1024)"`
Desc string `xorm:"'desc' varchar(100)"`
CreateTime time.Time `xorm:"'create_time' datetime created"`
UpdateTime time.Time `xorm:"'update_time' timestamp updated"`
Status int `xorm:"'status' TINYINT"`
DeleteTime time.Time `xorm:"'delete_time' timestamp deleted"`
}
type OrdersBackendList struct {
Orders `xorm:"extends"`
MerchantName string `xorm:"'merchant_name' varchar(128)"`
PayName string `xorm:"'pay_name' varchar(128)"`
AppName string `xorm:"'app_name' varchar(128)"`
}
type OrdersLeftPayChannelList struct {
Orders `xorm:"extends"`
ChannelType int `xorm:"'channel_type' int(11)"`
AppId string `xorm:"'app_id' varchar(255)"`
ExtJson string `xorm:"'ext_json' JSON"`
}
// 表名
func (m *Orders) TableName() string {
return "orders"
}
// 私有化防止被外部new
type OrdersModel struct {
db.Model //组合基础Model集成基础Model的属性和方法
}
// 单例模式
func GetInstance() *OrdersModel {
once.Do(func() {
m = new(OrdersModel)
//m.DiName = "" //设置数据库实例连接默认db.SingletonMain
})
return m
}

View File

@ -0,0 +1,43 @@
package orderthirdpaylogmodel
import (
"github.com/qit-team/snow-core/db"
"sync"
"time"
)
var (
once sync.Once
m *OrderThirdPayLogModel
)
// 实体
type OrderThirdPayLog struct {
Id int64
OrderId int64 `xorm:"'order_id' bigint(20)"`
PayCallback string `xorm:"'pay_callback' JSON"`
Status int `xorm:"'status' TINYINT"`
PayParam string `xorm:"'pay_param' JSON"`
MerchantCallback string `xorm:"'merchant_callback' JSON"`
Type int `xorm:"'type' TINYINT"`
CreateTime time.Time `xorm:"'create_time' datetime created"`
}
// 表名
func (m *OrderThirdPayLog) TableName() string {
return "order_third_pay_log"
}
// 私有化防止被外部new
type OrderThirdPayLogModel struct {
db.Model //组合基础Model集成基础Model的属性和方法
}
// 单例模式
func GetInstance() *OrderThirdPayLogModel {
once.Do(func() {
m = new(OrderThirdPayLogModel)
//m.DiName = "" //设置数据库实例连接默认db.SingletonMain
})
return m
}

View File

@ -0,0 +1,45 @@
package paychannelmodel
import (
"github.com/qit-team/snow-core/db"
"sync"
"time"
)
var (
once sync.Once
m *PayChannelModel
)
// 实体
type PayChannel struct {
Id int64
PayName string `xorm:"'pay_name' varchar(128)"`
MerchantId int64 `xorm:"'merchant_id' bigint(20)"`
ChannelType int `xorm:"'channel_type' tinyint(2)"`
AppId string `xorm:"'app_id' varchar(255)"`
ExtJson string `xorm:"'ext_json' JSON"`
ExpireTime time.Time `xorm:"'expire_time' datetime"`
CreateTime time.Time `xorm:"'create_time' datetime created"`
UpdateTime time.Time `xorm:"'update_time' timestamp updated"`
DeleteTime time.Time `xorm:"'delete_time' timestamp deleted"`
}
// 表名
func (m *PayChannel) TableName() string {
return "pay_channel"
}
// 私有化防止被外部new
type PayChannelModel struct {
db.Model //组合基础Model集成基础Model的属性和方法
}
// 单例模式
func GetInstance() *PayChannelModel {
once.Do(func() {
m = new(PayChannelModel)
//m.DiName = "" //设置数据库实例连接默认db.SingletonMain
})
return m
}

View File

@ -14,7 +14,7 @@ func SingleTalk(tag uint64, ch interface{}, msg []byte) {
// }
// }
// if !sendOk {
// common.PikaTool.Zadd(utils.GetRealKey(common2.USER_MSG)+data.Msg.To, data, time.Now().Unix())
// payCommon.PikaTool.Zadd(utils.GetRealKey(common2.USER_MSG)+data.Msg.To, data, time.Now().Unix())
// }
//}

View File

@ -0,0 +1,75 @@
package services
import (
"PaymentCenter/app/constants/common"
"PaymentCenter/app/constants/errorcode"
"PaymentCenter/app/http/entities/front"
"PaymentCenter/app/models/appmodel"
"PaymentCenter/app/services/apicrypt"
"PaymentCenter/app/utils"
"github.com/bytedance/sonic"
"strings"
)
type AppCheck struct {
AppId int64
Ip string
App *appmodel.App
Code int
}
func NewAppCheck(app *appmodel.App) *AppCheck {
return &AppCheck{
App: app,
Code: errorcode.Success,
}
}
func (a *AppCheck) IpCheck(ip string) bool {
if a.App.WhiteIp == "" {
return true
}
if !utils.ContainsString(strings.Split(a.App.WhiteIp, ","), ip) {
a.Code = errorcode.AppIpNotAllow
return false
}
return true
}
func (a *AppCheck) Check() *AppCheck {
if a.App.Status == common.STATUS_DISABLED {
a.Code = errorcode.AppDisabled
return a
}
return a
}
func (a *AppCheck) GetCode() int {
return a.Code
}
func (a *AppCheck) Crypt() (cryptFunc func(app *appmodel.App) apicrypt.ApiCrypt) {
var (
ok bool
)
if cryptFunc, ok = apicrypt.ApiCryptMap[a.App.KeyType]; !ok {
a.Code = errorcode.AppNotFound
return nil
}
return cryptFunc
}
func (a *AppCheck) ReCheckAfterDecrypt(data []byte, requestData *front.RequestBody) bool {
var requestCommonData front.ApiCommonBody
if err := sonic.Unmarshal(data, &requestCommonData); err != nil {
a.Code = errorcode.ParamError
return false
}
if requestCommonData.AppId != requestData.AppId || requestCommonData.Timestamp != requestData.Timestamp {
a.Code = errorcode.AppDecryptDataDiscrepancy
return false
}
return true
}

View File

@ -0,0 +1,44 @@
package apicrypt
import (
"PaymentCenter/app/constants/errorcode"
"PaymentCenter/app/models/appmodel"
"PaymentCenter/app/utils/encrypt/rsa"
"encoding/base64"
)
func NewRsa(app *appmodel.App) ApiCrypt {
return &Rsa{
App: app,
}
}
func (r *Rsa) Encrypt(data string) (encryptData []byte, errCode int) {
if r.App.PublicKey == "" {
return nil, errorcode.AppRsaEncryptKeyNotFound
}
publicKeyPEM := `-----BEGIN PUBLIC KEY-----
` + r.App.PublicKey + `
-----END PUBLIC KEY-----`
encryptByte, err := rsa.Encrypt(publicKeyPEM, data)
if err != nil {
return nil, errorcode.AppRsaEncryptFail
}
encryptData = []byte(base64.StdEncoding.EncodeToString(encryptByte))
return
}
func (r *Rsa) Decrypt(encryptData string) (decryptData []byte, errCode int) {
if r.App.PrivateKey == "" {
return nil, errorcode.AppRsaDecryptKeyNotFound
}
privateKeyPEM := `-----BEGIN RSA PRIVATE KEY-----
` + r.App.PrivateKey + `
-----END RSA PRIVATE KEY-----`
decryptData, err := rsa.Decrypt(privateKeyPEM, encryptData)
if err != nil || decryptData == nil {
return nil, errorcode.AppRsaDecryptFail
}
return
}

View File

@ -0,0 +1,40 @@
package apicrypt
import (
"PaymentCenter/app/constants/errorcode"
"PaymentCenter/app/models/appmodel"
"PaymentCenter/app/utils/encrypt/sm2"
)
func NewSm2(app *appmodel.App) ApiCrypt {
return &SM2{
App: app,
}
}
func (r *SM2) Encrypt(data string) (encryptData []byte, errCode int) {
if r.App.MerchantPublicKey == "" {
return nil, errorcode.AppSM2EncryptKeyNotFound
}
encryptDataString, err := sm2.SM2Encrypt(data, r.App.MerchantPublicKey)
if err != nil {
return nil, errorcode.AppSM2EncryptFail
}
encryptData = []byte(encryptDataString)
return
}
func (r *SM2) Decrypt(encryptData string) (decryptData []byte, errCode int) {
if r.App.PrivateKey == "" || r.App.PublicKey == "" {
return nil, errorcode.AppSM2DecryptKeyNotFound
}
decryptDataString, err := sm2.SM2Decrypt(encryptData, r.App.PublicKey, r.App.PrivateKey)
if err != nil {
return nil, errorcode.AppSM2DecryptFail
}
decryptData = []byte(decryptDataString)
return
}

View File

@ -0,0 +1,40 @@
package apicrypt
import (
"PaymentCenter/app/constants/errorcode"
"PaymentCenter/app/models/appmodel"
"PaymentCenter/app/utils/encrypt/sm4"
"strconv"
)
func NewSm4(app *appmodel.App) ApiCrypt {
return &SM4{
App: app,
}
}
func (r *SM4) Encrypt(data string) (encryptData []byte, errCode int) {
if r.App.MerchantPublicKey == "" || r.App.PrivateKey == "" {
return nil, errorcode.AppSM4DecryptKeyNotFound
}
encryptDataString, err := sm4.Sm4Encrypt(strconv.FormatInt(r.App.Id, 10), r.App.PrivateKey, r.App.MerchantPublicKey, data, "", true)
if err != nil {
return nil, errorcode.AppSM4EncryptFail
}
encryptData = []byte(encryptDataString)
return
}
func (r *SM4) Decrypt(encryptData string) (decryptData []byte, errCode int) {
if r.App.PrivateKey == "" || r.App.MerchantPublicKey == "" {
return nil, errorcode.AppSM4DecryptKeyNotFound
}
decryptDataString, err := sm4.Sm4Decrypt(strconv.FormatInt(r.App.Id, 10), r.App.PrivateKey, r.App.MerchantPublicKey, encryptData, true)
if err != nil {
return nil, errorcode.AppSM4DecryptFail
}
decryptData = []byte(decryptDataString)
return
}

View File

@ -0,0 +1,33 @@
package apicrypt
import (
"PaymentCenter/app/constants/pojo"
"PaymentCenter/app/models/appmodel"
)
type (
ApiCrypt interface {
Encrypt(data string) (encryptData []byte, errCode int)
Decrypt(encryptData string) (decryptData []byte, errCode int)
}
Rsa struct {
App *appmodel.App
}
SM2 struct {
App *appmodel.App
}
SM4 struct {
App *appmodel.App
}
)
var ApiCryptMap = map[int32]func(app *appmodel.App) ApiCrypt{
pojo.RSA: NewRsa,
pojo.SM2: NewSm2,
pojo.SM4: NewSm4,
}
const CryptNotError = 0

131
app/services/app.go Normal file
View File

@ -0,0 +1,131 @@
package services
import (
"PaymentCenter/app/constants/errorcode"
"PaymentCenter/app/data"
"PaymentCenter/app/http/entities"
"PaymentCenter/app/http/entities/backend"
"PaymentCenter/app/models/appmodel"
"PaymentCenter/app/models/merchantmodel"
"PaymentCenter/app/models/paychannelmodel"
"database/sql"
"xorm.io/builder"
)
func AppList(req backend.AppListRequest) (result []appmodel.App, total int64, code int) {
repo := data.NewAppRepo(paychannelmodel.GetInstance().GetDb())
// 拼接查询条件
conn := builder.NewCond()
if req.MerchantId > 0 {
conn = conn.And(builder.Eq{"merchant_id": req.MerchantId})
}
if req.Id > 0 {
conn = conn.And(builder.Eq{"id": req.Id})
}
// 调用repo
appList := make([]appmodel.App, 0)
count, err := repo.AppList(conn, req.PageRequest, &appList)
code = handErr(err)
return appList, count, code
}
func AppCreate(app *appmodel.App) (code int) {
db := paychannelmodel.GetInstance().GetDb()
repo := data.NewAppRepo(db)
merchantRepo := data.NewMerchantRepo(db)
// 拼接查询条件
conn := builder.NewCond()
conn = conn.And(builder.Eq{"id": app.MerchantId})
merchant := merchantmodel.Merchant{}
has, err := merchantRepo.MerchantDetail(&merchant, conn)
if err != nil {
return handErr(err)
}
if !has {
return errorcode.MerchantNotFound
}
_, err = repo.AppInsertOne(app)
code = handErr(err)
return
}
func AppUpdate(app *appmodel.App) (code int) {
var err error
db := paychannelmodel.GetInstance().GetDb()
repo := data.NewAppRepo(db)
// 拼接查询条件
conn := builder.NewCond()
conn = conn.And(builder.Eq{"Id": app.Id})
if app.AppName != "" {
// 编辑页面更新备注和白名单IP可更新为空
_, err = repo.AppUpdate(app, conn, "app_remark", "white_ip", "notify_url")
} else {
_, err = repo.AppUpdate(app, conn)
}
code = handErr(err)
return
}
func AppDelete(req entities.IdRequest) (code int) {
repo := data.NewAppRepo(paychannelmodel.GetInstance().GetDb())
// 拼接查询条件
conn := builder.NewCond()
conn = conn.And(builder.Eq{"Id": req.Id})
m := appmodel.App{Id: req.Id}
_, err := repo.AppDelete(&m, conn)
code = handErr(err)
return
}
func AppFindOne(req entities.IdRequest, col ...string) (row *appmodel.App, code int) {
repo := data.NewAppRepo(paychannelmodel.GetInstance().GetDb())
// 拼接查询条件
conn := builder.NewCond()
conn = conn.And(builder.Eq{"Id": req.Id})
m := appmodel.App{Id: req.Id}
row, err := repo.AppFindOne(&m, conn, col...)
if err != nil {
if err == sql.ErrNoRows {
return nil, errorcode.AppNotFound
}
return row, errorcode.SystemError
}
return row, errorcode.Success
}
func CheckApp(appCheckIn *AppCheck) {
errCode := errorcode.Success
appCheckIn.App, errCode = AppFindOne(entities.IdRequest{Id: appCheckIn.AppId})
if errCode != errorcode.Success {
appCheckIn.Code = errCode
return
}
//检查app可用性
appCheckIn.Check()
if appCheckIn.GetCode() != errorcode.Success {
return
}
//检查白名单
if appCheckIn.Ip != "" && !appCheckIn.IpCheck(appCheckIn.Ip) {
return
}
return
}
func GetAppCheck(appId int64, ip string) *AppCheck {
appCheck := &AppCheck{
AppId: appId,
Ip: ip,
Code: errorcode.Success,
}
CheckApp(appCheck)
return appCheck
}

20
app/services/common.go Normal file
View File

@ -0,0 +1,20 @@
package services
import (
"PaymentCenter/app/constants/errorcode"
"PaymentCenter/app/models/appmodel"
"PaymentCenter/app/utils"
)
func handErr(err error) int {
if err != nil {
utils.Log(nil, "sys err", err.Error())
return errorcode.SystemError
} else {
return errorcode.Success
}
}
func EnCryptData(app *appmodel.App) {
}

91
app/services/merchant.go Normal file
View File

@ -0,0 +1,91 @@
package services
import (
"PaymentCenter/app/constants/errorcode"
"PaymentCenter/app/data"
"PaymentCenter/app/http/entities"
"PaymentCenter/app/http/entities/backend"
"PaymentCenter/app/models/merchantmodel"
"database/sql"
"strings"
"xorm.io/builder"
)
// MerchantList 商户列表
func MerchantList(req backend.MerchantListRequest) (result []merchantmodel.Merchant, total int64, code int) {
repo := data.NewMerchantRepo(merchantmodel.GetInstance().GetDb())
// 拼接查询条件
conn := builder.NewCond()
if req.Name != "" {
conn = conn.And(builder.Like{"name", req.Name})
}
if req.Contact != "" {
conn = conn.And(builder.Like{"contact", req.Contact})
}
if req.Phone != "" {
conn = conn.And(builder.Like{"phone", req.Phone})
}
if req.GroupInfo != "" {
conn = conn.And(builder.In("creator", strings.Split(req.GroupInfo, ",")))
}
// 调用repo
merchantList := make([]merchantmodel.Merchant, 0)
count, err := repo.MerchantList(conn, req.PageRequest, &merchantList)
code = handErr(err)
return merchantList, count, code
}
func MerchantCreate(merchant *merchantmodel.Merchant) (code int) {
repo := data.NewMerchantRepo(merchantmodel.GetInstance().GetDb())
_, err := repo.MerchantInsertOne(merchant)
code = handErr(err)
return
}
func MerchantUpdate(merchant *merchantmodel.Merchant) (code int) {
repo := data.NewMerchantRepo(merchantmodel.GetInstance().GetDb())
// 拼接查询条件
conn := builder.NewCond()
conn = conn.And(builder.Eq{"Id": merchant.Id})
_, err := repo.MerchantUpdate(merchant, conn, "remark")
code = handErr(err)
return
}
func MerchantDelete(req entities.IdRequest) (code int) {
repo := data.NewMerchantRepo(merchantmodel.GetInstance().GetDb())
// 拼接查询条件
conn := builder.NewCond()
conn = conn.And(builder.Eq{"Id": req.Id})
m := merchantmodel.Merchant{Id: req.Id}
_, err := repo.MerchantDelete(&m, conn)
code = handErr(err)
return
}
func MerchantFindOne(merchant *merchantmodel.Merchant, conn builder.Cond, col ...string) (merchantInfo *merchantmodel.Merchant, code int) {
repo := data.NewMerchantRepo(merchantmodel.GetInstance().GetDb())
// 拼接查询条件
merchantInfo, err := repo.MerchantFindOne(merchant, conn, col...)
if err != nil {
if err == sql.ErrNoRows {
return nil, errorcode.MerchantNotFound
}
return nil, errorcode.SystemError
}
return merchantInfo, errorcode.Success
}
func GetAndCheckMerchant(merchant *merchantmodel.Merchant, conn builder.Cond, col ...string) (merchantInfo *merchantmodel.Merchant, code int) {
merchantInfo, code = MerchantFindOne(merchant, conn, col...)
if code != errorcode.Success {
return nil, code
}
return
}

151
app/services/order.go Normal file
View File

@ -0,0 +1,151 @@
package services
import (
"PaymentCenter/app/constants/errorcode"
"PaymentCenter/app/data"
"PaymentCenter/app/http/entities/backend"
"PaymentCenter/app/models/ordercallbacklogmodel"
"PaymentCenter/app/models/orderrequestlogmodel"
"PaymentCenter/app/models/ordersmodel"
"PaymentCenter/app/models/orderthirdpaylogmodel"
"PaymentCenter/app/models/paychannelmodel"
"database/sql"
"xorm.io/builder"
)
// 订单列表,后台查询
func OrderList(req backend.OrderList) (result []ordersmodel.OrdersBackendList, total int64, code int) {
repo := data.NewOrderRepo(paychannelmodel.GetInstance().GetDb())
// 拼接查询条件
conn := builder.NewCond()
if req.Id > 0 {
conn = conn.And(builder.Eq{"orders.id": req.Id})
}
if req.MerchantId > 0 {
conn = conn.And(builder.Eq{"orders.merchant_id": req.MerchantId})
}
if req.PayChannelId > 0 {
conn = conn.And(builder.Eq{"orders.pay_channel_id": req.PayChannelId})
}
if req.AppId > 0 {
conn = conn.And(builder.Eq{"orders.app_id": req.AppId})
}
if req.OutTradeNo != "" {
conn = conn.And(builder.Like{"orders.out_trade_no", req.OutTradeNo})
}
if req.Status > 0 {
conn = conn.And(builder.Eq{"orders.status": req.Status})
}
if req.OrderType > 0 {
conn = conn.And(builder.Eq{"orders.order_type": req.OrderType})
}
if !req.StartTime.IsZero() {
conn = conn.And(builder.Gte{"orders.create_time": req.StartTime})
}
if !req.EndTime.IsZero() {
conn = conn.And(builder.Lte{"orders.create_time": req.EndTime})
}
// 调用repo
orderList := make([]ordersmodel.OrdersBackendList, 0)
count, err := repo.OrdersBackendList(conn, req.PageRequest, &orderList)
code = handErr(err)
return orderList, count, code
}
// 请求日志列表
func OrderRequestLogsList(req backend.OrderLogsListRequest) (requestLog []orderrequestlogmodel.OrderRequestLog, total int64, code int) {
requestRepo := data.NewOrderRequestLogRepo(paychannelmodel.GetInstance().GetDb())
conn := builder.NewCond()
if req.OrderId > 0 {
conn = conn.And(builder.Eq{"orders.id": req.OrderId})
}
if req.Id > 0 {
conn = conn.And(builder.Eq{"order_request_log.id": req.Id})
}
if req.AppId > 0 {
conn = conn.And(builder.Eq{"order_request_log.app_id": req.AppId})
}
if req.OutTradeNo != "" {
conn = conn.And(builder.Like{"order_request_log.out_trade_no", req.OutTradeNo})
}
// 请求日志
orderLogList := make([]orderrequestlogmodel.OrderRequestLog, 0)
total, err := requestRepo.OrderRequestLogBackendList(conn, req.PageRequest, &orderLogList)
code = handErr(err)
return orderLogList, total, code
}
// 回调日志列表
func OrderCallbackLogsList(req backend.OrderLogsListRequest) (callback []ordercallbacklogmodel.OrderCallbackLog, total int64, code int) {
callbackRepo := data.NewOrderCallbackLogRepo(paychannelmodel.GetInstance().GetDb())
conn := builder.NewCond()
if req.OrderId > 0 {
conn = conn.And(builder.Eq{"order_id": req.OrderId})
}
if req.Id > 0 {
conn = conn.And(builder.Eq{"id": req.Id})
}
// 回调日志
callbackLogList := make([]ordercallbacklogmodel.OrderCallbackLog, 0)
total, err := callbackRepo.OrderCallbackLogList(conn, req.PageRequest, &callbackLogList)
code = handErr(err)
return callbackLogList, total, code
}
// 三方日志列表
func OrderThirdLogsList(req backend.OrderLogsListRequest) (thirdLod []orderthirdpaylogmodel.OrderThirdPayLog, total int64, code int) {
thirdRepo := data.NewOrderThirdPayLogRepo(paychannelmodel.GetInstance().GetDb())
// 三方日志
conn := builder.NewCond()
if req.OrderId > 0 {
conn = conn.And(builder.Eq{"order_id": req.OrderId})
}
if req.Id > 0 {
conn = conn.And(builder.Eq{"id": req.Id})
}
thirdLogList := make([]orderthirdpaylogmodel.OrderThirdPayLog, 0)
total, err := thirdRepo.OrderThirdPayLogList(conn, req.PageRequest, &thirdLogList)
code = handErr(err)
return thirdLogList, total, code
}
func OrderCreate(orderIn *ordersmodel.Orders) (orderOut *ordersmodel.Orders, code int) {
repo := data.NewOrderRepo(ordersmodel.GetInstance().GetDb())
_, err := repo.OrderInsertOne(orderIn)
code = handErr(err)
return orderIn, code
}
func OrderUpdate(order *ordersmodel.Orders, col ...string) (code int) {
repo := data.NewOrderRepo(ordersmodel.GetInstance().GetDb())
// 拼接查询条件
conn := builder.NewCond()
conn = conn.And(builder.Eq{"Id": order.Id})
_, err := repo.OrderUpdate(order, conn, col...)
code = handErr(err)
return
}
func OrderFindOne(order *ordersmodel.Orders, conn builder.Cond, col ...string) (merchantInfo *ordersmodel.Orders, code int) {
repo := data.NewOrderRepo(ordersmodel.GetInstance().GetDb())
// 拼接查询条件
orderInfo, err := repo.OrderFindOne(order, conn, col...)
if err != nil {
if err == sql.ErrNoRows {
return nil, errorcode.OrdersNotFound
}
return nil, errorcode.SystemError
}
return orderInfo, errorcode.Success
}
func PayOrderCheckRepeat(order *ordersmodel.Orders, conn builder.Cond) (exist bool, code int) {
_, code = OrderFindOne(order, conn)
if code != errorcode.OrdersNotFound {
return false, errorcode.Success
}
return true, code
}

117
app/services/pay_channel.go Normal file
View File

@ -0,0 +1,117 @@
package services
import (
"PaymentCenter/app/constants/errorcode"
"PaymentCenter/app/data"
"PaymentCenter/app/http/entities"
"PaymentCenter/app/http/entities/backend"
"PaymentCenter/app/models/merchantmodel"
"PaymentCenter/app/models/paychannelmodel"
"database/sql"
"xorm.io/builder"
)
func PayChannelList(req backend.PayChannelListRequest) (result []paychannelmodel.PayChannel, total int64, code int) {
repo := data.NewPayChannelRepo(paychannelmodel.GetInstance().GetDb())
// 拼接查询条件
conn := builder.NewCond()
if req.MerchantId > 0 {
conn = conn.And(builder.Eq{"merchant_id": req.MerchantId})
}
if req.Id > 0 {
conn = conn.And(builder.Eq{"id": req.Id})
}
// 调用repo
paychannelList := make([]paychannelmodel.PayChannel, 0)
count, err := repo.PayChannelList(conn, req.PageRequest, &paychannelList)
code = handErr(err)
return paychannelList, count, code
}
func PayChannelCreate(payChannel *paychannelmodel.PayChannel) (code int) {
db := paychannelmodel.GetInstance().GetDb()
repo := data.NewPayChannelRepo(db)
merchantRepo := data.NewMerchantRepo(db)
// 拼接查询条件
conn := builder.NewCond()
conn = conn.And(builder.Eq{"id": payChannel.MerchantId})
merchant := merchantmodel.Merchant{}
has, err := merchantRepo.MerchantDetail(&merchant, conn)
if err != nil {
return handErr(err)
}
if !has {
return errorcode.MerchantNotFound
}
_, err = repo.PayChannelInsertOne(payChannel)
code = handErr(err)
return
}
func PayChannelUpdate(payChannel *paychannelmodel.PayChannel) (code int) {
db := paychannelmodel.GetInstance().GetDb()
repo := data.NewPayChannelRepo(db)
// 拼接查询条件
uconn := builder.NewCond()
uconn = uconn.And(builder.Eq{"Id": payChannel.Id})
_, err := repo.PayChannelUpdate(payChannel, uconn, "white_ip")
code = handErr(err)
return
}
func PayChannelDelete(req entities.IdRequest) (code int) {
repo := data.NewPayChannelRepo(paychannelmodel.GetInstance().GetDb())
// 拼接查询条件
conn := builder.NewCond()
conn = conn.And(builder.Eq{"Id": req.Id})
m := paychannelmodel.PayChannel{Id: req.Id}
_, err := repo.PayChannelDelete(&m, conn)
code = handErr(err)
return
}
func PayChannelGet(payChannel *paychannelmodel.PayChannel) (code int) {
repo := data.NewPayChannelRepo(paychannelmodel.GetInstance().GetDb())
// 拼接查询条件
conn := builder.NewCond()
conn = conn.And(builder.Eq{"id": payChannel.Id})
has, err := repo.PayChannelGet(payChannel, conn)
if err != nil {
return handErr(err)
}
if !has {
return errorcode.PayChannelNotFound
}
code = errorcode.Success
return
}
func PayChannelFindOne(channel *paychannelmodel.PayChannel, conn builder.Cond, col ...string) (merchantInfo *paychannelmodel.PayChannel, code int) {
repo := data.NewPayChannelRepo(paychannelmodel.GetInstance().GetDb())
// 拼接查询条件
channelInfo, err := repo.PayChannelFindOne(channel, conn, col...)
if err != nil {
if err == sql.ErrNoRows {
return nil, errorcode.PayChannelNotFound
}
return nil, errorcode.SystemError
}
return channelInfo, errorcode.Success
}
func GetAndCheckPayChannel(channel *paychannelmodel.PayChannel, conn builder.Cond, col ...string) (channelInfo *paychannelmodel.PayChannel, code int) {
channelInfo, code = PayChannelFindOne(channel, conn, col...)
if code != errorcode.Success {
return nil, code
}
return
}

View File

@ -0,0 +1,43 @@
package services
import (
"PaymentCenter/app/constants/common"
"PaymentCenter/app/data"
"PaymentCenter/app/models/orderrequestlogmodel"
"xorm.io/builder"
)
func RequestLogCreate(log *orderrequestlogmodel.OrderRequestLog) (*orderrequestlogmodel.OrderRequestLog, int) {
db := orderrequestlogmodel.GetInstance().GetDb()
repo := data.NewOrderRequestLogRepo(db)
id, err := repo.OrderRequestLogInsertOne(log)
code := handErr(err)
if err != nil {
log.Id = id
}
return log, code
}
func RequestLogUpdate(log *orderrequestlogmodel.OrderRequestLog) (logOut *orderrequestlogmodel.OrderRequestLog, code int) {
db := orderrequestlogmodel.GetInstance().GetDb()
repo := data.NewOrderRequestLogRepo(db)
conn := builder.NewCond()
conn = conn.And(builder.Eq{"Id": log.Id})
id, err := repo.OrderRequestLogUpdate(log, conn)
code = handErr(err)
if err != nil {
log.Id = id
}
return log, code
}
func AddRequestLog(requestDataByte []byte, ip string, url string) (int64, int) {
requestLog, checkCode := RequestLogCreate(&orderrequestlogmodel.OrderRequestLog{
IpAddress: ip,
MerchantRequest: string(requestDataByte),
URL: url,
MerchantResponse: "{}",
Status: common.STATUS_ENABLE,
})
return requestLog.Id, checkCode
}

View File

@ -0,0 +1,32 @@
package api
type OrdersResp struct {
OrderNo int64 `json:"order_no"`
OrderType int `json:"order_type"`
OutTradeNo string `json:"out_trade_no"`
Amount int `json:"amount"`
PayerTotal int `json:"payer_total"`
Desc string `json:"desc"`
Status int `json:"status"`
CreateTime string `json:"create_time"`
}
type OrdersWithUrl struct {
Order *OrdersResp
Url string
}
func (o *OrdersResp) WithUrl(url string) *OrdersWithUrl {
return &OrdersWithUrl{
Order: o,
Url: url,
}
}
func (o *OrdersResp) WithAes(url string) *OrdersWithUrl {
return &OrdersWithUrl{
Order: o,
Url: url,
}
}

View File

@ -0,0 +1,44 @@
package api
import (
"PaymentCenter/app/constants/errorcode"
"PaymentCenter/app/models/appmodel"
"PaymentCenter/app/services"
"PaymentCenter/app/services/apicrypt"
"encoding/json"
)
func EnCrypt(app *appmodel.App, data interface{}) ([]byte, int) {
appCheck := services.NewAppCheck(app)
cryptFunc := appCheck.Crypt()
if cryptFunc == nil {
return nil, appCheck.Code
}
dataByte, err := json.Marshal(data)
if err != nil {
return nil, errorcode.AppAesEncryptFail
}
encryptData, errCode := cryptFunc(appCheck.App).Encrypt(string(dataByte))
if errCode != apicrypt.CryptNotError {
return nil, errCode
}
return encryptData, errorcode.Success
}
func DeCrypt(app *appmodel.App, data string, aesKey string) ([]byte, int) {
appCheck := services.NewAppCheck(app)
cryptFunc := appCheck.Crypt()
if cryptFunc == nil {
return nil, appCheck.Code
}
dataByte, errCode := cryptFunc(app).Decrypt(data)
if errCode != apicrypt.CryptNotError {
return nil, errCode
}
if len(dataByte) == 0 {
return nil, errorcode.AppDeEncryptFail
}
return dataByte, errorcode.Success
}

View File

@ -0,0 +1,146 @@
package do
import (
"PaymentCenter/app/constants/common"
"PaymentCenter/app/constants/errorcode"
"PaymentCenter/app/models/merchantmodel"
"PaymentCenter/app/models/paychannelmodel"
"PaymentCenter/app/services"
"PaymentCenter/app/third/paymentService/payCommon"
"context"
"PaymentCenter/app/models/ordersmodel"
"PaymentCenter/app/third/paymentService"
)
type PayParam struct {
Merchant *merchantmodel.Merchant
Channel *paychannelmodel.PayChannel
App_id int64
OutTradeNo string
Amount int
ExtJson string
Desc string
ClientIp string
}
type Pay struct {
ctx *context.Context
PayParam *PayParam
RelationOrder *ordersmodel.Orders
Order *ordersmodel.Orders
PayCode int
Url string
}
func NewPayWithPayCheck(paycheck *PayCheck) *Pay {
return &Pay{
ctx: paycheck.ctx,
PayParam: &PayParam{
Merchant: paycheck.Merchant,
Channel: paycheck.Channel,
App_id: paycheck.Reqs.AppId,
OutTradeNo: paycheck.Reqs.OutTradeNo,
Amount: paycheck.Reqs.Amount,
ExtJson: paycheck.Reqs.ExtJson,
Desc: paycheck.Reqs.Desc,
ClientIp: paycheck.AppCheck.Ip,
},
RelationOrder: paycheck.OldOrder,
PayCode: errorcode.Success,
}
}
func (w *Pay) CreateOrder(order_type int) {
if _, ok := common.OrderTypeList[order_type]; !ok { //判断是否是支持的支付渠道
w.PayCode = errorcode.PayChannelNotFound
return
}
order := &ordersmodel.Orders{
MerchantId: w.PayParam.Merchant.Id,
PayChannelId: w.PayParam.Channel.Id,
AppId: w.PayParam.App_id,
OutTradeNo: w.PayParam.OutTradeNo,
OrderType: order_type,
Amount: w.PayParam.Amount,
ExtJson: w.PayParam.ExtJson,
Desc: w.PayParam.Desc,
Status: common.ORDER_STATUS_WAITPAY,
}
if order_type == common.ORDER_TYPE_REFUND {
order.RefundOrderId = w.RelationOrder.Id
}
w.Order, w.PayCode = services.OrderCreate(order)
}
func (w *Pay) PayUrl() (url string) {
var (
payFunc func(commonPayInfo *paymentService.PayOrderRequest, channel *paychannelmodel.PayChannel) error
ok bool
)
thirdPay := &paymentService.PayOrderRequest{
PayChannelId: w.PayParam.Channel.Id,
OrderId: w.Order.Id,
ChannelType: w.PayParam.Channel.ChannelType,
Description: w.Order.Desc,
Amount: w.Order.Amount,
PayerClientIp: w.PayParam.ClientIp,
}
if payFunc, ok = PayWayList[w.PayParam.Channel.ChannelType]; !ok {
w.PayCode = errorcode.PayChannelNotBuild
return
}
err := payFunc(thirdPay, w.PayParam.Channel)
if err != nil {
w.PayCode = errorcode.PayChannelExtJsonError
return
}
res := paymentService.PaymentService(*w.ctx, *thirdPay)
if res.Code == payCommon.PAY_SUCCESS_CODE {
w.Order.Status = common.ORDER_STATUS_PAYING
code := services.OrderUpdate(w.Order, "status")
if code != errorcode.Success {
w.PayCode = code
}
w.Url = res.Result
} else {
w.PayCode = errorcode.PrePayFail
}
return
}
func (w *Pay) Refund() {
var (
refundFunc func(commonRefundInfo *paymentService.OrderRefundRequest, channel *paychannelmodel.PayChannel) error
ok bool
)
thirdPayRefund := &paymentService.OrderRefundRequest{
OrderId: w.Order.Id,
RefundOrderId: w.RelationOrder.Id,
RefundReason: w.PayParam.Desc,
RefundAmount: int64(w.PayParam.Amount),
}
if refundFunc, ok = RefundWayList[w.PayParam.Channel.ChannelType]; !ok {
w.PayCode = errorcode.PayChannelNotBuild
return
}
err := refundFunc(thirdPayRefund, w.PayParam.Channel)
if err != nil {
w.PayCode = errorcode.PayChannelExtJsonError
return
}
res := paymentService.OrderRefund(*w.ctx, *thirdPayRefund)
if res.Code == payCommon.PAY_SUCCESS_CODE {
w.Order.Status = common.ORDER_STATUS_PAYING
code := services.OrderUpdate(w.Order, "status")
if code != errorcode.Success {
w.PayCode = code
return
}
} else {
w.PayCode = errorcode.PreRefundFail
}
return
}

View File

@ -0,0 +1,119 @@
package do
import (
"PaymentCenter/app/constants/common"
"PaymentCenter/app/constants/errorcode"
"PaymentCenter/app/services"
"PaymentCenter/app/services/thirdpay/types"
"xorm.io/builder"
"PaymentCenter/app/models/merchantmodel"
"PaymentCenter/app/models/orderrequestlogmodel"
"PaymentCenter/app/models/ordersmodel"
"PaymentCenter/app/models/paychannelmodel"
"context"
)
type PayCheck struct {
ctx *context.Context
Reqs *types.Reqs
AppCheck *services.AppCheck
RequestLog *orderrequestlogmodel.OrderRequestLog
Channel *paychannelmodel.PayChannel
Merchant *merchantmodel.Merchant
OldOrder *ordersmodel.Orders
CheckCode int
}
func NewPayCheck(ctx *context.Context, reqs *types.Reqs, appCheck *services.AppCheck, ip string) *PayCheck {
if appCheck == nil {
appCheck = services.GetAppCheck(reqs.AppId, ip)
}
return &PayCheck{
ctx: ctx,
Reqs: reqs,
AppCheck: appCheck,
CheckCode: appCheck.Code,
}
}
func (w *PayCheck) CheckPayInfo() {
w.CheckMerchant()
if w.CheckCode != errorcode.Success {
return
}
w.CheckPayChannel()
if w.CheckCode != errorcode.Success {
return
}
}
func (w *PayCheck) CheckPayChannel() {
conn := builder.NewCond()
conn = conn.And(builder.Eq{"id": w.Reqs.PayChannelId})
w.Channel, w.CheckCode = services.GetAndCheckPayChannel(&paychannelmodel.PayChannel{}, conn)
}
func (w *PayCheck) CheckMerchant() {
conn := builder.NewCond()
conn = conn.And(builder.Eq{"id": w.AppCheck.App.MerchantId})
w.Merchant, w.CheckCode = services.GetAndCheckMerchant(&merchantmodel.Merchant{}, conn)
}
func (w *PayCheck) CheckOrderPay() {
w.GetOrder(&types.OrderFindOne{
OutTradeNo: w.Reqs.OutTradeNo,
})
if w.OldOrder != nil {
switch w.OldOrder.Status {
case common.ORDER_STATUS_CLOSE:
w.CheckCode = errorcode.OrderClosed
case common.ORDER_STATUS_FAILED:
w.CheckCode = errorcode.OrderFailed
case common.ORDER_STATUS_PAYED:
w.CheckCode = errorcode.OrderPayed
default:
}
}
return
}
func (w *PayCheck) CheckOrderRefund() {
w.GetOrder(&types.OrderFindOne{
OutTradeNo: w.Reqs.RefundOutTradeNo,
OrderId: w.Reqs.RefundOrderId,
})
if w.OldOrder == nil {
w.CheckCode = errorcode.RefundOrderNotFound
return
}
if w.OldOrder.Status != common.ORDER_STATUS_PAYED {
w.CheckCode = errorcode.OrderStatusRefundNotSupport
}
return
}
func (w *PayCheck) GetOrder(orderCol *types.OrderFindOne) {
cond := builder.NewCond()
cond = cond.And(builder.Eq{"app_id": w.AppCheck.AppId})
if orderCol.OrderId == "" && orderCol.OutTradeNo == "" {
w.CheckCode = errorcode.OrdersNotFound
return
}
if orderCol.OrderId != "" {
cond = cond.And(builder.Eq{"order_id": orderCol.OrderId})
}
if orderCol.OutTradeNo != "" {
cond = cond.And(builder.Eq{"out_trade_no": orderCol.OutTradeNo})
}
order, code := services.OrderFindOne(&ordersmodel.Orders{}, cond)
if code == errorcode.SystemError {
w.CheckCode = code
return
}
if code == errorcode.Success {
w.OldOrder = order
}
return
}

View File

@ -0,0 +1,31 @@
package do
import (
"PaymentCenter/app/constants/common"
"PaymentCenter/app/models/paychannelmodel"
"PaymentCenter/app/third/paymentService"
"github.com/bytedance/sonic"
)
var PayWayList = map[int]func(commonPayInfo *paymentService.PayOrderRequest, channel *paychannelmodel.PayChannel) error{
common.PAY_CHANNEL_WECHAT_H5: WechatH5,
common.PAY_CHANNEL_ALIPAY_WEB: AlipayWeb,
}
func WechatH5(commonPayInfo *paymentService.PayOrderRequest, channel *paychannelmodel.PayChannel) error {
err := sonic.Unmarshal([]byte(channel.ExtJson), &commonPayInfo.Wx)
if err != nil {
return err
}
commonPayInfo.Wx.AppId = channel.AppId
return nil
}
func AlipayWeb(commonPayInfo *paymentService.PayOrderRequest, channel *paychannelmodel.PayChannel) error {
err := sonic.Unmarshal([]byte(channel.ExtJson), &commonPayInfo.Ali)
if err != nil {
return err
}
commonPayInfo.Ali.AppId = channel.AppId
return nil
}

View File

@ -0,0 +1,33 @@
package do
import (
"PaymentCenter/app/constants/common"
"PaymentCenter/app/models/paychannelmodel"
"PaymentCenter/app/third/paymentService"
"github.com/bytedance/sonic"
)
var RefundWayList = map[int]func(commonRefundInfo *paymentService.OrderRefundRequest, channel *paychannelmodel.PayChannel) error{
common.PAY_CHANNEL_WECHAT_H5: WechatH5Refund,
common.PAY_CHANNEL_ALIPAY_WEB: AlipayWebRefund,
}
func WechatH5Refund(commonRefundInfo *paymentService.OrderRefundRequest, channel *paychannelmodel.PayChannel) error {
commonRefundInfo.PayChannel = common.PAY_CHANNLE_TYPE_WECHAT
err := sonic.Unmarshal([]byte(channel.ExtJson), &commonRefundInfo.Wx)
if err != nil {
return err
}
commonRefundInfo.Wx.AppId = channel.AppId
return nil
}
func AlipayWebRefund(commonRefundInfo *paymentService.OrderRefundRequest, channel *paychannelmodel.PayChannel) error {
commonRefundInfo.PayChannel = common.PAY_CHANNLE_TYPE_ZFB
err := sonic.Unmarshal([]byte(channel.ExtJson), &commonRefundInfo.Ali)
if err != nil {
return err
}
commonRefundInfo.Ali.AppId = channel.AppId
return nil
}

View File

@ -0,0 +1,80 @@
package thirdpay
import (
"PaymentCenter/app/constants/common"
"PaymentCenter/app/constants/errorcode"
"PaymentCenter/app/http/entities/front"
"PaymentCenter/app/models/ordersmodel"
"PaymentCenter/app/services"
"PaymentCenter/app/services/thirdpay/api"
thirdpay "PaymentCenter/app/services/thirdpay/do"
"PaymentCenter/app/services/thirdpay/types"
"context"
"github.com/jinzhu/copier"
)
func ThirdPayUrl(check *thirdpay.PayCheck) *thirdpay.Pay {
pay := ThirdPay(check)
// 支付
pay.PayUrl()
return pay
}
func NewOrdersResp(db *ordersmodel.Orders) *api.OrdersResp {
return &api.OrdersResp{
OrderNo: db.Id,
OutTradeNo: db.OutTradeNo,
Status: db.Status,
OrderType: db.OrderType,
Amount: db.Amount,
PayerTotal: db.PayerTotal,
Desc: db.Desc,
CreateTime: db.CreateTime.Format("2006-01-02 15:04:05"),
}
}
func ThirdPayInfoCheck(ctx context.Context, payReq *front.PayReqs, appCheck *services.AppCheck, ip string) (check *thirdpay.PayCheck) {
var req types.Reqs
copier.Copy(&req, payReq)
check = thirdpay.NewPayCheck(&ctx, &req, appCheck, ip)
// 校验表单
check.CheckPayInfo()
if check.CheckCode != errorcode.Success {
return
}
return
}
func ThirdPayRefund(ctx context.Context, refundReq *front.RefundReqs, appCheck *services.AppCheck, ip string) (refund *thirdpay.Pay) {
var req types.Reqs
copier.Copy(&req, refundReq)
check := thirdpay.NewPayCheck(&ctx, &req, appCheck, ip)
// 校验表单
check.CheckPayInfo()
if check.CheckCode != errorcode.Success {
return
}
check.CheckOrderRefund()
if check.CheckCode != errorcode.Success {
return
}
refund = thirdpay.NewPayWithPayCheck(check)
refund.CreateOrder(common.ORDER_TYPE_REFUND)
refund.Refund()
return
}
func ThirdPay(check *thirdpay.PayCheck) (pay *thirdpay.Pay) {
pay = thirdpay.NewPayWithPayCheck(check)
// 创建订单
if check.OldOrder != nil {
pay.Order = check.OldOrder
} else {
pay.CreateOrder(common.ORDER_TYPE_PAY)
if pay.PayCode != errorcode.Success {
return pay
}
}
return
}

View File

@ -0,0 +1,186 @@
package thirdpay_notify
import (
"PaymentCenter/app/constants/common"
"PaymentCenter/app/constants/errorcode"
"PaymentCenter/app/data"
"PaymentCenter/app/http/entities"
"PaymentCenter/app/models/appmodel"
"PaymentCenter/app/models/ordercallbacklogmodel"
"PaymentCenter/app/models/ordersmodel"
"PaymentCenter/app/services"
"PaymentCenter/app/utils"
"PaymentCenter/app/utils/httpclient"
"github.com/bytedance/sonic"
"time"
"xorm.io/builder"
)
type OrderNotify struct {
order *ordersmodel.Orders
Code int
app *appmodel.App
OrderId int64
Status int //订单状态
ActualAmount int
Msg string
CompleteTime time.Time
}
type OrderNotifyResp struct {
OrderId int64
Send bool
ErrCode int
SendTime time.Time
Content *OrderNotify
}
type OrderNotifySendContent struct {
OrderId int64 `json:"order_id"`
OutTradeNo string `json:"out_trade_no"`
CompleteTime time.Time `json:"complete_time"`
OrderType int `json:"order-type"`
Status int `json:"status"`
Msg string `json:"msg"`
ErrCode int `json:"err_code"`
AppId int64 `json:"app_id"`
ChannelId int64 `json:"channel_id"`
MerchantId int64 `json:"merchant_id"`
}
func NewOrderNotifyWithHandle(orderId int64, Status int, actualAmount int, msg string) *OrderNotifyResp {
orderNotify := &OrderNotify{
OrderId: orderId,
Status: Status,
ActualAmount: actualAmount,
Msg: msg,
Code: errorcode.Success,
}
return orderNotify.Handle()
}
func (o *OrderNotify) NotifyRespFail(errorCode int) *OrderNotifyResp {
return &OrderNotifyResp{
OrderId: o.OrderId,
Send: false,
ErrCode: errorCode,
Content: o,
}
}
func (o *OrderNotify) Handle() (res *OrderNotifyResp) {
o.checkOrder()
if o.Code != errorcode.Success {
return o.NotifyRespFail(o.Code)
}
o.checkApp()
if o.Code != errorcode.Success {
return o.NotifyRespFail(o.Code)
}
o.updateOrder()
if o.Code != errorcode.Success {
return o.NotifyRespFail(o.Code)
}
o.sendNotify(o.setBody())
if o.Code != errorcode.Success {
return o.NotifyRespFail(o.Code)
}
return &OrderNotifyResp{
OrderId: o.OrderId,
Send: true,
ErrCode: o.Code,
Content: o,
}
}
// 发送下游回调通知
func (o *OrderNotify) sendNotify(body *OrderNotifySendContent) {
if o.app.NotifyUrl == "" {
o.Code = errorcode.AppNotifyUrlNotFound
return
}
var callbackStatus = common.STATUS_ENABLE
var response string
bodyByte, _ := sonic.Marshal(&body)
headers := make(map[string]string, 1)
headers["Content-Type"] = "application/json"
resByte, err := httpclient.FastHttpPost(o.app.NotifyUrl, headers, bodyByte, 0)
if err != nil || string(resByte) != "success" {
o.Code = errorcode.NotifySendFail
callbackStatus = common.STATUS_DISABLED
response = "url=" + o.app.NotifyUrl + "|error=" + err.Error()
}
response = string(resByte) + response
// 记录回调日志
go func(orderId int64, status int, request, response string) {
repo := data.NewOrderCallbackLogRepo(ordercallbacklogmodel.GetInstance().GetDb())
log := ordercallbacklogmodel.OrderCallbackLog{
OrderId: orderId,
MerchantRequest: string(bodyByte),
Status: status,
MerchantResponse: response,
}
_, insertErr := repo.OrderCallbackLogInsertOne(&log)
if insertErr != nil {
utils.Log(nil, "回调写入日志error", insertErr)
}
}(o.OrderId, callbackStatus, string(bodyByte), response)
return
}
func (o *OrderNotify) setBody() *OrderNotifySendContent {
return &OrderNotifySendContent{
OrderId: o.OrderId,
OutTradeNo: o.order.OutTradeNo,
CompleteTime: o.CompleteTime,
Status: o.order.Status,
OrderType: o.order.OrderType,
Msg: o.Msg,
ErrCode: o.Status,
AppId: o.order.AppId,
ChannelId: o.order.PayChannelId,
MerchantId: o.order.MerchantId,
}
}
func (o *OrderNotify) updateOrder() {
if _, ok := common.OrderStatusMap[o.Status]; !ok {
o.Code = errorcode.OrderStatusErr
return
}
o.order.Status = o.Status
o.order.PayerTotal = o.ActualAmount
o.Code = services.OrderUpdate(o.order, "status")
return
}
func (o *OrderNotify) checkApp() {
o.app, o.Code = services.AppFindOne(entities.IdRequest{Id: o.order.AppId})
if o.Code != errorcode.Success {
return
}
//if o.app.NotifyUrl == "" {
// o.Code = errorcode.AppNotifyUrlNotFound
// return
//}
return
}
func (o *OrderNotify) checkOrder() {
cond := builder.NewCond()
cond = cond.And(builder.Eq{"id": o.OrderId})
o.order, o.Code = services.OrderFindOne(&ordersmodel.Orders{}, cond)
if o.Code != errorcode.OrdersExist {
return
}
if o.order.Status != common.ORDER_STATUS_PAYING {
o.Code = errorcode.OrderStatusErr
return
}
return
}

View File

@ -0,0 +1,15 @@
package types
import "PaymentCenter/app/http/entities/front"
type Reqs struct {
front.PayCommonReqBody
OutTradeNo string `json:"out_trade_no"`
RefundOutTradeNo string `json:"refund_out_trade_no"`
RefundOrderId string `json:"refundOrder_id"`
}
type OrderFindOne struct {
OrderId string
OutTradeNo string `json:"out_trade_no"`
}

View File

@ -1,90 +0,0 @@
package market
import (
"PaymentCenter/config"
"bytes"
"encoding/json"
"github.com/pkg/errors"
"io/ioutil"
"net/http"
)
type MarketClient struct {
cfg config.MarketConfig
}
type MarketSendRequest struct {
AppId string `json:"app_id"` //APP ID
Sign string `json:"sign"` //签名
ReqCode string `json:"req_code"` //固定值voucher.create
MemId string `json:"mem_id"` //商户号
ReqSerialNo string `json:"req_serial_no"` //请求唯一流水号 最大32位
TimeTamp string `json:"timestamp"` //时间戳 yyyyMMddHHmmss
PosId string `json:"pos_id"` //商户方平台号
VoucherId string `json:"voucher_id"` //制码批次号
VoucherNum int `json:"voucher_num"` //请券数量,默认是 1
MobileNo string `json:"mobile_no"` //11 手机号,可传空字符串
SendMsg string `json:"send_msg"` //是否发送短信2- 发送 1-不发送
}
type MarketSenResponse struct {
VoucherId string `json:"voucher_id"` //制码批次号
VoucherCode string `json:"voucher_code"` //券码
ShortUrl string `json:"short_url"` //含二维码、条码的短链接
VoucherSdate string `json:"voucher_sdate"` //有效期起
VoucherEdate string `json:"voucher_edate"` //有效期止
CodeType string `json:"code_type"` //码类型: 00- 代金券 01- 满减券
}
type MarketResponse struct {
ErrCode string `json:"errCode"` //00-成功 其他:失败
Msg string `json:"msg"` //描 述 (失败时必填)
Data MarketSenResponse `json:"data"`
}
func (this *MarketSendRequest) toMap() (resultMap map[string]interface{}) {
// Marshal the struct to JSON, ignoring omitempty fields.
jsonBytes, err := json.Marshal(this)
if err != nil {
return
}
// Unmarshal the JSON into a map to get the final result.
err = json.Unmarshal(jsonBytes, &resultMap)
if err != nil {
return
}
return resultMap
}
func (this *MarketClient) doPost(url string, jsonBytes []byte) (body []byte, err error) {
// 创建POST请求
url = this.cfg.Host + url
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonBytes))
if err != nil {
return
}
// 设置Content-Type头
req.Header.Set("Content-Type", "application/json")
// 创建HTTP客户端
client := &http.Client{}
// 发送请求并处理响应
resp, err := client.Do(req)
if err != nil {
return
}
defer resp.Body.Close()
// 读取响应体
if resp.StatusCode != http.StatusOK {
err = errors.New("HTTP request failed: " + resp.Status)
return
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
return
}
return
}

View File

@ -1,62 +0,0 @@
package market
import (
"PaymentCenter/app/utils/encrypt"
"PaymentCenter/config"
"encoding/json"
"time"
)
func NewMarketClient(cfg config.MarketConfig) *MarketClient {
cfg.Sign = "-----BEGIN RSA PRIVATE KEY-----\n" + cfg.Sign + "\n-----END RSA PRIVATE KEY-----"
return &MarketClient{
cfg: cfg,
}
}
/*
MarketSend
券码生成接口
- 请求地址/openApi/v1/market/key/send
- 说明发券接口应支持使用同一流水号进行重复请求当调用该接口失败时 以使用同一流水号进行再次请求接口需要根据请求的流水号进行判断若无该流水 号的券码信息则新生成后返回若有该流水号的券码信息则直接返回该券码的信息
orderNo: 订单号
VoucherId: 制码批次号
MobileNo: 11 手机号可传空字符串
SendMsg: 是否发送短信2- 发送 1-不发送
*/
func (this *MarketClient) MarketSend(orderNo, VoucherId, MobileNo, SendMsg string) (res MarketResponse, err error) {
url := "/openApi/v1/market/key/send"
request := MarketSendRequest{
AppId: this.cfg.AppId,
ReqCode: this.cfg.ReqCode,
MemId: this.cfg.MemId,
PosId: this.cfg.PosId,
TimeTamp: time.Now().Format("20060102150405"),
VoucherId: VoucherId,
ReqSerialNo: orderNo,
VoucherNum: 1,
MobileNo: MobileNo,
SendMsg: SendMsg,
}
request.Sign, err = MakeRsaSign(this.cfg.Sign, request.toMap())
if err != nil {
return res, err
}
bytes, err := json.Marshal(request)
if err != nil {
return res, err
}
data, err := this.doPost(url, bytes)
if err != nil {
return res, err
}
err = json.Unmarshal(data, &res)
// 加密
if len(res.Data.ShortUrl) > 0 {
res.Data.ShortUrl = encrypt.AesEncryptCBC([]byte(res.Data.ShortUrl), []byte(this.cfg.SecretKey))
}
return res, err
}

View File

@ -1,34 +0,0 @@
package market
import (
"PaymentCenter/app/utils"
"PaymentCenter/config"
"fmt"
"github.com/qit-team/snow-core/kernel/server"
"os"
"testing"
)
func TestMarketSendRequest_Market(t *testing.T) {
opts := config.GetOptions()
if opts.ShowVersion {
fmt.Printf("%s\ncommit %s\nbuilt on %s\n", server.Version, server.BuildCommit, server.BuildDate)
os.Exit(0)
}
//加载配置
conf, err := config.Load(opts.ConfFile)
if err != nil {
utils.Log(nil, "err", err.Error())
return
}
client := NewMarketClient(conf.OpenApiMarketConfig)
//data, err := client.MarketSend("123456789111", "1717567048171", "", "2")
data, err := client.MarketSend("123111", "1717", "", "2")
if err != nil {
t.Error(err)
}
t.Log(data)
}

View File

@ -1,137 +0,0 @@
package market
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
"sort"
)
// getSignString 使用 xx=aa&yy=bb 的字符串拼接
func getSignString(data map[string]interface{}) string {
keys := make([]string, 0, len(data))
for key := range data {
keys = append(keys, key)
}
sort.Strings(keys)
signString := ""
separator := ""
for _, key := range keys {
value := data[key]
if key == "sign" || value == nil {
continue
}
signString += fmt.Sprintf("%s%s=%v", separator, key, value)
separator = "&"
}
return signString
}
// VerifyRsaSign 签名验证
func VerifyRsaSign(publicKey string, data map[string]interface{}) (map[string]interface{}, error) {
// 对 sign nonce timestamp appId 升序排序
// 使用 xx=aa&yy=bb 的字符串拼接
// 商户的公钥验签 RSA2验签
signString := getSignString(data)
rsaPubKey, err := parseRSAPublicKeyFromPEM([]byte(publicKey))
if err != nil {
return nil, err
}
signature, err := base64.StdEncoding.DecodeString(data["sign"].(string))
if err != nil {
return nil, err
}
hashed := sha256.Sum256([]byte(signString))
err = rsa.VerifyPKCS1v15(rsaPubKey, crypto.SHA256, hashed[:], signature)
if err != nil {
return nil, errors.New("签名验证失败")
}
return data, nil
}
// MakeRsaSign 生成签名
func MakeRsaSign(privateKey string, data map[string]interface{}) (string, error) {
// 对 sign nonce timestamp appId 升序排序
// 使用 xx=aa&yy=bb 的字符串拼接
// 营销系统生成的私钥生成签名 RSA2加签
signString := getSignString(data)
privKey, err := parseRSAPrivateKeyFromPEM([]byte(privateKey))
if err != nil {
return "", errors.New("私钥解析失败")
}
hashed := sha256.Sum256([]byte(signString))
signature, err := rsa.SignPKCS1v15(rand.Reader, privKey, crypto.SHA256, hashed[:])
if err != nil {
return "", errors.New("签名失败")
}
return base64.StdEncoding.EncodeToString(signature), nil
}
// ParseRSAPrivateKeyFromPEM 解析私钥
func parseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error) {
var err error
// Parse PEM block
var block *pem.Block
if block, _ = pem.Decode(key); block == nil {
return nil, errors.New("私钥解析失败: 无效的PEM格式")
}
var parsedKey interface{}
if parsedKey, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {
if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil {
return nil, err
}
}
var pkey *rsa.PrivateKey
var ok bool
if pkey, ok = parsedKey.(*rsa.PrivateKey); !ok {
return nil, errors.New("密钥不是有效的RSA私钥")
}
return pkey, nil
}
// parseRSAPublicKeyFromPEM parses a PEM encoded PKCS1 or PKCS8 public key
func parseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error) {
var err error
// Parse PEM block
var block *pem.Block
if block, _ = pem.Decode(key); block == nil {
return nil, errors.New("公钥解析失败: 无效的PEM格式")
}
// Parse the key
var parsedKey interface{}
if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil {
if cert, err := x509.ParseCertificate(block.Bytes); err == nil {
parsedKey = cert.PublicKey
} else {
return nil, err
}
}
var pkey *rsa.PublicKey
var ok bool
if pkey, ok = parsedKey.(*rsa.PublicKey); !ok {
return nil, errors.New("密钥不是有效的RSA公钥")
}
return pkey, nil
}

View File

@ -1,164 +0,0 @@
package openapiService
import (
"PaymentCenter/app/models/orderdetailsmodel"
"PaymentCenter/app/models/ordersmodel"
"PaymentCenter/app/models/usercouponmodel"
"PaymentCenter/app/utils"
"PaymentCenter/config"
"context"
"crypto/aes"
"encoding/base64"
"gitee.com/chengdu_blue_brothers/openapi-go-sdk/api"
"gitee.com/chengdu_blue_brothers/openapi-go-sdk/notify"
"net/http"
"time"
)
func CreatClient() (client *api.Client, err error) {
merchantId := config.GetConf().OpenApi.MerchantId
secretKey := config.GetConf().OpenApi.SecretKey
isProd := config.GetConf().OpenApi.IsProd
timeout := 10 * time.Second // 请求超时时间
client, err = api.NewClient(merchantId, secretKey, isProd, timeout)
if err != nil {
return nil, err
}
return client, nil
}
type RechargeOrderReq struct {
OutTradeNo string
ProductId int
RechargeAccount string
AccountType int
Number int
}
func RechargeOrder(rechargeOrderReq RechargeOrderReq) (result *api.RechargeOrderResp, err error) {
req := &api.RechargeOrderReq{
OutTradeNo: rechargeOrderReq.OutTradeNo,
ProductId: rechargeOrderReq.ProductId,
RechargeAccount: rechargeOrderReq.RechargeAccount,
AccountType: 0,
Number: 1,
NotifyUrl: config.GetConf().OpenApi.NotifyUrl,
}
client, err := CreatClient()
if err != nil {
return nil, err
}
result, err = client.RechargeOrder(context.Background(), req)
if err != nil {
return nil, err
}
if result.Code != api.CodeCreateOrderSuccess {
return
}
return
}
func ReCardOrder(CardOrderReq api.CardOrderReq) (result *api.CardOrderResp, err error) {
req := &api.CardOrderReq{
OutTradeNo: CardOrderReq.OutTradeNo,
ProductId: CardOrderReq.ProductId,
AccountType: 0,
Number: 1,
NotifyUrl: config.GetConf().OpenApi.NotifyUrl,
}
client, err := CreatClient()
if err != nil {
return nil, err
}
result, err = client.CardOrder(context.Background(), req)
if err != nil {
return nil, err
}
if result.Code != api.CodeCreateOrderSuccess {
return
}
return
}
func DecryptCard(encCode string) (decode string, err error) {
defer func() error {
if r := recover(); r != nil {
utils.Log(nil, "解密错误", err)
}
return err
}()
client, err := CreatClient()
if err != nil {
return
}
decryptedCode, err := decryptAES(encCode, client.GetSecretKey())
if err != nil {
return
}
return decryptedCode, nil
}
// AES 解密
func decryptAES(encryptedData string, secretKey string) (string, error) {
// 第一步对加密的卡密做base64 decode
encryptedBytes, err := base64.StdEncoding.DecodeString(encryptedData)
if err != nil {
return "", err
}
// 第二步使用aes-256-ecb解密
cipher, _ := aes.NewCipher([]byte(secretKey))
decrypted := make([]byte, len(encryptedBytes))
size := 16
for bs, be := 0, size; bs < len(encryptedBytes); bs, be = bs+size, be+size {
cipher.Decrypt(decrypted[bs:be], encryptedBytes[bs:be])
}
paddingSize := int(decrypted[len(decrypted)-1])
return string(decrypted[0 : len(decrypted)-paddingSize]), nil
}
func ParseAndVerify(request *http.Request) (req *notify.OrderReq, err error) {
// 第一步初使化client实例
merchantId := config.GetConf().OpenApi.MerchantId
secretKey := config.GetConf().OpenApi.SecretKey
client := notify.NewNotify(merchantId, secretKey)
// 第二步:验签并解析结果
req, err = client.ParseAndVerify(request)
if err != nil {
return nil, err
}
return
}
// NotifyOperation /**
func NotifyOperation(order ordersmodel.Orders, req *notify.OrderReq) (err error) {
var session = ordersmodel.GetInstance().GetDb().NewSession()
session.Begin()
updateOrder := ordersmodel.Orders{Status: 3}
_, err = session.Where("Id = ?", order.Id).Update(&updateOrder)
if err != nil {
session.Rollback()
return
}
//卡密
if req.CardCode != "" {
//订单详情
updateOrderDetail := orderdetailsmodel.OrderDetails{Url: req.CardCode.Value()}
_, err = session.Where("OrderId = ?", order.Id).Update(&updateOrderDetail)
if err != nil {
session.Rollback()
return
}
userCouponDetail := usercouponmodel.UserCoupon{OrderInfo: req.CardCode.Value()}
_, err = session.Where("OrderId = ?", order.Id).Update(&userCouponDetail)
if err != nil {
session.Rollback()
return
}
}
session.Commit()
return
}

View File

@ -0,0 +1,293 @@
package paymentService
import (
"PaymentCenter/app/constants/common"
"PaymentCenter/app/constants/errorcode"
"PaymentCenter/app/services/thirdpay/thirdpay_notify"
"PaymentCenter/app/third/paymentService/payCommon"
"PaymentCenter/config"
"context"
"encoding/json"
"errors"
"fmt"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/alipay"
"github.com/qit-team/snow-core/log/logger"
"strconv"
)
// AliInitClient 使用提供的支付请求参数初始化支付宝客户端
func AliInitClient(aliConfig AliPay) (*alipay.Client, error) {
envConfig := config.GetConf()
// 初始化支付宝客户端
// appid应用ID
// privateKey应用私钥支持PKCS1和PKCS8
// isProd是否是正式环境沙箱环境请选择新版沙箱应用。
aliClient, aliClientErr := alipay.NewClient(aliConfig.AppId, aliConfig.PrivateKey, envConfig.PayService.IsProd)
// 自定义配置http请求接收返回结果body大小默认 10MB
aliClient.SetBodySize(10) // 没有特殊需求,可忽略此配置
// 打开Debug开关输出日志默认关闭
aliClient.DebugSwitch = gopay.DebugOn
// 设置支付宝请求 公共参数
// 注意:具体设置哪些参数,根据不同的方法而不同,此处列举出所有设置参数
aliClient.SetLocation(alipay.LocationShanghai). // 设置时区,不设置或出错均为默认服务器时间
SetCharset(alipay.UTF8). // 设置字符编码,不设置默认 utf-8
SetSignType(alipay.RSA2) // 设置签名类型,不设置默认 RSA2
// 自动同步验签(只支持证书模式)
// 传入 alipayPublicCert.crt 内容
aliClient.AutoVerifySign([]byte(aliConfig.AlipayPublicCert))
// 证书内容
aliClientErr = aliClient.SetCertSnByContent([]byte(aliConfig.AppPublicCert), []byte(aliConfig.AlipayRootCert), []byte(aliConfig.AlipayPublicCert))
if aliClient == nil {
return nil, errors.New("client not initialized")
}
if aliClientErr != nil {
return nil, aliClientErr
}
return aliClient, nil
}
// ALiH5PayInfo 支付宝手机网站支付
func ALiH5PayInfo(c context.Context, payOrderRequest PayOrderRequest) (string, error) {
// 初始化支付宝客户端
aliClient, err := AliInitClient(payOrderRequest.Ali)
if err != nil {
fmt.Println("Failed to get client:", err)
return "", err
}
// 初始化 BodyMap
amount := float64(payOrderRequest.Amount) / 100.0
envConfig := config.GetConf()
bm := make(gopay.BodyMap)
bm.Set("out_trade_no", payOrderRequest.OrderId).
Set("total_amount", amount).
Set("subject", payOrderRequest.Description).
Set("notify_url", fmt.Sprintf(envConfig.PayService.Host+payCommon.ALI_NOTIFY_URL_TEST+"%d", payOrderRequest.PayChannelId))
aliRsp, err := aliClient.TradeWapPay(c, bm)
if err != nil {
logger.Error(c, "ALiH5PayInfo 发生错误", fmt.Sprintf("错误信息:%s", err.Error()))
if bizErr, ok := alipay.IsBizError(err); ok {
return "", bizErr
}
return "", err
}
return aliRsp, nil
}
// ALiCallBack 支付宝支付回调
func ALiCallBack(notifyReq gopay.BodyMap, aliConfig AliPay) error {
ok, err := alipay.VerifySignWithCert([]byte(aliConfig.AlipayPublicCert), notifyReq)
if !ok || err != nil {
return err
}
// 拼装数据触发下游回调,触发下游回调
orderId, _ := strconv.Atoi(notifyReq.Get("out_trade_no"))
payerTotal, _ := strconv.Atoi(notifyReq.Get("buyer_pay_amount"))
// 订单状态
tradeStatus := notifyReq.Get("trade_status")
errCode := 0
msg := ""
switch tradeStatus {
case "TRADE_CLOSED":
errCode = errorcode.ParamError
msg = "未付款交易超时关闭,或支付完成后全额退款。"
case "TRADE_SUCCESS":
errCode = errorcode.Success
msg = "交易支付成功。"
case "TRADE_FINISHED":
errCode = errorcode.Success
msg = "交易结束,不可退款。"
}
if errCode == 0 {
// 等待买家付款,不走后续回调
return errors.New("订单状态异常,无法进行后续回调")
}
res := thirdpay_notify.NewOrderNotifyWithHandle(int64(orderId), errCode, payerTotal, msg)
merchantCallback, _ := json.Marshal(res)
// 记录日志
go func() {
payCallback, _ := json.Marshal(notifyReq)
payParam := "{}"
status := common.THIRD_ORDER_LOG_STATUS_SUCCESS
SaveLog(int64(orderId), common.THIRD_ORDER_TYPE_CALL_BACK, string(payCallback), payParam, "{}", status)
}()
if res.ErrCode != errorcode.Success {
logger.Error(context.Background(), "ALiCallBack 发生错误", fmt.Sprintf("回调时,下游处理订单失败,返回数据为:%s", string(merchantCallback)))
return errors.New("回调时,下游处理订单失败")
}
return nil
}
// ALiOrderQuery 查询支付宝订单
func ALiOrderQuery(ctx context.Context, aliConfig AliPay, OrderNo string) (PayOrderQueryInfo, error) {
// 初始化支付宝客户端
aliClient, err := AliInitClient(aliConfig)
if err != nil {
fmt.Println("Failed to get client:", err)
return PayOrderQueryInfo{}, err
}
// 请求参数
bm := make(gopay.BodyMap)
bm.Set("out_trade_no", OrderNo)
// 查询订单
aliRsp, err := aliClient.TradeQuery(ctx, bm)
if err != nil {
if bizErr, ok := alipay.IsBizError(err); ok {
return PayOrderQueryInfo{}, bizErr
}
return PayOrderQueryInfo{}, err
}
// 同步返回验签
ok, err := alipay.VerifySyncSignWithCert([]byte(aliConfig.AlipayPublicCert), aliRsp.SignData, aliRsp.Sign)
if err != nil || !ok {
return PayOrderQueryInfo{}, errors.New(fmt.Sprintf("验签失败,失败原因:%s", err.Error()))
}
// 映射交易状态
tradeState := ""
tradeStateDesc := ""
switch aliRsp.Response.TradeStatus {
case "TRADE_SUCCESS":
tradeState = "SUCCESS"
tradeStateDesc = "交易支付成功"
case "REFUND":
tradeState = "REFUND"
tradeStateDesc = "转入退款"
case "WAIT_BUYER_PAY":
tradeState = "NOTPAY"
tradeStateDesc = "交易创建,等待买家付款"
case "TRADE_CLOSED":
tradeState = "CLOSED"
tradeStateDesc = "未付款交易超时关闭,或支付完成后全额退款"
}
amountTotal, _ := strconv.ParseFloat(aliRsp.Response.TotalAmount, 64)
payerTotal, _ := strconv.ParseFloat(aliRsp.Response.BuyerPayAmount, 64)
// 构建数据
outTradeNo, _ := strconv.Atoi(aliRsp.Response.OutTradeNo)
return PayOrderQueryInfo{
AppId: aliConfig.AppId,
OutTradeNo: int64(outTradeNo),
TransactionId: aliRsp.Response.TradeNo,
TradeState: tradeState,
TradeStateDesc: tradeStateDesc,
SuccessTime: aliRsp.Response.SendPayDate,
AmountTotal: int64(amountTotal * 100),
PayerTotal: int64(payerTotal * 100),
}, nil
}
// AliRefundOrder 支付宝退款申请
func AliRefundOrder(ctx context.Context, orderRefundRequest OrderRefundRequest) (OrderRefundInfo, error) {
// 初始化支付宝客户端
aliClient, err := AliInitClient(orderRefundRequest.Ali)
if err != nil {
fmt.Println("Failed to get client:", err)
return OrderRefundInfo{}, err
}
// 请求参数
refundAmount := float64(orderRefundRequest.RefundAmount) / 100.0
bm := make(gopay.BodyMap)
bm.Set("out_trade_no", orderRefundRequest.OrderId).
Set("refund_amount", refundAmount).
Set("refund_reason", orderRefundRequest.RefundReason).
Set("out_request_no", orderRefundRequest.RefundOrderId)
// 发起退款请求
aliRsp, err := aliClient.TradeRefund(ctx, bm)
if err != nil {
logger.Error(ctx, "AliRefundOrder 发生错误", fmt.Sprintf("申请退款接口失败,错误信息:%s", err.Error()))
if bizErr, ok := alipay.IsBizError(err); ok {
return OrderRefundInfo{}, bizErr
}
return OrderRefundInfo{}, err
}
refundFee, _ := strconv.ParseFloat(aliRsp.Response.RefundFee, 64)
outTradeNo, _ := strconv.ParseFloat(aliRsp.Response.OutTradeNo, 64)
return OrderRefundInfo{
OutTradeNo: int64(outTradeNo),
TransactionId: aliRsp.Response.TradeNo,
RefundFee: int64(refundFee * 100),
RefundOrderId: orderRefundRequest.RefundOrderId,
RefundStatus: payCommon.PAY_REFUND_STATU_SUCCESS,
RefundSuccessTime: aliRsp.Response.GmtRefundPay,
}, nil
}
// AliRefundOrderQuery 支付宝订单退款查询
func AliRefundOrderQuery(ctx context.Context, orderRefundQueryRequest OrderRefundQueryRequest) (OrderRefundInfo, error) {
// 初始化支付宝客户端
aliClient, err := AliInitClient(orderRefundQueryRequest.Ali)
if err != nil {
fmt.Println("Failed to get client:", err)
return OrderRefundInfo{}, err
}
// 请求参数
bm := make(gopay.BodyMap)
bm.Set("out_trade_no", orderRefundQueryRequest.OrderId).
Set("out_request_no", orderRefundQueryRequest.RefundOrderId).
Set("query_options", []string{"gmt_refund_pay"})
// 发起退款查询请求
aliRsp, err := aliClient.TradeFastPayRefundQuery(ctx, bm)
if err != nil {
if bizErr, ok := alipay.IsBizError(err); ok {
return OrderRefundInfo{}, bizErr
}
return OrderRefundInfo{}, err
}
refundFee, _ := strconv.ParseFloat(aliRsp.Response.RefundAmount, 64)
outTradeNo, _ := strconv.Atoi(aliRsp.Response.OutTradeNo)
refundOrderId, _ := strconv.Atoi(aliRsp.Response.OutRequestNo)
return OrderRefundInfo{
OutTradeNo: int64(outTradeNo),
TransactionId: aliRsp.Response.TradeNo,
RefundFee: int64(refundFee * 100),
RefundOrderId: int64(refundOrderId),
RefundStatus: payCommon.PAY_REFUND_STATU_SUCCESS,
RefundSuccessTime: aliRsp.Response.GmtRefundPay,
}, nil
}
// AliCloseOrder 支付宝订单关闭
func AliCloseOrder(ctx context.Context, orderCloseRequest OrderCloseRequest) (OrderCloseInfo, error) {
// 初始化支付宝客户端
aliClient, err := AliInitClient(orderCloseRequest.Ali)
if err != nil {
fmt.Println("Failed to get client:", err)
return OrderCloseInfo{}, err
}
// 请求参数
bm := make(gopay.BodyMap)
bm.Set("out_trade_no", orderCloseRequest.OrderId)
// 关闭订单
aliRsp, err := aliClient.TradeClose(ctx, bm)
if err != nil {
logger.Error(ctx, "AliCloseOrder 发生错误", fmt.Sprintf("申请退款接口失败,错误信息:%s", err.Error()))
if bizErr, ok := alipay.IsBizError(err); ok {
return OrderCloseInfo{}, bizErr
}
return OrderCloseInfo{}, err
}
outTradeNo, _ := strconv.Atoi(aliRsp.Response.OutTradeNo)
return OrderCloseInfo{
OutTradeNo: int64(outTradeNo),
}, nil
}

View File

@ -0,0 +1,38 @@
package payCommon
import "PaymentCenter/app/constants/common"
const (
// 支付渠道枚举,1微信JSAPI2微信H53微信app4微信Native5微信小程序6支付宝网页&移动应用7支付宝小程序8支付宝JSAPI
PAY_CHANNEL_UNKNOWN = 0
PAY_CHANNEL_WECHAT_JSAPI = 1
PAY_CHANNEL_WECHAT_H5 = 2
PAY_CHANNEL_WECHAT_APP = 3
PAY_CHANNEL_WECHAT_NATIVE = 4
PAY_CHANNEL_WECHAT_MINI = 5
PAY_CHANNEL_ALIPAY_WEB = 6
PAY_CHANNEL_ALIPAY_MINI = 7
PAY_CHANNEL_ALIPAY_JSAPI = 8
PAY_SUCCESS_CODE = 200 // 支付响应成功
PAY_ERROR_CODE = 500 // 支付响应失败
PAY_NOT_FOUND_CODE = 404 // 未找到支付渠道
WX_SUCCESS_CODE = 200 // 微信状态码返回成功
WX_NOTIFY_URL_TEST = common.FRONT_V1 + "/notify/wx/" // 微信支付回调地址
WX_NOTIFY_URL_PROD = common.FRONT_V1 + "/notify/wx/" // 微信支付回调地址
ALI_NOTIFY_URL_TEST = common.FRONT_V1 + "/notify/ali/" // 支付宝支付回调地址
ALI_NOTIFY_URL_PROD = common.FRONT_V1 + "/notify/ali/" // 支付宝支付回调地址
ORDER_NO_TYPE_ORDER_NO = 2
PAY_CHANNLE_TYPE_WECHAT = 1 // 支付类型: 微信
PAY_CHANNLE_TYPE_ZFB = 2 // 支付类型:支付宝
PAY_REFUND_STATU_COMMON = 0 // 未申请
PAY_REFUND_STATU_ING = 1 // 退款中
PAY_REFUND_STATU_SUCCESS = 2 // 退款成功
PAY_REFUND_STATU_FAIL = 3 // 退款失败
)

View File

@ -0,0 +1,367 @@
package paymentService
import (
"PaymentCenter/app/constants/common"
"PaymentCenter/app/data"
"PaymentCenter/app/models/orderthirdpaylogmodel"
"PaymentCenter/app/models/paychannelmodel"
"PaymentCenter/app/third/paymentService/payCommon"
"context"
"encoding/json"
"fmt"
"strconv"
)
type PayOrderRequest struct {
PayChannelId int64 `json:"pay_channel_id"` // 支付方式ID
OrderId int64 `json:"order_id"` // 平台订单号
ChannelType int `json:"channel_type"` // 支付方式
Description string `json:"description"` // 商品描述
Amount int `json:"amount"` // 金额单位为分
PayerClientIp string `json:"payer_client_ip"` // 终端IP
Wx WxPay `json:"wx"`
Ali AliPay `json:"ali"`
}
type WxPay struct {
AppId string `json:"app_id"` // 应用ID
MchId string `json:"mch_id"` // 商户ID 或者服务商模式的 sp_mchid
SerialNo string `json:"serial_no"` // 商户证书的证书序列号
ApiV3Key string `json:"api_v_3_key"` // apiV3Key商户平台获取
PrivateKey string `json:"private_key"` // 私钥 apiclient_key.pem 读取后的内容
}
type AliPay struct {
AppId string `json:"app_id"` // 应用ID
PrivateKey string `json:"private_key"` // 应用私钥
AppPublicCert string `json:"app_public_cert"` // 应用公钥
AlipayRootCert string `json:"alipay_root_cert"` // 支付宝根证书
AlipayPublicCert string `json:"alipay_public_cert"` // 支付宝公钥
}
type PayOrderResponse struct {
Code int `json:"code"`
ErrorMsg string `json:"error_msg"`
Result string `json:"result"`
}
// PaymentService 统一发起支付
func PaymentService(c context.Context, payOrderRequest PayOrderRequest) PayOrderResponse {
//logger.Info(c, "PaymentService 收到支付请求", payOrderRequest)
var err error
var info string
var payOrderResponse PayOrderResponse
switch payOrderRequest.ChannelType {
case payCommon.PAY_CHANNEL_WECHAT_H5:
// 微信H5支付
info, err = WxH5PayInfo(c, payOrderRequest)
case payCommon.PAY_CHANNEL_ALIPAY_WEB:
// 支付宝H5支付
info, err = ALiH5PayInfo(c, payOrderRequest)
default:
payOrderResponse = PayOrderResponse{
Code: payCommon.PAY_NOT_FOUND_CODE,
ErrorMsg: "暂不支持该支付渠道,请后续再使用",
}
}
logStatus := common.THIRD_ORDER_LOG_STATUS_FAIL
if err != nil {
payOrderResponse = PayOrderResponse{
Code: payCommon.PAY_ERROR_CODE,
ErrorMsg: err.Error(),
}
logStatus = common.THIRD_ORDER_LOG_STATUS_FAIL
} else if payOrderResponse.ErrorMsg == "" {
payOrderResponse = PayOrderResponse{
Code: payCommon.PAY_SUCCESS_CODE,
ErrorMsg: "",
Result: info,
}
logStatus = common.THIRD_ORDER_LOG_STATUS_SUCCESS
}
// 记录日志
go func() {
orderId := payOrderRequest.OrderId
payCallback := fmt.Sprintf("{%s}", info)
payParam, _ := json.Marshal(payOrderRequest)
merchantCallback, _ := json.Marshal(payOrderResponse)
SaveLog(orderId, common.THIRD_ORDER_TYPE_PAY, payCallback, string(payParam), string(merchantCallback), logStatus)
}()
return payOrderResponse
}
type PayOrderQueryRequest struct {
OrderId int64 `json:"order_id"` // 商户订单号
PayChannel int `json:"pay_channel"` // 支付渠道类型 1:wx,2:zfb
Wx WxPay `json:"wx"`
Ali AliPay `json:"ali"`
}
type PayOrderQueryResponse struct {
Code int `json:"code"`
ErrorMsg string `json:"error_msg"`
Result PayOrderQueryInfo `json:"result"`
}
type PayOrderQueryInfo struct {
AppId string `json:"app_id"` // 应用ID
OutTradeNo int64 `json:"out_trade_no"` // 商家订单号
TransactionId string `json:"transaction_id"` // 第三方订单号
TradeState string `json:"trade_state"` // 交易状态 SUCCESS成功、REFUND转入退款、NOTPAY未支付、CLOSED已关闭
TradeStateDesc string `json:"trade_state_desc"` // 交易描述
SuccessTime string `json:"success_time"` // 交易完成时间
AmountTotal int64 `json:"amount_total"` // 订单总金额,单位:分
PayerTotal int64 `json:"payer_total"` // 支付总金额,单位:分
}
// PayOrderQuery 查询订单状态
func PayOrderQuery(c context.Context, payOrderQueryRequest PayOrderQueryRequest) PayOrderQueryResponse {
var err error
var info PayOrderQueryInfo
var payOrderQueryResponse PayOrderQueryResponse
switch payOrderQueryRequest.PayChannel {
case payCommon.PAY_CHANNLE_TYPE_WECHAT:
// 微信H5支付
info, err = WxOrderQuery(c, payOrderQueryRequest.Wx, strconv.FormatInt(payOrderQueryRequest.OrderId, 10))
case payCommon.PAY_CHANNLE_TYPE_ZFB:
info, err = ALiOrderQuery(c, payOrderQueryRequest.Ali, strconv.FormatInt(payOrderQueryRequest.OrderId, 10))
default:
payOrderQueryResponse = PayOrderQueryResponse{
Code: payCommon.PAY_ERROR_CODE,
ErrorMsg: "暂不支持该支付渠道,请后续再使用",
}
}
logStatus := common.THIRD_ORDER_LOG_STATUS_FAIL
if err != nil {
payOrderQueryResponse = PayOrderQueryResponse{
Code: payCommon.PAY_ERROR_CODE,
ErrorMsg: err.Error(),
}
logStatus = common.THIRD_ORDER_LOG_STATUS_FAIL
} else if payOrderQueryResponse.ErrorMsg == "" {
payOrderQueryResponse = PayOrderQueryResponse{
Code: payCommon.PAY_SUCCESS_CODE,
Result: info,
}
logStatus = common.THIRD_ORDER_LOG_STATUS_SUCCESS
}
// 记录日志
go func() {
orderId := payOrderQueryRequest.OrderId
payCallback, _ := json.Marshal(info)
payParam, _ := json.Marshal(payOrderQueryRequest)
merchantCallback, _ := json.Marshal(payOrderQueryResponse)
SaveLog(orderId, common.THIRD_ORDER_TYPE_ORDER_QUERY, string(payCallback), string(payParam), string(merchantCallback), logStatus)
}()
return payOrderQueryResponse
}
type OrderRefundRequest struct {
OrderId int64 `json:"order_id"` // 订单编号
RefundOrderId int64 `json:"refund_order_id"` // 退款订单号
RefundReason string `json:"refund_reason"` // 退款原因
RefundAmount int64 `json:"refund_amount"` // 退款金额,单位分
PayChannel int `json:"pay_channel"` // 支付渠道类型 1:wx,2:zfb
Wx WxPay `json:"wx"`
Ali AliPay `json:"ali"`
}
type OrderRefundResponse struct {
Code int `json:"code"`
ErrorMsg string `json:"error_msg"`
Result OrderRefundInfo `json:"result"`
}
type OrderRefundInfo struct {
OutTradeNo int64 `json:"out_trade_no"` // 商家订单号
TransactionId string `json:"transaction_id"` // 第三方订单号
RefundFee int64 `json:"refund_fee"` // 退款金额
RefundOrderId int64 `json:"refund_order_id"` // 退款订单号
RefundStatus int `json:"refund_status"` // 退款状态 0未申请1退款中2退款成功3退款失败
RefundSuccessTime string `json:"refund_success_time"` // 退款时间
}
// OrderRefund 订单退款
func OrderRefund(c context.Context, orderRefundRequest OrderRefundRequest) OrderRefundResponse {
//logger.Info(c, "PaymentService 收到退款请求", orderRefundRequest)
var err error
var info OrderRefundInfo
var orderRefundResponse OrderRefundResponse
switch orderRefundRequest.PayChannel {
case payCommon.PAY_CHANNLE_TYPE_WECHAT:
// 微信H5支付
info, err = WxOrderRefund(c, orderRefundRequest)
case payCommon.PAY_CHANNLE_TYPE_ZFB:
info, err = AliRefundOrder(c, orderRefundRequest)
default:
orderRefundResponse = OrderRefundResponse{
Code: payCommon.PAY_ERROR_CODE,
ErrorMsg: "暂不支持该支付渠道,请后续再使用",
}
}
logStatus := common.THIRD_ORDER_LOG_STATUS_FAIL
if err != nil {
orderRefundResponse = OrderRefundResponse{
Code: payCommon.PAY_ERROR_CODE,
ErrorMsg: err.Error(),
}
logStatus = common.THIRD_ORDER_LOG_STATUS_FAIL
} else if orderRefundResponse.ErrorMsg == "" {
orderRefundResponse = OrderRefundResponse{
Code: payCommon.PAY_SUCCESS_CODE,
Result: info,
}
logStatus = common.THIRD_ORDER_LOG_STATUS_SUCCESS
}
// 记录日志
go func() {
orderId := orderRefundRequest.OrderId
payCallback, _ := json.Marshal(info)
payParam, _ := json.Marshal(orderRefundRequest)
merchantCallback, _ := json.Marshal(orderRefundResponse)
SaveLog(orderId, common.THIRD_ORDER_TYPE_REFUND, string(payCallback), string(payParam), string(merchantCallback), logStatus)
}()
return orderRefundResponse
}
type OrderRefundQueryRequest struct {
RefundOrderId int64 `json:"refund_order_id"` // 退款订单号
OrderId int64 `json:"order_id"` // 支付订单号
PayChannel int `json:"pay_channel"` // 支付渠道类型 1:wx,2:zfb
Wx WxPay `json:"wx"`
Ali AliPay `json:"ali"`
}
type OrderRefundQueryResponse struct {
Code int `json:"code"`
ErrorMsg string `json:"error_msg"`
Result OrderRefundInfo `json:"result"`
}
// OrderRefundQuery 订单退款查询
func OrderRefundQuery(c context.Context, orderRefundQueryRequest OrderRefundQueryRequest) OrderRefundQueryResponse {
var err error
var info OrderRefundInfo
var orderRefundQueryResponse OrderRefundQueryResponse
switch orderRefundQueryRequest.PayChannel {
case payCommon.PAY_CHANNLE_TYPE_WECHAT:
// 微信H5支付
info, err = WxOrderRefundQuery(c, orderRefundQueryRequest)
case payCommon.PAY_CHANNLE_TYPE_ZFB:
info, err = AliRefundOrderQuery(c, orderRefundQueryRequest)
default:
orderRefundQueryResponse = OrderRefundQueryResponse{
Code: payCommon.PAY_ERROR_CODE,
ErrorMsg: "暂不支持该支付渠道,请后续再使用",
}
}
logStatus := common.THIRD_ORDER_LOG_STATUS_FAIL
if err != nil {
orderRefundQueryResponse = OrderRefundQueryResponse{
Code: payCommon.PAY_ERROR_CODE,
ErrorMsg: err.Error(),
}
logStatus = common.THIRD_ORDER_LOG_STATUS_FAIL
} else if orderRefundQueryResponse.ErrorMsg == "" {
orderRefundQueryResponse = OrderRefundQueryResponse{
Code: payCommon.PAY_SUCCESS_CODE,
Result: info,
}
logStatus = common.THIRD_ORDER_LOG_STATUS_SUCCESS
}
// 记录日志
go func() {
orderId := orderRefundQueryRequest.OrderId
payCallback, _ := json.Marshal(info)
payParam, _ := json.Marshal(orderRefundQueryRequest)
merchantCallback, _ := json.Marshal(orderRefundQueryResponse)
SaveLog(orderId, common.THIRD_ORDER_TYPE_REFUND_QUERY, string(payCallback), string(payParam), string(merchantCallback), logStatus)
}()
return orderRefundQueryResponse
}
type OrderCloseRequest struct {
OrderId int64 `json:"order_id"` // 支付订单号
PayChannel int `json:"pay_channel"` // 支付渠道类型 1:wx,2:zfb
Wx WxPay `json:"wx"`
Ali AliPay `json:"ali"`
}
type OrderCloseResponse struct {
Code int `json:"code"`
ErrorMsg string `json:"error_msg"`
Result OrderCloseInfo `json:"result"`
}
type OrderCloseInfo struct {
OutTradeNo int64 `json:"out_trade_no"` // 商家订单号
}
// OrderClose 关闭订单
func OrderClose(c context.Context, orderCloseRequest OrderCloseRequest) OrderCloseResponse {
//logger.Info(c, "PaymentService 收到关闭订单请求", orderCloseRequest)
var err error
var info OrderCloseInfo
var orderCloseResponse OrderCloseResponse
switch orderCloseRequest.PayChannel {
case payCommon.PAY_CHANNLE_TYPE_WECHAT:
// 微信H5支付
info, err = WxCloseOrder(c, orderCloseRequest)
case payCommon.PAY_CHANNLE_TYPE_ZFB:
info, err = AliCloseOrder(c, orderCloseRequest)
default:
orderCloseResponse = OrderCloseResponse{
Code: payCommon.PAY_ERROR_CODE,
ErrorMsg: "暂不支持该支付渠道,请后续再使用",
}
}
logStatus := common.THIRD_ORDER_LOG_STATUS_FAIL
if err != nil {
orderCloseResponse = OrderCloseResponse{
Code: payCommon.PAY_ERROR_CODE,
ErrorMsg: err.Error(),
}
logStatus = common.THIRD_ORDER_LOG_STATUS_FAIL
} else if orderCloseResponse.ErrorMsg == "" {
orderCloseResponse = OrderCloseResponse{
Code: payCommon.PAY_SUCCESS_CODE,
Result: info,
}
logStatus = common.THIRD_ORDER_LOG_STATUS_SUCCESS
}
// 记录日志
go func() {
orderId := orderCloseRequest.OrderId
payCallback, _ := json.Marshal(info)
payParam, _ := json.Marshal(orderCloseRequest)
merchantCallback, _ := json.Marshal(orderCloseResponse)
SaveLog(orderId, common.THIRD_ORDER_TYPE_CLOSE, string(payCallback), string(payParam), string(merchantCallback), logStatus)
}()
return orderCloseResponse
}
// SaveLog 记录操作日志
func SaveLog(orderId int64, OType int, payCallback string, PayParam string, MerchantCallback string, status int) {
thirdRepo := data.NewOrderThirdPayLogRepo(paychannelmodel.GetInstance().GetDb())
log := orderthirdpaylogmodel.OrderThirdPayLog{
OrderId: orderId,
PayCallback: payCallback,
Status: status,
PayParam: PayParam,
MerchantCallback: MerchantCallback,
Type: OType,
}
_, err := thirdRepo.OrderThirdPayLogInsertOne(&log)
if err != nil {
return
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,323 @@
package paymentService
import (
"PaymentCenter/app/constants/common"
"PaymentCenter/app/constants/errorcode"
"PaymentCenter/app/services/thirdpay/thirdpay_notify"
"PaymentCenter/app/third/paymentService/payCommon"
"PaymentCenter/config"
"context"
"encoding/json"
"errors"
"fmt"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/wechat/v3"
"github.com/qit-team/snow-core/log/logger"
"strconv"
"time"
)
// InitClient 使用提供的支付请求参数初始化微信客户端
func InitClient(wxConfig WxPay) (*wechat.ClientV3, error) {
// NewClientV3 初始化微信客户端 v3
// mchid商户ID 或者服务商模式的 sp_mchid
// serialNo商户证书的证书序列号
// apiV3KeyapiV3Key商户平台获取
// privateKey私钥 apiclient_key.pem 读取后的内容
wxClient, clientErr := wechat.NewClientV3(
wxConfig.MchId,
wxConfig.SerialNo,
wxConfig.ApiV3Key,
wxConfig.PrivateKey,
)
// 启用自动同步返回验签并定时更新微信平台API证书开启自动验签时无需单独设置微信平台API证书和序列号
clientErr = wxClient.AutoVerifySign()
if wxClient == nil {
return nil, errors.New("client not initialized")
}
if clientErr != nil {
return nil, clientErr
}
// 自定义配置http请求接收返回结果body大小默认 10MB
wxClient.SetBodySize(10) // 没有特殊需求,可忽略此配置
// 打开Debug开关输出日志默认是关闭的
wxClient.DebugSwitch = gopay.DebugOn
return wxClient, nil
}
// WxH5PayInfo 微信H5支付
func WxH5PayInfo(c context.Context, payOrderRequest PayOrderRequest) (string, error) {
// 初始化微信客户端
wxClient, err := InitClient(payOrderRequest.Wx)
if err != nil {
return "", err
}
expire := time.Now().Add(10 * time.Minute).Format(time.RFC3339)
// 初始化 BodyMap
envConfig := config.GetConf()
bm := make(gopay.BodyMap)
bm.Set("appid", payOrderRequest.Wx.AppId).
Set("mchid", payOrderRequest.Wx.MchId).
Set("description", payOrderRequest.Description).
Set("out_trade_no", strconv.FormatInt(payOrderRequest.OrderId, 10)).
Set("time_expire", expire).
Set("notify_url", fmt.Sprintf(envConfig.PayService.Host+payCommon.WX_NOTIFY_URL_TEST+"%d", payOrderRequest.PayChannelId)).
SetBodyMap("amount", func(bm gopay.BodyMap) {
bm.Set("total", payOrderRequest.Amount).
Set("currency", "CNY")
}).
SetBodyMap("scene_info", func(bm gopay.BodyMap) {
bm.Set("payer_client_ip", payOrderRequest.PayerClientIp).
SetBodyMap("h5_info", func(bm gopay.BodyMap) {
bm.Set("type", "common")
})
})
wxRsp, err := wxClient.V3TransactionH5(c, bm)
if err != nil {
return "", err
}
if wxRsp.Code != wechat.Success || wxRsp.Response.H5Url == "" {
logger.Error(c, "WxH5PayInfo 发生错误", fmt.Sprintf("错误状态码:%d, 错误信息:%s", wxRsp.Code, wxRsp.Error))
return "", errors.New(fmt.Sprintf("发起支付失败,失败状态码:%d, 失败原因:%s", wxRsp.Code, wxRsp.Error))
}
return wxRsp.Response.H5Url, nil
}
// WxPayCallBack 微信支付回调
func WxPayCallBack(notifyReq *wechat.V3NotifyReq, wxConfig WxPay) error {
// 初始化微信客户端
wxClient, err := InitClient(wxConfig)
if err != nil {
return err
}
// 获取微信平台证书
certMap := wxClient.WxPublicKeyMap()
// 验证异步通知的签名
err = notifyReq.VerifySignByPKMap(certMap)
if err != nil {
return err
}
var CallBackInfo struct {
AppId string `json:"app_id"` // 应用ID
Mchid string `json:"mchid"` // 商户号
OutTradeNo string `json:"out_trade_no"` // 商户系统内部订单号
TransactionId string `json:"transaction_id"` // 微信系统订单号
TradeType string `json:"trade_type"` // JSAPI公众号支付 NATIVE扫码支付 AppApp支付 MICROPAY付款码支付 MWEBH5支付 FACEPAY刷脸支付
TradeState string `json:"trade_state"` // SUCCESS支付成功 REFUND转入退款 NOTPAY未支付 CLOSED已关闭 REVOKED已撤销付款码支付 USERPAYING用户支付中付款码支付 PAYERROR支付失败(其他原因,如银行返回失败)
TradeStateDesc string `json:"trade_state_desc"` // 交易状态描述
SuccessTime string `json:"success_time"` // 支付完成时间
Amount struct {
Total int64 `json:"total"`
PayerTotal int64 `json:"payer_total"`
} `json:"amount"`
}
// 通用通知解密(推荐此方法)
err = notifyReq.DecryptCipherTextToStruct(wxConfig.ApiV3Key, &CallBackInfo)
if err != nil {
return err
}
errCode := 0
msg := ""
// 订单状态
switch CallBackInfo.TradeState {
case "SUCCESS":
errCode = errorcode.Success
msg = "支付成功"
case "CLOSED":
errCode = errorcode.ParamError
msg = "已关闭"
case "REVOKED":
errCode = errorcode.ParamError
msg = "已撤销(付款码支付)"
case "PAYERROR":
errCode = errorcode.ParamError
msg = "支付失败(其他原因,如银行返回失败)"
}
// 触发下游回调的格式
orderId, _ := strconv.Atoi(CallBackInfo.OutTradeNo)
res := thirdpay_notify.NewOrderNotifyWithHandle(int64(orderId), errCode, int(CallBackInfo.Amount.PayerTotal), msg)
merchantCallback, _ := json.Marshal(res)
// 记录日志
go func() {
payCallback, _ := json.Marshal(CallBackInfo)
payParam := "{}"
status := common.THIRD_ORDER_LOG_STATUS_SUCCESS
SaveLog(int64(orderId), common.THIRD_ORDER_TYPE_CALL_BACK, string(payCallback), payParam, "{}", status)
}()
if res.ErrCode != errorcode.Success {
logger.Error(context.Background(), "WxPayCallBack 发生错误", fmt.Sprintf("回调时,下游处理订单失败,返回数据为:%s", string(merchantCallback)))
return errors.New("回调时,下游处理订单失败")
}
return nil
}
// WxOrderQuery 查询微信订单
func WxOrderQuery(ctx context.Context, wxConfig WxPay, orderNo string) (PayOrderQueryInfo, error) {
// 初始化微信客户端
wxClient, err := InitClient(wxConfig)
if err != nil {
return PayOrderQueryInfo{}, err
}
wxRsp, err := wxClient.V3TransactionQueryOrder(ctx, payCommon.ORDER_NO_TYPE_ORDER_NO, orderNo)
if err != nil {
return PayOrderQueryInfo{}, err
}
if wxRsp.Code != wechat.Success {
return PayOrderQueryInfo{}, errors.New(fmt.Sprintf("查询订单接口错误,错误状态码:%d, 错误信息:%s", wxRsp.Code, wxRsp.Error))
}
// 映射交易状态
tradeState := ""
switch wxRsp.Response.TradeState {
case "SUCCESS":
tradeState = "SUCCESS"
case "REFUND":
tradeState = "REFUND"
case "NOTPAY":
tradeState = "NOTPAY"
case "CLOSED":
tradeState = "CLOSED"
}
amountTotal := wxRsp.Response.Amount.Total
payerTotal := wxRsp.Response.Amount.PayerTotal
outTradeNo, _ := strconv.Atoi(wxRsp.Response.OutTradeNo)
return PayOrderQueryInfo{
AppId: wxRsp.Response.Appid,
OutTradeNo: int64(outTradeNo),
TransactionId: wxRsp.Response.TransactionId,
TradeState: tradeState,
TradeStateDesc: wxRsp.Response.TradeStateDesc,
SuccessTime: wxRsp.Response.SuccessTime,
AmountTotal: int64(amountTotal),
PayerTotal: int64(payerTotal),
}, nil
}
// WxOrderRefund 微信退款申请
func WxOrderRefund(ctx context.Context, orderRefundRequest OrderRefundRequest) (OrderRefundInfo, error) {
// 初始化微信客户端
wxClient, err := InitClient(orderRefundRequest.Wx)
if err != nil {
return OrderRefundInfo{}, err
}
// 初始化 BodyMap
bm := make(gopay.BodyMap)
bm.Set("out_trade_no", strconv.FormatInt(orderRefundRequest.OrderId, 10)).
Set("sign_type", "MD5").
// 必填 退款订单号(程序员定义的)
Set("out_refund_no", strconv.FormatInt(orderRefundRequest.RefundOrderId, 10)).
// 选填 退款描述
Set("reason", orderRefundRequest.RefundReason).
SetBodyMap("amount", func(bm gopay.BodyMap) {
// 退款金额:单位是分
bm.Set("refund", orderRefundRequest.RefundAmount). //实际退款金额
Set("total", orderRefundRequest.RefundAmount). // 折扣前总金额(不是实际退款数)
Set("currency", "CNY")
})
// body参数Body
refund, err := wxClient.V3Refund(ctx, bm)
if err != nil {
return OrderRefundInfo{}, err
}
// 将非正常退款异常记录
if refund.Code != wechat.Success {
logger.Error(ctx, "WxOrderRefund 发生错误", fmt.Sprintf("申请退款接口失败,错误状态码:%d, 错误信息:%s", refund.Code, refund.Error))
// 这里时对非正常退款的一些处理message我们将code统一使用自定义的然后把message抛出去
return OrderRefundInfo{}, errors.New(fmt.Sprintf("申请退款接口失败,错误状态码:%d, 错误信息:%s", refund.Code, refund.Error))
}
outTradeNo, _ := strconv.Atoi(refund.Response.OutTradeNo)
outRefundNo, _ := strconv.Atoi(refund.Response.OutRefundNo)
refundStatus := payCommon.PAY_REFUND_STATU_COMMON
switch refund.Response.Status {
case "SUCCESS":
refundStatus = payCommon.PAY_REFUND_STATU_SUCCESS
case "CLOSED":
refundStatus = payCommon.PAY_REFUND_STATU_FAIL
case "PROCESSING":
refundStatus = payCommon.PAY_REFUND_STATU_ING
case "ABNORMAL":
refundStatus = payCommon.PAY_REFUND_STATU_FAIL
}
return OrderRefundInfo{
OutTradeNo: int64(outTradeNo),
TransactionId: refund.Response.TransactionId,
RefundFee: int64(refund.Response.Amount.PayerRefund),
RefundOrderId: int64(outRefundNo),
RefundStatus: refundStatus,
RefundSuccessTime: refund.Response.SuccessTime,
}, nil
}
// WxOrderRefundQuery 微信订单退款查询
func WxOrderRefundQuery(ctx context.Context, orderRefundQueryRequest OrderRefundQueryRequest) (OrderRefundInfo, error) {
// 初始化微信客户端
wxClient, err := InitClient(orderRefundQueryRequest.Wx)
if err != nil {
return OrderRefundInfo{}, err
}
wxRsp, err := wxClient.V3RefundQuery(ctx, strconv.FormatInt(orderRefundQueryRequest.RefundOrderId, 10), nil)
if err != nil {
return OrderRefundInfo{}, err
}
if wxRsp.Code != wechat.Success {
return OrderRefundInfo{}, errors.New(fmt.Sprintf("查询订单接口错误,错误状态码:%d, 错误信息:%s", wxRsp.Code, wxRsp.Error))
}
outTradeNo, _ := strconv.Atoi(wxRsp.Response.OutTradeNo)
outRefundNo, _ := strconv.Atoi(wxRsp.Response.OutRefundNo)
refundStatus := payCommon.PAY_REFUND_STATU_COMMON
switch wxRsp.Response.Status {
case "SUCCESS":
refundStatus = payCommon.PAY_REFUND_STATU_SUCCESS
case "CLOSED":
refundStatus = payCommon.PAY_REFUND_STATU_FAIL
case "PROCESSING":
refundStatus = payCommon.PAY_REFUND_STATU_ING
case "ABNORMAL":
refundStatus = payCommon.PAY_REFUND_STATU_FAIL
}
return OrderRefundInfo{
OutTradeNo: int64(outTradeNo),
TransactionId: wxRsp.Response.TransactionId,
RefundFee: int64(wxRsp.Response.Amount.PayerRefund),
RefundOrderId: int64(outRefundNo),
RefundStatus: refundStatus,
RefundSuccessTime: wxRsp.Response.SuccessTime,
}, nil
}
// WxCloseOrder 微信订单关闭
func WxCloseOrder(ctx context.Context, orderCloseRequest OrderCloseRequest) (OrderCloseInfo, error) {
// 初始化微信客户端
wxClient, err := InitClient(orderCloseRequest.Wx)
if err != nil {
return OrderCloseInfo{}, err
}
wxRsp, err := wxClient.V3TransactionCloseOrder(ctx, strconv.FormatInt(orderCloseRequest.OrderId, 10))
if err != nil {
return OrderCloseInfo{}, err
}
if wxRsp.Code != wechat.Success {
logger.Error(ctx, "WxCloseOrder 发生错误", fmt.Sprintf("查询订单接口错误,错误状态码:%d, 错误信息:%s", wxRsp.Code, wxRsp.Error))
return OrderCloseInfo{}, errors.New(fmt.Sprintf("查询订单接口错误,错误状态码:%d, 错误信息:%s", wxRsp.Code, wxRsp.Error))
}
return OrderCloseInfo{
OutTradeNo: orderCloseRequest.OrderId,
}, nil
}

View File

@ -0,0 +1,101 @@
package aes
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"fmt"
"io"
"math/big"
)
const (
TestKey = "ROK0nAaxcsI0KRCy8VJgLlnlfjWYAOHk"
)
// 加密函数
func Encrypt(plaintext, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
// 加密后的数据会比原文长,因为需要添加一些填充字节
// PKCS#7填充
plaintext = pkcs7Padding(plaintext, block.BlockSize())
// 创建一个cipher.BlockMode这里使用CBC模式
// 需要一个iv初始化向量它的长度和Block的块大小相同
ciphertext := make([]byte, aes.BlockSize+len(plaintext))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)
// 返回的密文包括iv和加密后的数据
return ciphertext, nil
}
// 解密函数
func Decrypt(ciphertext, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
if len(ciphertext) < aes.BlockSize {
return nil, fmt.Errorf("ciphertext too short")
}
// 提取iv
iv := ciphertext[:aes.BlockSize]
ciphertext = ciphertext[aes.BlockSize:]
// 创建一个cipher.BlockMode
mode := cipher.NewCBCDecrypter(block, iv)
// 解密
mode.CryptBlocks(ciphertext, ciphertext)
// 去除PKCS#7填充
plaintext, err := pkcs7Unpadding(ciphertext, block.BlockSize())
if err != nil {
return nil, err
}
return plaintext, nil
}
// PKCS#7填充
func pkcs7Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
// 去除PKCS#7填充
func pkcs7Unpadding(ciphertext []byte, blockSize int) ([]byte, error) {
length := len(ciphertext)
unpadding := int(ciphertext[length-1])
if unpadding > blockSize || unpadding == 0 {
return nil, fmt.Errorf("invalid padding")
}
return ciphertext[:(length - unpadding)], nil
}
func GenerateRandomStringCrypto(length int) (string, error) {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
var bytes = make([]byte, length)
for i := range bytes {
num, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
if err != nil {
return "", err
}
bytes[i] = charset[num.Int64()]
}
return string(bytes), nil
}

View File

@ -0,0 +1,34 @@
package aes
import (
"fmt"
"testing"
)
func TestCreateKey(t *testing.T) {
key, _ := GenerateRandomStringCrypto(32)
fmt.Printf(key)
}
func TestAesEncrypt(t *testing.T) {
fmt.Printf("%s\n", encrypt())
}
func TestAesDecrypt(t *testing.T) {
data := "dk9trGQn2kZyE0rmfYglxwNmr7gc0DGFWvnPDVpLHfzwPiaWKkr+ZffKu0wj9/DQurIgjrQZIW8uV5/e+jxzBE6+5YZuKcT9+5xZSv6ieiXB7WUrKKnjxX/bHEaWgfoa0eDqEBhGth+mgql7qWFZLmuXpoKuAc0FPhdHTiynAttqjNMU4rMeXq52yJLuXrKHGlavhd9DETNDrhPFzyIAw7XjoMfGtBIFkXyDK5X+AKGkRvMJ8KfXR3lk9sSzTyGKRK/S"
res, err := Decrypt([]byte(data), []byte(TestKey))
fmt.Println(string(res), err)
}
func encrypt() string {
data := "{\"pay_channel_id\":8935141660703064070,\"out_trade_no\":\"asdadasdas\",\"order_type\":1,\"amount\":1,\"desc\":\"abc\",\"ext_json\":\"\",\"app_id\":5476377146882523138,\"timestamp\":53612533412643}{\"pay_channel_id\":8935141660703064070,\"out_trade_no\":\"asdadasdas\",\"order_type\":1,\"amount\":1,\"desc\":\"abc\",\"ext_json\":\"\",\"app_id\":5476377146882523138,\"timestamp\":53612533412643}{\"pay_channel_id\":8935141660703064070,\"out_trade_no\":\"asdadasdas\",\"order_type\":1,\"amount\":1,\"desc\":\"abc\",\"ext_json\":\"\",\"app_id\":5476377146882523138,\"timestamp\":53612533412643}{\"pay_channel_id\":8935141660703064070,\"out_trade_no\":\"asdadasdas\",\"order_type\":1,\"amount\":1,\"desc\":\"abc\",\"ext_json\":\"\",\"app_id\":5476377146882523138,\"timestamp\":53612533412643}{\"pay_channel_id\":8935141660703064070,\"out_trade_no\":\"asdadasdas\",\"order_type\":1,\"amount\":1,\"desc\":\"abc\",\"ext_json\":\"\",\"app_id\":5476377146882523138,\"timestamp\":53612533412643}{\"pay_channel_id\":8935141660703064070,\"out_trade_no\":\"asdadasdas\",\"order_type\":1,\"amount\":1,\"desc\":\"abc\",\"ext_json\":\"\",\"app_id\":5476377146882523138,\"timestamp\":53612533412643}{\"pay_channel_id\":8935141660703064070,\"out_trade_no\":\"asdadasdas\",\"order_type\":1,\"amount\":1,\"desc\":\"abc\",\"ext_json\":\"\",\"app_id\":5476377146882523138,\"timestamp\":53612533412643}{\"pay_channel_id\":8935141660703064070,\"out_trade_no\":\"asdadasdas\",\"order_type\":1,\"amount\":1,\"desc\":\"abc\",\"ext_json\":\"\",\"app_id\":5476377146882523138,\"timestamp\":53612533412643}"
en, err := Encrypt([]byte(data), []byte(TestKey))
if err != nil {
panic(err)
}
return string(en)
}

View File

@ -0,0 +1,160 @@
package rsa
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"strings"
)
// parseRSAPublicKeyFromPEM 解析PEM编码的RSA公钥
func parseRSAPublicKeyFromPEM(pemData []byte) (*rsa.PublicKey, error) {
block, _ := pem.Decode(pemData)
if block == nil || block.Type != "PUBLIC KEY" {
return nil, fmt.Errorf("failed to parse PEM block containing the RSA public key")
}
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
switch pub := pub.(type) {
case *rsa.PublicKey:
return pub, nil
default:
return nil, fmt.Errorf("unknown public key type in PKIX wrapping")
}
}
// encrypt 使用RSA公钥加密数据
func Encrypt(publicKeyPEM string, plaintext string) ([]byte, error) {
var encryptedData []byte
// 将PEM编码的公钥转换为[]byte
pemData := []byte(publicKeyPEM)
// 解析PEM数据以获取公钥
pubKey, err := parseRSAPublicKeyFromPEM(pemData)
if err != nil {
return nil, err
}
hash := sha256.New()
maxBlockSize := pubKey.Size() - 2*hash.Size() - 2
// 创建用于加密的随机填充
label := []byte("") // OAEP标签对于某些情况可能是非空的
for len(plaintext) > 0 {
blockSize := maxBlockSize
if len(plaintext) < maxBlockSize {
blockSize = len(plaintext)
}
block := plaintext[:blockSize]
encryptedBlock, err := rsa.EncryptOAEP(hash, rand.Reader, pubKey, []byte(block), label)
if err != nil {
return nil, err
}
encryptedData = append(encryptedData, encryptedBlock...)
plaintext = plaintext[blockSize:]
}
return encryptedData, nil
}
// parseRSAPrivateKeyFromPEM 解析PEM编码的RSA私钥
func parseRSAPrivateKeyFromPEM(pemData []byte) (*rsa.PrivateKey, error) {
block, _ := pem.Decode(pemData)
if block == nil || block.Type != "RSA PRIVATE KEY" {
return nil, fmt.Errorf("failed to parse PEM block containing the RSA private key")
}
// 尝试使用PKCS#1 v1.5
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
// 如果失败尝试使用PKCS#8
privInterface, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
switch k := privInterface.(type) {
case *rsa.PrivateKey:
priv = k
default:
return nil, fmt.Errorf("unknown private key type in PKCS#8 wrapping")
}
}
return priv, nil
}
// decrypt 使用RSA私钥解密数据
func Decrypt(privateKeyPEM string, encryptedDataBase64 string) ([]byte, error) {
var decryptedData []byte
// 将PEM编码的私钥转换为[]byte
pemData := []byte(privateKeyPEM)
// 解析PEM数据以获取私钥
privKey, err := parseRSAPrivateKeyFromPEM(pemData)
if err != nil {
return nil, err
}
keySize := privKey.PublicKey.Size()
label := []byte("") // OAEP标签对于某些情况可能是非空的
hash := sha256.New()
// 将Base64编码的加密数据解码为字节切片
encryptedData, err := base64.StdEncoding.DecodeString(encryptedDataBase64)
if err != nil {
return nil, err
}
for len(encryptedData) > 0 {
block := encryptedData[:keySize]
// 这里假设使用的是OAEP填充和SHA-256哈希函数
decryptedBlock, err := rsa.DecryptOAEP(hash, rand.Reader, privKey, block, label)
if err != nil {
//// 如果失败可以尝试使用PKCS#1 v1.5填充
decryptedBlock, err = rsa.DecryptPKCS1v15(rand.Reader, privKey, encryptedData)
if err != nil {
return nil, err
}
}
decryptedData = append(decryptedData, decryptedBlock...)
encryptedData = encryptedData[keySize:]
}
return decryptedData, nil
}
// 生成密钥对
func GenerateKey() (string, string, error) {
// 生成私钥
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return "", "", err
}
// 导出私钥PKCS#1格式
privKey := x509.MarshalPKCS1PrivateKey(privateKey)
// 将私钥转换为PEM编码
var privBlock = &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: privKey,
}
privPem := pem.EncodeToMemory(privBlock)
// 导出公钥
pubKey := &privateKey.PublicKey
derPkix, err := x509.MarshalPKIXPublicKey(pubKey)
if err != nil {
return "", "", err
}
// 将公钥转换为PEM编码
var pubBlock = &pem.Block{
Type: "PUBLIC KEY",
Bytes: derPkix,
}
pubPem := pem.EncodeToMemory(pubBlock)
pri := strings.Replace(string(privPem), "-----BEGIN RSA PRIVATE KEY-----\n", "", -1)
pri = strings.Replace(pri, "\n-----END RSA PRIVATE KEY-----\n", "", -1)
pub := strings.Replace(string(pubPem), "-----BEGIN PUBLIC KEY-----\n", "", -1)
pub = strings.Replace(pub, "\n-----END PUBLIC KEY-----\n", "", -1)
return pub, pri, nil
}

View File

@ -0,0 +1,63 @@
package rsa
import (
"PaymentCenter/app/utils/encrypt/aes"
"encoding/base64"
"fmt"
"testing"
)
const (
PRI = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCmjK6SPkrP03ssex5Dxn4KZeVm9rQoJMslru13GNgoGKJAq13xwUGl6lpGlhzM9IMzg0ldOO2a0SQPPjc0fLrDjIgHPfIT6H1hDGkZdPaF4D0OfiHGzlLwXq7FyyyGc2JXILsniMBXcCxNFhbW0jAaJyYrBJb3q84a5y5AtK3IC+eDV6Bj2J4HlQVGKgW9u2ic6Jxl23sacXY6iifi+KEuoXNCJrmYlgRWTaQovLmTCLkErkzxzG9DFRDWGoz25LthDPqcCSUmWEbJ+obwIGB4r2WCbFXvaeVBQORlyVRyNUvYMItjHBQIKinDWZ6y8KzA0YKOoxEfr0KfE8Uk4PQBAgMBAAECggEABTAX0PzelN4uyvTT0sMi/R0YRKPgepP40vtBsNvF10E7Lp4ClAupHpYFSrJq178xu1/2dVBXEGM9hw8GUQd7RCjuD3cFwcp/EKU+Zy6uQ38iZRTskEDa3bC+q3EXzuFXDxqOfIhai262UTlkATw0sjUwJRdkbMxoeWHkSNuH7UBVddxFL8Bq1DKaPzRCqQ8zlkMZHy8Xbf2b8rFoi1oPBoPjHyxCo33zcnSg5xntIoA5pkD6x4z5cAnU55aBoYUiRv7jmG+MVp1jpDvAmJLfUayVZNakgX1r74bMPsl9kpA7dVdgOlWrIkbJE1plVXXswBb8QKN0/Yx2vv/YASSi0QKBgQDaO9BkRjvht/lrsQEur1wXf5ITnsVWsqlUhYQKGHihzOj7e0rGO9QICM4eQZH9ssHfxEXhmEoFdkaqo3Fo47NI+yinpWm+KruwrRFkCGejlKZ4bhn9zWPb8L1qJbN4UD1c5jUNk1B0EEdnLRFg0/O7xm602bGilvY5x2nf0v95+QKBgQDDXyiiGNV7GO4h8OQYJwq0IqenPyIanRgYI3rw//tg147mhWcxT6ms6dMUh9nEXEFali2J1En+aVvx1Xn47CuGrRmZOLaGkw9ESFA/4ngYdea+xgttbKMXm0QwIwvATq2jxxrYEKmnr/+EUUWzIWioM1zQffAhVlkLFVnImquMSQKBgDe7qNfC/A4ELwWqubOTg0BZCxRJqvoePJJiWrs9Tql7rFB1Rz5jDx5SKVmew0r4OP0Nog8gFl9YumlfvlncNPBBfDt8SgoP3ckcGeHjJ5ymHPGKpMaliogj7ivKnw/t5g3wmMHzyksp0SJvZw3Ec22UGrfDFNOCHDXbUJWhzC75AoGBALbM6rAAnH65LNcFFeajYRh69HNAVyCfrFOpnvawDPzntAVs/MjeyNvJTH8BPXjE+UFREvrLbxBkdGsqWx3VnEQ+4pzCu8XfA4HYR33+4G/CoUwO8dJIu7DyzjJcGDqvYzjCqxNPQ+5qdqHPiW+56rq2lDlgHLaUnGwKZh+U2L5BAoGAeogtSQbnZaLQofSGcUOol2fyG24NU1JK3My+N1ypkSpD+y1KiFQeM5kg7UcJB6LBKZ/lRCKcjEMaxMyj57ETMkbRVKBLvsIL5QYolJLIHYqBul0AeFJ4K51SKK2Xk5rFvyuJKkJBux26WodtCXTnEzP1KRZGlSxJeN/V1yXjSEU="
PUB = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApoyukj5Kz9N7LHseQ8Z+CmXlZva0KCTLJa7tdxjYKBiiQKtd8cFBpepaRpYczPSDM4NJXTjtmtEkDz43NHy6w4yIBz3yE+h9YQxpGXT2heA9Dn4hxs5S8F6uxcsshnNiVyC7J4jAV3AsTRYW1tIwGicmKwSW96vOGucuQLStyAvng1egY9ieB5UFRioFvbtonOicZdt7GnF2Ooon4vihLqFzQia5mJYEVk2kKLy5kwi5BK5M8cxvQxUQ1hqM9uS7YQz6nAklJlhGyfqG8CBgeK9lgmxV72nlQUDkZclUcjVL2DCLYxwUCCopw1mesvCswNGCjqMRH69CnxPFJOD0AQIDAQAB"
)
func TestRsaEncrypt(t *testing.T) {
fmt.Printf("%s\n", encrypt())
}
func TestRsaDecrypt(t *testing.T) {
data := "pPnAPy7v2SY9Fu0LFcQH8UBA6VQ2FCfSg3nRZdMXS7mWjBwlacKHuFnh9UhobL7mxnmMyZPP100bpjCg2kvcfOpOp3ci85p+OYWINt4Fh3qgEOTG5FUyziaagGLm882t/I36KsDTVvbMZvC5sg4gZ9JQ5yAR+nuJfr0IxI0se/iD5luV1rms1kZHggd30iXdZtbkbX7xJ4xtnIiJmZU7kL+Xmvv1rDdPLxbol65QfnM1me1IHkXJapqSBnhEEmFQyBx31vp1ccNjkza8ZWbvTPCngc1k4kvlm6lKfwsG4hMuSdXUzveDm+Oo8StAKnyVoerJ202n7Vfx1XhehineQT0TPD7bO0HCEsDXXYEWwvcax8VdzYvHk7qSbH6e154qCr4LgDRSHKwAAExinTrzxx2rtSimieBLaEpDL2v5ch45HnhjRhWTRmM61W1g6sdHaVX1mQxaXvrT4v+h+f4TbIV4r4qeGJ6rXG+yKRoYseLzyGgystoOny9P0UH15W8rWPytV2eioWT7i3Cglg04BWP9mst67LQXeFH4CA6CkwVV2w9nCHrzxX2ouYSQELUEkTlIMry2AlkZubUnupGJLmLLUyZj7pM/6cLjyAgm02/gRc4wwf7JBBq/ipmKXpkhHXWLtQDWJEZTT+ug2v9EXy5dgPNPe8ZI0MILAeipjIc="
privateKeyPEM := `-----BEGIN RSA PRIVATE KEY-----
` + PRI + `
-----END RSA PRIVATE KEY-----`
res, err := Decrypt(privateKeyPEM, data)
fmt.Println(string(res), err)
}
func encrypt() string {
data := "{\"pay_channel_id\":8935141660703064070,\"out_trade_no\":\"refundOutTreadNo001\",\"amount\":1,\"desc\":\"退款\",\"ext_json\":\"\",\"app_id\":5476377146882523138,\"timestamp\":53612533412643,\"refund_out_trade_no\":\"asdadasdas\"}"
pub := `-----BEGIN PUBLIC KEY-----
` + PUB + `
-----END PUBLIC KEY-----`
en, err := Encrypt(pub, data)
if err != nil {
panic(err)
}
return base64.StdEncoding.EncodeToString(en)
}
func encryptWithAes() string {
data := "{\"pay_channel_id\":8935141660703064070,\"out_trade_no\":\"refundOutTreadNo001\",\"amount\":1,\"desc\":\"退款\",\"ext_json\":\"\",\"app_id\":5476377146882523138,\"timestamp\":53612533412643\"refund_out_trade_no\":\"asdadasdas\"}"
aes.Encrypt([]byte(data), []byte(aes.TestKey))
dataJson := base64.StdEncoding.EncodeToString([]byte(data))
pub := `-----BEGIN PUBLIC KEY-----
` + PUB + `
-----END PUBLIC KEY-----`
en, err := Encrypt(pub, dataJson)
if err != nil {
panic(err)
}
return base64.StdEncoding.EncodeToString(en)
}
// 测试生成密钥对
func TestGenerateRSAKey(t *testing.T) {
pub, pri, _ := GenerateKey()
fmt.Println("pub:", pub, "\n", "pri:", pri)
}

View File

@ -1,7 +1,6 @@
package sm2
import (
"PaymentCenter/config"
"crypto/rand"
"encoding/base64"
"encoding/hex"
@ -16,12 +15,21 @@ func GenerateSM2Key() (PublicKey string, PrivateKey string, err error) {
// 生成私钥、公钥
privKey, err := sm2.GenerateKey(rand.Reader)
if err != nil {
fmt.Println("生成密钥对失败:", err)
return "", "", err
}
return PublicKeyToString(&privKey.PublicKey), PrivateKeyToString(privKey), nil
}
// GenerateKey 生成密钥对
func GenerateKey() (string, string) {
pri, _ := sm2.GenerateKey(rand.Reader)
hexPri := pri.D.Text(16)
// 获取公钥
publicKeyHex := PublicKeyToString(&pri.PublicKey)
return strings.ToUpper(hexPri), publicKeyHex
}
// PublicKeyToString 公钥sm2.PublicKey转字符串(与java中org.bouncycastle.crypto生成的公私钥完全互通使用)
func PublicKeyToString(publicKey *sm2.PublicKey) string {
xBytes := publicKey.X.Bytes()
@ -48,27 +56,27 @@ func PrivateKeyToString(privateKey *sm2.PrivateKey) string {
return strings.ToUpper(hex.EncodeToString(privateKey.D.Bytes()))
}
func SM2Decrypt(cipherText string) (string, error) {
func SM2Decrypt(cipherText, publicKey string, privateKey string) (string, error) {
if cipherText == "" {
return "", nil
}
decodedBytes, err := base64.StdEncoding.DecodeString(cipherText)
if err != nil {
fmt.Println("解码错误:", err)
return "", nil
}
decrypt, err := decryptLoc(config.GetConf().Sm2.PublicKey, config.GetConf().Sm2.PrivateKey, string(decodedBytes))
decrypt, err := decryptLoc(publicKey, privateKey, string(decodedBytes))
if err != nil {
return "", err
}
return decrypt, nil
}
func SM2Encrypt(cipherText string) (string, error) {
func SM2Encrypt(cipherText string, privateKey string) (string, error) {
if cipherText == "" {
return "", nil
}
decrypt, err := encryptLoc(config.GetConf().Sm2.PublicKey, cipherText)
decrypt, err := encryptLoc(privateKey, cipherText)
if err != nil {
return "", err
}
@ -78,11 +86,11 @@ func SM2Encrypt(cipherText string) (string, error) {
func encryptLoc(publicKeyStr, data string) (string, error) {
publicKeyObj, err := StringToPublicKey(publicKeyStr)
if err != nil {
fmt.Println(err)
return "", err
}
decrypt, err := sm2.Encrypt(publicKeyObj, []byte(data), rand.Reader, sm2.C1C2C3)
if err != nil {
fmt.Println(err)
return "", err
}
resultStr := hex.EncodeToString(decrypt)
return base64.StdEncoding.EncodeToString([]byte(resultStr)), nil
@ -103,7 +111,6 @@ func decryptLoc(publicKeyStr, privateKeyStr, cipherText string) (string, error)
fmt.Println(err)
}
resultStr := string(decrypt)
fmt.Println("解密后的字符串:", resultStr)
return resultStr, nil
}

View File

@ -0,0 +1,56 @@
package sm2
import (
"fmt"
"testing"
)
const (
SELF_PRI = "BD13D44C74422F80ADDF54AD2DE2888C4092AF6F61E41FABADA6C47F17574A41"
SELF_PUB = "04F26C6CDA5E58153999FDF3023259F16EDE0A556C39FCF4BC97C04A10A8A6CF1D52297B53DB4DD72FEEF8221394C8478E6424F4E84E4A6E2784C1A4A1C99F2DC2"
)
func TestGenerateSM2KeyPair(t *testing.T) {
// Generate a new SM2 key pair
privateKey, publicKey := GenerateKey()
// Print the private and public keys
fmt.Printf("Private Key: %s\n", privateKey)
fmt.Printf("Public Key: %s\n", publicKey)
data := "{\"name\":\"张三\",\"sex\":1,\"is_human\":true}"
en, err := SM2Encrypt(data, publicKey)
if err != nil {
panic(err)
}
decrypt, err := SM2Decrypt(en, publicKey, privateKey)
if err != nil {
panic(err)
}
t.Log(decrypt)
}
func TestSM2Encrypt(t *testing.T) {
t.Log(encrypt())
}
func TestSM2Decrypt(t *testing.T) {
en := encrypt()
decrypt, err := SM2Decrypt(en, SELF_PUB, SELF_PRI)
if err != nil {
panic(err)
}
t.Log(decrypt)
}
func encrypt() string {
data := "{\"order_no\":5476377146882523228,\"order_type\":1,\"out_tread_no\":\"asdadasdas\",\"amount\":1,\"desc\":\"abc\",\"status\":2,\"create_time\":\"2024-08-08 14:47:35\"}"
en, err := SM2Encrypt(data, SELF_PUB)
if err != nil {
panic(err)
}
return en
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,2 @@
* 来源于 github.com/tjfoc/gmsm
* 修改适配 邮储对接

View File

@ -0,0 +1,219 @@
package sm2
// reference to ecdsa
import (
"crypto"
"crypto/elliptic"
"crypto/rand"
"encoding/asn1"
"errors"
"github.com/tjfoc/gmsm/sm3"
"io"
"math/big"
)
var (
default_uid = []byte{0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38}
)
type PublicKey struct {
elliptic.Curve
X, Y *big.Int
}
type PrivateKey struct {
PublicKey
D *big.Int
}
type sm2Signature struct {
R, S *big.Int
}
func (priv *PrivateKey) Public() crypto.PublicKey {
return &priv.PublicKey
}
var errZeroParam = errors.New("zero parameter")
var one = new(big.Int).SetInt64(1)
var two = new(big.Int).SetInt64(2)
func (priv *PrivateKey) Sign(random io.Reader, msg []byte, signer crypto.SignerOpts) ([]byte, error) {
r, s, err := Sm2Sign(priv, msg, nil, random)
if err != nil {
return nil, err
}
return asn1.Marshal(sm2Signature{r, s})
}
func Sm2Sign(priv *PrivateKey, msg, uid []byte, random io.Reader) (r, s *big.Int, err error) {
digest, err := priv.PublicKey.Sm3Digest(msg, uid)
if err != nil {
return nil, nil, err
}
e := new(big.Int).SetBytes(digest)
c := priv.PublicKey.Curve
N := c.Params().N
if N.Sign() == 0 {
return nil, nil, errZeroParam
}
var k *big.Int
for { // 调整算法细节以实现SM2
for {
k, err = randFieldElement(c, random)
if err != nil {
r = nil
return
}
r, _ = priv.Curve.ScalarBaseMult(k.Bytes())
r.Add(r, e)
r.Mod(r, N)
if r.Sign() != 0 {
if t := new(big.Int).Add(r, k); t.Cmp(N) != 0 {
break
}
}
}
rD := new(big.Int).Mul(priv.D, r)
s = new(big.Int).Sub(k, rD)
d1 := new(big.Int).Add(priv.D, one)
d1Inv := new(big.Int).ModInverse(d1, N)
s.Mul(s, d1Inv)
s.Mod(s, N)
if s.Sign() != 0 {
break
}
}
return
}
func (pub *PublicKey) Sm3Digest(msg, uid []byte) ([]byte, error) {
if len(uid) == 0 {
uid = default_uid
}
za, err := getZ(pub, uid)
if err != nil {
return nil, err
}
e, err := msgHash(za, msg)
if err != nil {
return nil, err
}
return e.Bytes(), nil
}
func Sm2Verify(pub *PublicKey, msg, uid []byte, r, s *big.Int) bool {
c := pub.Curve
N := c.Params().N
one := new(big.Int).SetInt64(1)
if r.Cmp(one) < 0 || s.Cmp(one) < 0 {
return false
}
if r.Cmp(N) >= 0 || s.Cmp(N) >= 0 {
return false
}
if len(uid) == 0 {
uid = default_uid
}
za, err := getZ(pub, uid)
if err != nil {
return false
}
e, err := msgHash(za, msg)
if err != nil {
return false
}
t := new(big.Int).Add(r, s)
t.Mod(t, N)
if t.Sign() == 0 {
return false
}
var x *big.Int
x1, y1 := c.ScalarBaseMult(s.Bytes())
x2, y2 := c.ScalarMult(pub.X, pub.Y, t.Bytes())
x, _ = c.Add(x1, y1, x2, y2)
x.Add(x, e)
x.Mod(x, N)
return x.Cmp(r) == 0
}
func msgHash(za, msg []byte) (*big.Int, error) {
e := sm3.New()
e.Write(za)
e.Write(msg)
return new(big.Int).SetBytes(e.Sum(nil)[:32]), nil
}
func bigIntToByte(n *big.Int) []byte {
byteArray := n.Bytes()
// If the most significant byte's most significant bit is set,
// prepend a 0 byte to the slice to avoid being interpreted as a negative number.
if (byteArray[0] & 0x80) != 0 {
byteArray = append([]byte{0}, byteArray...)
}
return byteArray
}
func getZ(pub *PublicKey, uid []byte) ([]byte, error) {
z := sm3.New()
uidLen := len(uid) * 8
entla := []byte{byte(uidLen >> 8), byte(uidLen & 255)}
z.Write(entla)
z.Write(uid)
// a 先写死,原来的没有暴露
z.Write(bigIntToByte(sm2P256ToBig(&sm2P256.a)))
z.Write(bigIntToByte(sm2P256.B))
z.Write(bigIntToByte(sm2P256.Gx))
z.Write(bigIntToByte(sm2P256.Gy))
z.Write(bigIntToByte(pub.X))
z.Write(bigIntToByte(pub.Y))
return z.Sum(nil), nil
}
func randFieldElement(c elliptic.Curve, random io.Reader) (k *big.Int, err error) {
if random == nil {
random = rand.Reader //If there is no external trusted random source,please use rand.Reader to instead of it.
}
params := c.Params()
b := make([]byte, params.BitSize/8+8)
_, err = io.ReadFull(random, b)
if err != nil {
return
}
k = new(big.Int).SetBytes(b)
n := new(big.Int).Sub(params.N, one)
k.Mod(k, n)
k.Add(k, one)
return
}
func GenerateKey(random io.Reader) (*PrivateKey, error) {
c := P256Sm2()
if random == nil {
random = rand.Reader //If there is no external trusted random source,please use rand.Reader to instead of it.
}
params := c.Params()
b := make([]byte, params.BitSize/8+8)
_, err := io.ReadFull(random, b)
if err != nil {
return nil, err
}
k := new(big.Int).SetBytes(b)
n := new(big.Int).Sub(params.N, two)
k.Mod(k, n)
k.Add(k, one)
priv := new(PrivateKey)
priv.PublicKey.Curve = c
priv.D = k
priv.PublicKey.X, priv.PublicKey.Y = c.ScalarBaseMult(k.Bytes())
return priv, nil
}

View File

@ -0,0 +1,45 @@
package sm2
import (
"encoding/hex"
"errors"
"math/big"
)
func ReadPrivateKeyFromHex(Dhex string) (*PrivateKey, error) {
c := P256Sm2()
d, err := hex.DecodeString(Dhex)
if err != nil {
return nil, err
}
k := new(big.Int).SetBytes(d)
params := c.Params()
one := new(big.Int).SetInt64(1)
n := new(big.Int).Sub(params.N, one)
if k.Cmp(n) >= 0 {
return nil, errors.New("privateKey's D is overflow.")
}
priv := new(PrivateKey)
priv.PublicKey.Curve = c
priv.D = k
priv.PublicKey.X, priv.PublicKey.Y = c.ScalarBaseMult(k.Bytes())
return priv, nil
}
func ReadPublicKeyFromHex(Qhex string) (*PublicKey, error) {
q, err := hex.DecodeString(Qhex)
if err != nil {
return nil, err
}
if len(q) == 65 && q[0] == byte(0x04) {
q = q[1:]
}
if len(q) != 64 {
return nil, errors.New("publicKey is not uncompressed.")
}
pub := new(PublicKey)
pub.Curve = P256Sm2()
pub.X = new(big.Int).SetBytes(q[:32])
pub.Y = new(big.Int).SetBytes(q[32:])
return pub, nil
}

View File

@ -0,0 +1,163 @@
package util
import (
"PaymentCenter/app/utils/encrypt/sm4/internal/sm2"
"bytes"
"crypto/elliptic"
"crypto/rand"
"errors"
zzsm2 "github.com/ZZMarquis/gm/sm2"
"github.com/tjfoc/gmsm/sm3"
"github.com/tjfoc/gmsm/x509"
"math/big"
)
func Sm2Decrypt(privateKey *sm2.PrivateKey, encryptData []byte) ([]byte, error) {
C1Byte := make([]byte, 65)
copy(C1Byte, encryptData[:65])
x, y := elliptic.Unmarshal(privateKey.Curve, C1Byte)
dBC1X, dBC1Y := privateKey.Curve.ScalarMult(x, y, bigIntToByte(privateKey.D))
dBC1Bytes := elliptic.Marshal(privateKey.Curve, dBC1X, dBC1Y)
kLen := len(encryptData) - 65 - 32
t, err := kdf(dBC1Bytes, kLen)
if err != nil {
return nil, err
}
M := make([]byte, kLen)
for i := 0; i < kLen; i++ {
M[i] = encryptData[65+i] ^ t[i]
}
C3 := make([]byte, 32)
copy(C3, encryptData[len(encryptData)-32:])
u := calculateHash(dBC1X, M, dBC1Y)
if bytes.Compare(u, C3) == 0 {
return M, nil
} else {
return nil, errors.New("解密失败")
}
}
func Sm2Encrypt(publicKey *sm2.PublicKey, m []byte) ([]byte, error) {
kLen := len(m)
var C1, t []byte
var err error
var kx, ky *big.Int
for {
k, _ := rand.Int(rand.Reader, publicKey.Params().N)
C1x, C1y := zzsm2.GetSm2P256V1().ScalarBaseMult(bigIntToByte(k))
// C1x, C1y := sm2.P256Sm2().ScalarBaseMult(bigIntToByte(k))
C1 = elliptic.Marshal(publicKey.Curve, C1x, C1y)
kx, ky = publicKey.ScalarMult(publicKey.X, publicKey.Y, bigIntToByte(k))
kpbBytes := elliptic.Marshal(publicKey, kx, ky)
t, err = kdf(kpbBytes, kLen)
if err != nil {
return nil, err
}
if !isAllZero(t) {
break
}
}
C2 := make([]byte, kLen)
for i := 0; i < kLen; i++ {
C2[i] = m[i] ^ t[i]
}
C3 := calculateHash(kx, m, ky)
r := make([]byte, 0, len(C1)+len(C2)+len(C3))
r = append(r, C1...)
r = append(r, C2...)
r = append(r, C3...)
return r, nil
}
func isAllZero(m []byte) bool {
for i := 0; i < len(m); i++ {
if m[i] != 0 {
return false
}
}
return true
}
func calculateHash(x *big.Int, M []byte, y *big.Int) []byte {
digest := sm3.New()
digest.Write(bigIntToByte(x))
digest.Write(M)
digest.Write(bigIntToByte(y))
result := digest.Sum(nil)[:32]
return result
}
func bigIntToByte(n *big.Int) []byte {
byteArray := n.Bytes()
// If the most significant byte's most significant bit is set,
// prepend a 0 byte to the slice to avoid being interpreted as a negative number.
if (byteArray[0] & 0x80) != 0 {
byteArray = append([]byte{0}, byteArray...)
}
return byteArray
}
func kdf(Z []byte, klen int) ([]byte, error) {
ct := 1
end := (klen + 31) / 32
result := make([]byte, 0)
for i := 1; i <= end; i++ {
b, err := sm3hash(Z, toByteArray(ct))
if err != nil {
return nil, err
}
result = append(result, b...)
ct++
}
last, err := sm3hash(Z, toByteArray(ct))
if err != nil {
return nil, err
}
if klen%32 == 0 {
result = append(result, last...)
} else {
result = append(result, last[:klen%32]...)
}
return result, nil
}
func sm3hash(sources ...[]byte) ([]byte, error) {
b, err := joinBytes(sources...)
if err != nil {
return nil, err
}
md := make([]byte, 32)
h := x509.SM3.New()
h.Write(b)
h.Sum(md[:0])
return md, nil
}
func joinBytes(params ...[]byte) ([]byte, error) {
var buffer bytes.Buffer
for i := 0; i < len(params); i++ {
_, err := buffer.Write(params[i])
if err != nil {
return nil, err
}
}
return buffer.Bytes(), nil
}
func toByteArray(i int) []byte {
byteArray := []byte{
byte(i >> 24),
byte((i & 16777215) >> 16),
byte((i & 65535) >> 8),
byte(i & 255),
}
return byteArray
}

View File

@ -0,0 +1,62 @@
package util
import (
"crypto/md5"
"crypto/rand"
"encoding/hex"
"math/big"
"strings"
"time"
)
func GenerateSM4Key() []byte {
str := "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890"
buffer := make([]byte, 16)
for i := 0; i < 16; i++ {
nextInt, _ := rand.Int(rand.Reader, big.NewInt(int64(len(str))))
buffer[i] = str[nextInt.Int64()]
}
return buffer
}
func GenAccessToken(token string) string {
if token != "" {
return token
}
now := time.Now()
return strings.ToUpper(Md5Hash(now.Format("2006A01B02CD15E04F05"), ""))
}
func Md5Hash(password, salt string) string {
m := md5.New()
m.Write([]byte(salt + password))
return hex.EncodeToString(m.Sum(nil))
}
// GetSM4IV 获取SM4的IV
func GetSM4IV() []byte {
return []byte("UISwD9fW6cFh9SNS")
}
func Padding(input []byte, mode int) []byte {
if input == nil {
return nil
} else {
var ret []byte
if mode == 1 {
p := 16 - len(input)%16
ret = make([]byte, len(input)+p)
copy(ret, input)
for i := 0; i < p; i++ {
ret[len(input)+i] = byte(p)
}
} else {
p := input[len(input)-1]
ret = make([]byte, len(input)-int(p))
copy(ret, input[:len(input)-int(p)])
}
return ret
}
}

203
app/utils/encrypt/sm4/sm.go Normal file
View File

@ -0,0 +1,203 @@
package sm4
import (
"PaymentCenter/app/utils/encrypt/sm4/internal/sm2"
"PaymentCenter/app/utils/encrypt/sm4/internal/util"
"crypto/rand"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"github.com/ZZMarquis/gm/sm4"
"math/big"
"strings"
)
func checkInData(reqData map[string]string, key string) (string, error) {
data, ok := reqData[key]
if !ok {
return "", errors.New("请求数据中不存在" + key)
}
return data, nil
}
func Sm4Decrypt(merchantId, privateKey, sopPublicKey, respJson string, isRequest bool) (string, error) {
var reqData map[string]string
err := json.Unmarshal([]byte(respJson), &reqData)
if err != nil {
return "", err
}
keys := [4]string{}
if isRequest {
keys = [4]string{"request", "signature", "encryptKey", "accessToken"}
} else {
keys = [4]string{"response", "signature", "encryptKey", "accessToken"}
}
var inEncryptKey, inAccessToken, inData, inSignature string
for i := 0; i < 4; i++ {
data, err := checkInData(reqData, keys[i])
if err != nil {
return "", err
}
switch keys[i] {
case "request", "response":
inData = data
case "signature":
inSignature = data
case "encryptKey":
inEncryptKey = data
case "accessToken":
inAccessToken = data
}
}
checked := verify(fmt.Sprintf("%s%s%s", inData, inEncryptKey, inAccessToken), inSignature, sopPublicKey, merchantId)
if !checked {
return "", errors.New("签名验证失败")
}
priKey, err := sm2.ReadPrivateKeyFromHex(privateKey)
if err != nil {
return "", errors.New("读取私钥失败")
}
hexEncryptKey, err := hex.DecodeString(inEncryptKey)
if err != nil {
return "", errors.New("解密sm4key失败")
}
sm4Key, err := util.Sm2Decrypt(priKey, hexEncryptKey)
request, _ := base64.StdEncoding.DecodeString(inData)
encryptedSm4Key, err := sm4.CBCDecrypt(sm4Key, util.GetSM4IV(), request)
return string(util.Padding(encryptedSm4Key, 0)), nil
}
func Sm4Encrypt(merchantId, privateKey, sopPublicKey, inputJson, token string, isRequest bool) (string, error) {
sm4Key := util.GenerateSM4Key()
iv := util.GetSM4IV()
tmp, err := sm4.CBCEncrypt(sm4Key, iv, util.Padding([]byte(inputJson), 1))
if err != nil {
return "", err
}
responseMsg := base64.StdEncoding.EncodeToString(tmp)
responseMsg = addNewline(responseMsg)
pubKey, err := sm2.ReadPublicKeyFromHex(sopPublicKey)
if err != nil {
return "", errors.New("读取私钥失败")
}
encryptKeyBytes, err := util.Sm2Encrypt(pubKey, sm4Key)
encryptKey := strings.ToUpper(hex.EncodeToString(encryptKeyBytes))
accessToken := util.GenAccessToken(token)
signContent := fmt.Sprintf("%s%s%s", responseMsg, encryptKey, accessToken)
signature, err := sign(merchantId, privateKey, signContent)
var reqData map[string]string
if isRequest {
reqData = map[string]string{
"request": responseMsg,
"signature": signature,
"encryptKey": encryptKey,
"accessToken": accessToken,
}
} else {
reqData = map[string]string{
"response": responseMsg,
"signature": signature,
"encryptKey": encryptKey,
"accessToken": accessToken,
}
}
jsonStr, err := json.Marshal(reqData)
if err != nil {
return "", err
}
return string(jsonStr), err
}
// GenerateKey 生成密钥对
func GenerateKey() (string, string) {
pri, _ := sm2.GenerateKey(rand.Reader)
hexPri := pri.D.Text(16)
// 获取公钥
publicKeyHex := publicKeyToString(&pri.PublicKey)
return strings.ToUpper(hexPri), publicKeyHex
}
// publicKeyToString 公钥sm2.PublicKey转字符串(与java中org.bouncycastle.crypto生成的公私钥完全互通使用)
func publicKeyToString(publicKey *sm2.PublicKey) string {
xBytes := publicKey.X.Bytes()
yBytes := publicKey.Y.Bytes()
// 确保坐标字节切片长度相同
byteLen := len(xBytes)
if len(yBytes) > byteLen {
byteLen = len(yBytes)
}
// 为坐标补齐前导零
xBytes = append(make([]byte, byteLen-len(xBytes)), xBytes...)
yBytes = append(make([]byte, byteLen-len(yBytes)), yBytes...)
// 添加 "04" 前缀
publicKeyBytes := append([]byte{0x04}, append(xBytes, yBytes...)...)
return strings.ToUpper(hex.EncodeToString(publicKeyBytes))
}
func addNewline(str string) string {
lineLength := 76
var result strings.Builder
for i := 0; i < len(str); i++ {
if i > 0 && i%lineLength == 0 {
result.WriteString("\r\n")
}
result.WriteByte(str[i])
}
return result.String()
}
func sign(merchantId string, privateKeyHex string, signContent string) (string, error) {
privateKey, err := sm2.ReadPrivateKeyFromHex(privateKeyHex)
if err != nil {
return "", err
}
r, s, err := sm2.Sm2Sign(privateKey, []byte(signContent), []byte(merchantId), rand.Reader)
if err != nil {
return "", err
}
return rSToSign(r, s), nil
}
func verify(content string, signature string, publicKeyStr string, merchantId string) bool {
pubKey, err := sm2.ReadPublicKeyFromHex(publicKeyStr)
if err != nil {
panic(fmt.Sprintf("pubKeyBytes sm2 ReadPublicKeyFromHex err: %v", err))
}
r, s := signToRS(signature)
return sm2.Sm2Verify(pubKey, []byte(content), []byte(merchantId), r, s)
}
func signToRS(signStr string) (*big.Int, *big.Int) {
signSub := strings.Split(signStr, "#")
if len(signSub) != 2 {
panic(fmt.Sprintf("err rs: %x", signSub))
}
r, _ := new(big.Int).SetString(signSub[0], 16)
s, _ := new(big.Int).SetString(signSub[1], 16)
return r, s
}
func rSToSign(r *big.Int, s *big.Int) string {
rStr := r.Text(16)
sStr := s.Text(16)
return fmt.Sprintf("%s#%s", rStr, sStr)
}

View File

@ -0,0 +1,48 @@
package sm4
import (
"fmt"
"strconv"
"testing"
)
const (
SELF_PRI = "EA7CB6F907A96264D6763F8C46AB96B476538D2ABC880A459E10BE5A1C30013D"
SELF_PUB = "04363DF574D4FE34EE58FB8A3F7CB08E6CA5EBB3B7335CBAE10A2900551F6450AB3AD25DBC0A76EFA9E6D44D2C51E3027483F7BFD09996457888BAFD1AF673817F"
PARTY_PRI = "EEB0A34DE1F05154E4B96E50BFC1F8B9750EEAE799F5708C7AFBABB9AFCFA1BF"
PARTY_PUB = "0420425DF33945C9D5596A33ED60ABEEFD0165C27BA7D6C95D37344E9223149FEAF4C10CEACD7EC88E8DD1E0BDEA71FD09BE2077A1BDC61BC45587B7CC3613F85A"
)
func TestGenerateKey(t *testing.T) {
hexPri, publicKeyHex := GenerateKey()
fmt.Println(hexPri, publicKeyHex)
}
func TestSM4Encrypt(t *testing.T) {
t.Log(encrypt())
}
func TestSM4Decrypt(t *testing.T) {
uid, en := encrypt()
//uid := "1729382256910270475"
//en := "{\"accessToken\":\"77F59F157466F309E703F43CFDCFFECF\",\"encryptKey\":\"04237241E4A465C18645985B3C1DAE987B5DDF1C077AA27D677C843C52E833A24711C63F2512D1A4C37E85A5C9CEAC94156C97460CB5E8966CAD2A0C2596A64ED69CF3306D031C4AADAA73D165FB7EEC34E5AAF532A301169847560329F7F1E40E9FD09EB191976BB49ABFE611E05158EF\",\"request\":\"mkuquQ1RB2AtZYzBtWtLPJ3MMULWj7I5RmK3dKENYVW8OgB//I/+MAD/XnjxYYJlRWM8uL/rWL9o\\r\\n0L7W9fSBvOXWwz8reiwcAF/JZ5dnacZtVe0NPujDfeVIJp+9ua7hxQcegcEsIRS9CQqtF/rJN7M9\\r\\nxxCLSzEiJY8bIdRLBsfmB0uVIE2144s2tH7Q8R2fSOVbiSTH1PW3Ye0kkyCcBw==\",\"signature\":\"ed99a2ce827a4d443e2639ccfb78865e913854e2d61d1746dafb640e19cbb4f8#3793b1cac082d9e1cdfc2b7b8387a8c29e44b2f3fb35b352d02f1b2c9321e0a7\"}"
decrypt, err := Sm4Decrypt(uid, SELF_PRI, PARTY_PUB, en, true)
if err != nil {
panic(err)
}
t.Log(decrypt)
}
func encrypt() (string, string) {
uid := strconv.FormatInt(int64(5476377146882523149), 10)
data := "{\"pay_channel_id\":1729382256910270476,\"out_trade_no\":\"asdadasdas\",\"order_type\":1,\"amount\":1,\"desc\":\"abc\",\"ext_json\":\"\",\"app_id\":5476377146882523149,\"timestamp\":1723096731}"
en, err := Sm4Encrypt(uid, PARTY_PRI, SELF_PUB, data, "", true)
if err != nil {
panic(err)
}
return uid, en
}

View File

@ -1,8 +1,6 @@
package mq
import (
common3 "PaymentCenter/app/constants/common"
mq "PaymentCenter/app/utils/mq/mqs"
"sync"
)
@ -25,8 +23,8 @@ func (this *CMqManager) InitMq() {
this.mqs = make(map[string]Imq)
//this.mqs[common.MQ_RABBIT] = RabbitMq{}
//this.mqs[common.MQ_NSQ] = NsqMq{}
this.mqs[common3.MQ_NATS] = mq.NatsMq{}
this.mqs[common3.MQ_KFK] = mq.KafkaMq{}
//this.mqs[common3.MQ_NATS] = mq.NatsMq{}
//this.mqs[common3.MQ_KFK] = mq.KafkaMq{}
}
func (this *CMqManager) GetMqByName(name string) Imq {
once.Do(func() {

View File

@ -1,89 +1,89 @@
package mq
import (
"PaymentCenter/app/utils"
"PaymentCenter/config"
"context"
"encoding/json"
"fmt"
"github.com/Shopify/sarama"
"github.com/qit-team/snow-core/redis"
"strconv"
"sync"
)
type KafkaMq struct {
}
// 同步
func (n KafkaMq) Produce(name string, log interface{}, delayTime int, args ...interface{}) error {
kafConfig := sarama.NewConfig()
kafConfig.Producer.RequiredAcks = sarama.WaitForAll // 发送完数据需要leader和follow都确认
kafConfig.Producer.Partitioner = sarama.NewRandomPartitioner // 新选出一个partition
kafConfig.Producer.Return.Successes = true // 成功交付的消息将在success channel返回
// 构造一个消息
msg := &sarama.ProducerMessage{}
msg.Topic = name
var data, _ = json.Marshal(log)
msg.Value = sarama.StringEncoder(string(data))
// 连接kafka
client, err := sarama.NewSyncProducer(config.GetConf().KafkaUrl, kafConfig)
if err != nil {
fmt.Println("producer closed, err:", err)
return nil
}
defer client.Close()
// 发送消息
pid, offset, err := client.SendMessage(msg)
if err != nil {
utils.Log(nil, "send msg failed, err:", err, pid, offset)
return nil
}
return nil
}
func (n KafkaMq) Consume(name string, hand interface{}) {
consumer, err := sarama.NewConsumer(config.GetConf().KafkaUrl, nil)
if err != nil {
utils.Log(nil, "kafka comsume", err.Error())
return
}
partitionList, err := consumer.Partitions(name) // 根据topic取到所有的分区
if err != nil {
utils.Log(nil, "kafka comsume", err.Error())
return
}
//utils.Log(nil,"kafka comsume",name,partitionList)
for partition := range partitionList { // 遍历所有的分区
// 针对每个分区创建一个对应的分区消费者
var offsetReDis, _ = redis.GetRedis().Incr(context.Background(), "kafka_consume:"+strconv.Itoa(int(partition))).Result() //保证多消费者不重复消费
var offset int64 = sarama.OffsetNewest
if offsetReDis > 0 {
//offset = int64(offsetReDis)
}
pc, err := consumer.ConsumePartition(name, int32(partition), offset)
//utils.Log(nil,"partion",int32(partition))
if err != nil {
fmt.Printf("failed to start consumer for partition %d,err:%v\n", partition, err)
return
}
defer pc.AsyncClose()
var wg sync.WaitGroup
wg.Add(1)
// 异步从每个分区消费信息
go func(sarama.PartitionConsumer) {
for msg := range pc.Messages() {
defer wg.Done()
var handler = hand.(func(tag uint64, ch interface{}, msg []byte))
handler(0, nil, msg.Value)
//utils.Log(nil,"hand msg",string(msg.Value),msg.Offset)
}
}(pc)
wg.Wait()
}
}
func (n KafkaMq) DelyConsume(name string, hand interface{}) {
}
//
//import (
// "PaymentCenter/app/utils"
// "PaymentCenter/config"
// "context"
// "fmt"
// "github.com/Shopify/sarama"
// "github.com/qit-team/snow-core/redis"
//
// "strconv"
// "sync"
//)
//
//type KafkaMq struct {
//}
//
//// 同步
//func (n KafkaMq) Produce(name string, log interface{}, delayTime int, args ...interface{}) error {
// //kafConfig := sarama.NewConfig()
// //kafConfig.Producer.RequiredAcks = sarama.WaitForAll // 发送完数据需要leader和follow都确认
// //kafConfig.Producer.Partitioner = sarama.NewRandomPartitioner // 新选出一个partition
// //kafConfig.Producer.Return.Successes = true // 成功交付的消息将在success channel返回
// //
// //// 构造一个消息
// //msg := &sarama.ProducerMessage{}
// //msg.Topic = name
// //var data, _ = json.Marshal(log)
// //msg.Value = sarama.StringEncoder(string(data))
// //// 连接kafka
// //client, err := sarama.NewSyncProducer(config.GetConf().KafkaUrl, kafConfig)
// //if err != nil {
// // fmt.Println("producer closed, err:", err)
// // return nil
// //}
// //defer client.Close()
// //// 发送消息
// //pid, offset, err := client.SendMessage(msg)
// //if err != nil {
// // utils.Log(nil, "send msg failed, err:", err, pid, offset)
// // return nil
// //}
// return nil
//}
//
//func (n KafkaMq) Consume(name string, hand interface{}) {
// consumer, err := sarama.NewConsumer(config.GetConf().KafkaUrl, nil)
// if err != nil {
// utils.Log(nil, "kafka comsume", err.Error())
// return
// }
// partitionList, err := consumer.Partitions(name) // 根据topic取到所有的分区
// if err != nil {
// utils.Log(nil, "kafka comsume", err.Error())
// return
// }
// //utils.Log(nil,"kafka comsume",name,partitionList)
// for partition := range partitionList { // 遍历所有的分区
// // 针对每个分区创建一个对应的分区消费者
// var offsetReDis, _ = redis.GetRedis().Incr(context.Background(), "kafka_consume:"+strconv.Itoa(int(partition))).Result() //保证多消费者不重复消费
// var offset int64 = sarama.OffsetNewest
// if offsetReDis > 0 {
// //offset = int64(offsetReDis)
// }
// pc, err := consumer.ConsumePartition(name, int32(partition), offset)
// //utils.Log(nil,"partion",int32(partition))
// if err != nil {
// fmt.Printf("failed to start consumer for partition %d,err:%v\n", partition, err)
// return
// }
// defer pc.AsyncClose()
// var wg sync.WaitGroup
// wg.Add(1)
// // 异步从每个分区消费信息
// go func(sarama.PartitionConsumer) {
// for msg := range pc.Messages() {
// defer wg.Done()
// var handler = hand.(func(tag uint64, ch interface{}, msg []byte))
// handler(0, nil, msg.Value)
// //utils.Log(nil,"hand msg",string(msg.Value),msg.Offset)
// }
// }(pc)
// wg.Wait()
// }
//}
//func (n KafkaMq) DelyConsume(name string, hand interface{}) {
//
//}

View File

@ -11,6 +11,7 @@ import (
"encoding/hex"
"fmt"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"github.com/bytedance/sonic"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v4"
"github.com/pkg/errors"
@ -410,3 +411,54 @@ func ParseToken(tokenString string) (*jwt.Token, *Claims, error) {
})
return token, Claims, err
}
func PopLast[T any](slice *[]T) (T, bool) {
if len(*slice) == 0 {
var zero T
return zero, false
}
index := len(*slice) - 1
lastElement := (*slice)[index]
*slice = (*slice)[:index] // 移除最后一个元素
return lastElement, true
}
func SonicApiDataToStruct(data interface{}, structInterFace interface{}) (dataStruct interface{}, err error) {
bytes, err := sonic.Marshal(data)
if err != nil {
return nil, err
}
err = sonic.Unmarshal(bytes, &structInterFace)
return structInterFace, err
}
func ContainsString(slice []string, s string) bool {
for _, item := range slice {
if item == s {
return true
}
}
return false
}
// 判断切片是否包含指定字符串
func SliceInStr(s string, slice []string) bool {
for _, v := range slice {
if s == v {
return true
}
}
return false
}
// 判断支付方式支付宝或者微信
func PayType(payChannel int) int {
switch payChannel {
case common.PAY_CHANNEL_WECHAT_H5, common.PAY_CHANNEL_WECHAT_JSAPI, common.PAY_CHANNEL_WECHAT_NATIVE, common.PAY_CHANNEL_WECHAT_APP, common.PAY_CHANNEL_WECHAT_MINI:
return common.PAY_CHANNLE_TYPE_WECHAT
case common.PAY_CHANNEL_ALIPAY_JSAPI, common.PAY_CHANNEL_ALIPAY_WEB, common.PAY_CHANNEL_ALIPAY_MINI:
return common.PAY_CHANNLE_TYPE_ZFB
default:
return 0
}
}

View File

@ -4,11 +4,10 @@ import (
"PaymentCenter/app/jobs"
"PaymentCenter/app/jobs/basejob"
"PaymentCenter/config"
"github.com/qit-team/snow-core/log/accesslogger"
"github.com/qit-team/snow-core/db"
"github.com/qit-team/snow-core/kernel/close"
"github.com/qit-team/snow-core/kernel/container"
"github.com/qit-team/snow-core/log/accesslogger"
"github.com/qit-team/snow-core/log/logger"
"github.com/qit-team/snow-core/redis"
)
@ -26,6 +25,8 @@ func Bootstrap(conf *config.Config) (err error) {
//注册db服务
//第一个参数为注入别名,第二个参数为配置,第三个参数可选为是否懒加载
err = db.Pr.Register(db.SingletonMain, conf.Db)
//cacher := caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 1000)
//db.GetDb().SetDefaultCacher(cacher)
if err != nil {
return

View File

@ -6,4 +6,4 @@ arch=$2 #架构amd64
rootPath=$(cd `dirname $0`/../../; pwd)
#编译
GOOS=$os GOARCH=$arch go build -o build/bin/snow main.go
GOOS=$os GOARCH=$arch go build -o build/bin/snow main.go

View File

@ -22,6 +22,7 @@ type Config struct {
Debug bool `toml:"Debug"`
PrometheusCollectEnable bool `toml:"PrometheusCollectEnable"`
SkyWalkingOapServer string `toml:"SkyWalkingOapServer"`
TimeOut int64 `toml:"TimeOut"`
Log config.LogConfig `toml:"Log"`
Redis config.RedisConfig `toml:"Redis"`
Mns config.MnsConfig `toml:"AliMns"`
@ -36,6 +37,9 @@ type Config struct {
OpenApi OpenApi `toml:"OpenApi"`
Jwt Jwt `toml:"Jwt"`
AliOss AliOss `toml:"AliOss"`
AdminGate []string `toml:"AdminGate"`
CronConfig CronConfig `toml:"CronConfig"`
PayService PayService `toml:"PayService"`
}
type AliOss struct {
@ -82,6 +86,17 @@ type Nacos struct {
Port int64
}
type CronConfig struct {
CloseOrderTime int `toml:"CloseOrderTime"`
QueryOrderTime int `toml:"QueryOrderTime"`
ConcurrentNumber int `toml:"ConcurrentNumber"`
}
type PayService struct {
Host string `toml:"Host"`
IsProd bool `toml:"IsProd"`
}
func newConfig() *Config {
return new(Config)
}

View File

@ -7,7 +7,7 @@ import (
/**
*事件触发
event.EventManger.TrigerEvent(common.Event_USER_LOG_IN, param)
event.EventManger.TrigerEvent(payCommon.Event_USER_LOG_IN, param)
*/
var EventManger *EventManagerFactory

35
go.mod
View File

@ -6,9 +6,13 @@ require (
gitee.com/chengdu_blue_brothers/openapi-go-sdk v0.0.2
github.com/BurntSushi/toml v0.4.1
github.com/Shopify/sarama v1.19.0
github.com/ZZMarquis/gm v1.3.2
github.com/ahmetb/go-linq/v3 v3.2.0
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
github.com/bytedance/sonic v1.10.2
github.com/forgoer/openssl v1.6.0
github.com/gin-gonic/gin v1.7.7
github.com/go-pay/gopay v1.5.103
github.com/go-playground/locales v0.14.0
github.com/go-playground/universal-translator v0.18.0
github.com/go-sql-driver/mysql v1.6.0
@ -21,6 +25,7 @@ require (
github.com/qit-team/snow-core v0.1.28
github.com/qit-team/work v0.3.11
github.com/robfig/cron v1.2.0
github.com/spf13/cobra v1.2.1
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271
github.com/swaggo/gin-swagger v1.3.3
github.com/swaggo/swag v1.7.9
@ -29,6 +34,8 @@ require (
google.golang.org/grpc v1.56.3
google.golang.org/protobuf v1.30.0
gopkg.in/go-playground/validator.v9 v9.31.0
xorm.io/builder v0.3.9
xorm.io/xorm v1.2.5
)
require (
@ -46,17 +53,25 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/eapache/go-resiliency v1.1.0 // indirect
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 // indirect
github.com/eapache/queue v1.1.0 // indirect
github.com/emirpasic/gods v1.12.0 // indirect
github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-pay/crypto v0.0.1 // indirect
github.com/go-pay/errgroup v0.0.2 // indirect
github.com/go-pay/util v0.0.2 // indirect
github.com/go-pay/xlog v0.0.3 // indirect
github.com/go-pay/xtime v0.0.2 // indirect
github.com/go-playground/validator/v10 v10.9.0 // indirect
github.com/go-redis/redis/v8 v8.11.4 // indirect
github.com/goccy/go-json v0.8.1 // indirect
@ -66,10 +81,13 @@ require (
github.com/google/uuid v1.3.0 // indirect
github.com/hetiansu5/accesslog v1.0.0 // indirect
github.com/hetiansu5/cores v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jinzhu/copier v0.4.0 // indirect
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.13.6 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible // indirect
github.com/lestrrat-go/strftime v1.0.5 // indirect
@ -88,29 +106,30 @@ require (
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a // indirect
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/tidwall/gjson v1.12.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.6 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.21.0 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/arch v0.6.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.1.0 // indirect
golang.org/x/tools v0.6.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
gopkg.in/ini.v1 v1.66.2 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
stathat.com/c/consistent v1.0.0 // indirect
xorm.io/builder v0.3.9 // indirect
xorm.io/core v0.7.3 // indirect
xorm.io/xorm v1.2.5 // indirect
)

80
go.sum
View File

@ -58,9 +58,13 @@ github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWX
github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/ZZMarquis/gm v1.3.2 h1:lFtpzg5zeeVMZ/gKi0gtYcKLBEo9XTqsZDHDz6s3Gow=
github.com/ZZMarquis/gm v1.3.2/go.mod h1:wWbjZYgruQVd7Bb8UkSN8ujU931kx2XUW6nZLCiDE0Q=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs=
github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=
github.com/ahmetb/go-linq/v3 v3.2.0 h1:BEuMfp+b59io8g5wYzNoFe9pWPalRklhlhbiU3hYZDE=
github.com/ahmetb/go-linq/v3 v3.2.0/go.mod h1:haQ3JfOeWK8HpVxMtHHEMPVgBKiYyQ+f1/kLZh/cj9U=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@ -108,6 +112,10 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE=
github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@ -115,6 +123,13 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@ -173,6 +188,7 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6 h1:6VSn3hB5U5GeA6kQw4TwWIWbOhtvR2hmbBJnTOtqTWc=
github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6/go.mod h1:YxOVT5+yHzKvwhsiSIWmbAYM3Dr9AEEbER2dVayfBkg=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/gzip v0.0.3 h1:etUaeesHhEORpZMp18zoOhepboiWnFtXrBZxszWUn4k=
@ -208,6 +224,18 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-pay/crypto v0.0.1 h1:B6InT8CLfSLc6nGRVx9VMJRBBazFMjr293+jl0lLXUY=
github.com/go-pay/crypto v0.0.1/go.mod h1:41oEIvHMKbNcYlWUlRWtsnC6+ASgh7u29z0gJXe5bes=
github.com/go-pay/errgroup v0.0.2 h1:5mZMdm0TDClDm2S3G0/sm0f8AuQRtz0dOrTHDR9R8Cc=
github.com/go-pay/errgroup v0.0.2/go.mod h1:0+4b8mvFMS71MIzsaC+gVvB4x37I93lRb2dqrwuU8x8=
github.com/go-pay/gopay v1.5.103 h1:tjteCpcApf0CpiwKywMl6UnbLkMeYLJVEuVBtsQbyb8=
github.com/go-pay/gopay v1.5.103/go.mod h1:4+jKRvgmB8clKN1/E9M60miXKbnZ8wWGcg/Hsxn7k44=
github.com/go-pay/util v0.0.2 h1:goJ4f6kNY5zzdtg1Cj8oWC+Cw7bfg/qq2rJangMAb9U=
github.com/go-pay/util v0.0.2/go.mod h1:qM8VbyF1n7YAPZBSJONSPMPsPedhUTktewUAdf1AjPg=
github.com/go-pay/xlog v0.0.3 h1:avyMhCL/JgBHreoGx/am/kHxfs1udDOAeVqbmzP/Yes=
github.com/go-pay/xlog v0.0.3/go.mod h1:mH47xbobrdsSHWsmFtSF5agWbMHFP+tK0ZbVCk5OAEw=
github.com/go-pay/xtime v0.0.2 h1:7YR4/iuELsEHpJ6LUO0SVK80hQxDO9MLCfuVYIiTCRM=
github.com/go-pay/xtime v0.0.2/go.mod h1:W1yRbJaSt4CSBcdAtLBQ8xajiN/Pl5hquGczUcUE9xE=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
@ -297,8 +325,8 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@ -365,6 +393,7 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
@ -414,6 +443,8 @@ github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0f
github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
@ -445,6 +476,10 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@ -668,9 +703,11 @@ github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJ
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw=
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
@ -680,6 +717,8 @@ github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5J
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@ -687,6 +726,7 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
@ -710,6 +750,8 @@ github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhso
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0=
@ -772,6 +814,9 @@ go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.6.0 h1:S0JTfE48HbRj80+4tbvZDYsJ3tGv6BUU3XxyZ7CirAc=
golang.org/x/arch v0.6.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@ -793,8 +838,8 @@ golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -831,8 +876,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -885,8 +930,8 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -911,8 +956,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -987,8 +1032,9 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211213223007-03aa0b5f6827/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1000,8 +1046,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -1071,8 +1117,8 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -1373,7 +1419,9 @@ modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
modernc.org/z v1.2.19/go.mod h1:+ZpP0pc4zz97eukOzW3xagV/lS82IpPN9NGG5pNF9vY=
modernc.org/z v1.2.20/go.mod h1:zU9FiF4PbHdOTUxw+IF8j7ArBMRPsHgq10uVPt6xTzo=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=