This commit is contained in:
parent
cd6a66bdf3
commit
1aaae37beb
89
Makefile
89
Makefile
|
|
@ -1,3 +1,52 @@
|
|||
# 强制指定 Go 版本(需与主程序编译环境一致)
|
||||
REQUIRED_GO_VERSION := 1.23.6
|
||||
# 目标平台配置(根据运行环境调整,如 arm64)
|
||||
TARGET_GOOS := linux
|
||||
TARGET_GOARCH := amd64
|
||||
# 交叉编译工具链(Mac 编译 Linux amd64 必需)
|
||||
TARGET_CC := x86_64-linux-musl-gcc
|
||||
|
||||
build-linux1:
|
||||
@echo "===== 开始编译 Linux 插件 ====="
|
||||
@echo "要求 Go 版本: $(REQUIRED_GO_VERSION)"
|
||||
@echo "目标平台: $(TARGET_GOOS)/$(TARGET_GOARCH)"
|
||||
|
||||
# 1. 检查 Go 版本
|
||||
@go version | grep -q $(REQUIRED_GO_VERSION) || ( \
|
||||
echo "错误:需要 Go $(REQUIRED_GO_VERSION),当前版本:$(shell go version)"; \
|
||||
exit 1 \
|
||||
)
|
||||
|
||||
# 2. 检查插件目录
|
||||
@[ -d "plugins/${name}" ] || ( \
|
||||
echo "错误:插件目录 plugins/${name} 不存在"; \
|
||||
exit 1 \
|
||||
)
|
||||
|
||||
# 3. 核心:所有命令写在同一行,确保环境变量作用域生效
|
||||
# 注意:&& 连接,且环境变量紧跟 cd 后,与 go build 同进程
|
||||
cd plugins/${name} && \
|
||||
CGO_ENABLED=1 \
|
||||
CC=$(TARGET_CC) \
|
||||
GOOS=$(TARGET_GOOS) \
|
||||
GOARCH=$(TARGET_GOARCH) \
|
||||
GOPROXY=https://goproxy.cn,direct \
|
||||
go build -buildmode=plugin -trimpath -o ../../pkg/linux/${name}.so .
|
||||
|
||||
# 4. 验证编译结果
|
||||
@echo "===== 编译完成 ====="
|
||||
@echo "插件路径: pkg/linux/${name}.so"
|
||||
@echo "插件格式检测:"
|
||||
@file pkg/linux/${name}.so
|
||||
|
||||
build-linux:
|
||||
cd plugins; \
|
||||
export GOOS=linux; \
|
||||
export GOARCH=amd64; \
|
||||
export CGO_ENABLED=0; \
|
||||
export GOPROXY=https://goproxy.cn,direct; \
|
||||
cd ${name} && go build -o ../../pkg/linux/${name}.so .
|
||||
|
||||
build-mac:
|
||||
cd plugins; \
|
||||
export GOOS=darwin; \
|
||||
|
|
@ -15,73 +64,77 @@ build-win:
|
|||
export GOPROXY=https://goproxy.cn,direct; \
|
||||
cd ${name} && go build -o ../../pkg/win/${name}.so .
|
||||
|
||||
build-linux:
|
||||
cd plugins; \
|
||||
export GOOS=linux; \
|
||||
export GOARCH=amd64; \
|
||||
export CGO_ENABLED=0; \
|
||||
export GOPROXY=https://goproxy.cn,direct; \
|
||||
cd ${name} && go build -o ../../pkg/linux/${name}.so .
|
||||
|
||||
.PHONY: zltx_v1
|
||||
zltx_v1:
|
||||
make build-mac name=zltx_v1 && \
|
||||
make build-linux name=zltx_v1 && \
|
||||
make build-mac name=zltx_v1 && \
|
||||
make build-win name=zltx_v1
|
||||
|
||||
.PHONY: zltx_card_v1
|
||||
zltx_card_v1:
|
||||
make build-mac name=zltx_card_v1 && \
|
||||
make build-linux name=zltx_card_v1 && \
|
||||
make build-mac name=zltx_card_v1 && \
|
||||
make build-win name=zltx_card_v1
|
||||
|
||||
.PHONY: zltx_v2
|
||||
zltx_v2:
|
||||
make build-mac name=zltx_v2 && \
|
||||
make build-linux name=zltx_v2 && \
|
||||
make build-mac name=zltx_v2 && \
|
||||
make build-win name=zltx_v2
|
||||
|
||||
.PHONY: alipay_cpn
|
||||
alipay_cpn:
|
||||
make build-mac name=alipay_cpn && \
|
||||
make build-linux name=alipay_cpn && \
|
||||
make build-mac name=alipay_cpn && \
|
||||
make build-win name=alipay_cpn
|
||||
|
||||
.PHONY: alipay_redpack
|
||||
alipay_redpack:
|
||||
make build-mac name=alipay_redpack && \
|
||||
make build-linux name=alipay_redpack && \
|
||||
make build-mac name=alipay_redpack && \
|
||||
make build-win name=alipay_redpack
|
||||
|
||||
.PHONY: wechat_cpn
|
||||
wechat_cpn:
|
||||
make build-mac name=wechat_cpn && \
|
||||
make build-linux name=wechat_cpn && \
|
||||
make build-mac name=wechat_cpn && \
|
||||
make build-win name=wechat_cpn
|
||||
|
||||
.PHONY: wechat_redpack
|
||||
wechat_redpack:
|
||||
make build-mac name=wechat_redpack && \
|
||||
make build-linux name=wechat_redpack && \
|
||||
make build-mac name=wechat_redpack && \
|
||||
make build-win name=wechat_redpack
|
||||
|
||||
.PHONY: wechat_redpack_v2
|
||||
wechat_redpack_v2:
|
||||
make build-mac name=wechat_redpack_v2 && \
|
||||
make build-linux name=wechat_redpack_v2 && \
|
||||
make build-mac name=wechat_redpack_v2 && \
|
||||
make build-win name=wechat_redpack_v2
|
||||
|
||||
.PHONY: union_pay_cpn
|
||||
union_pay_cpn:
|
||||
make build-mac name=union_pay_cpn && \
|
||||
make build-linux name=union_pay_cpn && \
|
||||
make build-mac name=union_pay_cpn && \
|
||||
make build-win name=union_pay_cpn
|
||||
|
||||
.PHONY: union_pay_redpack
|
||||
union_pay_redpack:
|
||||
make build-mac name=union_pay_redpack && \
|
||||
make build-linux name=union_pay_redpack && \
|
||||
make build-mac name=union_pay_redpack && \
|
||||
make build-win name=union_pay_redpack
|
||||
|
||||
.PHONY: qixing_alipay_redpack
|
||||
qixing_alipay_redpack:
|
||||
make build-linux name=qixing_alipay_redpack && \
|
||||
make build-mac name=qixing_alipay_redpack && \
|
||||
make build-win name=qixing_alipay_redpack
|
||||
|
||||
.PHONY: qixing_wechat_redpack
|
||||
qixing_wechat_redpack:
|
||||
make build-linux name=qixing_wechat_redpack && \
|
||||
make build-mac name=qixing_wechat_redpack && \
|
||||
make build-win name=qixing_wechat_redpack
|
||||
|
||||
.PHONY: all
|
||||
all: zltx_v1 zltx_card_v1 zltx_v2 alipay_cpn alipay_redpack wechat_cpn wechat_redpack wechat_redpack_v2
|
||||
|
|
@ -7,11 +7,12 @@ func main() {
|
|||
|
||||
//WechatRedPackV2Order()
|
||||
//WechatRedPackV2Query()
|
||||
WechatRedPackV2Notify()
|
||||
//WechatRedPackV2Notify()
|
||||
|
||||
//alipayOrderRedPack()
|
||||
//alipayQueryRedPack()
|
||||
|
||||
//zltxQuery()
|
||||
zltxCardQuery()
|
||||
//zltxCardNotify()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@ var wechatCpnConf = &manage.Config{
|
|||
|
||||
func getWechatCpnConf() []byte {
|
||||
c := &wechat.Server{
|
||||
MchID: "1605446142", // 证书所属商户
|
||||
MchCertificateSerialNumber: "4D081089DEB385316CBDCB55C070287E4920AC76", // old
|
||||
//MchCertificateSerialNumber: "46B64A9AF817BCE0425AB2ED003E7FC3C3DC48D9",
|
||||
MchID: "1605446142", // 证书所属商户
|
||||
//MchCertificateSerialNumber: "4D081089DEB385316CBDCB55C070287E4920AC76", // old 过期
|
||||
MchCertificateSerialNumber: "46B64A9AF817BCE0425AB2ED003E7FC3C3DC48D9", // 新的
|
||||
}
|
||||
marshal, _ := json.Marshal(c)
|
||||
return marshal
|
||||
|
|
@ -37,12 +37,12 @@ func wechatOrderCpn() {
|
|||
request := &proto.OrderRequest{
|
||||
Config: getWechatCpnConf(),
|
||||
Order: &proto.OrderRequest_Order{
|
||||
OrderNo: "2024112215282589700100009",
|
||||
Account: "oknbq5tQyff_vCjjRjCJBElhF1og",
|
||||
Extra: []byte(`{"app_id":"wx83fd6da8093f55b7","stock_creator_mchid":"1679625521"}`),
|
||||
OrderNo: "202512051115219880010012", // {status:ING order_no:"202512051115219880010012" trade_no:"141771369565" message:"成功"}
|
||||
Account: "oknbq5mAjfgPiCJV028g-ZFDx1WU",
|
||||
Extra: []byte(`{"app_id":"wx83fd6da8093f55b7","stock_creator_mchid":"1652465541"}`),
|
||||
},
|
||||
Product: &proto.OrderRequest_Product{
|
||||
ProductNo: "19519911",
|
||||
ProductNo: "21144469",
|
||||
Extra: []byte(`{}`),
|
||||
},
|
||||
}
|
||||
|
|
@ -63,9 +63,9 @@ func wechatQueryCpn() {
|
|||
Config: getWechatCpnConf(),
|
||||
Order: &proto.QueryRequest_Order{
|
||||
OrderNo: "",
|
||||
TradeNo: "69445765514",
|
||||
Account: "oO3vO5AxRWgTjmMD38FTvnB5Rq6o",
|
||||
Extra: []byte(`{"app_id":"wx9ed74283ad25bca1"}`),
|
||||
TradeNo: "141771369565",
|
||||
Account: "oknbq5mAjfgPiCJV028g-ZFDx1WU",
|
||||
Extra: []byte(`{"app_id":"wx83fd6da8093f55b7"}`),
|
||||
},
|
||||
}
|
||||
resQuery, err := instance.Query(context.Background(), alpayRedConf.Tag, queryRequest)
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ func WechatRedPackV2Query() {
|
|||
request := &proto.QueryRequest{
|
||||
Config: getWechatRedPackV2Conf(),
|
||||
Order: &proto.QueryRequest_Order{
|
||||
OrderNo: "19497351672832450564",
|
||||
OrderNo: "19957295972489175049",
|
||||
TradeNo: "",
|
||||
Account: "",
|
||||
Extra: []byte(``),
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ import (
|
|||
)
|
||||
|
||||
var zltxCardConf = &manage.Config{
|
||||
Cmd: "pkg/mac/zltx_card_v1_1.so",
|
||||
Tag: "zltx_card_v1_1",
|
||||
Cmd: "pkg/mac/zltx_card_v1.so",
|
||||
Tag: "zltx_card_v1",
|
||||
Version: 1,
|
||||
CookieKey: "zltx_card_v1_1",
|
||||
CookieValue: "zltx_card_v1_1",
|
||||
CookieKey: "zltx_card_v1",
|
||||
CookieValue: "zltx_card_v1",
|
||||
}
|
||||
|
||||
func zlxtCardCf() []byte {
|
||||
|
|
@ -39,6 +39,39 @@ func zlxtCardCf() []byte {
|
|||
//return marshal
|
||||
}
|
||||
|
||||
func zltxCardQuery() {
|
||||
|
||||
err := manage.Add(zltxCardConf)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
fmt.Println("zltx_card_v1_1 close start")
|
||||
manage.Close()
|
||||
fmt.Println("zltx_card_v1_1 close end")
|
||||
}()
|
||||
|
||||
for i := 0; i < 1; i++ {
|
||||
req := &proto.QueryRequest{
|
||||
Config: zlxtCardCf(),
|
||||
Order: &proto.QueryRequest_Order{
|
||||
OrderNo: "test_plugin_zltx_v1_card_3",
|
||||
TradeNo: "",
|
||||
Account: "",
|
||||
Extra: nil,
|
||||
},
|
||||
}
|
||||
res, err := instance.Query(context.Background(), zltxCardConf.Tag, req)
|
||||
if err != nil {
|
||||
log.Printf("query err:%+v i:%d", err, i)
|
||||
} else {
|
||||
log.Printf("query res:%+v i:%d", res, i)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// main 这只是一个演示
|
||||
func zltxCardNotify() {
|
||||
|
||||
|
|
@ -53,12 +86,6 @@ func zltxCardNotify() {
|
|||
fmt.Println("zltx_card_v1_1 close end")
|
||||
}()
|
||||
|
||||
//req := &proto.NotifyRequest{
|
||||
// Config: zlxtCardCf(),
|
||||
// Queries: []byte(``),
|
||||
// Headers: []byte(`{"Accept-Encoding":["gzip, deflate, br"],"Authorization":["MD5 appid=101,sign=1A821E1E6FA824C7099D7F17F58E1650"],"Connection":["close"],"Content-Length":["183"],"Content-Type":["application/json"],"Cookie":[""],"User-Agent":["GuzzleHttp/6.5.5 curl/7.69.1 PHP/7.2.34"],"X-Forwarded-For":["47.96.248.136"],"X-Real-Ip":["47.96.248.136"],"X-Remoteaddr":["172.18.0.1"]}`),
|
||||
// Body: []byte(`{"merchantId":25943,"outTradeNo":"202506161700247580010056","tradeNo":"789175179564695553","status":"01","cardCode":"XPHrv0+uPVQOqfymz1jJAsLOOuGpfvgXi9RIF1m4tRCsDdvcZDDNY21M/22F56M1"`),
|
||||
//}
|
||||
// transport: error while dialing: dial unix /tmp/plugin2702936918: connect: connection refused
|
||||
for i := 0; i < 5; i++ {
|
||||
req := &proto.NotifyRequest{
|
||||
|
|
@ -69,9 +96,9 @@ func zltxCardNotify() {
|
|||
}
|
||||
res, err := instance.Notify(context.Background(), zltxCardConf.Tag, req)
|
||||
if err != nil {
|
||||
log.Printf("Order err:%+v i:%d", err, i)
|
||||
log.Printf("notify err:%+v i:%d", err, i)
|
||||
} else {
|
||||
log.Printf("Order res:%+v i:%d", res, i)
|
||||
log.Printf("notify res:%+v i:%d", res, i)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
module plugins/qixing_alipay_redpack
|
||||
|
||||
go 1.22.2
|
||||
|
||||
replace plugins/utils => ../../utils
|
||||
|
||||
require (
|
||||
gitea.cdlsxd.cn/sdk/plugin v1.0.19
|
||||
github.com/go-playground/validator/v10 v10.22.0
|
||||
github.com/hashicorp/go-plugin v1.6.1
|
||||
github.com/stretchr/testify v1.9.0
|
||||
plugins/utils v0.0.0-00010101000000-000000000000
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fatih/color v1.7.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/go-kratos/kratos/v2 v2.8.2 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/hashicorp/go-hclog v0.14.1 // indirect
|
||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 // indirect
|
||||
github.com/oklog/run v1.0.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/crypto v0.26.0 // indirect
|
||||
golang.org/x/net v0.27.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/text v0.17.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
|
||||
google.golang.org/grpc v1.64.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
gitea.cdlsxd.cn/sdk/plugin v1.0.17 h1:agk+9iA1ZI6fLVLtxEnuOWxcDzSq9QH7VBFvhlZZsbw=
|
||||
gitea.cdlsxd.cn/sdk/plugin v1.0.17/go.mod h1:O/bYQWg1o9g/cBq9qNA3kLIpuPt7VDZqj1bPE6s04NM=
|
||||
gitea.cdlsxd.cn/sdk/plugin v1.0.19 h1:j0Ifn3q+C7ibxSTfL1KbmnX1k/VO9e0XMDJSuPutixU=
|
||||
gitea.cdlsxd.cn/sdk/plugin v1.0.19/go.mod h1:O/bYQWg1o9g/cBq9qNA3kLIpuPt7VDZqj1bPE6s04NM=
|
||||
github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
|
||||
github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/go-kratos/kratos/v2 v2.8.2 h1:EsEA7AmPQ2YQQ0FZrDWO2HgBNqeWM8z/mWKzS5UkQaQ=
|
||||
github.com/go-kratos/kratos/v2 v2.8.2/go.mod h1:+Vfe3FzF0d+BfMdajA11jT0rAyJWublRE/seZQNZVxE=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
|
||||
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU=
|
||||
github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-plugin v1.6.1 h1:P7MR2UP6gNKGPp+y7EZw2kOiq4IR9WiqLvp0XOsVdwI=
|
||||
github.com/hashicorp/go-plugin v1.6.1/go.mod h1:XPHFku2tFo3o3QKFgSYo+cghcUhw1NA1hZyMK0PWAw0=
|
||||
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
||||
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
||||
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
|
||||
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg=
|
||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
|
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
|
||||
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package po
|
||||
|
||||
import "plugins/qixing_alipay_redpack/internal/vo"
|
||||
|
||||
type Notify struct {
|
||||
BatchId string `json:"batchId"`
|
||||
BizNo string `json:"bizNo"`
|
||||
Type vo.Type `json:"type"`
|
||||
FailMsg string `json:"failMsg"`
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
package po
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitea.cdlsxd.cn/sdk/plugin/proto"
|
||||
)
|
||||
|
||||
type OrderReq struct {
|
||||
Amount float32 `validate:"required" json:"amount"` // 微信:0.3-200;支付宝:<400单位:元
|
||||
BatchId string `validate:"required" json:"batchId"` // 批次ID
|
||||
BizNo string `validate:"required" json:"bizNo"` // 业务单号,需唯一
|
||||
|
||||
Name string `json:"name"` // 真实姓名 支付宝必填
|
||||
Phone string `json:"phone"` // 支付宝必填
|
||||
|
||||
//WxAppId string `json:"wxAppId"` // 公众号ID 微信必填
|
||||
//OpenId string `json:"openId"` // 微信openId 微信必填
|
||||
//Wishing string `json:"wishing"` // 红包祝福语,微信使用
|
||||
|
||||
//RedPacketName string `son:"redPacketName"` // 红包名称 默认"现金红包"
|
||||
//Expand string `json:"expand"` // 拓展信息
|
||||
//Remark string `json:"remark"` // 红包备注 默认“现金红包”
|
||||
//SendName string `json:"sendName"` // 发送名称 微信使用默认与redPacketName相同
|
||||
|
||||
NotifyUrl string `validate:"required" json:"notifyUrl"`
|
||||
|
||||
Sign string `json:"sign"` // MD5签名
|
||||
}
|
||||
|
||||
type OrderWechatReq struct {
|
||||
Amount float32 `validate:"required" json:"amount"` // 微信:0.3-200;支付宝:<400单位:元
|
||||
BatchId string `validate:"required" json:"batchId"` // 批次ID
|
||||
BizNo string `validate:"required" json:"bizNo"` // 业务单号,需唯一
|
||||
|
||||
WxAppId string `validate:"required" json:"wxAppId"` // 公众号ID 微信必填
|
||||
OpenId string `validate:"required" json:"openId"` // 微信openId 微信必填
|
||||
Wishing string `validate:"required" json:"wishing"` // 红包祝福语,微信使用
|
||||
|
||||
RedPacketName string `validate:"required" json:"redPacketName"` // 红包名称 默认"现金红包"
|
||||
Expand string `json:"expand"` // 拓展信息
|
||||
Remark string `json:"remark"` // 红包备注 默认“现金红包”
|
||||
SendName string `validate:"required" json:"sendName"` // 发送名称 微信使用默认与redPacketName相同
|
||||
|
||||
Sign string `json:"sign"` // MD5签名
|
||||
}
|
||||
|
||||
type OrderResp struct {
|
||||
Code any `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
func (o *OrderResp) IsSuccess() bool {
|
||||
strCode := fmt.Sprintf("%v", o.Code)
|
||||
return strCode == "000000"
|
||||
}
|
||||
|
||||
func (o *OrderResp) GetMsg() string {
|
||||
if o.IsSuccess() {
|
||||
return o.Msg
|
||||
}
|
||||
return fmt.Sprintf("code:[%v],msg:[%s]", o.Code, o.Msg)
|
||||
}
|
||||
|
||||
func (o *OrderResp) GetOrderStatus() proto.Status {
|
||||
if o.IsSuccess() {
|
||||
return proto.Status_ING
|
||||
}
|
||||
return proto.Status_FAIL
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package po
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
type Req interface {
|
||||
Validate() error
|
||||
ToJson() ([]byte, error)
|
||||
SetSign(sign string)
|
||||
}
|
||||
|
||||
var _ Req = (*OrderReq)(nil)
|
||||
var _ Req = (*QueryReq)(nil)
|
||||
|
||||
func (req *OrderReq) Validate() error {
|
||||
err := validator.New().Struct(req)
|
||||
if err != nil {
|
||||
for _, err = range err.(validator.ValidationErrors) {
|
||||
return fmt.Errorf("参数有误:" + err.Error())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (req *OrderReq) ToJson() ([]byte, error) {
|
||||
b, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (req *OrderReq) SetSign(sign string) {
|
||||
req.Sign = sign
|
||||
}
|
||||
|
||||
func (req *QueryReq) Validate() error {
|
||||
err := validator.New().Struct(req)
|
||||
if err != nil {
|
||||
for _, err = range err.(validator.ValidationErrors) {
|
||||
return fmt.Errorf("参数有误:" + err.Error())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (req *QueryReq) ToJson() ([]byte, error) {
|
||||
b, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (req *QueryReq) SetSign(sign string) {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package po
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitea.cdlsxd.cn/sdk/plugin/proto"
|
||||
"plugins/qixing_alipay_redpack/internal/vo"
|
||||
)
|
||||
|
||||
type QueryReq struct {
|
||||
BizNo string `json:"bizNo" validate:"required"`
|
||||
BatchId string `json:"batchId" validate:"required"`
|
||||
}
|
||||
|
||||
type Data struct {
|
||||
BatchId string `json:"batchId"`
|
||||
WxAppId string `json:"wxAppId"`
|
||||
OpenId string `json:"openId"`
|
||||
Name string `json:"name"`
|
||||
Phone string `json:"phone"`
|
||||
Amount float32 `json:"amount"`
|
||||
BizNo string `json:"bizNo"`
|
||||
Status string `json:"status"`
|
||||
FailMsg string `json:"failMsg"`
|
||||
Expand string `json:"expand"`
|
||||
MchId string `json:"mchId"`
|
||||
SendName string `json:"sendName"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
type QueryResp struct {
|
||||
Code any `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data *Data `json:"data"`
|
||||
}
|
||||
|
||||
func (o *QueryResp) IsSuccess() bool {
|
||||
strCode := fmt.Sprintf("%v", o.Code)
|
||||
return strCode == "000000"
|
||||
}
|
||||
|
||||
func (o *QueryResp) GetMsg() string {
|
||||
code := vo.Code(fmt.Sprintf("%v", o.Code))
|
||||
return fmt.Sprintf("code[%s],msg[%s],text[%s]", code, o.Msg, code.GetText())
|
||||
}
|
||||
|
||||
func (o *QueryResp) GetOrderStatus() proto.Status {
|
||||
return vo.Status(o.Data.Status).GetOrderStatus()
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gitea.cdlsxd.cn/sdk/plugin/proto"
|
||||
"plugins/qixing_alipay_redpack/internal/po"
|
||||
)
|
||||
|
||||
// 插件通信信息,若不对应则会报错panic
|
||||
const (
|
||||
Tag = "qixing_alipay_redpack"
|
||||
Version = 1
|
||||
CookieKey = "qixing_alipay_redpack"
|
||||
CookieValue = "qixing_alipay_redpack"
|
||||
)
|
||||
|
||||
const (
|
||||
orderMethod = "/send"
|
||||
queryMethod = "/received"
|
||||
)
|
||||
|
||||
type RedPackService struct{}
|
||||
|
||||
func (s *RedPackService) Order(ctx context.Context, request *proto.OrderRequest) (resp2 *proto.OrderResponse, respErr error) {
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
respErr = fmt.Errorf("panic: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
c, err := transConfig(request.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
poReq, err := c.orderReq(request.Order, request.Product)
|
||||
if err != nil {
|
||||
return nil, proto.ErrorParamFail(err.Error())
|
||||
}
|
||||
|
||||
reqBody, err := req(c, poReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
url := c.BasUrl + orderMethod
|
||||
bodyBytes, _, err := Post(ctx, url, reqBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var response *po.OrderResp
|
||||
if err = json.Unmarshal(bodyBytes, &response); err != nil {
|
||||
return nil, proto.ErrorResponseFail(err.Error())
|
||||
}
|
||||
|
||||
return orderResp(request, response, bodyBytes), nil
|
||||
}
|
||||
|
||||
func (s *RedPackService) Query(ctx context.Context, request *proto.QueryRequest) (resp2 *proto.QueryResponse, respErr error) {
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
respErr = fmt.Errorf("panic: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
c, err := transConfig(request.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqBody, err := getReq(c.queryReq(request.Order))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
url := c.BasUrl + queryMethod
|
||||
bodyBytes, _, err := Get(ctx, url, reqBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var response *po.QueryResp
|
||||
if err = json.Unmarshal(bodyBytes, &response); err != nil {
|
||||
return nil, proto.ErrorResponseFail(err.Error())
|
||||
}
|
||||
|
||||
if !response.IsSuccess() {
|
||||
return nil, proto.ErrorRequestFail(response.GetMsg())
|
||||
}
|
||||
|
||||
return queryResp(request, response, bodyBytes), nil
|
||||
}
|
||||
|
||||
func (s *RedPackService) Balance(ctx context.Context, request *proto.QueryRequest) (resp []byte, respErr error) {
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
respErr = fmt.Errorf("panic: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
c, err := transConfig(request.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
url := c.BasUrl + "/config?batchId=" + c.BatchId
|
||||
bodyBytes, _, err := Get(ctx, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bodyBytes, nil
|
||||
}
|
||||
|
||||
func (s *RedPackService) Notify(_ context.Context, request *proto.NotifyRequest) (resp2 *proto.NotifyResponse, respErr error) {
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
respErr = fmt.Errorf("panic: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
n, err := notifyReq(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return notifyResp(n, request.Body)
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gitea.cdlsxd.cn/sdk/plugin/proto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var server = &RedPackService{}
|
||||
|
||||
// config
|
||||
// https://ceshi.myviding.com/cdc-api/api/external/red-packet 测试
|
||||
// https://cdc.myviding.com/cdc-api/api/external/red-packet 生产
|
||||
func config() []byte {
|
||||
c := &Config{
|
||||
NchName: "启星支付宝红包",
|
||||
BasUrl: "https://cdc.myviding.com/cdc-api/api/external/red-packet",
|
||||
AppKey: "ZU2Y2HHQK00N5TP4",
|
||||
BatchId: "20964224",
|
||||
NotifyUrl: "https://gateway.dev.cdlsxd.cn/yxh5api/v1/supplier/notify/52",
|
||||
}
|
||||
marshal, _ := json.Marshal(c)
|
||||
return marshal
|
||||
}
|
||||
|
||||
func TestConfig(t *testing.T) {
|
||||
t.Run("TestConfig", func(t *testing.T) {
|
||||
c := config()
|
||||
fmt.Printf("%s\n", string(c))
|
||||
assert.NotEmpty(t, c)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_OrderAlipay(t *testing.T) {
|
||||
request := &proto.OrderRequest{
|
||||
Config: config(),
|
||||
Order: &proto.OrderRequest_Order{
|
||||
OrderNo: "lsxd20250929001",
|
||||
Account: "18666173766",
|
||||
Quantity: 1,
|
||||
Amount: 0.01,
|
||||
Extra: []byte(`{"name":"李子铭"}`),
|
||||
},
|
||||
Product: &proto.OrderRequest_Product{
|
||||
ProductNo: "",
|
||||
Price: 0.01,
|
||||
Extra: []byte(`{"wishing":"支付宝测试红包"}`),
|
||||
},
|
||||
}
|
||||
t.Run("Test_OrderAlipay", func(t *testing.T) {
|
||||
got, err := server.Order(context.Background(), request)
|
||||
if err != nil {
|
||||
t.Errorf("Order() error = %v", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("%s", got.String())
|
||||
assert.Equal(t, int(proto.Status_ING), int(got.Result.Status))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Query(t *testing.T) {
|
||||
request := &proto.QueryRequest{
|
||||
Config: config(),
|
||||
Order: &proto.QueryRequest_Order{
|
||||
OrderNo: "lsxd202306071545141532",
|
||||
TradeNo: "",
|
||||
Account: "",
|
||||
Extra: []byte(``),
|
||||
},
|
||||
}
|
||||
t.Run("Test_Query", func(t *testing.T) {
|
||||
got, err := server.Query(context.Background(), request)
|
||||
if err != nil {
|
||||
t.Errorf("Query() error = %v", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("%+v \n", got)
|
||||
assert.Equal(t, int(proto.Status_SUCCESS), int(got.Result.Status))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Balance(t *testing.T) {
|
||||
request := &proto.QueryRequest{
|
||||
Config: config(),
|
||||
Order: &proto.QueryRequest_Order{
|
||||
OrderNo: "lsxd202306071545141532",
|
||||
TradeNo: "",
|
||||
Account: "",
|
||||
Extra: []byte(`{"product_no":"20976200"}`),
|
||||
},
|
||||
}
|
||||
t.Run("Test_Balance", func(t *testing.T) {
|
||||
got, err := server.Balance(context.Background(), request)
|
||||
if err != nil {
|
||||
t.Errorf("Query() error = %v", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("%s \n", string(got))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Notify(t *testing.T) {
|
||||
in := &proto.NotifyRequest{
|
||||
Config: config(),
|
||||
Queries: []byte(``),
|
||||
Headers: []byte(``),
|
||||
Body: []byte(`{
|
||||
"batchId": "20300480",
|
||||
"bizNo": "订单123456",
|
||||
"type": "RECEIVED",
|
||||
"failMsg": "失败原因"
|
||||
}`),
|
||||
}
|
||||
t.Run("TestNotify", func(t *testing.T) {
|
||||
got, err := server.Notify(context.Background(), in)
|
||||
if err != nil {
|
||||
t.Errorf("Notify() error = %v", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("TestNotify : %+v \n", got)
|
||||
assert.Equal(t, int(proto.Status_SUCCESS), int(got.Result.Status))
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gitea.cdlsxd.cn/sdk/plugin/proto"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"net/http"
|
||||
"plugins/qixing_alipay_redpack/internal/po"
|
||||
"plugins/utils/helper"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
NchName string `validate:"required" json:"mch_name"` // 商户名称
|
||||
BasUrl string `validate:"required" json:"base_url"` // 请求地址
|
||||
AppKey string `validate:"required" json:"app_key"` // appKey密钥
|
||||
BatchId string `validate:"required" json:"batch_id"` // 批次账号
|
||||
NotifyUrl string `validate:"required" json:"notify_url"` // 回调地址
|
||||
}
|
||||
|
||||
func (c *Config) validate() error {
|
||||
|
||||
err := validator.New().Struct(c)
|
||||
if err != nil {
|
||||
for _, err = range err.(validator.ValidationErrors) {
|
||||
return proto.ErrorConfigFail(fmt.Sprintf("配置参数有误:%s", err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func transConfig(config []byte) (*Config, error) {
|
||||
|
||||
var c Config
|
||||
|
||||
if err := json.Unmarshal(config, &c); err != nil {
|
||||
return nil, proto.ErrorConfigFail(fmt.Sprintf("配置参数解析失败: %v", err))
|
||||
}
|
||||
|
||||
if err := c.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func (c *Config) orderReq(order *proto.OrderRequest_Order, product *proto.OrderRequest_Product) (*po.OrderReq, error) {
|
||||
|
||||
//var productExtra struct {
|
||||
// Wishing string `json:"wishing"`
|
||||
//}
|
||||
//if err := json.Unmarshal(product.Extra, &productExtra); err != nil {
|
||||
// return nil, proto.ErrorParamFail(fmt.Sprintf("启星product拓展参数解析失败: %v", err))
|
||||
//}
|
||||
|
||||
if !helper.IsPhoneNumber(order.Account) && !helper.IsEmail(order.Account) {
|
||||
return nil, proto.ErrorParamFail("启星支付宝红包只支持账号领取")
|
||||
}
|
||||
|
||||
var orderExtra struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
if err := json.Unmarshal(order.Extra, &orderExtra); err != nil {
|
||||
return nil, proto.ErrorConfigFail(fmt.Sprintf("支付宝红包order拓展参数解析失败: %v", err))
|
||||
}
|
||||
|
||||
return &po.OrderReq{
|
||||
Amount: order.Amount,
|
||||
BatchId: c.BatchId,
|
||||
BizNo: order.OrderNo,
|
||||
|
||||
Name: orderExtra.Name,
|
||||
Phone: order.Account,
|
||||
|
||||
NotifyUrl: c.NotifyUrl,
|
||||
|
||||
Sign: "",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func orderResp(request *proto.OrderRequest, resp *po.OrderResp, bodyBytes []byte) *proto.OrderResponse {
|
||||
return &proto.OrderResponse{
|
||||
Result: &proto.Result{
|
||||
Status: resp.GetOrderStatus(),
|
||||
OrderNo: request.Order.OrderNo,
|
||||
TradeNo: "",
|
||||
Message: resp.GetMsg(),
|
||||
Data: bodyBytes,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) queryReq(in *proto.QueryRequest_Order) *po.QueryReq {
|
||||
|
||||
return &po.QueryReq{
|
||||
BizNo: in.OrderNo,
|
||||
BatchId: c.BatchId,
|
||||
}
|
||||
}
|
||||
|
||||
func queryResp(request *proto.QueryRequest, resp *po.QueryResp, bodyBytes []byte) *proto.QueryResponse {
|
||||
|
||||
return &proto.QueryResponse{
|
||||
Result: &proto.Result{
|
||||
OrderNo: request.Order.OrderNo,
|
||||
TradeNo: "",
|
||||
Status: resp.GetOrderStatus(),
|
||||
Message: resp.Msg,
|
||||
Data: bodyBytes,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func notifyReq(in *proto.NotifyRequest) (*po.Notify, error) {
|
||||
|
||||
var n *po.Notify
|
||||
if err := json.Unmarshal(in.Body, &n); err != nil {
|
||||
return nil, proto.ErrorParamFail(fmt.Sprintf("notify拓展参数解析失败: %v", err))
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func notifyResp(n *po.Notify, data []byte) (*proto.NotifyResponse, error) {
|
||||
|
||||
pb := &proto.NotifyResponse{
|
||||
Result: &proto.Result{
|
||||
Status: n.Type.GetOrderType(),
|
||||
OrderNo: n.BizNo,
|
||||
TradeNo: "",
|
||||
Message: n.FailMsg,
|
||||
Data: data,
|
||||
Extra: nil,
|
||||
},
|
||||
Return: `{"code":"success"}`,
|
||||
}
|
||||
|
||||
headers := make(http.Header)
|
||||
headers.Set("Content-Type", "application/json")
|
||||
|
||||
headersBytes, err := json.Marshal(headers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pb.Headers = string(headersBytes)
|
||||
|
||||
return pb, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"gitea.cdlsxd.cn/sdk/plugin/proto"
|
||||
"gitea.cdlsxd.cn/sdk/plugin/utils"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"plugins/qixing_alipay_redpack/internal/po"
|
||||
"plugins/utils/request"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func req(config *Config, req po.Req) ([]byte, error) {
|
||||
|
||||
if err := req.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var strToBeSigned strings.Builder
|
||||
|
||||
kvRows := utils.SortStructJsonTag(req)
|
||||
for _, kv := range kvRows {
|
||||
if kv.Key == "sign" || kv.Value == "" {
|
||||
continue
|
||||
}
|
||||
strToBeSigned.WriteString(fmt.Sprintf("%s=%s&", kv.Key, kv.Value))
|
||||
}
|
||||
s := strToBeSigned.String() + config.AppKey
|
||||
|
||||
req.SetSign(MD5(s))
|
||||
|
||||
return req.ToJson()
|
||||
}
|
||||
|
||||
func getReq(req po.Req) (url.Values, error) {
|
||||
|
||||
if err := req.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//var strToBeSigned strings.Builder
|
||||
uv := url.Values{}
|
||||
kvRows := utils.SortStructJsonTag(req)
|
||||
for _, kv := range kvRows {
|
||||
if kv.Key == "sign" || kv.Value == "" {
|
||||
continue
|
||||
}
|
||||
uv.Set(kv.Key, kv.Value)
|
||||
//strToBeSigned.WriteString(fmt.Sprintf("%s=%s&", kv.Key, kv.Value))
|
||||
}
|
||||
//s := strToBeSigned.String() + config.AppKey
|
||||
//uv.Set("sign", MD5(s))
|
||||
|
||||
return uv, nil
|
||||
}
|
||||
|
||||
func Post(ctx context.Context, url string, reqBody []byte) ([]byte, http.Header, error) {
|
||||
|
||||
//fmt.Printf("url: %s\n", url)
|
||||
//fmt.Printf("reqBody: %s\n", reqBody)
|
||||
|
||||
h := http.Header{
|
||||
"Content-Type": []string{"application/json"},
|
||||
}
|
||||
|
||||
respHeader, respBody, err := request.Post(ctx, url, reqBody, request.WithHeaders(h))
|
||||
if err != nil {
|
||||
return nil, nil, proto.ErrorRequestFail(err.Error())
|
||||
}
|
||||
|
||||
return respBody, respHeader, nil
|
||||
}
|
||||
|
||||
func Get(ctx context.Context, url string, uv url.Values) ([]byte, http.Header, error) {
|
||||
|
||||
requestUrl := url
|
||||
if uv != nil {
|
||||
requestUrl = url + "?" + uv.Encode()
|
||||
}
|
||||
|
||||
h := http.Header{
|
||||
"Content-Type": []string{"application/x-www-form-urlencoded"},
|
||||
}
|
||||
respHeader, respBody, err := request.Get(ctx, requestUrl, request.WithHeaders(h))
|
||||
if err != nil {
|
||||
return nil, nil, proto.ErrorRequestFail(err.Error())
|
||||
}
|
||||
|
||||
return respBody, respHeader, nil
|
||||
}
|
||||
|
||||
func MD5(data string) string {
|
||||
// 创建一个 MD5 哈希对象
|
||||
hash := md5.New()
|
||||
// 写入待加密的数据
|
||||
hash.Write([]byte(data))
|
||||
// 获取 MD5 哈希值
|
||||
hashBytes := hash.Sum(nil)
|
||||
// 将 MD5 哈希值转换为16进制字符串
|
||||
return hex.EncodeToString(hashBytes)
|
||||
}
|
||||
|
||||
func Verify(n *po.Notify, appKey string) (bool, error) {
|
||||
var strToBeSigned strings.Builder
|
||||
uv := url.Values{}
|
||||
kvRows := utils.SortStructJsonTag(n)
|
||||
for _, kv := range kvRows {
|
||||
if kv.Key == "sign" || kv.Value == "" {
|
||||
continue
|
||||
}
|
||||
uv.Set(kv.Key, kv.Value)
|
||||
strToBeSigned.WriteString(fmt.Sprintf("%s=%s&", kv.Key, kv.Value))
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package vo
|
||||
|
||||
type Code string
|
||||
|
||||
var CodeTextMap = map[Code]string{
|
||||
"000000": "成功",
|
||||
"100000": "内部错误(联系技术支持)",
|
||||
"100001": "验签错误(检查签名算法)",
|
||||
"100010": "活动批次不存在(检查batchId)",
|
||||
"100011": "活动预算不足(联系客服增加预算)",
|
||||
"100012": "业务编号已存在(更换bizNo)",
|
||||
"100014": "缺少请求参数(检查必填字段)",
|
||||
"100015": "发放错误(检查金额/账户信息)",
|
||||
"100088": "接口请求频繁(重新调用接口)",
|
||||
}
|
||||
|
||||
func (o Code) GetText() string {
|
||||
msg, ok := CodeTextMap[o]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package vo
|
||||
|
||||
import "gitea.cdlsxd.cn/sdk/plugin/proto"
|
||||
|
||||
type Status string
|
||||
|
||||
const (
|
||||
StatusUnclaimed = "0"
|
||||
StatusReceived = "1"
|
||||
StatusExpired = "2"
|
||||
StatusFailed = "3"
|
||||
)
|
||||
|
||||
var StatusTextMap = map[Status]string{
|
||||
StatusUnclaimed: "未领取",
|
||||
StatusReceived: "已领取",
|
||||
StatusExpired: "已过期",
|
||||
StatusFailed: "发放失败",
|
||||
}
|
||||
|
||||
var StatusMap = map[Status]proto.Status{
|
||||
StatusUnclaimed: proto.Status_ING,
|
||||
StatusReceived: proto.Status_SUCCESS,
|
||||
StatusExpired: proto.Status_FAIL,
|
||||
StatusFailed: proto.Status_FAIL,
|
||||
}
|
||||
|
||||
func (o Status) GetText() string {
|
||||
msg, ok := StatusTextMap[o]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
func (o Status) GetOrderStatus() proto.Status {
|
||||
if o == "" {
|
||||
return proto.Status_INVALID
|
||||
}
|
||||
if resultStatus, ok := StatusMap[o]; ok {
|
||||
return resultStatus
|
||||
}
|
||||
return proto.Status_INVALID
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package vo
|
||||
|
||||
import "gitea.cdlsxd.cn/sdk/plugin/proto"
|
||||
|
||||
type Type string
|
||||
|
||||
const (
|
||||
TypeReceived = "RECEIVED"
|
||||
TypeRefund = "REFUND"
|
||||
TypeFailed = "FAILED"
|
||||
)
|
||||
|
||||
var TypeTextMap = map[Type]string{
|
||||
TypeReceived: "领取成功",
|
||||
TypeRefund: "已过期退回",
|
||||
TypeFailed: "领取失败",
|
||||
}
|
||||
|
||||
var TypeMap = map[Type]proto.Status{
|
||||
TypeReceived: proto.Status_SUCCESS,
|
||||
TypeRefund: proto.Status_FAIL,
|
||||
TypeFailed: proto.Status_FAIL,
|
||||
}
|
||||
|
||||
func (o Type) GetText() string {
|
||||
msg, ok := TypeTextMap[o]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
func (o Type) GetOrderType() proto.Status {
|
||||
if o == "" {
|
||||
return proto.Status_INVALID
|
||||
}
|
||||
if resultType, ok := TypeMap[o]; ok {
|
||||
return resultType
|
||||
}
|
||||
return proto.Status_INVALID
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"gitea.cdlsxd.cn/sdk/plugin/shared"
|
||||
"github.com/hashicorp/go-plugin"
|
||||
"plugins/qixing_alipay_redpack/internal"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.Serve(&plugin.ServeConfig{
|
||||
HandshakeConfig: shared.HandshakeConfig(internal.Version, internal.CookieKey, internal.CookieValue),
|
||||
Plugins: shared.PluginSet(shared.NewPlugin(&internal.RedPackService{}, internal.Tag)),
|
||||
GRPCServer: plugin.DefaultGRPCServer,
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
module plugins/qixing_wechat_redpack
|
||||
|
||||
go 1.22.2
|
||||
|
||||
replace plugins/utils => ../../utils
|
||||
|
||||
require (
|
||||
gitea.cdlsxd.cn/sdk/plugin v1.0.19
|
||||
github.com/go-playground/validator/v10 v10.22.0
|
||||
github.com/hashicorp/go-plugin v1.6.1
|
||||
github.com/stretchr/testify v1.9.0
|
||||
plugins/utils v0.0.0-00010101000000-000000000000
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fatih/color v1.7.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/go-kratos/kratos/v2 v2.8.2 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/hashicorp/go-hclog v0.14.1 // indirect
|
||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 // indirect
|
||||
github.com/oklog/run v1.0.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/crypto v0.26.0 // indirect
|
||||
golang.org/x/net v0.27.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/text v0.17.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
|
||||
google.golang.org/grpc v1.64.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
gitea.cdlsxd.cn/sdk/plugin v1.0.17 h1:agk+9iA1ZI6fLVLtxEnuOWxcDzSq9QH7VBFvhlZZsbw=
|
||||
gitea.cdlsxd.cn/sdk/plugin v1.0.17/go.mod h1:O/bYQWg1o9g/cBq9qNA3kLIpuPt7VDZqj1bPE6s04NM=
|
||||
gitea.cdlsxd.cn/sdk/plugin v1.0.19 h1:j0Ifn3q+C7ibxSTfL1KbmnX1k/VO9e0XMDJSuPutixU=
|
||||
gitea.cdlsxd.cn/sdk/plugin v1.0.19/go.mod h1:O/bYQWg1o9g/cBq9qNA3kLIpuPt7VDZqj1bPE6s04NM=
|
||||
github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
|
||||
github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/go-kratos/kratos/v2 v2.8.2 h1:EsEA7AmPQ2YQQ0FZrDWO2HgBNqeWM8z/mWKzS5UkQaQ=
|
||||
github.com/go-kratos/kratos/v2 v2.8.2/go.mod h1:+Vfe3FzF0d+BfMdajA11jT0rAyJWublRE/seZQNZVxE=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
|
||||
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU=
|
||||
github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-plugin v1.6.1 h1:P7MR2UP6gNKGPp+y7EZw2kOiq4IR9WiqLvp0XOsVdwI=
|
||||
github.com/hashicorp/go-plugin v1.6.1/go.mod h1:XPHFku2tFo3o3QKFgSYo+cghcUhw1NA1hZyMK0PWAw0=
|
||||
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
||||
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
||||
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
|
||||
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg=
|
||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
|
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
|
||||
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package po
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"plugins/qixing_wechat_redpack/internal/vo"
|
||||
)
|
||||
|
||||
type Notify struct {
|
||||
BatchId string `json:"batchId" validate:"required"`
|
||||
BizNo string `json:"bizNo" validate:"required"`
|
||||
Type vo.Type `json:"type" validate:"required"`
|
||||
Sign string `json:"sign" validate:"required"`
|
||||
FailMsg string `json:"failMsg"`
|
||||
}
|
||||
|
||||
func (req *Notify) Validate() error {
|
||||
err := validator.New().Struct(req)
|
||||
if err != nil {
|
||||
for _, err = range err.(validator.ValidationErrors) {
|
||||
return fmt.Errorf("参数有误:" + err.Error())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package po
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitea.cdlsxd.cn/sdk/plugin/proto"
|
||||
)
|
||||
|
||||
type OrderReq struct {
|
||||
Amount float32 `validate:"required" json:"amount"` // 微信:0.3-200;支付宝:<400单位:元
|
||||
BatchId string `validate:"required" json:"batchId"` // 批次ID
|
||||
BizNo string `validate:"required" json:"bizNo"` // 业务单号,需唯一
|
||||
|
||||
//Name string `json:"name"` // 支付宝必填
|
||||
//Phone string `json:"phone"` // 支付宝必填
|
||||
|
||||
WxAppId string `validate:"required" json:"wxAppId"` // 公众号ID 微信必填
|
||||
OpenId string `validate:"required" json:"openId"` // 微信openId 微信必填
|
||||
Wishing string `validate:"required" json:"wishing"` // 红包祝福语,微信使用
|
||||
|
||||
RedPacketName string `json:"redPacketName"` // 红包名称 默认"现金红包"
|
||||
Remark string `json:"remark"` // 红包备注 默认“现金红包”-- 微信红包微信后台能导出每日的对账单里面有remark不给用户展示
|
||||
SendName string `json:"sendName"` // 发送名称 微信使用默认与redPacketName相同
|
||||
|
||||
NotifyUrl string `validate:"required" json:"notifyUrl"`
|
||||
|
||||
//Expand string `json:"expand"` // 拓展信息
|
||||
Sign string `json:"sign"` // MD5签名
|
||||
}
|
||||
|
||||
type OrderResp struct {
|
||||
Code any `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
func (o *OrderResp) IsSuccess() bool {
|
||||
return fmt.Sprintf("%v", o.Code) == "000000"
|
||||
}
|
||||
|
||||
func (o *OrderResp) GetMsg() string {
|
||||
|
||||
if o.IsSuccess() {
|
||||
return o.Msg
|
||||
}
|
||||
return fmt.Sprintf("code:[%v],msg:[%s]", o.Code, o.Msg)
|
||||
}
|
||||
|
||||
func (o *OrderResp) GetOrderStatus() proto.Status {
|
||||
|
||||
if o.IsSuccess() {
|
||||
return proto.Status_ING
|
||||
}
|
||||
return proto.Status_FAIL
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package po
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
type Req interface {
|
||||
Validate() error
|
||||
ToJson() ([]byte, error)
|
||||
SetSign(sign string)
|
||||
}
|
||||
|
||||
var _ Req = (*OrderReq)(nil)
|
||||
var _ Req = (*QueryReq)(nil)
|
||||
|
||||
func (req *OrderReq) Validate() error {
|
||||
err := validator.New().Struct(req)
|
||||
if err != nil {
|
||||
for _, err = range err.(validator.ValidationErrors) {
|
||||
return fmt.Errorf("参数有误:" + err.Error())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (req *OrderReq) ToJson() ([]byte, error) {
|
||||
b, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (req *OrderReq) SetSign(sign string) {
|
||||
req.Sign = sign
|
||||
}
|
||||
|
||||
func (req *QueryReq) Validate() error {
|
||||
err := validator.New().Struct(req)
|
||||
if err != nil {
|
||||
for _, err = range err.(validator.ValidationErrors) {
|
||||
return fmt.Errorf("参数有误:" + err.Error())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (req *QueryReq) ToJson() ([]byte, error) {
|
||||
b, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (req *QueryReq) SetSign(sign string) {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package po
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitea.cdlsxd.cn/sdk/plugin/proto"
|
||||
"plugins/qixing_wechat_redpack/internal/vo"
|
||||
)
|
||||
|
||||
type QueryReq struct {
|
||||
BizNo string `json:"bizNo" validate:"required"`
|
||||
BatchId string `json:"batchId" validate:"required"`
|
||||
}
|
||||
|
||||
type Data struct {
|
||||
BatchId string `json:"batchId"`
|
||||
WxAppId string `json:"wxAppId"`
|
||||
OpenId string `json:"openId"`
|
||||
Name string `json:"name"`
|
||||
Phone string `json:"phone"`
|
||||
Amount float32 `json:"amount"`
|
||||
BizNo string `json:"bizNo"`
|
||||
Status string `json:"status"`
|
||||
FailMsg string `json:"failMsg"`
|
||||
Expand string `json:"expand"`
|
||||
MchId string `json:"mchId"`
|
||||
SendName string `json:"sendName"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
type QueryResp struct {
|
||||
Code any `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data *Data `json:"data"`
|
||||
}
|
||||
|
||||
func (o *QueryResp) IsSuccess() bool {
|
||||
strCode := fmt.Sprintf("%v", o.Code)
|
||||
return strCode == "000000"
|
||||
}
|
||||
|
||||
func (o *QueryResp) GetMsg() string {
|
||||
code := vo.Code(fmt.Sprintf("%v", o.Code))
|
||||
return fmt.Sprintf("code[%s],msg[%s],text[%s]", code, o.Msg, code.GetText())
|
||||
}
|
||||
|
||||
func (o *QueryResp) GetOrderStatus() proto.Status {
|
||||
return vo.Status(o.Data.Status).GetOrderStatus()
|
||||
}
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gitea.cdlsxd.cn/sdk/plugin/proto"
|
||||
"plugins/qixing_wechat_redpack/internal/po"
|
||||
)
|
||||
|
||||
// 插件通信信息,若不对应则会报错panic
|
||||
const (
|
||||
Tag = "qixing_wechat_redpack"
|
||||
Version = 1
|
||||
CookieKey = "qixing_wechat_redpack"
|
||||
CookieValue = "qixing_wechat_redpack"
|
||||
)
|
||||
|
||||
const (
|
||||
orderMethod = "/send"
|
||||
queryMethod = "/received"
|
||||
)
|
||||
|
||||
type QiXingWechatRedPackService struct{}
|
||||
|
||||
func (s *QiXingWechatRedPackService) Order(ctx context.Context, request *proto.OrderRequest) (resp2 *proto.OrderResponse, respErr error) {
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
respErr = fmt.Errorf("panic: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
c, err := transConfig(request.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
poReq, err := c.orderReq(request.Order, request.Product)
|
||||
if err != nil {
|
||||
return nil, proto.ErrorParamFail(err.Error())
|
||||
}
|
||||
|
||||
reqBody, err := req(c, poReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
url := c.BasUrl + orderMethod
|
||||
bodyBytes, _, err := Post(ctx, url, reqBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var response *po.OrderResp
|
||||
if err = json.Unmarshal(bodyBytes, &response); err != nil {
|
||||
return nil, proto.ErrorResponseFail(err.Error())
|
||||
}
|
||||
|
||||
return orderResp(request, response, bodyBytes), nil
|
||||
}
|
||||
|
||||
func (s *QiXingWechatRedPackService) Query(ctx context.Context, request *proto.QueryRequest) (resp2 *proto.QueryResponse, respErr error) {
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
respErr = fmt.Errorf("panic: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
c, err := transConfig(request.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqBody, err := getReq(c.queryReq(request.Order))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
url := c.BasUrl + queryMethod
|
||||
bodyBytes, _, err := Get(ctx, url, reqBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var response *po.QueryResp
|
||||
if err = json.Unmarshal(bodyBytes, &response); err != nil {
|
||||
return nil, proto.ErrorResponseFail(err.Error())
|
||||
}
|
||||
|
||||
if !response.IsSuccess() {
|
||||
return nil, proto.ErrorRequestFail(response.GetMsg())
|
||||
}
|
||||
|
||||
return queryResp(request, response, bodyBytes), nil
|
||||
}
|
||||
|
||||
func (s *QiXingWechatRedPackService) Balance(ctx context.Context, request *proto.QueryRequest) (resp []byte, respErr error) {
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
respErr = fmt.Errorf("panic: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
c, err := transConfig(request.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
url := c.BasUrl + "/config?batchId=" + c.BatchId
|
||||
bodyBytes, _, err := Get(ctx, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bodyBytes, nil
|
||||
}
|
||||
|
||||
func (s *QiXingWechatRedPackService) Notify(_ context.Context, request *proto.NotifyRequest) (resp2 *proto.NotifyResponse, respErr error) {
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
respErr = fmt.Errorf("panic: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if request.Body == nil {
|
||||
return nil, proto.ErrorParamFail("body is nil")
|
||||
}
|
||||
|
||||
n, err := notifyReq(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := transConfig(request.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !Verify(n, c.AppKey) {
|
||||
return nil, proto.ErrorSignFail("签名验证失败")
|
||||
}
|
||||
|
||||
return notifyResp(n, request.Body)
|
||||
}
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gitea.cdlsxd.cn/sdk/plugin/proto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var server = &QiXingWechatRedPackService{}
|
||||
|
||||
// config
|
||||
// https://ceshi.myviding.com/cdc-api/api/external/red-packet 测试
|
||||
// https://cdc.myviding.com/cdc-api/api/external/red-packet 生产
|
||||
func config() []byte {
|
||||
c := &Config{
|
||||
Mode: "wechat_redpack_mode",
|
||||
NchName: "启星微信红包",
|
||||
AppKey: "ZU2Y2HHQK00N5TP4",
|
||||
BatchId: "20062389",
|
||||
BasUrl: "https://cdc.myviding.com/cdc-api/api/external/red-packet",
|
||||
NotifyUrl: "https://gateway.dev.cdlsxd.cn/yxh5api/v1/supplier/notify/51",
|
||||
Official: Official{
|
||||
Name: "福建兴旺网络科技有限公司",
|
||||
AppId: "wxe3bd59243545fa8a",
|
||||
AppSecret: "4c9649cb998f71038e187b4c58f5fda0",
|
||||
OauthKey: "bcee0c6753b2a31c792a91fe9f9f1666",
|
||||
OauthUrl: "https://utils.85938.cn/utils/v1/wechat/oauth/fjxw",
|
||||
},
|
||||
}
|
||||
marshal, _ := json.Marshal(c)
|
||||
return marshal
|
||||
}
|
||||
|
||||
func Test_Config(t *testing.T) {
|
||||
t.Run("TestConfig", func(t *testing.T) {
|
||||
c := config()
|
||||
fmt.Printf("%s\n", string(c))
|
||||
assert.NotEmpty(t, c)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Order(t *testing.T) {
|
||||
request := &proto.OrderRequest{
|
||||
Config: config(),
|
||||
Order: &proto.OrderRequest_Order{
|
||||
OrderNo: "lsxd20250929004",
|
||||
Account: "ojbqr6HpeWKFy9Sgdx8yCmmeVJiw",
|
||||
Quantity: 1,
|
||||
Amount: 0.3,
|
||||
Extra: []byte(``),
|
||||
},
|
||||
Product: &proto.OrderRequest_Product{
|
||||
ProductNo: "",
|
||||
Price: 0.3,
|
||||
Type: 0,
|
||||
Extra: []byte(`{"batch_name":"batch name恭喜发财,红包拿来,红包祝福语", "batch_remark":"batch remark 恭喜发财,红包拿来,红包备注", "goods_name":"红包名称xx红包"}`),
|
||||
},
|
||||
}
|
||||
t.Run("Test_Order", func(t *testing.T) {
|
||||
got, err := server.Order(context.Background(), request)
|
||||
if err != nil {
|
||||
t.Errorf("Order() error = %v", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("响应报文=%s\n", got.Result.Data)
|
||||
fmt.Printf("%s", got.String())
|
||||
assert.Equal(t, int(proto.Status_ING), int(got.Result.Status))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Query(t *testing.T) {
|
||||
request := &proto.QueryRequest{
|
||||
Config: config(),
|
||||
Order: &proto.QueryRequest_Order{
|
||||
OrderNo: "lsxd20250929003",
|
||||
TradeNo: "",
|
||||
Account: "",
|
||||
Extra: []byte(``),
|
||||
},
|
||||
}
|
||||
t.Run("Test_Query", func(t *testing.T) {
|
||||
got, err := server.Query(context.Background(), request)
|
||||
if err != nil {
|
||||
t.Errorf("Query() error = %v", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("%+v \n", got)
|
||||
assert.Equal(t, int(proto.Status_SUCCESS), int(got.Result.Status))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Balance(t *testing.T) {
|
||||
request := &proto.QueryRequest{
|
||||
Config: config(),
|
||||
Order: &proto.QueryRequest_Order{
|
||||
OrderNo: "lsxd202306071545141532",
|
||||
TradeNo: "",
|
||||
Account: "",
|
||||
Extra: []byte(``),
|
||||
},
|
||||
}
|
||||
t.Run("Test_Balance", func(t *testing.T) {
|
||||
got, err := server.Balance(context.Background(), request)
|
||||
if err != nil {
|
||||
t.Errorf("Query() error = %v", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("%s \n", string(got))
|
||||
})
|
||||
}
|
||||
|
||||
func TestNotify(t *testing.T) {
|
||||
in := &proto.NotifyRequest{
|
||||
Config: config(),
|
||||
Queries: []byte(``),
|
||||
Headers: []byte(``),
|
||||
Body: []byte(`{"bizNo":"lsxd20250929003","sign":"e71e685d8d9544aad0f32e699fd98e06","batchId":"20062389","type":"SENT"}`),
|
||||
}
|
||||
t.Run("TestNotify", func(t *testing.T) {
|
||||
got, err := server.Notify(context.Background(), in)
|
||||
if err != nil {
|
||||
t.Errorf("Notify() error = %v", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("TestNotify : %+v \n", got)
|
||||
assert.Equal(t, int(proto.Status_SUCCESS), int(got.Result.Status))
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gitea.cdlsxd.cn/sdk/plugin/proto"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"net/http"
|
||||
"plugins/qixing_wechat_redpack/internal/po"
|
||||
"plugins/qixing_wechat_redpack/internal/vo"
|
||||
)
|
||||
|
||||
type Official struct {
|
||||
Name string `json:"name"` // 公众号名称
|
||||
AppId string `validate:"required" json:"app_id"`
|
||||
AppSecret string `json:"app_secret"`
|
||||
OauthKey string `json:"oauth_key"`
|
||||
OauthUrl string `json:"oauth_url"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Mode vo.Mode `validate:"required" json:"mode"` // 商户名称
|
||||
NchName string `validate:"required" json:"mch_name"` // 商户名称
|
||||
AppKey string `validate:"required" json:"app_key"` // appKey密钥
|
||||
BatchId string `validate:"required" json:"batch_id"` // 批次账号
|
||||
BasUrl string `validate:"required" json:"base_url"` // 请求地址
|
||||
NotifyUrl string `validate:"required" json:"notify_url"` // 回调地址
|
||||
Official Official `validate:"required" json:"official"` // 公众号信息
|
||||
}
|
||||
|
||||
func (c *Config) validate() error {
|
||||
|
||||
if err := validator.New().Struct(c); err != nil {
|
||||
for _, err = range err.(validator.ValidationErrors) {
|
||||
return proto.ErrorConfigFail(fmt.Sprintf("配置参数有误:%s", err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
if c.Mode.IsRedPack() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return proto.ErrorConfigFail("微信发放红包模式不正确")
|
||||
}
|
||||
|
||||
func transConfig(config []byte) (*Config, error) {
|
||||
|
||||
var c Config
|
||||
|
||||
if err := json.Unmarshal(config, &c); err != nil {
|
||||
return nil, proto.ErrorConfigFail(fmt.Sprintf("配置参数解析失败: %v", err))
|
||||
}
|
||||
|
||||
if err := c.validate(); err != nil {
|
||||
return nil, proto.ErrorConfigFail(err.Error())
|
||||
}
|
||||
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func (c *Config) orderReq(order *proto.OrderRequest_Order, product *proto.OrderRequest_Product) (*po.OrderReq, error) {
|
||||
|
||||
var productExtra struct {
|
||||
BatchName string `json:"batch_name"`
|
||||
BatchRemark string `json:"batch_remark"`
|
||||
GoodsName string `json:"goods_name"`
|
||||
}
|
||||
if err := json.Unmarshal(product.Extra, &productExtra); err != nil {
|
||||
return nil, proto.ErrorParamFail(fmt.Sprintf("product拓展参数解析失败: %v", err))
|
||||
}
|
||||
|
||||
return &po.OrderReq{
|
||||
Amount: order.Amount,
|
||||
BatchId: c.BatchId,
|
||||
BizNo: order.OrderNo,
|
||||
|
||||
WxAppId: c.Official.AppId,
|
||||
OpenId: order.Account,
|
||||
|
||||
SendName: productExtra.GoodsName,
|
||||
RedPacketName: productExtra.GoodsName,
|
||||
Wishing: productExtra.BatchName,
|
||||
Remark: productExtra.BatchRemark,
|
||||
|
||||
NotifyUrl: c.NotifyUrl,
|
||||
|
||||
Sign: "",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func orderResp(request *proto.OrderRequest, resp *po.OrderResp, bodyBytes []byte) *proto.OrderResponse {
|
||||
|
||||
return &proto.OrderResponse{
|
||||
Result: &proto.Result{
|
||||
Status: resp.GetOrderStatus(),
|
||||
OrderNo: request.Order.OrderNo,
|
||||
TradeNo: "",
|
||||
Message: resp.GetMsg(),
|
||||
Data: bodyBytes,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) queryReq(in *proto.QueryRequest_Order) *po.QueryReq {
|
||||
|
||||
return &po.QueryReq{
|
||||
BizNo: in.OrderNo,
|
||||
BatchId: c.BatchId,
|
||||
}
|
||||
}
|
||||
|
||||
func queryResp(request *proto.QueryRequest, resp *po.QueryResp, bodyBytes []byte) *proto.QueryResponse {
|
||||
|
||||
return &proto.QueryResponse{
|
||||
Result: &proto.Result{
|
||||
OrderNo: request.Order.OrderNo,
|
||||
TradeNo: "",
|
||||
Status: resp.GetOrderStatus(),
|
||||
Message: resp.Msg,
|
||||
Data: bodyBytes,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func notifyReq(in *proto.NotifyRequest) (*po.Notify, error) {
|
||||
|
||||
var n *po.Notify
|
||||
if err := json.Unmarshal(in.Body, &n); err != nil {
|
||||
return nil, proto.ErrorParamFail(fmt.Sprintf("notify拓展参数解析失败: %v", err))
|
||||
}
|
||||
|
||||
if err := n.Validate(); err != nil {
|
||||
return nil, proto.ErrorParamFail(fmt.Sprintf("notify参数有误:%s", err.Error()))
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func notifyResp(n *po.Notify, data []byte) (*proto.NotifyResponse, error) {
|
||||
|
||||
pb := &proto.NotifyResponse{
|
||||
Result: &proto.Result{
|
||||
Status: n.Type.GetOrderType(),
|
||||
OrderNo: n.BizNo,
|
||||
TradeNo: "",
|
||||
Message: n.FailMsg,
|
||||
Data: data,
|
||||
Extra: nil,
|
||||
},
|
||||
Return: `{"code":"success"}`,
|
||||
}
|
||||
|
||||
if pb.Result.Status == proto.Status_SUCCESS {
|
||||
pb.Result.Message = `成功`
|
||||
}
|
||||
|
||||
headers := make(http.Header)
|
||||
headers.Set("Content-Type", "application/json")
|
||||
|
||||
headersBytes, err := json.Marshal(headers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pb.Headers = string(headersBytes)
|
||||
|
||||
return pb, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"gitea.cdlsxd.cn/sdk/plugin/proto"
|
||||
"gitea.cdlsxd.cn/sdk/plugin/utils"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"plugins/qixing_wechat_redpack/internal/po"
|
||||
"plugins/utils/request"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func req(config *Config, req po.Req) ([]byte, error) {
|
||||
|
||||
if err := req.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var strToBeSigned strings.Builder
|
||||
|
||||
kvRows := utils.SortStructJsonTag(req)
|
||||
for _, kv := range kvRows {
|
||||
if kv.Key == "sign" || kv.Value == "" {
|
||||
continue
|
||||
}
|
||||
strToBeSigned.WriteString(fmt.Sprintf("%s=%s&", kv.Key, kv.Value))
|
||||
}
|
||||
s := strToBeSigned.String() + config.AppKey
|
||||
|
||||
req.SetSign(MD5(s))
|
||||
|
||||
return req.ToJson()
|
||||
}
|
||||
|
||||
func getReq(req po.Req) (url.Values, error) {
|
||||
|
||||
if err := req.Validate(); err != nil {
|
||||
return nil, proto.ErrorParamFail(err.Error())
|
||||
}
|
||||
|
||||
uv := url.Values{}
|
||||
kvRows := utils.SortStructJsonTag(req)
|
||||
for _, kv := range kvRows {
|
||||
if kv.Key == "sign" || kv.Value == "" {
|
||||
continue
|
||||
}
|
||||
uv.Set(kv.Key, kv.Value)
|
||||
}
|
||||
|
||||
return uv, nil
|
||||
}
|
||||
|
||||
func Post(ctx context.Context, url string, reqBody []byte) ([]byte, http.Header, error) {
|
||||
|
||||
h := http.Header{
|
||||
"Content-Type": []string{"application/json"},
|
||||
}
|
||||
|
||||
respHeader, respBody, err := request.Post(ctx, url, reqBody, request.WithHeaders(h))
|
||||
if err != nil {
|
||||
return nil, nil, proto.ErrorRequestFail(err.Error())
|
||||
}
|
||||
|
||||
return respBody, respHeader, nil
|
||||
}
|
||||
|
||||
func Get(ctx context.Context, url string, uv url.Values) ([]byte, http.Header, error) {
|
||||
|
||||
requestUrl := url
|
||||
if uv != nil {
|
||||
requestUrl = url + "?" + uv.Encode()
|
||||
}
|
||||
|
||||
h := http.Header{
|
||||
"Content-Type": []string{"application/x-www-form-urlencoded"},
|
||||
}
|
||||
respHeader, respBody, err := request.Get(ctx, requestUrl, request.WithHeaders(h))
|
||||
if err != nil {
|
||||
return nil, nil, proto.ErrorRequestFail(err.Error())
|
||||
}
|
||||
|
||||
return respBody, respHeader, nil
|
||||
}
|
||||
|
||||
func MD5(data string) string {
|
||||
// 创建一个 MD5 哈希对象
|
||||
hash := md5.New()
|
||||
// 写入待加密的数据
|
||||
hash.Write([]byte(data))
|
||||
// 获取 MD5 哈希值
|
||||
hashBytes := hash.Sum(nil)
|
||||
// 将 MD5 哈希值转换为16进制字符串
|
||||
return hex.EncodeToString(hashBytes)
|
||||
}
|
||||
|
||||
func Verify(n *po.Notify, appKey string) bool {
|
||||
|
||||
var strToBeSigned strings.Builder
|
||||
uv := url.Values{}
|
||||
|
||||
kvRows := utils.SortStructJsonTag(n)
|
||||
|
||||
for _, kv := range kvRows {
|
||||
if kv.Key == "sign" || kv.Value == "" {
|
||||
continue
|
||||
}
|
||||
uv.Set(kv.Key, kv.Value)
|
||||
strToBeSigned.WriteString(fmt.Sprintf("%s=%s&", kv.Key, kv.Value))
|
||||
}
|
||||
|
||||
s := strToBeSigned.String() + appKey
|
||||
|
||||
return MD5(s) == n.Sign
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package vo
|
||||
|
||||
type Code string
|
||||
|
||||
var CodeTextMap = map[Code]string{
|
||||
"000000": "成功",
|
||||
"100000": "内部错误(联系技术支持)",
|
||||
"100001": "验签错误(检查签名算法)",
|
||||
"100010": "活动批次不存在(检查batchId)",
|
||||
"100011": "活动预算不足(联系客服增加预算)",
|
||||
"100012": "业务编号已存在(更换bizNo)",
|
||||
"100014": "缺少请求参数(检查必填字段)",
|
||||
"100015": "发放错误(检查金额/账户信息)",
|
||||
"100088": "接口请求频繁(重新调用接口)",
|
||||
}
|
||||
|
||||
func (o Code) GetText() string {
|
||||
msg, ok := CodeTextMap[o]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package vo
|
||||
|
||||
type Mode string
|
||||
|
||||
const (
|
||||
ModeRedPack = "wechat_redpack_mode"
|
||||
ModeTransfer = "wechat_transfer_mode"
|
||||
)
|
||||
|
||||
var ModeTextMap = map[Mode]string{
|
||||
ModeRedPack: "微信红包模式",
|
||||
ModeTransfer: "微信转账模式",
|
||||
}
|
||||
|
||||
func (o Mode) GetText() string {
|
||||
msg, ok := ModeTextMap[o]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
func (o Mode) GetMode() string {
|
||||
return string(o)
|
||||
}
|
||||
|
||||
func (o Mode) IsRedPack() bool {
|
||||
return o == ModeRedPack
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package vo
|
||||
|
||||
import "gitea.cdlsxd.cn/sdk/plugin/proto"
|
||||
|
||||
type Status string
|
||||
|
||||
const (
|
||||
StatusUnclaimed = "0"
|
||||
StatusReceived = "1"
|
||||
StatusExpired = "2"
|
||||
StatusFailed = "3"
|
||||
)
|
||||
|
||||
var StatusTextMap = map[Status]string{
|
||||
StatusUnclaimed: "未领取",
|
||||
StatusReceived: "已领取",
|
||||
StatusExpired: "已过期",
|
||||
StatusFailed: "发放失败",
|
||||
}
|
||||
|
||||
var StatusMap = map[Status]proto.Status{
|
||||
StatusUnclaimed: proto.Status_ING,
|
||||
StatusReceived: proto.Status_SUCCESS,
|
||||
StatusExpired: proto.Status_FAIL,
|
||||
StatusFailed: proto.Status_FAIL,
|
||||
}
|
||||
|
||||
func (o Status) GetText() string {
|
||||
msg, ok := StatusTextMap[o]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
func (o Status) GetOrderStatus() proto.Status {
|
||||
|
||||
if o == "" {
|
||||
return proto.Status_INVALID
|
||||
}
|
||||
|
||||
if resultStatus, ok := StatusMap[o]; ok {
|
||||
return resultStatus
|
||||
}
|
||||
|
||||
return proto.Status_INVALID
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package vo
|
||||
|
||||
import "gitea.cdlsxd.cn/sdk/plugin/proto"
|
||||
|
||||
type Type string
|
||||
|
||||
const (
|
||||
TypeSent = "SENT"
|
||||
TypeReceived = "RECEIVED"
|
||||
TypeRefund = "REFUND"
|
||||
TypeFailed = "FAILED"
|
||||
)
|
||||
|
||||
var TypeTextMap = map[Type]string{
|
||||
TypeSent: "下单成功,领取中",
|
||||
TypeReceived: "领取成功",
|
||||
TypeRefund: "已过期退回",
|
||||
TypeFailed: "领取失败",
|
||||
}
|
||||
|
||||
var TypeMap = map[Type]proto.Status{
|
||||
TypeSent: proto.Status_ING,
|
||||
TypeReceived: proto.Status_SUCCESS,
|
||||
TypeRefund: proto.Status_FAIL,
|
||||
TypeFailed: proto.Status_FAIL,
|
||||
}
|
||||
|
||||
func (o Type) GetText() string {
|
||||
msg, ok := TypeTextMap[o]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
func (o Type) GetOrderType() proto.Status {
|
||||
|
||||
if o == "" {
|
||||
return proto.Status_INVALID
|
||||
}
|
||||
|
||||
if resultType, ok := TypeMap[o]; ok {
|
||||
return resultType
|
||||
}
|
||||
|
||||
return proto.Status_INVALID
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"gitea.cdlsxd.cn/sdk/plugin/shared"
|
||||
"github.com/hashicorp/go-plugin"
|
||||
"plugins/qixing_wechat_redpack/internal"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.Serve(&plugin.ServeConfig{
|
||||
HandshakeConfig: shared.HandshakeConfig(internal.Version, internal.CookieKey, internal.CookieValue),
|
||||
Plugins: shared.PluginSet(shared.NewPlugin(&internal.QiXingWechatRedPackService{}, internal.Tag)),
|
||||
GRPCServer: plugin.DefaultGRPCServer,
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package miniprogram
|
||||
|
||||
import (
|
||||
"plugins/wechat_redpack_v2/internal/wechat/srv/redpack"
|
||||
)
|
||||
|
||||
const (
|
||||
_sendUrl = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendminiprogramhb"
|
||||
_queryUrl = "https://api.mch.weixin.qq.com/mmpaymkttransfers/gethbinfo"
|
||||
)
|
||||
|
||||
type MiniProgram struct {
|
||||
*redpack.RedPack
|
||||
}
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
package miniprogram
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
type Str interface {
|
||||
Str() (string, error)
|
||||
}
|
||||
|
||||
type Req interface {
|
||||
Str
|
||||
Validate() error
|
||||
ToXML() ([]byte, error)
|
||||
}
|
||||
|
||||
var _ Req = (*SendRequest)(nil)
|
||||
var _ Str = (*SendResponse)(nil)
|
||||
|
||||
var _ Req = (*QueryRequest)(nil)
|
||||
var _ Str = (*QueryResponse)(nil)
|
||||
|
||||
// SendRequest 红包发送请求结构体(兼容 JSON/XML 双格式,XML 无 CDATA 包裹)
|
||||
type SendRequest struct {
|
||||
XMLName xml.Name `xml:"xml" json:"-"` // XML 根节点为 <xml>,JSON 忽略该字段
|
||||
NonceStr string `xml:"nonce_str" json:"nonce_str" validate:"required"` // 随机字符串(无 CDATA)
|
||||
Sign string `xml:"sign" json:"sign"` // 签名(无 CDATA)
|
||||
MchBillno string `xml:"mch_billno" json:"mch_billno" validate:"required"` // 商户订单号(无 CDATA)
|
||||
MchId string `xml:"mch_id" json:"mch_id" validate:"required"` // 商户号(无 CDATA)
|
||||
Wxappid string `xml:"wxappid" json:"wxappid" validate:"required"` // 小程序 appid(无 CDATA)
|
||||
SendName string `xml:"send_name,omitempty" json:"send_name,omitempty" validate:"required"` // 发送者名称(无 CDATA,空值忽略)
|
||||
ReOpenid string `xml:"re_openid" json:"re_openid" validate:"required"` // 用户 openid(无 CDATA)
|
||||
TotalAmount int32 `xml:"total_amount" json:"total_amount" validate:"required"` // 付款金额(分,数值类型)
|
||||
TotalNum int32 `xml:"total_num" json:"total_num" validate:"required"` // 发放人数(数值类型)
|
||||
Wishing string `xml:"wishing" json:"wishing" validate:"required"` // 祝福语(无 CDATA)
|
||||
ActName string `xml:"act_name" json:"act_name" validate:"required"` // 活动名称(无 CDATA)
|
||||
Remark string `xml:"remark" json:"remark" validate:"required"` // 备注(无 CDATA)
|
||||
NotifyWay string `xml:"notify_way,omitempty" json:"notify_way"` // 通知方式(无 CDATA,空值忽略)
|
||||
SceneId string `xml:"scene_id,omitempty" json:"scene_id,omitempty"` // 场景 ID(无 CDATA,空值忽略)
|
||||
}
|
||||
|
||||
// SendResponse 红包发送响应结构体(兼容 JSON/XML 双格式,XML 无 CDATA 包裹)
|
||||
type SendResponse struct {
|
||||
XMLName xml.Name `xml:"xml" json:"-"` // XML 根节点
|
||||
ReturnCode string `xml:"return_code,omitempty" json:"return_code,omitempty"` // 返回状态码(无 CDATA)
|
||||
ReturnMsg string `xml:"return_msg,omitempty" json:"return_msg,omitempty"` // 返回信息(无 CDATA)
|
||||
// 以下字段在return_code为SUCCESS的时候有返回
|
||||
ResultCode string `xml:"result_code,omitempty" json:"result_code,omitempty"` // 业务结果(无 CDATA)
|
||||
ErrCode string `xml:"err_code,omitempty" json:"err_code,omitempty"` // 错误码(无 CDATA)
|
||||
ErrCodeDes string `xml:"err_code_des,omitempty" json:"err_code_des,omitempty"` // 错误描述(无 CDATA,修正为 string)
|
||||
// 以下字段在return_code和result_code都为SUCCESS的时候有返回
|
||||
MchBillno string `xml:"mch_billno,omitempty" json:"mch_billno,omitempty"` // 商户订单号(无 CDATA)
|
||||
MchId string `xml:"mch_id,omitempty" json:"mch_id,omitempty"` // 商户号(无 CDATA)
|
||||
Wxappid string `xml:"wxappid,omitempty" json:"wxappid,omitempty"` // 小程序 appid(无 CDATA)
|
||||
ReOpenid string `xml:"re_openid,omitempty" json:"re_openid,omitempty"` // 用户 openid(无 CDATA)
|
||||
TotalAmount string `xml:"total_amount,omitempty" json:"total_amount,omitempty"` // 付款金额(无 CDATA)
|
||||
SendListid string `xml:"send_listid,omitempty" json:"send_listid,omitempty"` // 红包列表 ID(无 CDATA)
|
||||
Package string `xml:"package,omitempty" json:"package,omitempty"` // 小程序跳转参数(无 CDATA)
|
||||
}
|
||||
|
||||
func (req *SendRequest) Str() (string, error) {
|
||||
|
||||
b, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
func (req *SendRequest) Validate() error {
|
||||
|
||||
err := validator.New().Struct(req)
|
||||
if err != nil {
|
||||
for _, err = range err.(validator.ValidationErrors) {
|
||||
return fmt.Errorf("参数有误:" + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToXML 将 SendRequest 序列化为无 CDATA 的 XML 字节流(带 XML 声明)
|
||||
func (req *SendRequest) ToXML() ([]byte, error) {
|
||||
// 生成格式化 XML
|
||||
data, err := xml.MarshalIndent(req, "", " ")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("xml marshal failed: %w", err)
|
||||
}
|
||||
// 拼接 XML 声明(指定 UTF-8 编码)
|
||||
xmlData := []byte(xml.Header + string(data))
|
||||
return xmlData, nil
|
||||
}
|
||||
|
||||
func (req *SendResponse) Str() (string, error) {
|
||||
|
||||
b, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
// QueryRequest .
|
||||
type QueryRequest struct {
|
||||
XMLName xml.Name `xml:"xml" json:"-"` // XML 根节点为 <xml>,JSON 忽略该字段
|
||||
NonceStr string `xml:"nonce_str" json:"nonce_str" validate:"required"` // 随机字符串(无 CDATA)
|
||||
MchBillno string `xml:"mch_billno" json:"mch_billno" validate:"required"` // 商户订单号(无 CDATA)
|
||||
MchId string `xml:"mch_id" json:"mch_id" validate:"required"` // 商户号(无 CDATA)
|
||||
Appid string `xml:"appid" json:"appid" validate:"required"` // 小程序 appid(无 CDATA)
|
||||
BillType string `xml:"bill_type" json:"bill_type" validate:"required"` // 场景 ID(无 CDATA,空值忽略) MCHT:通过商户订单号获取红包信息。
|
||||
Sign string `xml:"sign" json:"sign"` // 签名(无 CDATA)
|
||||
}
|
||||
|
||||
// QueryResponse .
|
||||
type QueryResponse struct {
|
||||
XMLName xml.Name `xml:"xml" json:"-"` // XML 根节点
|
||||
ReturnCode string `xml:"return_code" json:"return_code"` // 返回状态码(无 CDATA)
|
||||
ReturnMsg string `xml:"return_msg" json:"return_msg"` // 返回信息(无 CDATA)
|
||||
// 以下字段在return_code为SUCCESS的时候有返回
|
||||
ResultCode string `xml:"result_code,omitempty" json:"result_code,omitempty"` // 业务结果(无 CDATA)
|
||||
ErrCode string `xml:"err_code,omitempty" json:"err_code,omitempty"` // 错误码(无 CDATA)
|
||||
ErrCodeDes string `xml:"err_code_des,omitempty" json:"err_code_des,omitempty"` // 错误描述(无 CDATA,修正为 string)
|
||||
// 以下字段在return_code 和result_code都为SUCCESS的时候有返回
|
||||
MchBillno string `xml:"mch_billno,omitempty" json:"mch_billno,omitempty"` // 商户订单号(无 CDATA)
|
||||
MchId string `xml:"mch_id,omitempty" json:"mch_id,omitempty"` // 商户号(无 CDATA)
|
||||
DetailId string `xml:"detail_id,omitempty" json:"detail_id,omitempty"` // 使用API发放现金红包时返回的红包单号
|
||||
Status string `xml:"status,omitempty" json:"status,omitempty"` // 红包状态
|
||||
SendType string `xml:"send_type,omitempty" json:"send_type,omitempty"` // 发放类型
|
||||
TotalNum int `xml:"total_num,omitempty" json:"total_num,omitempty"` // 红包个数
|
||||
TotalAmount int `xml:"total_amount,omitempty" json:"total_amount,omitempty"` // 小程序跳转参数(无 CDATA)
|
||||
Reason string `xml:"reason,omitempty" json:"reason,omitempty"` // 失败原因
|
||||
SendTime string `xml:"send_time,omitempty" json:"send_time,omitempty"` // 红包发送时间
|
||||
RefundTime string `xml:"refund_time,omitempty" json:"refund_time,omitempty"` // 红包退款时间
|
||||
RefundAmount string `xml:"refund_amount,omitempty" json:"refund_amount,omitempty"` // 红包退款金额
|
||||
Wishing string `xml:"wishing,omitempty" json:"wishing,omitempty"` // 祝福语
|
||||
Remark string `xml:"remark,omitempty" json:"remark,omitempty"` // 活动描述,低版本微信可见
|
||||
ActName string `xml:"act_name,omitempty" json:"act_name,omitempty"` // 活动名称
|
||||
Hblist []Hbinfo `xml:"hblist,omitempty" json:"hblist,omitempty"` // 裂变红包领取列表
|
||||
Openid string `xml:"openid,omitempty" json:"openid,omitempty"` // 领取红包的Openid
|
||||
Amount int `xml:"amount,omitempty" json:"amount,omitempty"` // 金额
|
||||
RcvTime string `xml:"rcv_time,omitempty" json:"rcv_time,omitempty"` // 接收时间
|
||||
}
|
||||
|
||||
type Hbinfo struct {
|
||||
Openid string `xml:"openid" json:"openid"`
|
||||
Amount int `xml:"amount" json:"amount"`
|
||||
RcvTime string `xml:"rcv_time" json:"rcv_time"`
|
||||
}
|
||||
|
||||
func (req *QueryRequest) Str() (string, error) {
|
||||
|
||||
b, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
func (req *QueryRequest) Validate() error {
|
||||
|
||||
err := validator.New().Struct(req)
|
||||
if err != nil {
|
||||
for _, err = range err.(validator.ValidationErrors) {
|
||||
return fmt.Errorf("参数有误:" + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToXML 将 SendRequest 序列化为无 CDATA 的 XML 字节流(带 XML 声明)
|
||||
func (req *QueryRequest) ToXML() ([]byte, error) {
|
||||
// 生成格式化 XML
|
||||
data, err := xml.MarshalIndent(req, "", " ")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("xml marshal failed: %w", err)
|
||||
}
|
||||
// 拼接 XML 声明(指定 UTF-8 编码)
|
||||
xmlData := []byte(xml.Header + string(data))
|
||||
return xmlData, nil
|
||||
}
|
||||
|
||||
func (req *QueryResponse) Str() (string, error) {
|
||||
|
||||
b, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
type SendBizRedPacket struct {
|
||||
TimeStamp string `json:"timeStamp" validate:"required"` // 调用方生成的时间戳,需为字符串
|
||||
NonceStr string `json:"nonceStr" validate:"required"` // 调用方生成的随机字符串
|
||||
Package string `json:"package" validate:"required"` // 商户将红包信息组成该串,具体方案参见package的说明,package需要进行urlencode再传给页面
|
||||
SignType string `json:"signType" validate:"required"` // 按照文档中所示填入,目前仅支持MD5
|
||||
PaySign string `json:"paySign" validate:"required"` // 签名
|
||||
}
|
||||
|
||||
func (req *SendBizRedPacket) Str() (string, error) {
|
||||
|
||||
b, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package miniprogram
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Query 查询红包记录
|
||||
// @link https://pay.weixin.qq.com/doc/v2/merchant/4011937431
|
||||
func (srv *MiniProgram) Query(ctx context.Context, req *QueryRequest) (response *SendResponse, respBody []byte, err error) {
|
||||
|
||||
reqBody, err := srv.queryReqBody(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
respBody, err = srv.Post(ctx, _queryUrl, reqBody)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
response, err = srv.ParseXMLQueryResponse(respBody)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (srv *MiniProgram) queryReqBody(req *QueryRequest) (reqBody []byte, err error) {
|
||||
|
||||
req.BillType = "MCHT"
|
||||
|
||||
if err = req.Validate(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
req.Sign = srv.generateQueryReqBodySign(req)
|
||||
|
||||
reqBody, err = req.ToXML()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (srv *MiniProgram) generateQueryReqBodySign(req *QueryRequest) string {
|
||||
|
||||
params := make(map[string]string)
|
||||
if req.NonceStr != "" {
|
||||
params["nonce_str"] = req.NonceStr
|
||||
}
|
||||
if req.MchBillno != "" {
|
||||
params["mch_billno"] = req.MchBillno
|
||||
}
|
||||
if req.MchId != "" {
|
||||
params["mch_id"] = req.MchId
|
||||
}
|
||||
if req.Appid != "" {
|
||||
params["appid"] = req.Appid
|
||||
}
|
||||
if req.BillType != "" {
|
||||
params["bill_type"] = req.BillType
|
||||
}
|
||||
|
||||
return srv.Sign(params)
|
||||
}
|
||||
|
||||
func (srv *MiniProgram) ParseXMLQueryResponse(xmlData []byte) (*SendResponse, error) {
|
||||
|
||||
var v *SendResponse
|
||||
|
||||
if err := xml.Unmarshal(xmlData, &v); err != nil {
|
||||
return nil, fmt.Errorf("xml unmarshal failed: %w", err)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package miniprogram
|
||||
|
||||
import (
|
||||
"context"
|
||||
"plugins/wechat_redpack_v2/internal/wechat/srv/redpack"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRedPack_Query(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *QueryRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "小程序查询红包",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
req: &QueryRequest{
|
||||
NonceStr: "123456",
|
||||
MchBillno: "1652322442TEST001",
|
||||
MchId: "1652322442",
|
||||
Appid: "wx075c784fe71d4d04",
|
||||
BillType: "",
|
||||
Sign: "",
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
b, err := redpack.NewRedPack(cf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
srv := MiniProgram{b}
|
||||
|
||||
gotResponse, bodyBytes, err := srv.Query(tt.args.ctx, tt.args.req)
|
||||
if err != nil {
|
||||
t.Errorf("Send() error = %v", err)
|
||||
return
|
||||
}
|
||||
t.Errorf("Send() gotResponse = %v, bodyBytes = %s", gotResponse, string(bodyBytes))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
package miniprogram
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Send 发送红包
|
||||
// @link https://pay.weixin.qq.com/doc/v2/merchant/4011937425
|
||||
func (srv *MiniProgram) Send(ctx context.Context, req *SendRequest) (response *SendResponse, respBody []byte, err error) {
|
||||
|
||||
reqBody, err := srv.sendReqBody(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
respBody, err = srv.Post(ctx, _sendUrl, reqBody)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
response, err = srv.ParseXMLSendResponse(respBody)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (srv *MiniProgram) sendReqBody(req *SendRequest) (reqBody []byte, err error) {
|
||||
|
||||
if err = req.Validate(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
req.Sign = srv.generateSendReqBodySign(req)
|
||||
|
||||
reqBody, err = req.ToXML()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (srv *MiniProgram) generateSendReqBodySign(req *SendRequest) string {
|
||||
// 1. 构造参数字典
|
||||
params := make(map[string]string)
|
||||
if req.NonceStr != "" {
|
||||
params["nonce_str"] = req.NonceStr
|
||||
}
|
||||
if req.MchBillno != "" {
|
||||
params["mch_billno"] = req.MchBillno
|
||||
}
|
||||
if req.MchId != "" {
|
||||
params["mch_id"] = req.MchId
|
||||
}
|
||||
if req.Wxappid != "" {
|
||||
params["wxappid"] = req.Wxappid
|
||||
}
|
||||
if req.SendName != "" {
|
||||
params["send_name"] = req.SendName
|
||||
}
|
||||
if req.ReOpenid != "" {
|
||||
params["re_openid"] = req.ReOpenid
|
||||
}
|
||||
if req.TotalAmount != 0 {
|
||||
params["total_amount"] = strconv.FormatInt(int64(req.TotalAmount), 10)
|
||||
}
|
||||
if req.TotalNum != 0 {
|
||||
params["total_num"] = strconv.FormatInt(int64(req.TotalNum), 10)
|
||||
}
|
||||
if req.Wishing != "" {
|
||||
params["wishing"] = req.Wishing
|
||||
}
|
||||
if req.ActName != "" {
|
||||
params["act_name"] = req.ActName
|
||||
}
|
||||
if req.Remark != "" {
|
||||
params["remark"] = req.Remark
|
||||
}
|
||||
if req.NotifyWay != "" {
|
||||
params["notify_way"] = req.NotifyWay
|
||||
}
|
||||
if req.SceneId != "" {
|
||||
params["scene_id"] = req.SceneId
|
||||
}
|
||||
|
||||
return srv.Sign(params)
|
||||
}
|
||||
|
||||
func (srv *MiniProgram) ParseXMLSendResponse(xmlData []byte) (*SendResponse, error) {
|
||||
|
||||
var v *SendResponse
|
||||
|
||||
if err := xml.Unmarshal(xmlData, &v); err != nil {
|
||||
return nil, fmt.Errorf("xml unmarshal failed: %w", err)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package miniprogram
|
||||
|
||||
import "context"
|
||||
|
||||
func (srv *MiniProgram) SendBizRedPacketSign(ctx context.Context, req *SendBizRedPacket) {
|
||||
|
||||
req.PaySign = srv.generateSendBizRedPacketSignSign(req)
|
||||
return
|
||||
}
|
||||
|
||||
func (srv *MiniProgram) generateSendBizRedPacketSignSign(req *SendBizRedPacket) string {
|
||||
|
||||
params := make(map[string]string)
|
||||
|
||||
if req.TimeStamp != "" {
|
||||
params["timeStamp"] = req.TimeStamp
|
||||
}
|
||||
if req.NonceStr != "" {
|
||||
params["nonce_str"] = req.NonceStr
|
||||
}
|
||||
if req.Package != "" {
|
||||
params["package"] = req.Package
|
||||
}
|
||||
params["appid"] = "MD5"
|
||||
|
||||
return srv.Sign(params)
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
package miniprogram
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"plugins/wechat_redpack_v2/internal/wechat/srv/redpack"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var cf = &redpack.Config{
|
||||
MchId: "1652322442",
|
||||
ApiV2Key: "bcee0c6753b2a31c792a91fe9f9f1666",
|
||||
CertFile: "/Users/lsxd/code/go/lsxd/market/plugins//cert/wechat/1652322442_2/apiclient_cert.pem",
|
||||
KeyFile: "/Users/lsxd/code/go/lsxd/market/plugins//cert/wechat/1652322442_2/apiclient_key.pem",
|
||||
}
|
||||
|
||||
func TestRedPack_Send(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *SendRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "小程序发红包",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
req: &SendRequest{
|
||||
NonceStr: "wxe3bd59243545fa8a",
|
||||
Sign: "",
|
||||
MchBillno: "1652322442TEST001",
|
||||
MchId: "1652322442",
|
||||
Wxappid: "wx075c784fe71d4d04",
|
||||
SendName: "福建兴旺",
|
||||
ReOpenid: "odX1x1za5bIooAz7gG9Jx3lHSHxk",
|
||||
TotalAmount: 10,
|
||||
TotalNum: 1,
|
||||
Wishing: "测试红包",
|
||||
ActName: "测试活动红包",
|
||||
Remark: "测试红包备注",
|
||||
NotifyWay: "MINI_PROGRAM_JSAPI",
|
||||
SceneId: "",
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
b, err := redpack.NewRedPack(cf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
srv := MiniProgram{b}
|
||||
|
||||
gotResponse, bodyBytes, err := srv.Send(tt.args.ctx, tt.args.req)
|
||||
if err != nil {
|
||||
t.Errorf("Send() error = %v", err)
|
||||
return
|
||||
}
|
||||
t.Errorf("Send() gotResponse = %v, bodyBytes = %s", gotResponse, string(bodyBytes))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRedPack_SendBizRedPacketSign(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *SendBizRedPacket
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "小程序发红包",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
req: &SendBizRedPacket{
|
||||
TimeStamp: fmt.Sprintf("%d", time.Now().Unix()),
|
||||
NonceStr: "wxe3bd59243545fa8a11",
|
||||
Package: "sendid%3D242e8abd163d300019b2cae74ba8e8c06e3f0e51ab84d16b3c80decd22a5b672%26ver%3D8%26sign%3D4110d649a5aef52dd6b95654ddf91ca7d5411ac159ace4e1a766b7d3967a1c3dfe1d256811445a4abda2d9cfa4a9b377a829258bd00d90313c6c346f2349fe5d%26mchid%3D11475856%26appid%3Dwxd27ebc41b85ce36d",
|
||||
SignType: "MD5",
|
||||
PaySign: "",
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
b, err := redpack.NewRedPack(cf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
srv := MiniProgram{b}
|
||||
|
||||
srv.SendBizRedPacketSign(tt.args.ctx, tt.args.req)
|
||||
|
||||
str, _ := tt.args.req.Str()
|
||||
t.Logf("SendBizRedPacketSign() gotResponse = %s", str)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package official
|
||||
|
||||
import (
|
||||
"plugins/wechat_redpack_v2/internal/wechat/srv/redpack"
|
||||
)
|
||||
|
||||
const (
|
||||
_sendUrl = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack"
|
||||
_queryUrl = "https://api.mch.weixin.qq.com/mmpaymkttransfers/gethbinfo"
|
||||
)
|
||||
|
||||
type Official struct {
|
||||
*redpack.RedPack
|
||||
}
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
package official
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
type Str interface {
|
||||
Str() (string, error)
|
||||
}
|
||||
|
||||
type Req interface {
|
||||
Str
|
||||
Validate() error
|
||||
ToXML() ([]byte, error)
|
||||
}
|
||||
|
||||
var _ Req = (*SendRequest)(nil)
|
||||
var _ Str = (*SendResponse)(nil)
|
||||
|
||||
var _ Req = (*QueryRequest)(nil)
|
||||
var _ Str = (*QueryResponse)(nil)
|
||||
|
||||
// SendRequest 红包发送请求结构体(兼容 JSON/XML 双格式,XML 无 CDATA 包裹)
|
||||
type SendRequest struct {
|
||||
XMLName xml.Name `xml:"xml" json:"-"` // XML 根节点为 <xml>,JSON 忽略该字段
|
||||
NonceStr string `xml:"nonce_str" json:"nonce_str" validate:"required"` // 随机字符串(无 CDATA)
|
||||
Sign string `xml:"sign" json:"sign"` // 签名(无 CDATA)
|
||||
MchBillno string `xml:"mch_billno" json:"mch_billno" validate:"required"` // 商户订单号(无 CDATA)
|
||||
MchId string `xml:"mch_id" json:"mch_id" validate:"required"` // 商户号(无 CDATA)
|
||||
Wxappid string `xml:"wxappid" json:"wxappid" validate:"required"` // 小程序 appid(无 CDATA)
|
||||
SendName string `xml:"send_name,omitempty" json:"send_name,omitempty" validate:"required"` // 发送者名称(无 CDATA,空值忽略)
|
||||
ReOpenid string `xml:"re_openid" json:"re_openid" validate:"required"` // 用户 openid(无 CDATA)
|
||||
TotalAmount int32 `xml:"total_amount" json:"total_amount" validate:"required"` // 付款金额(分,数值类型)
|
||||
TotalNum int32 `xml:"total_num" json:"total_num" validate:"required"` // 发放人数(数值类型)
|
||||
Wishing string `xml:"wishing" json:"wishing" validate:"required"` // 祝福语(无 CDATA)
|
||||
ClientIp string `xml:"client_ip" json:"client_ip" validate:"required"` // 该IP同在商户平台设置的IP白名单中的IP没有关联,该IP可传用户端或者服务端的IP。
|
||||
ActName string `xml:"act_name" json:"act_name" validate:"required"` // 活动名称(无 CDATA)
|
||||
Remark string `xml:"remark" json:"remark" validate:"required"` // 备注(无 CDATA)
|
||||
SceneId string `xml:"scene_id,omitempty" json:"scene_id,omitempty"` // 场景 ID(无 CDATA,空值忽略)
|
||||
RiskInfo string `xml:"risk_info,omitempty" json:"risk_info,omitempty"` // 活动信息
|
||||
}
|
||||
|
||||
// SendResponse 红包发送响应结构体(兼容 JSON/XML 双格式,XML 无 CDATA 包裹)
|
||||
type SendResponse struct {
|
||||
XMLName xml.Name `xml:"xml" json:"-"` // XML 根节点
|
||||
ReturnCode string `xml:"return_code,omitempty" json:"return_code"` // 返回状态码(无 CDATA)
|
||||
ReturnMsg string `xml:"return_msg,omitempty" json:"return_msg"` // 返回信息(无 CDATA)
|
||||
// 以下字段在return_code为SUCCESS的时候有返回
|
||||
ResultCode string `xml:"result_code,omitempty" json:"result_code,omitempty"` // 业务结果(无 CDATA)
|
||||
ErrCode string `xml:"err_code,omitempty" json:"err_code,omitempty"` // 错误码(无 CDATA)
|
||||
ErrCodeDes string `xml:"err_code_des,omitempty" json:"err_code_des,omitempty"` // 错误描述(无 CDATA,修正为 string)
|
||||
// 以下字段在return_code和result_code都为SUCCESS的时候有返回
|
||||
MchBillno string `xml:"mch_billno,omitempty" json:"mch_billno,omitempty"` // 商户订单号(无 CDATA)
|
||||
MchId string `xml:"mch_id,omitempty" json:"mch_id,omitempty"` // 商户号(无 CDATA)
|
||||
Wxappid string `xml:"wxappid,omitempty" json:"wxappid,omitempty"` // 小程序 appid(无 CDATA)
|
||||
ReOpenid string `xml:"re_openid,omitempty" json:"re_openid,omitempty"` // 用户 openid(无 CDATA)
|
||||
SendListid string `xml:"send_listid,omitempty" json:"send_listid,omitempty"` // 付款金额(无 CDATA)
|
||||
}
|
||||
|
||||
func (req *SendRequest) Str() (string, error) {
|
||||
|
||||
b, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
func (req *SendRequest) Validate() error {
|
||||
|
||||
err := validator.New().Struct(req)
|
||||
if err != nil {
|
||||
for _, err = range err.(validator.ValidationErrors) {
|
||||
return fmt.Errorf("参数有误:" + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToXML 将 SendRequest 序列化为无 CDATA 的 XML 字节流(带 XML 声明)
|
||||
func (req *SendRequest) ToXML() ([]byte, error) {
|
||||
// 生成格式化 XML
|
||||
data, err := xml.MarshalIndent(req, "", " ")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("xml marshal failed: %w", err)
|
||||
}
|
||||
// 拼接 XML 声明(指定 UTF-8 编码)
|
||||
xmlData := []byte(xml.Header + string(data))
|
||||
return xmlData, nil
|
||||
}
|
||||
|
||||
func (req *SendResponse) Str() (string, error) {
|
||||
|
||||
b, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
// QueryRequest .
|
||||
type QueryRequest struct {
|
||||
XMLName xml.Name `xml:"xml" json:"-"` // XML 根节点为 <xml>,JSON 忽略该字段
|
||||
NonceStr string `xml:"nonce_str" json:"nonce_str" validate:"required"` // 随机字符串(无 CDATA)
|
||||
MchBillno string `xml:"mch_billno" json:"mch_billno" validate:"required"` // 商户订单号(无 CDATA)
|
||||
MchId string `xml:"mch_id" json:"mch_id" validate:"required"` // 商户号(无 CDATA)
|
||||
Appid string `xml:"appid" json:"appid" validate:"required"` // 小程序 appid(无 CDATA)
|
||||
BillType string `xml:"bill_type" json:"bill_type" validate:"required"` // 场景 ID(无 CDATA,空值忽略) MCHT:通过商户订单号获取红包信息。
|
||||
Sign string `xml:"sign" json:"sign"` // 签名(无 CDATA)
|
||||
}
|
||||
|
||||
// QueryResponse .
|
||||
type QueryResponse struct {
|
||||
XMLName xml.Name `xml:"xml" json:"-"` // XML 根节点
|
||||
ReturnCode string `xml:"return_code" json:"return_code"` // 返回状态码(无 CDATA)
|
||||
ReturnMsg string `xml:"return_msg" json:"return_msg"` // 返回信息(无 CDATA)
|
||||
// 以下字段在return_code为SUCCESS的时候有返回
|
||||
ResultCode string `xml:"result_code,omitempty" json:"result_code,omitempty"` // 业务结果(无 CDATA)
|
||||
ErrCode string `xml:"err_code,omitempty" json:"err_code,omitempty"` // 错误码(无 CDATA)
|
||||
ErrCodeDes string `xml:"err_code_des,omitempty" json:"err_code_des,omitempty"` // 错误描述(无 CDATA,修正为 string)
|
||||
// 以下字段在return_code 和result_code都为SUCCESS的时候有返回
|
||||
MchBillno string `xml:"mch_billno,omitempty" json:"mch_billno,omitempty"` // 商户订单号(无 CDATA)
|
||||
MchId string `xml:"mch_id,omitempty" json:"mch_id,omitempty"` // 商户号(无 CDATA)
|
||||
DetailId string `xml:"detail_id,omitempty" json:"detail_id,omitempty"` // 使用API发放现金红包时返回的红包单号
|
||||
Status string `xml:"status,omitempty" json:"status,omitempty"` // 红包状态
|
||||
SendType string `xml:"send_type,omitempty" json:"send_type,omitempty"` // 发放类型
|
||||
TotalNum int `xml:"total_num,omitempty" json:"total_num,omitempty"` // 红包个数
|
||||
TotalAmount int `xml:"total_amount,omitempty" json:"total_amount,omitempty"` // 小程序跳转参数(无 CDATA)
|
||||
Reason string `xml:"reason,omitempty" json:"reason,omitempty"` // 失败原因
|
||||
SendTime string `xml:"send_time,omitempty" json:"send_time,omitempty"` // 红包发送时间
|
||||
RefundTime string `xml:"refund_time,omitempty" json:"refund_time,omitempty"` // 红包退款时间
|
||||
RefundAmount string `xml:"refund_amount,omitempty" json:"refund_amount,omitempty"` // 红包退款金额
|
||||
Wishing string `xml:"wishing,omitempty" json:"wishing,omitempty"` // 祝福语
|
||||
Remark string `xml:"remark,omitempty" json:"remark,omitempty"` // 活动描述,低版本微信可见
|
||||
ActName string `xml:"act_name,omitempty" json:"act_name,omitempty"` // 活动名称
|
||||
Hblist []Hbinfo `xml:"hblist,omitempty" json:"hblist,omitempty"` // 裂变红包领取列表
|
||||
Openid string `xml:"openid,omitempty" json:"openid,omitempty"` // 领取红包的Openid
|
||||
Amount int `xml:"amount,omitempty" json:"amount,omitempty"` // 金额
|
||||
RcvTime string `xml:"rcv_time,omitempty" json:"rcv_time,omitempty"` // 接收时间
|
||||
}
|
||||
|
||||
type Hbinfo struct {
|
||||
Openid string `xml:"openid" json:"openid"`
|
||||
Amount int `xml:"amount" json:"amount"`
|
||||
RcvTime string `xml:"rcv_time" json:"rcv_time"`
|
||||
}
|
||||
|
||||
func (req *QueryRequest) Str() (string, error) {
|
||||
|
||||
b, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
func (req *QueryRequest) Validate() error {
|
||||
|
||||
err := validator.New().Struct(req)
|
||||
if err != nil {
|
||||
for _, err = range err.(validator.ValidationErrors) {
|
||||
return fmt.Errorf("参数有误:" + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToXML 将 SendRequest 序列化为无 CDATA 的 XML 字节流(带 XML 声明)
|
||||
func (req *QueryRequest) ToXML() ([]byte, error) {
|
||||
// 生成格式化 XML
|
||||
data, err := xml.MarshalIndent(req, "", " ")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("xml marshal failed: %w", err)
|
||||
}
|
||||
// 拼接 XML 声明(指定 UTF-8 编码)
|
||||
xmlData := []byte(xml.Header + string(data))
|
||||
return xmlData, nil
|
||||
}
|
||||
|
||||
func (req *QueryResponse) Str() (string, error) {
|
||||
|
||||
b, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package official
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Query 查询红包记录
|
||||
// @link https://pay.weixin.qq.com/doc/v2/merchant/4011981612
|
||||
func (srv *Official) Query(ctx context.Context, req *QueryRequest) (response *SendResponse, respBody []byte, err error) {
|
||||
|
||||
reqBody, err := srv.queryReqBody(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
respBody, err = srv.Post(ctx, _queryUrl, reqBody)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
response, err = srv.ParseXMLQueryResponse(respBody)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (srv *Official) queryReqBody(req *QueryRequest) (reqBody []byte, err error) {
|
||||
|
||||
req.BillType = "MCHT"
|
||||
|
||||
if err = req.Validate(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
req.Sign = srv.generateQueryReqBodySign(req)
|
||||
|
||||
reqBody, err = req.ToXML()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (srv *Official) generateQueryReqBodySign(req *QueryRequest) string {
|
||||
|
||||
params := make(map[string]string)
|
||||
if req.NonceStr != "" {
|
||||
params["nonce_str"] = req.NonceStr
|
||||
}
|
||||
if req.MchBillno != "" {
|
||||
params["mch_billno"] = req.MchBillno
|
||||
}
|
||||
if req.MchId != "" {
|
||||
params["mch_id"] = req.MchId
|
||||
}
|
||||
if req.Appid != "" {
|
||||
params["appid"] = req.Appid
|
||||
}
|
||||
if req.BillType != "" {
|
||||
params["bill_type"] = req.BillType
|
||||
}
|
||||
|
||||
return srv.Sign(params)
|
||||
}
|
||||
|
||||
func (srv *Official) ParseXMLQueryResponse(xmlData []byte) (*SendResponse, error) {
|
||||
|
||||
var v *SendResponse
|
||||
|
||||
if err := xml.Unmarshal(xmlData, &v); err != nil {
|
||||
return nil, fmt.Errorf("xml unmarshal failed: %w", err)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package official
|
||||
|
||||
import (
|
||||
"context"
|
||||
"plugins/wechat_redpack_v2/internal/wechat/srv/redpack"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRedPack_Query(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *QueryRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "小程序查询红包",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
req: &QueryRequest{
|
||||
NonceStr: "123456",
|
||||
MchBillno: "1652322442TEST001",
|
||||
MchId: "1652322442",
|
||||
Appid: "wx075c784fe71d4d04",
|
||||
BillType: "",
|
||||
Sign: "",
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
b, err := redpack.NewRedPack(&redpack.Config{
|
||||
MchId: "1652322442",
|
||||
ApiV2Key: "bcee0c6753b2a31c792a91fe9f9f1666",
|
||||
CertFile: "/Users/lsxd/code/go/lsxd/market/plugins//cert/wechat/1652322442_2/apiclient_cert.pem",
|
||||
KeyFile: "/Users/lsxd/code/go/lsxd/market/plugins//cert/wechat/1652322442_2/apiclient_key.pem",
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
srv := Official{b}
|
||||
|
||||
gotResponse, bodyBytes, err := srv.Query(tt.args.ctx, tt.args.req)
|
||||
if err != nil {
|
||||
t.Errorf("Send() error = %v", err)
|
||||
return
|
||||
}
|
||||
t.Errorf("Send() gotResponse = %v, bodyBytes = %s", gotResponse, string(bodyBytes))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
package official
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Send 发送红包
|
||||
// @link https://pay.weixin.qq.com/doc/v2/merchant/4011974053
|
||||
func (srv *Official) Send(ctx context.Context, req *SendRequest) (response *SendResponse, respBody []byte, err error) {
|
||||
|
||||
reqBody, err := srv.sendReqBody(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
respBody, err = srv.Post(ctx, _sendUrl, reqBody)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
response, err = srv.ParseXMLSendResponse(respBody)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (srv *Official) sendReqBody(req *SendRequest) (reqBody []byte, err error) {
|
||||
|
||||
if err = req.Validate(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
req.Sign = srv.generateSendReqBodySign(req)
|
||||
|
||||
reqBody, err = req.ToXML()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (srv *Official) generateSendReqBodySign(req *SendRequest) string {
|
||||
// 1. 构造参数字典
|
||||
params := make(map[string]string)
|
||||
if req.NonceStr != "" {
|
||||
params["nonce_str"] = req.NonceStr
|
||||
}
|
||||
if req.MchBillno != "" {
|
||||
params["mch_billno"] = req.MchBillno
|
||||
}
|
||||
if req.MchId != "" {
|
||||
params["mch_id"] = req.MchId
|
||||
}
|
||||
if req.Wxappid != "" {
|
||||
params["wxappid"] = req.Wxappid
|
||||
}
|
||||
if req.SendName != "" {
|
||||
params["send_name"] = req.SendName
|
||||
}
|
||||
if req.ReOpenid != "" {
|
||||
params["re_openid"] = req.ReOpenid
|
||||
}
|
||||
if req.TotalAmount != 0 {
|
||||
params["total_amount"] = strconv.FormatInt(int64(req.TotalAmount), 10)
|
||||
}
|
||||
if req.TotalNum != 0 {
|
||||
params["total_num"] = strconv.FormatInt(int64(req.TotalNum), 10)
|
||||
}
|
||||
if req.Wishing != "" {
|
||||
params["wishing"] = req.Wishing
|
||||
}
|
||||
if req.ActName != "" {
|
||||
params["act_name"] = req.ActName
|
||||
}
|
||||
if req.Remark != "" {
|
||||
params["remark"] = req.Remark
|
||||
}
|
||||
if req.ClientIp != "" {
|
||||
params["client_ip"] = req.ClientIp
|
||||
}
|
||||
if req.SceneId != "" {
|
||||
params["scene_id"] = req.SceneId
|
||||
}
|
||||
if req.RiskInfo != "" {
|
||||
params["risk_info"] = req.RiskInfo
|
||||
}
|
||||
|
||||
return srv.Sign(params)
|
||||
}
|
||||
|
||||
func (srv *Official) ParseXMLSendResponse(xmlData []byte) (*SendResponse, error) {
|
||||
|
||||
var v *SendResponse
|
||||
|
||||
if err := xml.Unmarshal(xmlData, &v); err != nil {
|
||||
return nil, fmt.Errorf("xml unmarshal failed: %w", err)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package official
|
||||
|
||||
import (
|
||||
"context"
|
||||
"plugins/wechat_redpack_v2/internal/wechat/srv/redpack"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRedPack_Send(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *SendRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "小程序发红包",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
req: &SendRequest{
|
||||
NonceStr: "wxe3bd59243545fa8a",
|
||||
Sign: "",
|
||||
MchBillno: "1652322442TEST001",
|
||||
MchId: "1652322442",
|
||||
Wxappid: "wxe3bd59243545fa8a",
|
||||
SendName: "福建兴旺",
|
||||
ReOpenid: "ojbqr6HpeWKFy9Sgdx8yCmmeVJiw",
|
||||
TotalAmount: 10,
|
||||
TotalNum: 1,
|
||||
ClientIp: "127.0.0.1",
|
||||
Wishing: "测试公众号红包",
|
||||
ActName: "测试公众号活动红包",
|
||||
Remark: "测试公众号红包备注",
|
||||
SceneId: "",
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
b, err := redpack.NewRedPack(&redpack.Config{
|
||||
MchId: "1652322442",
|
||||
ApiV2Key: "bcee0c6753b2a31c792a91fe9f9f1666",
|
||||
CertFile: "/Users/lsxd/code/go/lsxd/market/plugins//cert/wechat/1652322442_2/apiclient_cert.pem",
|
||||
KeyFile: "/Users/lsxd/code/go/lsxd/market/plugins//cert/wechat/1652322442_2/apiclient_key.pem",
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
srv := Official{b}
|
||||
|
||||
gotResponse, bodyBytes, err := srv.Send(tt.args.ctx, tt.args.req)
|
||||
if err != nil {
|
||||
t.Errorf("Send() error = %v", err)
|
||||
return
|
||||
}
|
||||
t.Errorf("Send() gotResponse = %v, bodyBytes = %s", gotResponse, string(bodyBytes))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
package redpack
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
MchId string
|
||||
ApiV2Key string
|
||||
CertFile string // 证书路径
|
||||
KeyFile string // 私钥路径
|
||||
}
|
||||
|
||||
type RedPack struct {
|
||||
Config *Config
|
||||
|
||||
transport *http.Transport
|
||||
}
|
||||
|
||||
func NewRedPack(config *Config) (*RedPack, error) {
|
||||
|
||||
// 加载证书,配置双向 TLS
|
||||
cert, err := tls.LoadX509KeyPair(config.CertFile, config.KeyFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load cert failed: %w", err)
|
||||
}
|
||||
|
||||
// 关键修改:不创建新的 certPool,而是使用系统默认根证书池
|
||||
// 这样客户端能验证微信支付服务端的证书合法性
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert}, // 仅添加商户证书(用于双向认证)
|
||||
// 注释掉 RootCAs 配置,让 Go 使用系统默认根证书池
|
||||
// RootCAs: clientCertPool,
|
||||
}
|
||||
transport := &http.Transport{TLSClientConfig: tlsConfig}
|
||||
|
||||
return &RedPack{Config: config, transport: transport}, nil
|
||||
}
|
||||
|
||||
func (srv *RedPack) Sign(params map[string]string) string {
|
||||
|
||||
// 排序
|
||||
var keys []string
|
||||
for k := range params {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
// 拼接字符串
|
||||
var buf strings.Builder
|
||||
for _, k := range keys {
|
||||
buf.WriteString(k)
|
||||
buf.WriteString("=")
|
||||
buf.WriteString(params[k])
|
||||
buf.WriteString("&")
|
||||
}
|
||||
// 拼接 API 密钥
|
||||
buf.WriteString("key=")
|
||||
buf.WriteString(srv.Config.ApiV2Key)
|
||||
|
||||
hash := md5.Sum([]byte(buf.String()))
|
||||
sign := strings.ToUpper(hex.EncodeToString(hash[:]))
|
||||
return sign
|
||||
}
|
||||
|
||||
func (srv *RedPack) Post(_ context.Context, url string, reqBody []byte) (respBytes []byte, err error) {
|
||||
|
||||
// 发送 POST 请求
|
||||
httpReq, err := http.NewRequest("POST", url, bytes.NewReader(reqBody))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create request failed: %w", err)
|
||||
}
|
||||
httpReq.Header.Set("Content-Type", "application/xml; charset=utf-8")
|
||||
|
||||
client := &http.Client{Transport: srv.transport, Timeout: 20 * time.Second}
|
||||
|
||||
// 执行请求
|
||||
httpResp, err := client.Do(httpReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("send request failed: %w", err)
|
||||
}
|
||||
defer httpResp.Body.Close()
|
||||
|
||||
// 解析响应
|
||||
respBytes, err = io.ReadAll(httpResp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read response failed: %w", err)
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func MD5ToUpper(data string) string {
|
||||
// 创建一个 MD5 哈希对象
|
||||
hash := md5.New()
|
||||
// 写入待加密的数据
|
||||
hash.Write([]byte(data))
|
||||
// 获取 MD5 哈希值
|
||||
hashBytes := hash.Sum(nil)
|
||||
// 将 MD5 哈希值转换为16进制字符串
|
||||
str := hex.EncodeToString(hashBytes)
|
||||
return strings.ToUpper(str)
|
||||
}
|
||||
|
||||
func MD5(data string) string {
|
||||
// 创建一个 MD5 哈希对象
|
||||
hash := md5.New()
|
||||
// 写入待加密的数据
|
||||
hash.Write([]byte(data))
|
||||
// 获取 MD5 哈希值
|
||||
hashBytes := hash.Sum(nil)
|
||||
// 将 MD5 哈希值转换为16进制字符串
|
||||
return hex.EncodeToString(hashBytes)
|
||||
}
|
||||
|
|
@ -480,8 +480,7 @@ func (srv *MchConfig) Request(host, method, path string, reqBody []byte) (respon
|
|||
|
||||
respBody, err := ExtractResponseBody(httpResponse)
|
||||
if err != nil {
|
||||
fmt.Printf("获取应答失败,Error: %v", err)
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("获取应答失败:%w", err)
|
||||
}
|
||||
|
||||
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ module plugins/zltx_card_v1
|
|||
go 1.22.2
|
||||
|
||||
require (
|
||||
gitea.cdlsxd.cn/sdk/plugin v1.0.24
|
||||
gitea.cdlsxd.cn/sdk/plugin v1.0.25
|
||||
github.com/go-playground/validator/v10 v10.22.0
|
||||
github.com/hashicorp/go-plugin v1.6.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
gitea.cdlsxd.cn/sdk/plugin v1.0.24 h1:B6sizQzFhb9Cin+iCYLKXiIBGyH/hCYQfl7CmkkF+ck=
|
||||
gitea.cdlsxd.cn/sdk/plugin v1.0.24/go.mod h1:4fLMp/xB9GEBa3nJi62kXpHh7wnb9Lwjf0I8Vjaasx0=
|
||||
gitea.cdlsxd.cn/sdk/plugin v1.0.25 h1:z7AnsIUiakqbcTg9/RAxicCLT4Y5NkayA1jLT+Ptw58=
|
||||
gitea.cdlsxd.cn/sdk/plugin v1.0.25/go.mod h1:4fLMp/xB9GEBa3nJi62kXpHh7wnb9Lwjf0I8Vjaasx0=
|
||||
github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
|
||||
github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@ import (
|
|||
|
||||
// 插件通信信息,若不对应则会报错panic
|
||||
const (
|
||||
Tag = "zltx_card_v1_1"
|
||||
Tag = "zltx_card_v1"
|
||||
Version = 1
|
||||
CookieKey = "zltx_card_v1_1"
|
||||
CookieValue = "zltx_card_v1_1"
|
||||
CookieKey = "zltx_card_v1"
|
||||
CookieValue = "zltx_card_v1"
|
||||
)
|
||||
|
||||
type ZLTXCardV1Service struct{}
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ func Test_Get(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
t.Logf("响应体:", string(respBody))
|
||||
t.Logf("响应头:", respHeader)
|
||||
t.Logf("响应体:%s", string(respBody))
|
||||
t.Logf("响应头:%v", respHeader)
|
||||
}
|
||||
|
||||
func Test_RequestHeaders(t *testing.T) {
|
||||
|
|
@ -43,8 +43,8 @@ func Test_RequestHeaders(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
t.Logf("响应体:", string(respBody))
|
||||
t.Logf("响应头:", respHeader)
|
||||
t.Log("响应体:", string(respBody))
|
||||
t.Log("响应头:", respHeader)
|
||||
}
|
||||
|
||||
func Test_RequestStatusCode(t *testing.T) {
|
||||
|
|
@ -61,6 +61,6 @@ func Test_RequestStatusCode(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
t.Logf("响应体:", string(respBody))
|
||||
t.Logf("响应头:", respHeader)
|
||||
t.Log("响应体:", string(respBody))
|
||||
t.Log("响应头:", respHeader)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue