From ccd273840acee212f2a43aa0a92eaf11098ced81 Mon Sep 17 00:00:00 2001 From: maguodong Date: Thu, 9 Oct 2025 15:56:47 +0800 Subject: [PATCH] first commit --- .idea/.gitignore | 8 ++ .idea/goframehelperCache.xml | 6 + .idea/lklSDK.iml | 9 ++ .idea/modules.xml | 8 ++ .idea/vcs.xml | 6 + README.md | 259 +++++++++++++++++++++++++++++++++++ consts/url.go | 34 +++++ go.mod | 43 ++++++ go.sum | 88 ++++++++++++ lklsdk/account.go | 60 ++++++++ lklsdk/client.go | 122 +++++++++++++++++ lklsdk/mergePre.go | 38 +++++ lklsdk/sdk.go | 96 +++++++++++++ lklsdk/split_ledger.go | 97 +++++++++++++ lklsdk/split_ledger_more.go | 77 +++++++++++ lklsdk/trade.go | 74 ++++++++++ lklsdk/uploadFile.go | 47 +++++++ model/applyBind.go | 50 +++++++ model/applyLedgerMer.go | 50 +++++++ model/applyLedgerReceiver.go | 104 ++++++++++++++ model/balanceQuery.go | 67 +++++++++ model/baseModel.go | 7 + model/mergePreorder.go | 121 ++++++++++++++++ model/orderSplitLedger.go | 55 ++++++++ model/preorder.go | 76 ++++++++++ model/queryLedgerMer.go | 90 ++++++++++++ model/refund.go | 70 ++++++++++ model/separate.go | 43 ++++++ model/splitBalanceQuery.go | 39 ++++++ model/tradeQuery.go | 73 ++++++++++ model/uploadFile.go | 36 +++++ model/withdraw.go | 67 +++++++++ 32 files changed, 2020 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/goframehelperCache.xml create mode 100644 .idea/lklSDK.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 README.md create mode 100644 consts/url.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 lklsdk/account.go create mode 100644 lklsdk/client.go create mode 100644 lklsdk/mergePre.go create mode 100644 lklsdk/sdk.go create mode 100644 lklsdk/split_ledger.go create mode 100644 lklsdk/split_ledger_more.go create mode 100644 lklsdk/trade.go create mode 100644 lklsdk/uploadFile.go create mode 100644 model/applyBind.go create mode 100644 model/applyLedgerMer.go create mode 100644 model/applyLedgerReceiver.go create mode 100644 model/balanceQuery.go create mode 100644 model/baseModel.go create mode 100644 model/mergePreorder.go create mode 100644 model/orderSplitLedger.go create mode 100644 model/preorder.go create mode 100644 model/queryLedgerMer.go create mode 100644 model/refund.go create mode 100644 model/separate.go create mode 100644 model/splitBalanceQuery.go create mode 100644 model/tradeQuery.go create mode 100644 model/uploadFile.go create mode 100644 model/withdraw.go diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/goframehelperCache.xml b/.idea/goframehelperCache.xml new file mode 100644 index 0000000..1d86da2 --- /dev/null +++ b/.idea/goframehelperCache.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/lklSDK.iml b/.idea/lklSDK.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/lklSDK.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..c699fea --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..758f166 --- /dev/null +++ b/README.md @@ -0,0 +1,259 @@ +# 拉卡拉SDK + +这是一个通用的拉卡拉SDK,提供了分账、交易、账户等功能的API接口封装,方便开发者快速集成拉卡拉支付服务。 + +## 目录结构 + +``` +lklsdk/ +├── client.go # 核心客户端 +├── split_ledger.go # 分账基本功能 +├── split_ledger_more.go # 分账扩展功能 +├── trade.go # 交易相关功能 +├── account.go # 账户相关功能 +├── merge_pre.go # 主扫合单交易功能 +└── sdk.go # SDK主入口 +``` + +## 安装 + +```bash +# 将SDK引入到您的项目中 +go get -u github.com/black1552/lkl_sdk/lklsdk +``` + +## 快速开始 + +### 初始化SDK + +```go +import ( + "github.com/black1552/lkl_sdk/lklsdk" + "github.com/black1552/lkl_sdk/model" + "github.com/gogf/gf/v2/os/gctx" +) + +// 创建配置 +config := &lklsdk.Config{ + AppID: "your_app_id", // 拉卡拉分配的AppID + TermNo: "your_term_no", // 终端号 + MerchantNo: "your_merchant_no", // 商户号 + SettleMerNo: "your_settle_mer_no", // 结算商户号 + SettleTermNo: "your_settle_term_no", // 结算终端号 + AccountType: "WECHAT", // 账户类型,如WECHAT、ALIPAY等 + TransType: "71", // 交易类型 + Version: "3.0", // API版本 + NotifyURL: "your_notify_url", // 回调URL + SerialNo: "your_mch_api_key", // 商户API密钥 +} + +// 初始化SDK(使用泛型指定响应类型) +sdk := lklsdk.NewSDK[model.ResponseType](gctx.New(), config) +``` + +## 功能模块 + +### 1. 主扫合单交易 + +```go +// 初始化特定响应类型的SDK +sdk := lklsdk.NewSDK[model.MergePreorderResponse](gctx.New(), config) + +// 准备拆单信息 +outSplitInfo := []*model.OutSplitInfo{ + { + OutSubTradeNo: "子交易流水号1", + MerchantNo: config.MerchantNo, + TermNo: config.TermNo, + Amount: "100", // 1元 + }, + { + OutSubTradeNo: "子交易流水号2", + MerchantNo: config.MerchantNo, + TermNo: config.TermNo, + Amount: "200", // 2元 + }, +} + +// 构建位置信息 +locationInfo := &model.LocationInfo{ + RequestIp: "127.0.0.1", +} + +// 构建请求参数 +mergePreorderReq := &model.MergePreorderReqData{ + MerchantNo: config.MerchantNo, + TermNo: config.TermNo, + OutTradeNo: "商户交易流水号", + OutSplitInfo: outSplitInfo, + AccountType: "WECHAT", + TransType: "71", + TotalAmount: "300", // 3元 + LocationInfo: locationInfo, + Subject: "测试订单", + NotifyUrl: "https://your-notify-url.com", +} + +// 调用接口 +mergePreorderResp, err := sdk.MergePreOrder(mergePreorderReq) +if err != nil { + log.Printf("主扫合单交易失败: %v\n", err) +} + +// 使用SuccessOrFail方法判断请求是否成功 +if !mergePreorderResp.SuccessOrFail() { + log.Printf("主扫合单交易失败: %s\n", mergePreorderResp.Msg) +} +``` + +### 2. 商户分账业务开通申请 + +```go +// 初始化特定响应类型的SDK +sdk := lklsdk.NewSDK[model.ApplyLedgerMerResponse](gctx.New(), config) + +// 构建请求参数 +applyLedgerReq := &model.ApplyLedgerMerReqData{ + Version: "1.0", + OrderNo: "12345678901234567890123456789012", // 32位订单号 + OrgCode: "123456789012", // 机构代码 + MerInnerNo: "1234567821", // 拉卡拉内部商户号 + MerCupNo: "", // 银联商户号(与内部商户号二选一) + ContactMobile: "13311111111", // 联系手机号 + SplitLowestRatio: 3.51, // 最低分账比例 + SplitEntrustFileName: "授权委托书.pdf", // 授权委托书文件名 + SplitEntrustFilePath: "path", // 授权委托书文件路径 + SplitRange: "ALL", // 分账范围(ALL全部交易分账,MARK标记交易分账) + RetUrl: "notifyUrl.com", // 回调URL +} + +// 调用接口 +expectResp, err := sdk.ApplyLedgerMer(applyLedgerReq) +if err != nil { + log.Printf("商户分账业务开通申请失败: %v\n", err) +} + +// 使用SuccessOrFail方法判断请求是否成功 +if !expectResp.SuccessOrFail() { + log.Printf("商户分账业务开通申请失败: %s\n", expectResp.RetMsg) +} +``` + +### 3. 交易查询 + +```go +// 初始化特定响应类型的SDK +sdk := lklsdk.NewSDK[model.TradeQueryResponse](gctx.New(), config) + +// 构建请求参数 +tradeQueryReq := &model.TradeQueryReqData{ + MerchantNo: config.MerchantNo, + TermNo: config.TermNo, + OutTradeNo: "商户订单号", // 替换为实际的商户订单号 +} + +// 调用接口 +tradeQueryResp, err := sdk.TradeQuery(tradeQueryReq) +if err != nil { + log.Printf("交易查询失败: %v\n", err) +} + +// 使用SuccessOrFail方法判断请求是否成功 +if !tradeQueryResp.SuccessOrFail() { + log.Printf("交易查询失败: %s\n", tradeQueryResp.Msg) +} +``` + +### 4. 订单分账 + +```go +// 初始化特定响应类型的SDK +sdk := lklsdk.NewSDK[model.OrderSplitLedgerResponse](gctx.New(), config) + +// 准备分账接收方数据 +var recvDatas []*model.OrderSplitLedgerRecvDatas +// 可以向recvDatas数组中添加分账接收方信息 + +// 构建请求参数 +splitLedgerReq := &model.OrderSplitLedgerReqData{ + MerchantNo: config.MerchantNo, + LogDate: "", // 交易日期,格式为yyyyMMdd + LogNo: "", // 拉卡拉对账单流水号 + OutSeparateNo: "", // 商户分账指令流水号 + TotalAmt: "", // 分账总金额,单位为分 + LklOrgNo: "", // 拉卡拉机构编号 + CalType: "", // 分账计算类型(0-按金额,1-按比例) + NotifyUrl: "", // 回调地址 + RecvDatas: recvDatas, // 分账接收方数据 +} + +// 调用接口 +splitLedgerResp, err := sdk.OrderSplitLedger(splitLedgerReq) +if err != nil { + log.Printf("订单分账失败: %v\n", err) +} + +// 使用SuccessOrFail方法判断请求是否成功 +if !splitLedgerResp.SuccessOrFail() { + log.Printf("订单分账失败: %s\n", splitLedgerResp.Msg) +} +``` + +### 5. 账户余额查询 + +```go +// 初始化特定响应类型的SDK +sdk := lklsdk.NewSDK[model.BalanceQueryResponse](gctx.New(), config) + +// 构建请求参数 +balanceQueryReq := &model.BalanceQueryReqData{ + MerchantNo: config.MerchantNo, + OrgNo: "", // 机构号 + PayNo: "", // 支付单号 + PayType: "", // 支付类型 + MgtFlag: "", // 管理标志 +} + +// 调用接口 +balanceQueryResp, err := sdk.BalanceQuery(balanceQueryReq) +if err != nil { + log.Printf("账户余额查询失败: %v\n", err) +} + +// 使用SuccessOrFail方法判断请求是否成功 +if !balanceQueryResp.SuccessOrFail() { + log.Printf("账户余额查询失败: %s\n", balanceQueryResp.Msg) +} +``` + +## 错误处理 + +SDK使用两层错误处理机制,请确保同时检查网络错误和业务响应状态: + +```go +// 1. 首先检查网络或SDK层面的错误 +resp, err := sdk.SomeFunction(req) +if err != nil { + log.Printf("调用接口失败: %v\n", err) + // 处理网络错误或SDK内部错误 + return err +} + +// 2. 然后使用SuccessOrFail()方法判断业务响应是否成功 +if !resp.SuccessOrFail() { + log.Printf("业务处理失败: %s\n", resp.Msg) // 或resp.RetMsg + // 处理业务失败情况 + return errors.New("业务处理失败") +} + +// 处理成功响应 +// 使用resp获取返回的数据 +``` + +## 注意事项 + +1. 请妥善保管您的AppID、商户号、密钥等敏感信息 +2. 确保网络环境稳定,避免请求超时 +3. 建议添加请求重试机制,处理网络波动 +4. 请遵循拉卡拉的接口规范,不要频繁调用接口 +5. 如遇到问题,请参考拉卡拉官方文档或联系技术支持 \ No newline at end of file diff --git a/consts/url.go b/consts/url.go new file mode 100644 index 0000000..947a0a1 --- /dev/null +++ b/consts/url.go @@ -0,0 +1,34 @@ +package consts + +const ( + BASE_URL = "https://s2.lakala.com/api" +) + +const ( + // LKL_UPLOAD_FILE_URL 拉卡拉附件上传地址 + LKL_UPLOAD_FILE_URL = "/v2/mms/openApi/uploadFile" + // LKL_SPLIT_LEDGER_URL 拉卡拉商户分账业务开通申请 + LKL_SPLIT_LEDGER_URL = "/v2/mms/openApi/ledger/applyLedgerMer" + // LKL_SPLIT_LEDGER_QUERY_URL 拉卡拉商户分账信息查询 + LKL_SPLIT_LEDGER_QUERY_URL = "/v2/mms/openApi/ledger/queryLedgerMer" + // LKL_SPLIT_LEDGER_RECEIVE_URL 拉卡拉分账接收方创建申请 + LKL_SPLIT_LEDGER_RECEIVE_URL = "/v2/mms/openApi/ledger/applyLedgerReceiver" + // LKL_SPLIT_LEDGER_RECEIVE_BIND_URL 拉卡拉分账关系绑定申请 + LKL_SPLIT_LEDGER_RECEIVE_BIND_URL = "/v2/mms/openApi/ledger/applyBind" + // LKL_SPLIT_LEDGER_BALANCE_URL 拉卡拉可分账金额查询 + LKL_SPLIT_LEDGER_BALANCE_URL = "/v3/sacs/queryAmt" + // LKL_ORDER_SPLIT_LEDGER_URL 拉卡拉订单分账 + LKL_ORDER_SPLIT_LEDGER_URL = "/v3/sacs/separate" + // LKL_ACCOUNT_BALANCE_QUERY_URL 拉卡拉账户余额查询 + LKL_ACCOUNT_BALANCE_QUERY_URL = "/v2/laep/industry/ewalletBalanceQuery" + // LKL_ACCOUNT_WITHDRAW_URL 拉卡拉账户提现 + LKL_ACCOUNT_WITHDRAW_URL = "/v2/laep/industry/ewalletWithdrawD1" + // LKL_TRADE_QUERY_URL 拉卡拉交易查询 + LKL_TRADE_QUERY_URL = "/v3/labs/query/tradequery" + // LKL_PRE_ORDER_URL 拉卡拉聚合预下单 + LKL_PRE_ORDER_URL = "/v3/labs/trans/preorder" + // LKL_MERGE_ORDER_URL 拉卡拉主扫合单交易 + LKL_MERGE_ORDER_URL = "/v3/labs/trans/merge/preorder" + // LKL_REFOUND_URL 拉卡拉退款 + LKL_REFOUND_URL = "/v3/rfd/refund_front/refund" +) diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f5fe5c9 --- /dev/null +++ b/go.mod @@ -0,0 +1,43 @@ +module github.com/black1552/lkl_sdk + +go 1.24.3 + +toolchain go1.24.7 + +require ( + github.com/black1552/base-common v1.0.93 + github.com/gogf/gf/v2 v2.9.3 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/BurntSushi/toml v1.5.0 // indirect + github.com/clbanning/mxj/v2 v2.7.0 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-sql-driver/mysql v1.9.2 // indirect + github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.3 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/grokify/html-strip-tags-go v0.1.0 // indirect + github.com/magiconair/properties v1.8.10 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect + github.com/olekukonko/errors v1.1.0 // indirect + github.com/olekukonko/ll v0.0.9 // indirect + github.com/olekukonko/tablewriter v1.0.9 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + golang.org/x/net v0.44.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/text v0.29.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..8bd32d7 --- /dev/null +++ b/go.sum @@ -0,0 +1,88 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/black1552/base-common v1.0.93 h1:wyWVmXL8YpzMOzyhyZaOzPzakA2iKBAUu6ZyjTNe6XA= +github.com/black1552/base-common v1.0.93/go.mod h1:SNRCsxP8d0itcZa0ZGCnNNRGdsxQ5tWc2h6GlobFkrs= +github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= +github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= +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/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU= +github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.3 h1:P4jrnp+Vmh3kDeaH/kyHPI6rfoMmQD+sPJa716aMbS0= +github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.3/go.mod h1:yEhfx78wgpxUJhH9C9bWJ7I3JLcVCzUg11A4ORYTKeg= +github.com/gogf/gf/v2 v2.9.3 h1:qjN4s55FfUzxZ1AE8vUHNDX3V0eIOUGXhF2DjRTVZQ4= +github.com/gogf/gf/v2 v2.9.3/go.mod h1:w6rcfD13SmO7FKI80k9LSLiSMGqpMYp50Nfkrrc2sEE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grokify/html-strip-tags-go v0.1.0 h1:03UrQLjAny8xci+R+qjCce/MYnpNXCtgzltlQbOBae4= +github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc= +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/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +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/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM= +github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= +github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI= +github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g= +github.com/olekukonko/tablewriter v1.0.9 h1:XGwRsYLC2bY7bNd93Dk51bcPZksWZmLYuaTHR0FqfL8= +github.com/olekukonko/tablewriter v1.0.9/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo= +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/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +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= diff --git a/lklsdk/account.go b/lklsdk/account.go new file mode 100644 index 0000000..0891b82 --- /dev/null +++ b/lklsdk/account.go @@ -0,0 +1,60 @@ +package lklsdk + +import ( + "time" + + "github.com/black1552/lkl_sdk/consts" + "github.com/black1552/lkl_sdk/model" +) + +// AccountService 账户服务 +type AccountService[T any] struct { + client *Client[T] +} + +// NewAccountService 创建账户服务实例 +func NewAccountService[T any](client *Client[T]) *AccountService[T] { + return &AccountService[T]{ + client: client, + } +} + +// BalanceQuery 账户余额查询 +func (a *AccountService[T]) BalanceQuery(req *model.BalanceQueryReqData) (*T, error) { + // 构建请求参数 + url := consts.BASE_URL + consts.LKL_ACCOUNT_BALANCE_QUERY_URL + + // 构建BaseModel请求 + baseReq := model.BalanceQueryRequest{ + ReqTime: time.Now().Format("20060102150405"), + Version: "3.0", + ReqData: req, + } + // 发送请求 + respBody, err := a.client.doRequest(url, baseReq) + if err != nil { + return nil, err + } + + return respBody, nil +} + +// Withdraw 账户提现 +func (a *AccountService[T]) Withdraw(req *model.WithdrawReqData) (*T, error) { + // 构建请求参数 + url := consts.BASE_URL + consts.LKL_ACCOUNT_WITHDRAW_URL + + // 构建BaseModel请求 + baseReq := model.WithdrawRequest{ + ReqTime: time.Now().Format("20060102150405"), + Version: "3.0", + ReqData: req, + } + + // 发送请求 + respBody, err := a.client.doRequest(url, baseReq) + if err != nil { + return nil, err + } + return respBody, nil +} diff --git a/lklsdk/client.go b/lklsdk/client.go new file mode 100644 index 0000000..1f2e47f --- /dev/null +++ b/lklsdk/client.go @@ -0,0 +1,122 @@ +package lklsdk + +import ( + "context" + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "encoding/json" + "encoding/pem" + "fmt" + ran "math/rand" + "time" + + "github.com/black1552/base-common/utils" + "github.com/gogf/gf/v2/os/gfile" +) + +// Config 拉卡拉SDK配置 +type Config struct { + AppID string `json:"app_id"` + TermNo string `json:"term_no"` + MerchantNo string `json:"merchant_no"` + SettleMerNo string `json:"settle_merchant_no"` + SettleTermNo string `json:"settle_term_no"` + AccountType string `json:"account_type"` + TransType string `json:"trans_type"` + Version string `json:"version"` + NotifyURL string `json:"notify_url"` + PublicKey string `json:"public_key"` + PrivateKey string `json:"private_key"` + SerialNo string `json:"serial_no"` +} + +// Client 拉卡拉SDK客户端 +type Client[T any] struct { + config *Config + response T + ctx context.Context +} + +// NewClient 创建拉卡拉SDK客户端 +func NewClient[T any](ctx context.Context, config *Config) *Client[T] { + return &Client[T]{ + config: config, + ctx: ctx, + } +} +func (c *Client[T]) generateNonceStr() string { + const letterBytes = "abcdefghijklmnopqrstuvwxyz0123456789" + nonce := make([]byte, 12) + for i := range nonce { + nonce[i] = letterBytes[ran.Intn(len(letterBytes))] + } + return string(nonce) +} + +// generateSign 生成签名 +func (c *Client[T]) generateSign(request []byte) (string, error) { + // 生成随机字符串 + nonceStr := c.generateNonceStr() + // 获取当前时间戳(秒) + timestamp := fmt.Sprintf("%d", time.Now().Unix()) + + // 构建待签名报文 + signData := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n", c.config.AppID, c.config.SerialNo, timestamp, nonceStr, string(request)) + + // 计算签名 + hashed := sha256.Sum256([]byte(signData)) + + privateKey, err := c.loadPrivateKey() + if err != nil { + return "", err + } + signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed[:]) + if err != nil { + return "", err + } + signatureBase64 := base64.StdEncoding.EncodeToString(signature) + + // 构建Authorization头 + authorization := fmt.Sprintf("LKLAPI-SHA256withRSA appid=\"%s\",serial_no=\"%s\",timestamp=\"%s\",nonce_str=\"%s\",signature=\"%s\"", + c.config.AppID, c.config.SerialNo, timestamp, nonceStr, signatureBase64) + return authorization, nil +} + +func (c *Client[T]) loadPrivateKey() (*rsa.PrivateKey, error) { + block, _ := pem.Decode(gfile.GetBytes(c.config.PrivateKey)) + if block == nil { + return nil, fmt.Errorf("无法解码私钥PEM数据") + } + // 解析PKCS#8格式私钥 + privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + return privateKey.(*rsa.PrivateKey), nil +} + +// doRequest 发送HTTP请求 +func (c *Client[T]) doRequest(url string, reqData interface{}) (*T, error) { + // 序列化为JSON + jsonData, err := json.Marshal(reqData) + if err != nil { + return nil, fmt.Errorf("序列化请求数据失败: %v", err) + } + + auth, err := c.generateSign(jsonData) + if err != nil { + return nil, fmt.Errorf("生成签名失败: %v", err) + } + header := map[string]string{ + "Authorization": auth, + "Content-Type": "application/json", + "Accept": "application/json", + } + // 设置其他必要的请求头 + + return utils.NewClient[T](jsonData, url, header).Post(c.ctx) +} diff --git a/lklsdk/mergePre.go b/lklsdk/mergePre.go new file mode 100644 index 0000000..d7f51f1 --- /dev/null +++ b/lklsdk/mergePre.go @@ -0,0 +1,38 @@ +package lklsdk + +import ( + "github.com/black1552/lkl_sdk/consts" + "github.com/black1552/lkl_sdk/model" + "github.com/gogf/gf/v2/os/gtime" +) + +type MergePreService[T any] struct { + client *Client[T] +} + +// NewMergePreService 创建拉卡拉主扫合单交易 +func NewMergePreService[T any](client *Client[T]) *MergePreService[T] { + return &MergePreService[T]{ + client: client, + } +} + +func (s *MergePreService[T]) PreOrder(req *model.MergePreorderReqData) (*T, error) { + // 构建请求参数 + url := consts.BASE_URL + consts.LKL_MERGE_ORDER_URL + + // 构建BaseModel请求 + baseReq := model.MergePreorder{ + ReqTime: gtime.Now().Format("YmdHis"), + Version: "2.0", + ReqData: req, + } + + // 发送请求 + respBody, err := s.client.doRequest(url, baseReq) + if err != nil { + return nil, err + } + + return respBody, nil +} diff --git a/lklsdk/sdk.go b/lklsdk/sdk.go new file mode 100644 index 0000000..931453e --- /dev/null +++ b/lklsdk/sdk.go @@ -0,0 +1,96 @@ +package lklsdk + +import ( + "context" + + "github.com/black1552/lkl_sdk/model" +) + +// SDK 拉卡拉SDK主入口 +type SDK[T any] struct { + Client *Client[T] + SplitLedger *SplitLedgerService[T] + Trade *TradeService[T] + Account *AccountService[T] + UploadFile *UploadFileService[T] + MergePre *MergePreService[T] +} + +// NewSDK 创建拉卡拉SDK实例 +func NewSDK[T any](ctx context.Context, config *Config) *SDK[T] { + client := NewClient[T](ctx, config) + return &SDK[T]{ + Client: client, + SplitLedger: NewSplitLedgerService(client), + Trade: NewTradeService(client), + Account: NewAccountService(client), + UploadFile: NewUploadFileService(client), + MergePre: NewMergePreService(client), + } +} + +// 以下为便捷方法,直接通过SDK调用各服务的主要功能 + +// MergePreOrder 主扫合单交易 +func (s *SDK[T]) MergePreOrder(req *model.MergePreorderReqData) (*T, error) { + return s.MergePre.PreOrder(req) +} + +// Refound 退款 +func (s *SDK[T]) Refound(req *model.RefundReqData) (*T, error) { + return s.Trade.Refound(req) +} + +// ApplyLedgerMer 商户分账业务开通申请 +func (s *SDK[T]) ApplyLedgerMer(req *model.ApplyLedgerMerReqData) (*T, error) { + return s.SplitLedger.ApplyLedgerMer(req) +} + +// QueryLedgerMer 商户分账信息查询 +func (s *SDK[T]) QueryLedgerMer(req *model.QueryLedgerMerReqData) (*T, error) { + return s.SplitLedger.QueryLedgerMer(req) +} + +// ApplyLedgerReceiver 分账接收方创建申请 +func (s *SDK[T]) ApplyLedgerReceiver(req *model.ApplyLedgerReceiverReqData) (*T, error) { + return s.SplitLedger.ApplyLedgerReceiver(req) +} + +// ApplyBind 分账关系绑定申请 +func (s *SDK[T]) ApplyBind(req *model.ApplyBindReqData) (*T, error) { + return s.SplitLedger.ApplyBind(req) +} + +// QuerySplitBalance 可分账金额查询 +func (s *SDK[T]) QuerySplitBalance(req *model.SplitBalanceReqData) (*T, error) { + return s.SplitLedger.QuerySplitBalance(req) +} + +// OrderSplitLedger 订单分账 +func (s *SDK[T]) OrderSplitLedger(req *model.OrderSplitLedgerReqData) (*T, error) { + return s.SplitLedger.OrderSplitLedger(req) +} + +// TradeQuery 交易查询 +func (s *SDK[T]) TradeQuery(req *model.TradeQueryReqData) (*T, error) { + return s.Trade.TradeQuery(req) +} + +// PreOrder 聚合预下单 +func (s *SDK[T]) PreOrder(req *model.PreorderReqData) (*T, error) { + return s.Trade.PreOrder(req) +} + +// BalanceQuery 账户余额查询 +func (s *SDK[T]) BalanceQuery(req *model.BalanceQueryReqData) (*T, error) { + return s.Account.BalanceQuery(req) +} + +func (s *SDK[T]) UploadFileQuery(req *model.UploadFileReqData) (*T, error) { + return s.UploadFile.UploadFileQuery(req) +} + +// Withdraw 账户提现 +func (s *SDK[T]) Withdraw(req *model.WithdrawReqData) (*T, error) { + return s.Account.Withdraw(req) +} diff --git a/lklsdk/split_ledger.go b/lklsdk/split_ledger.go new file mode 100644 index 0000000..bc76415 --- /dev/null +++ b/lklsdk/split_ledger.go @@ -0,0 +1,97 @@ +package lklsdk + +import ( + "fmt" + "time" + + "github.com/black1552/lkl_sdk/consts" + "github.com/black1552/lkl_sdk/model" + "github.com/gogf/gf/v2/crypto/gmd5" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/util/gconv" +) + +// SplitLedgerService 分账服务 +type SplitLedgerService[T any] struct { + client *Client[T] +} + +// NewSplitLedgerService 创建分账服务实例 +func NewSplitLedgerService[T any](client *Client[T]) *SplitLedgerService[T] { + return &SplitLedgerService[T]{ + client: client, + } +} + +// ApplyLedgerMer 商户分账业务开通申请 +func (s *SplitLedgerService[T]) ApplyLedgerMer(req *model.ApplyLedgerMerReqData) (*T, error) { + // 构建请求参数 + url := consts.BASE_URL + consts.LKL_SPLIT_LEDGER_URL + + md5, err := gmd5.Encrypt(gconv.String(time.Now().Unix())) + if err != nil { + return nil, fmt.Errorf("创建ReqId失败") + } + // 构建BaseModel请求 + baseReq := model.ApplyLedgerMerRequest{ + ReqTime: gtime.Now().Format("YmdHis"), + Version: "2.0", + ReqData: req, + ReqId: md5, + } + + // 发送请求 + respBody, err := s.client.doRequest(url, baseReq) + if err != nil { + return nil, err + } + + return respBody, nil +} + +// ApplyLedgerReceiver 分账接收方创建申请 +func (s *SplitLedgerService[T]) ApplyLedgerReceiver(req *model.ApplyLedgerReceiverReqData) (*T, error) { + // 构建请求参数 + url := consts.BASE_URL + consts.LKL_SPLIT_LEDGER_RECEIVE_URL + + md5, err := gmd5.Encrypt(gconv.String(time.Now().Unix())) + if err != nil { + return nil, fmt.Errorf("创建ReqId失败") + } + // 构建BaseModel请求 + baseReq := model.ApplyLedgerReceiverRequest{ + ReqTime: gtime.Now().Format("YmdHis"), + Version: "2.0", + ReqId: md5, + ReqData: req, + } + + // 发送请求 + respBody, err := s.client.doRequest(url, baseReq) + if err != nil { + return nil, err + } + + return respBody, nil +} + +// QueryLedgerMer 商户分账信息查询 +func (s *SplitLedgerService[T]) QueryLedgerMer(req *model.QueryLedgerMerReqData) (*T, error) { + // 构建请求参数 + url := consts.BASE_URL + consts.LKL_SPLIT_LEDGER_QUERY_URL + + // 构建BaseModel请求 + baseReq := model.QueryLedgerMerRequest{ + ReqTime: time.Now().Format("20060102150405"), + Version: "3.0", + ReqData: req, + } + + // 发送请求 + respBody, err := s.client.doRequest(url, baseReq) + if err != nil { + return nil, err + } + + return respBody, nil +} diff --git a/lklsdk/split_ledger_more.go b/lklsdk/split_ledger_more.go new file mode 100644 index 0000000..a0a3291 --- /dev/null +++ b/lklsdk/split_ledger_more.go @@ -0,0 +1,77 @@ +package lklsdk + +import ( + "fmt" + "time" + + "github.com/black1552/lkl_sdk/consts" + "github.com/black1552/lkl_sdk/model" + "github.com/gogf/gf/v2/crypto/gmd5" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/util/gconv" +) + +// ApplyBind 分账关系绑定申请 +func (s *SplitLedgerService[T]) ApplyBind(req *model.ApplyBindReqData) (*T, error) { + // 构建请求参数 + url := consts.BASE_URL + consts.LKL_SPLIT_LEDGER_RECEIVE_BIND_URL + + md5, err := gmd5.Encrypt(gconv.String(time.Now().Unix())) + if err != nil { + return nil, fmt.Errorf("创建ReqId失败") + } + // 构建BaseModel请求 + baseReq := model.ApplyBindRequest{ + ReqTime: time.Now().Format("20060102150405"), + Version: "2.0", + ReqData: req, + ReqId: md5, + } + + // 发送请求 + respBody, err := s.client.doRequest(url, baseReq) + if err != nil { + return nil, err + } + return respBody, nil +} + +// QuerySplitBalance 可分账金额查询 +func (s *SplitLedgerService[T]) QuerySplitBalance(req *model.SplitBalanceReqData) (*T, error) { + // 构建请求参数 + url := consts.BASE_URL + consts.LKL_SPLIT_LEDGER_BALANCE_URL + + // 构建BaseModel请求 + baseReq := model.SplitBalanceRequest{ + ReqTime: gtime.Now().Format("YmdHis"), + Version: "3.0", + ReqData: req, + } + + // 发送请求 + respBody, err := s.client.doRequest(url, baseReq) + if err != nil { + return nil, err + } + return respBody, nil +} + +// OrderSplitLedger 订单分账 +func (s *SplitLedgerService[T]) OrderSplitLedger(req *model.OrderSplitLedgerReqData) (*T, error) { + // 构建请求参数 + url := consts.BASE_URL + consts.LKL_ORDER_SPLIT_LEDGER_URL + + // 构建BaseModel请求 + baseReq := model.OrderSplitLedgerRequest{ + ReqTime: time.Now().Format("20060102150405"), + Version: "3.0", + ReqData: req, + } + + // 发送请求 + respBody, err := s.client.doRequest(url, baseReq) + if err != nil { + return nil, err + } + return respBody, nil +} diff --git a/lklsdk/trade.go b/lklsdk/trade.go new file mode 100644 index 0000000..c9f9354 --- /dev/null +++ b/lklsdk/trade.go @@ -0,0 +1,74 @@ +package lklsdk + +import ( + "time" + + "github.com/black1552/lkl_sdk/consts" + "github.com/black1552/lkl_sdk/model" +) + +// TradeService 交易服务 +type TradeService[T any] struct { + client *Client[T] +} + +// NewTradeService 创建交易服务实例 +func NewTradeService[T any](client *Client[T]) *TradeService[T] { + return &TradeService[T]{ + client: client, + } +} + +// TradeQuery 交易查询 +func (t *TradeService[T]) TradeQuery(req *model.TradeQueryReqData) (*T, error) { + // 构建请求参数 + url := consts.BASE_URL + consts.LKL_TRADE_QUERY_URL + + // 构建BaseModel请求 + baseReq := model.TradeQuery{ + ReqTime: time.Now().Format("20060102150405"), + Version: "3.0", + OutOrgCode: t.client.config.AppID, + ReqData: req, + } + + // 发送请求 + respBody, err := t.client.doRequest(url, baseReq) + if err != nil { + return nil, err + } + return respBody, nil +} + +// PreOrder 聚合预下单 +func (t *TradeService[T]) PreOrder(req *model.PreorderReqData) (*T, error) { + // 构建请求参数 + url := consts.BASE_URL + consts.LKL_PRE_ORDER_URL + + // 构建BaseModel请求 + baseReq := model.NewPreorder(req) + + // 发送请求 + respBody, err := t.client.doRequest(url, baseReq) + if err != nil { + return nil, err + } + + return respBody, nil +} + +// Refound 退款 +func (t *TradeService[T]) Refound(req *model.RefundReqData) (*T, error) { + // 构建请求参数 + url := consts.BASE_URL + consts.LKL_REFOUND_URL + + // 构建BaseModel请求 + baseReq := model.NewRefund(req) + // 发送请求 + respBody, err := t.client.doRequest(url, baseReq) + if err != nil { + return nil, err + } + + return respBody, nil +} diff --git a/lklsdk/uploadFile.go b/lklsdk/uploadFile.go new file mode 100644 index 0000000..e2490d9 --- /dev/null +++ b/lklsdk/uploadFile.go @@ -0,0 +1,47 @@ +package lklsdk + +import ( + "fmt" + "time" + + "github.com/black1552/lkl_sdk/consts" + "github.com/black1552/lkl_sdk/model" + "github.com/gogf/gf/v2/crypto/gmd5" + "github.com/gogf/gf/v2/util/gconv" +) + +// UploadFileService 交易服务 +type UploadFileService[T any] struct { + client *Client[T] +} + +// NewUploadFileService 创建交易服务实例 +func NewUploadFileService[T any](client *Client[T]) *UploadFileService[T] { + return &UploadFileService[T]{ + client: client, + } +} + +// UploadFileQuery 交易查询 +func (t *UploadFileService[T]) UploadFileQuery(req *model.UploadFileReqData) (*T, error) { + // 构建请求参数 + url := consts.BASE_URL + consts.LKL_UPLOAD_FILE_URL + md5, err := gmd5.Encrypt(gconv.String(time.Now().Unix())) + if err != nil { + return nil, fmt.Errorf("创建ReqId失败") + } + // 构建BaseModel请求 + baseReq := model.UploadFileRequest{ + Timestamp: gconv.String(time.Now().Unix()), + Ver: "1.0", + ReqId: md5, + ReqData: req, + } + + // 发送请求 + respBody, err := t.client.doRequest(url, baseReq) + if err != nil { + return nil, err + } + return respBody, nil +} diff --git a/model/applyBind.go b/model/applyBind.go new file mode 100644 index 0000000..ccc5e55 --- /dev/null +++ b/model/applyBind.go @@ -0,0 +1,50 @@ +package model + +// ApplyBindRequest 分账关系绑定请求结构体 +// 用于发起分账接收方与商户的关系绑定申请 +// 拉卡拉SDK接口文档:分账关系绑定接口 + +type ApplyBindRequest struct { + ReqData *ApplyBindReqData `json:"reqData"` // 请求业务数据 + ReqId string `json:"reqId"` // 请求ID,唯一标识一笔请求 + Version string `json:"version"` // 接口版本号 + ReqTime string `json:"reqTime"` // 请求时间,格式为yyyyMMddHHmmss +} + +// ApplyBindReqData 分账关系绑定请求业务数据结构体 +// 包含分账关系绑定所需的详细业务参数 + +type ApplyBindReqData struct { + Version string `json:"version"` // 接口版本号,必传,长度8,取值说明:1.0 + OrderNo string `json:"orderNo"` // 订单编号,必传,长度32,用于后续跟踪排查问题及核对报文,格式为14位年月日(24小时制)分秒+8位随机数(不重复) + OrgCode string `json:"orgCode"` // 分账接收方所属机构代码,必传,长度32 + MerInnerNo string `json:"merInnerNo"` // 分账商户内部商户号,必传,长度32,与MerCupNo选传其一,不能都为空 + MerCupNo string `json:"merCupNo"` // 分账商户银联商户号,必传,长度32,与MerInnerNo选传其一,不能都为空 + ReceiverNo string `json:"receiverNo"` // 分账接收方编号,必传,长度32 + EntrustFileName string `json:"entrustFileName"` // 合作协议附件名称,必传,长度32 + EntrustFilePath string `json:"entrustFilePath"` // 合作协议附件路径,必传,长度32,通过调用附件上传接口获取 + RetUrl string `json:"retUrl"` // 回调通知地址,必传,长度128,审核通过后通知地址 + Attachments []struct { + AttachType string `json:"attachType"` // 附件类型编码,必传,长度32 + AttachName string `json:"attachName"` // 附件名称,必传,长度32 + AttachStorePath string `json:"attachStorePath"` // 附件路径,必传,长度128,通过调用附件上传接口获取 + } `json:"attachments,omitempty"` // 附加资料,可选,集合类型,其他附加资料文件信息 +} + +// ApplyBindResponse 分账关系绑定响应结构体 +// 包含分账关系绑定申请的处理结果信息 + +type ApplyBindResponse struct { + RetCode string `json:"retCode"` // 响应码,000000表示成功 + RetMsg string `json:"retMsg"` // 响应消息,描述响应结果 + RespData struct { + Version string `json:"version"` // 接口版本号,例如:547110502170558464 + OrderNo string `json:"orderNo"` // 订单编号,例如:2021020112000012345678 + OrgCode string `json:"orgCode"` // 机构代码,例如:200669 + ApplyId int64 `json:"applyId"` // 受理编号,例如:548099616395186176 + } `json:"respData"` // 响应业务数据,当retCode为000000时返回 +} + +func (a *ApplyBindResponse) SuccessOrFail() bool { + return a.RetCode == "000000" +} diff --git a/model/applyLedgerMer.go b/model/applyLedgerMer.go new file mode 100644 index 0000000..03a3f2b --- /dev/null +++ b/model/applyLedgerMer.go @@ -0,0 +1,50 @@ +package model + +// ApplyLedgerMerRequest 商户分账业务开通申请请求结构体 +type ApplyLedgerMerRequest struct { + ReqData *ApplyLedgerMerReqData `json:"reqData"` // 请求业务数据 + ReqId string `json:"reqId"` // 请求ID + Version string `json:"version"` // 接口版本号 + ReqTime string `json:"reqTime"` // 请求时间 +} + +// ApplyLedgerMerReqData 商户分账业务开通申请请求业务数据结构体 +type ApplyLedgerMerReqData struct { + Version string `json:"version"` // 版本号,必传,长度8,取值说明:1.0 + OrderNo string `json:"orderNo"` // 订单编号,必传,长度32,用于后续处理查询及回调通知消息标识,2014年月日时分秒毫秒组成 + OrgCode string `json:"orgCode"` // 机构代码,必传,长度12 + MerInnerNo string `json:"merInnerNo"` // 拉卡拉内部商户号,可选,长度32,拉卡拉内部商户号和银联商户号必须传一个,默认以内部商户号为准 + MerCupNo string `json:"merCupNo"` // 银联商户号,可选,长度32,拉卡拉内部商户号和银联商户号必须传一个,默认以内部商户号为准 + ContactMobile string `json:"contactMobile"` // 联系手机号,必传,长度32 + SplitLowestRatio float64 `json:"splitLowestRatio"` // 最低分账比例,必传,长度12,百分比,支持2位精度,取值范围:70-70.50 + SplitEntrustFileName string `json:"splitEntrustFileName"` // 分账授权委托书文件名称,必传,长度64,文件格式:pdf + SplitEntrustFilePath string `json:"splitEntrustFilePath"` // 分账授权委托书文件路径,必传,长度64,调用附件上传接口获取 + SplitRange string `json:"splitRange"` // 分账范围,必传,长度32,取值说明:ALL-全部交易分账(所有交易默认都分账),MARK-标记交易分账(只有带标记交易才分账,其余交易正常结算) + SplitFundSource string `json:"splitFundSource"` // 分账依据,非必传,长度32,取值说明:TRA-交易分账,BAR-金额分账 + ElecContractId string `json:"elecContractId"` // 电子合同编号,非必传,长度32,收单已签约交易电子合同编号,供审核人员复核使用 + SplitLaunchMode string `json:"splitLaunchMode"` // 分账发起方式,非必传,长度32,取值说明:AUTO-自动触发分账,POINTTRUE-指定规则分账,MANUAL-手动分账 + SettleType string `json:"settleType"` // 结算类型,非必传,长度32,取值说明:01-主扫现结,02-复扫现结,03-交易自动结算 + SplitRuleSource string `json:"splitRuleSource"` // 分账规则来源,条件必传,长度32,取值说明:MER-商户自定规则,PLATFORM-平台分润规则(分润规则必传) + RetUrl string `json:"retUrl"` // 回调通知地址,必传,长度128,分账申请结果以异步消息或同步返回的方式通知,如需无线路由处理,也可以通过第三方商户信息查询接口确定结算结果 + Attachments []struct { + AttachType string `json:"attachType"` // 附件类型编码,必传,长度32 + AttachName string `json:"attachName"` // 附件名称,必传,长度32 + AttachStorePath string `json:"attachStorePath"` // 附件路径,必传,长度128,调用附件上传接口获取 + } `json:"attachments,omitempty"` // 附加资料,可选,集合,其他需附加的文件信息 +} + +// ApplyLedgerMerResponse 商户分账业务开通申请响应结构体 +type ApplyLedgerMerResponse struct { + RetCode string `json:"retCode"` // 返回码,000000表示成功 + RetMsg string `json:"retMsg"` // 返回消息 + RespData struct { + Version string `json:"version"` // 接口版本号,例如:547110502170558464 + OrderNo string `json:"orderNo"` // 订单编号,例如:2021020112000012345678 + OrgCode string `json:"orgCode"` // 机构代码,例如:200669 + ApplyId int64 `json:"applyId"` // 受理编号,例如:548099616395186176 + } `json:"respData"` // 响应数据 +} + +func (a *ApplyLedgerMerResponse) SuccessOrFail() bool { + return a.RetCode == "000000" +} diff --git a/model/applyLedgerReceiver.go b/model/applyLedgerReceiver.go new file mode 100644 index 0000000..900ec52 --- /dev/null +++ b/model/applyLedgerReceiver.go @@ -0,0 +1,104 @@ +package model + +// ApplyLedgerReceiverRequest 分账接收方创建请求结构体 +// 用于向拉卡拉接口发送分账接收方创建请求 +// 包含请求头信息和业务数据 +type ApplyLedgerReceiverRequest struct { + // 请求业务数据 + ReqData *ApplyLedgerReceiverReqData `json:"reqData"` + // 请求ID,用于幂等性校验 + ReqId string `json:"reqId"` + // 接口版本号 + Version string `json:"version"` + // 请求时间,格式为yyyyMMddHHmmss + ReqTime string `json:"reqTime"` +} + +// ApplyLedgerReceiverReqData 分账接收方创建请求业务数据结构体 +// 包含分账接收方创建所需的具体业务参数 +// 用于创建分账接收方信息 +type ApplyLedgerReceiverReqData struct { + // 接口版本号,必传,长度8,取值说明:1.0 + Version string `json:"version"` + // 订单编号(便于后续跟踪排查问题及核对报文),必传,长度32,取值说明:14位年月日(24小时制)分秒+8位的随机数(不重复) + OrderNo string `json:"orderNo"` + // 机构代码,必传,长度32 + OrgCode string `json:"orgCode"` + // 分账接收方名称,必传,长度64 + ReceiverName string `json:"receiverName"` + // 联系手机号,必传,长度16 + ContactMobile string `json:"contactMobile"` + // 营业执照号码,可选,长度32,取值说明:收款账户类型为对公,必须上送 + LicenseNo string `json:"licenseNo"` + // 营业执照名称,可选,长度128,取值说明:收款账户类型为对公,必须上送 + LicenseName string `json:"licenseName"` + // 法人姓名,可选,长度32,取值说明:收款账户类型为对公,必须上送 + LegalPersonName string `json:"legalPersonName"` + // 法人证件类型,可选,长度32,取值说明:17身份证,18护照,19港澳居民来往内地通行证,20台湾居民来往内地通行证,收款账户类型为对公,必须上送,身份证外类型先咨询后再使用 + LegalPersonCertificateType string `json:"legalPersonCertificateType"` + // 法人证件号,可选,长度32,取值说明:收款账户类型为对公,必须上送 + LegalPersonCertificateNo string `json:"legalPersonCertificateNo"` + // 收款账户卡号,必传,长度32 + AcctNo string `json:"acctNo"` + // 收款账户名称,必传,长度32 + AcctName string `json:"acctName"` + // 收款账户类型代码,必传,长度32,取值说明:57:对公,58:对私 + AcctTypeCode string `json:"acctTypeCode"` + // 收款账户证件类型,必传,长度32,取值说明:17身份证,18护照,19港澳居民来往内地通行证,20台湾居民来往内地通行证,身份证外类型先咨询后再使用 + AcctCertificateType string `json:"acctCertificateType"` + // 收款账户证件号,必传,长度32 + AcctCertificateNo string `json:"acctCertificateNo"` + // 收款账户开户行号,必传,长度32,取值说明:参照FBI.N信息查询,仅支持对私结算账户 + AcctOpenBankCode string `json:"acctOpenBankCode"` + // 收款账户开户行名称,必传,长度64,取值说明:参照FBI.N信息查询,仅支持对私结算账户 + AcctOpenBankName string `json:"acctOpenBankName"` + // 收款账户清算行行号,必传,长度32,取值说明:参照FBI.N信息查询,仅支持对私结算账户 + AcctClearBankCode string `json:"acctClearBankCode"` + // 接收方附件资料,可选,集合 + AttachList []struct { + // 附件名称,可选,长度32 + AttachName string `json:"attachName"` + // 附件路径,可选,长度32,取值说明:(调用进件附件上传接口获取到附件路径) + AttachStorePath string `json:"attachStorePath"` + // 附件类型编码,可选,长度32 + AttachType string `json:"attachType"` + } `json:"attachList"` + // 提款类型,可选,长度32,取值说明:01:主动提款,03:交易自动结算,不填默认01 + SettleType string `json:"settleType"` +} + +// ApplyLedgerReceiverResponse 分账接收方创建响应结构体 +// 拉卡拉接口返回的分账接收方创建响应数据 +// 包含响应状态码、消息和业务数据 +type ApplyLedgerReceiverResponse struct { + // 响应状态码,000000表示成功 + RetCode string `json:"retCode"` + // 响应消息 + RetMsg string `json:"retMsg"` + // 响应业务数据,当retCode为000000时返回 + RespData *ApplyLedgerReceiverRespData `json:"respData"` +} + +// ApplyLedgerReceiverRespData 分账接收方创建响应业务数据结构体 +// 包含分账接收方创建返回的具体业务信息 +type ApplyLedgerReceiverRespData struct { + // 接口版本号(回传) + Version string `json:"version"` + // 订单编号(回传) + OrderNo string `json:"orderNo"` + // 申请机构代码(回传) + OrgCode string `json:"orgCode"` + // 接收方所属机构 + OrgId string `json:"orgId"` + // 接收方所属机构名称 + OrgName string `json:"orgName"` + // 接收方编号 + ReceiverNo string `json:"receiverNo"` +} + +// SuccessOrFail 判断分账接收方创建请求是否成功 +// 成功条件:响应码为000000 +// 返回值:true表示成功,false表示失败 +func (resp *ApplyLedgerReceiverResponse) SuccessOrFail() bool { + return resp.RetCode == "000000" +} diff --git a/model/balanceQuery.go b/model/balanceQuery.go new file mode 100644 index 0000000..309a56c --- /dev/null +++ b/model/balanceQuery.go @@ -0,0 +1,67 @@ +package model + +// BalanceQueryRequest 余额查询请求结构体 +// 用于向拉卡拉接口发送余额查询请求 +// 包含请求头信息和业务数据 +type BalanceQueryRequest struct { + // 请求业务数据 + ReqData *BalanceQueryReqData `json:"req_data"` + // 接口版本号 + Version string `json:"version"` + // 请求时间,格式为yyyyMMddHHmmss + ReqTime string `json:"req_time"` +} + +// BalanceQueryReqData 余额查询请求业务数据结构体 +// 包含余额查询所需的具体业务参数 +// 用于查询账户的实时余额信息 +type BalanceQueryReqData struct { + // 机构号,必传,最大长度32 + OrgNo string `json:"org_no"` + // 商户号或receiveNo或商户用户编号,必传,最大长度32 + MerchantNo string `json:"merchant_no"` + // 账号,若该参数上送,则payType将无效,非必传,最大长度32 + PayNo string `json:"pay_no"` + // 账号类型(01:收款账户,02:付款账户,03:分账商户账户,04:分账接收方账户,05:充值代付账户,06:结算代付账户)-未上送则默认01,非必传,最大长度32 + PayType string `json:"pay_type"` + // 账户标志(01:一般账户;03:虚户)-未上送则默认01,非必传,最大长度32 + MgtFlag string `json:"mgt_flag"` +} + +// BalanceQueryResponse 余额查询响应结构体 +// 拉卡拉接口返回的余额查询响应数据 +// 包含响应状态码、消息和业务数据 +type BalanceQueryResponse struct { + // 响应状态码,000000 + Code string `json:"code"` + // 响应消息 + Msg string `json:"msg"` + // 响应业务数据,当code为SACS0000时返回 + RespData *BalanceQueryRespData `json:"resp_data"` +} + +// BalanceQueryRespData 余额查询响应业务数据结构体 +// 包含余额查询返回的具体账户信息 +type BalanceQueryRespData struct { + // 账号,必传 + PayNo string `json:"pay_no"` + // 账户类型,必传 + PayType string `json:"pay_type"` + // 账户状态,必传,取值说明:CLOSE销户,NORMAL正常,FREEZE冻结,STOPAY止付 + AcctSt string `json:"acct_st"` + // 预付余额(单位元),必传 + ForceBalance string `json:"force_balance"` + // 上日余额(单位元)-该字段已废弃使用,必传 + HisBalance string `json:"his_balance"` + // 实时余额(单位元),必传 + ReBalance string `json:"re_balance"` + // 当前可用余额(单位元),必传 + CuBalance string `json:"cu_balance"` +} + +// SuccessOrFail 判断余额查询请求是否成功 +// 成功条件:响应码为SACS0000 +// 返回值:true表示成功,false表示失败 +func (resp *BalanceQueryResponse) SuccessOrFail() bool { + return resp.Code == "000000" +} diff --git a/model/baseModel.go b/model/baseModel.go new file mode 100644 index 0000000..4c8def1 --- /dev/null +++ b/model/baseModel.go @@ -0,0 +1,7 @@ +package model + +type BaseModel[T any] struct { + ReqTime string `json:"req_time"` + Version string `json:"version"` + ReqData T `json:"req_data"` +} diff --git a/model/mergePreorder.go b/model/mergePreorder.go new file mode 100644 index 0000000..804a466 --- /dev/null +++ b/model/mergePreorder.go @@ -0,0 +1,121 @@ +package model + +import "github.com/gogf/gf/v2/os/gtime" + +// MergePreorder 主扫合单交易请求结构体 +type MergePreorder struct { + ReqTime string `json:"req_time"` // 请求时间,格式:YYYYMMDDHHMMSS + Version string `json:"version"` // 接口版本号,固定值"3.0" + ReqData *MergePreorderReqData `json:"req_data"` // 请求业务数据 +} + +// MergePreorderReqData 主扫合单交易请求业务数据 +type MergePreorderReqData struct { + MerchantNo string `json:"merchant_no"` // 商户号,拉卡拉分配的商户号,String(32),必填 + TermNo string `json:"term_no"` // 终端号,拉卡拉分配的业务终端号,String(32),必填 + OutTradeNo string `json:"out_trade_no"` // 商户交易流水号,商户系统唯一,String(32),必填 + OutSplitInfo []*OutSplitInfo `json:"out_split_info"` // 拆单信息,List,必填 + AccountType string `json:"account_type"` // 钱包类型,微信:WECHAT 支付宝:ALIPAY 银联:UQRCODEPAY 京东钱包:JD,String(32),必填 + TransType string `json:"trans_type"` // 接入方式,41:NATIVE(扫码支付)(仅ALIPAY支持) 51:JSAPI(微信公众号支付,支付宝服务窗支付,银联JS支付,支付宝JS支付、拉卡拉钱包支付)71:微信小程序支付 81:支付宝H5支付(需特殊商户账户端支持),String(2),必填 + TotalAmount string `json:"total_amount"` // 金额,单位分,整数型字符,String(12),必填 + LocationInfo *LocationInfo `json:"location_info"` // 地址位置信息,Object,风控要求必送,必填 + BusiMode string `json:"busi_mode"` // 业务模式,ACQ-收单 PAY-付款不填,默认为"ACQ-收单",String(8),选填 + Subject string `json:"subject"` // 订单标题,用于简单描述订单或商品主题,传递给账户端(账户端控制,实际最多42个字节),String(42),选填 + NotifyUrl string `json:"notify_url"` // 商户通知地址,如果上传,且 pay_order_no 不存在情况下,则按此地址通知商户,String(128),选填 + Remark string `json:"remark"` // 备注,String(128),选填 + IdentityInfo string `json:"identity_info"` // 实名支付信息,json字符串,如{"identityNo": "3200000000000000XX", "name": "张三"},然后国密sm2加密,String(1024),选填 + AccBusiFields *AccBusiFields `json:"acc_busi_fields"` // 账户端业务信息域,Object,选填 + CompleteNotifyUrl string `json:"complete_notify_url"` // 发货确认通知地址,发货类小程序确认收货后通知商户的地址,String(128),选填 +} + +// OutSplitInfo 拆单信息 +type OutSplitInfo struct { + OutSubTradeNo string `json:"out_sub_trade_no"` // 外部子交易流水号,商户子交易流水号,商户号下唯一,String(32),必填 + MerchantNo string `json:"merchant_no"` // 商户号,拉卡拉分配的商户号,String(32),必填 + TermNo string `json:"term_no"` // 终端号,拉卡拉分配的业务终端号,String(32),必填 + Amount string `json:"amount"` // 金额,单位分,整数型字符,String(12),必填 + SettleType string `json:"settle_type"` // 结算类型(单台),"0"或者空,常规结算方式,String(4),选填 + SubRemark string `json:"sub_remark"` // 子备注,子单备注信息,String(64),选填 +} + +// LocationInfo 地址位置信息 +type LocationInfo struct { + RequestIp string `json:"request_ip"` // 请求方IP地址,请求方的IP地址,存在必填,格式如36.45.36.95,String(64),必填 + BaseStation string `json:"base_station"` // 基站信息,客户端设备的基站信息(主扫时基站信息使用该字段),String(128),选填 + Location string `json:"location"` // 维度,经度,商户终端的地理位置,存在必填格式:纬度,经度,+表示北纬、东经,-表示南纬、西经,精度最长支持小数点后9位。举例:+37.123456789,121.123456789,String(32),选填 +} + +// AccBusiFields 账户端业务信息域,微信主扫场景 +type AccBusiFields struct { + TimeoutExpress string `json:"timeout_express"` // 预下单的订单的有效时间,以分钟为单位。如果在有效时间内没有完成付款,则在账户端该订单失效。如用户超时,则账户端完成失效处理,建议不超过15分钟。不传值则默认5分钟,String(2),选填 + SubAppId string `json:"sub_appid"` // 子商户公众账号ID,sub_appid(即微信小程序支付-71、公众号支付-51、微信支付-61),此参数必传,只对微信支付生效;拉卡拉钱包情况下,该字段上送LAKALA的openid,String(32),选填 + UserId string `json:"user_id"` // 用户标识,用户在子商户sub_appid下的唯一标识,sub_openid,(即微信小程序支付-71、公众号支付-51),此参数必传,只对微信支付有效,String(64),选填 + Detail string `json:"detail"` // 商品详情,单品优惠功能字段,详见下文说明,String(1024),选填 + GoodsTag string `json:"goods_tag"` // 订单优惠标记,微信平台配置的商品标记,用于优惠券或者满减使用,accountType为WECHAT时,可选填此字段,String(32),选填 + Attach string `json:"attach"` // 附加域,附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据。商户定制字段,直接送到账户端,String(128),选填 + GoodsDetail []*GoodsDetail `json:"goods_detail"` // 商品详情列表,微信商品详情字段说明 +} + +// GoodsDetail 微信商品详情字段说明 +type GoodsDetail struct { + GoodsId string `json:"goods_id"` // 商品ID,由半角的大小写字母、数字、中划线、下划线中的一种或几种组成。如"商品编码",必填 + WxpayGoodsId string `json:"wxpay_goods_id"` // 微信支付定义的统一商品编号,String(32),选填 + GoodsName string `json:"goods_name"` // 商品的实际名称,String(256),选填 + Quantity string `json:"quantity"` // 用户购买的数量,String(12),必填 + Price string `json:"price"` // 单价,单位为:分。如果商户有优惠,需传输商户优惠后的单价,String(12),必填 +} + +// NewMergePreorder 创建主扫合单交易请求 +func NewMergePreorder(param *MergePreorderReqData) *MergePreorder { + return &MergePreorder{ + ReqTime: gtime.Now().Format("YmdHis"), + Version: "3.0", + ReqData: param, + } +} + +// MergePreorderResponse 主扫合单交易响应结构体 +type MergePreorderResponse struct { + Code string `json:"code"` // 响应码,BBS00000表示成功 + Msg string `json:"msg"` // 响应信息,对响应码的文字描述 + ReqData MergePreorderRespData `json:"resp_data"` // 响应业务数据 + RespTime string `json:"resp_time"` // 响应时间 +} + +// MergePreorderRespData 主扫合单交易响应业务数据 +type MergePreorderRespData struct { + MerchantNo string `json:"merchant_no"` // 商户号(待上线),拉卡拉分配的商户号(请求接口中商户号) + OutTradeNo string `json:"out_trade_no"` // 商户请求流水号,请求报文中的商户请求流水号 + TradeNo string `json:"trade_no"` // 拉卡拉交易流水号 + LogNo string `json:"log_no"` // 拉卡拉对账单流水号 + SplitInfo []*SplitInfo `json:"split_info"` // 拆单信息 + AccRespFields interface{} `json:"acc_resp_fields"` // 账户端返回信息域 +} + +// SplitInfo 拆单信息 +type SplitInfo struct { + SubTradeNo string `json:"sub_trade_no"` // 子单交易流水号 + SubLogNo string `json:"sub_log_no"` // 子单对账单流水号 + OutSubTradeNo string `json:"out_sub_trade_no"` // 外部子交易流水号,商户子交易流水号,商户号下唯一 + MerchantNo string `json:"merchant_no"` // 商户号,拉卡拉分配的商户号 + MerchantName string `json:"merchant_name"` // 商户名称 + TermNo string `json:"term_no"` // 终端号,拉卡拉分配的业务终端号 + Amount string `json:"amount"` // 金额,单位为:分。整数型字符 +} + +// WxAccRespFields 微信(71-小程序/微信(51-JSAPI)场景下账户端返回信息域 +type WxAccRespFields struct { + PrepayId string `json:"prepay_id"` // 预下单ID,预支付交易会话ID + PaySign string `json:"pay_sign"` // 支付签名信息 + AppId string `json:"app_id"` // 小程序ID,商户注册具有支付权限的小程序成功后即可获得小程序ID + TimeStamp string `json:"time_stamp"` // 时间戳,当前的时间 + NonceStr string `json:"nonce_str"` // 随机字符串 + Package string `json:"package"` // 订单详情扩展字符串 + SignType string `json:"sign_type"` // 签名方式,签名类型,支持RSA + SubMchId string `json:"sub_mch_id"` // 子商户号,账户端子商户号 +} + +// SuccessOrFail 判断主扫合单交易是否成功 +func (m *MergePreorderResponse) SuccessOrFail() bool { + return m.Code == "BBS00000" +} \ No newline at end of file diff --git a/model/orderSplitLedger.go b/model/orderSplitLedger.go new file mode 100644 index 0000000..846bc58 --- /dev/null +++ b/model/orderSplitLedger.go @@ -0,0 +1,55 @@ +package model + +// OrderSplitLedgerRequest 订单分账请求结构体 +// 用于发起订单分账操作,支持向多个接收方进行分账 +// 拉卡拉SDK接口文档:订单分账接口 + +type OrderSplitLedgerRequest struct { + ReqData *OrderSplitLedgerReqData `json:"req_data"` // 请求业务数据 + Version string `json:"version"` // 接口版本号 + ReqTime string `json:"req_time"` // 请求时间,格式为yyyyMMddHHmmss +} + +// OrderSplitLedgerReqData 订单分账请求业务数据结构体 +// 包含订单分账所需的详细业务参数 + +type OrderSplitLedgerReqData struct { + MerchantNo string `json:"merchant_no"` // 商户号,必传,长度32 + LogNo string `json:"log_no"` // 拉卡拉对账单流水号,必传,长度14,posp流水号,用于查询结算 + LogDate string `json:"log_date"` // 交易日期,必传,长度8,posp日期,格式为yyyyMMdd,用于查询结算 + OutSeparateNo string `json:"out_separate_no"` // 商户分账指令流水号,必传,长度32,每个商户号下唯一,否则会校验失败 + TotalAmt string `json:"total_amt"` // 分账总金额,必传,长度15,单位为分 + LklOrgNo string `json:"lkl_org_no"` // 拉卡拉机构编号,条件必传,长度16 + CalType string `json:"cal_type"` // 分账计算类型,条件必传,长度2,取值说明:0-按照指定金额,1-按照指定比例,默认0 + NotifyUrl string `json:"notify_url"` // 回调地址,条件必传,长度128,分账、分账撤销或分账回退时,通过该地址通知商户最终处理结果,不传时不回调 + RecvDatas []*OrderSplitLedgerRecvDatas `json:"recv_datas,omitempty"` // 分账接收数据对象,条件必传,列表类型,分账接收方编号必须已创建 +} + +type OrderSplitLedgerRecvDatas struct { + RecvMerchantNo string `json:"recv_merchant_no"` // 接收方商户号,条件必传,长度32,分账接收方商户号,分给自己时使用,需和merchantNo一样,否则检查报错;分账接收方商户号和分账接收方只能填写一个 + RecvNo string `json:"recv_no"` // 分账接收方编号,条件必传,长度32,分账接收方编号,分给他人时使用;分账接收方商户号和分账接收方只能填写一个 + SeparateValue string `json:"separate_value"` // 分账数值,必传,长度32,calType为0时按照固定金额分账(单位:分),calType为1时按照比例分账(单位:百分比的小数值,如0.55表示55%) +} + +// OrderSplitLedgerResponse 订单分账响应结构体 +// 包含订单分账操作的处理结果信息 + +type OrderSplitLedgerResponse struct { + Msg string `json:"msg"` // 响应消息,描述响应结果 + RespTime string `json:"resp_time"` // 响应时间,格式为yyyyMMddHHmmss + Code string `json:"code"` // 响应码,SACS0000表示成功 + RespData struct { + SeparateNo string `json:"separate_no"` // 分账指令流水号,必传,长度32,分账系统生成唯一流水 + OutSeparateNo string `json:"out_separate_no"` // 商户订单号,必传,长度32,请求报文中的商户外部订单号 + Status string `json:"status"` // 分账状态,条件必传,长度16,取值说明:处理中-PROCESSING,已受理-ACCEPTED,成功-SUCCESS,失败-FAIL + LogNo string `json:"log_no"` // 拉卡拉对账单流水号,条件必传,长度14,请求透返 + LogDate string `json:"log_date"` // 拉卡拉订单日期,条件必传,长度8,posp日期,格式为yyyyMMdd,用于查询结算 + TotalAmt string `json:"total_amt"` // 分账总金额,条件必传,长度15,单位为分 + } `json:"resp_data"` // 响应业务数据,当code为SACS0000时返回 +} + +// SuccessOrFail 判断分账请求是否成功 +// 返回true表示成功,false表示失败 +func (s *OrderSplitLedgerResponse) SuccessOrFail() bool { + return s.Code == "SACS0000" +} diff --git a/model/preorder.go b/model/preorder.go new file mode 100644 index 0000000..41ea4ac --- /dev/null +++ b/model/preorder.go @@ -0,0 +1,76 @@ +package model + +import "github.com/gogf/gf/v2/os/gtime" + +// Preorder 预下单请求结构体 +type Preorder struct { + ReqTime string `json:"req_time"` // 请求时间,格式:YYYYMMDDHHMMSS + Version string `json:"version"` // 接口版本号,固定值"3.0" + ReqData *PreorderReqData `json:"req_data"` // 请求业务数据 +} + +// PreorderReqData 预下单请求业务数据 +type PreorderReqData struct { + MerchantNo string `json:"merchant_no"` // 商户号,拉卡拉分配的商户号,String(32) + TermNo string `json:"term_no"` // 终端号,拉卡拉分配的业务终端号,String(32) + OutTradeNo string `json:"out_trade_no"` // 商户交易流水号,商户系统唯一,对应数据库表中外请求流水号,String(32) + AccountType string `json:"account_type"` // 钱包类型,微信:WECHAT 支付宝:ALIPAY 银联:UQRCODEPAY 翼支付:BESTPAY 苏宁易付宝:SUNING 拉卡拉支付账户:LKLACC 网联小钱包:NUCSPAY 京东钱包:JD,String(32) + TransType string `json:"trans_type"` // 接入方式,41:NATIVE(ALIPAY, 云闪付支持, 京东白条分期)51:JSAPI(微信公众号支付, 支付宝服务窗、JS支付, 翼支付JS支付, 拉卡拉钱包支付, 京东白条分期)71:微信小程序支付61:APP支付(微信APP支付),String(2) + TotalAmount string `json:"total_amount"` // 金额,单位分,整数型字符,String(12) + NotifyUrl string `json:"notify_url"` // 商户通知地址,商户通知地址,如果上传,且 pay_order_no 不存在情况下,则按此地址通知商户,String(128) + LocationInfo struct { + RequestIp string `json:"request_ip"` // 请求方IP地址,存在必填,格式如36.45.36.95,String(64) + Location string `json:"location"` // 纬度,经度,商户终端的地理位置,银联二维码交易必填,整体格式:纬度,经度,+表示北纬、东经,-表示南纬、西经。经度格式:1位正负号+3位整数+1位小数点+5位小数;纬度格式:1位正负号+2位整数+1位小数点+6位小数;举例:+31.221345,+121.12345,String(32) + } `json:"location_info"` // 地址位置信息,Object + Subject string `json:"subject"` // 订单标题,用于简单描述订单或商品主题,传输给账户端(账户端控制,实际最多42个字节),微信支付必送,String(42) +} + +func NewPreorder(param *PreorderReqData) *Preorder { + return &Preorder{ + ReqTime: gtime.Now().Format("YmdHis"), + Version: "3.0", + ReqData: param, + } +} + +// PreorderResponse 预下单响应结构体 +type PreorderResponse struct { + Code string `json:"code"` // 响应码,BBS00000表示成功 + Msg string `json:"msg"` // 响应信息,对响应码的文字描述 + ReqData ReqData `json:"resp_data"` // 响应业务数据 + RespTime string `json:"resp_time"` // 响应时间 +} + +// ReqData 响应业务数据 +type ReqData struct { + MerchantNo string `json:"merchant_no"` // 商户号 + OutTradeNo string `json:"out_trade_no"` // 外部订单号(商户订单号) + TradeNo string `json:"trade_no"` // 交易号,拉卡拉生成的订单号 + LogNo string `json:"log_no"` // 拉卡拉对账单流水号 + SettleMerchantNo string `json:"settle_merchant_no"` // 结算商户号 + SettleTermNo string `json:"settle_term_no"` // 结算终端号 + AccRespFields WxPreorderResponse `json:"acc_resp_fields"` // 支付通道返回的具体信息 + +} + +// WxPreorderResponse 支付通道返回的具体信息 +type WxPreorderResponse struct { + Code string `json:"code"` // 返回码 + CodeImage string `json:"code_image"` // 二维码图片Base64编码 + PrepayId string `json:"prepay_id"` // 预支付ID + AppId string `json:"app_id"` // 应用ID + PaySign string `json:"pay_sign"` // 签名 + TimeStamp string `json:"time_stamp"` // 时间戳 + NonceStr string `json:"nonce_str"` // 随机字符串 + Package string `json:"package"` // 订单详情扩展字符串 + SignType string `json:"sign_type"` // 签名方式 + FormData string `json:"form_data"` // 表单数据 + RedirectUrl string `json:"redirect_url"` // 重定向URL + BestPayInfo string `json:"best_pay_info"` // 翼支付信息 + PartnerId string `json:"partner_id"` // 合作伙伴ID + SubMchId string `json:"sub_mch_id"` // 子商户ID +} + +func (p *PreorderResponse) SuccessOrFail() bool { + return p.Code == "BBS00000" +} diff --git a/model/queryLedgerMer.go b/model/queryLedgerMer.go new file mode 100644 index 0000000..a1b388d --- /dev/null +++ b/model/queryLedgerMer.go @@ -0,0 +1,90 @@ +package model + +// QueryLedgerMerRequest 分账商户查询请求结构体 +// 用于向拉卡拉接口发送分账商户查询请求 +// 包含请求头信息和业务数据 +type QueryLedgerMerRequest struct { + // 请求业务数据 + ReqData *QueryLedgerMerReqData `json:"req_data"` + // 接口版本号 + Version string `json:"version"` + // 请求时间,格式为yyyyMMddHHmmss + ReqTime string `json:"req_time"` +} + +// QueryLedgerMerReqData 分账商户查询请求业务数据结构体 +// 包含分账商户查询所需的具体业务参数 +// 用于查询商户的分账设置信息 +type QueryLedgerMerReqData struct { + // 接口版本号,必传,长度8,取值说明:1.0 + Version string `json:"version"` + // 订单编号(便于后续跟踪排查问题及核对报文),必传,长度32,取值说明:14位年月日(24小时制)分秒+8位的随机数(不重复) + OrderNo string `json:"order_no"` + // 机构代码,必传,长度32 + OrgCode string `json:"org_code"` + // 拉卡拉内部商户号,可选,长度32,取值说明:拉卡拉内部商户号和银联商户号必须传一个,都送以内部商户号为准 + MerInnerNo string `json:"mer_inner_no"` + // 银联商户号,可选,长度32,取值说明:拉卡拉内部商户号和银联商户号必须传一个,都送以内部商户号为准 + MerCupNo string `json:"mer_cup_no"` +} + +// QueryLedgerMerResponse 分账商户查询响应结构体 +// 拉卡拉接口返回的分账商户查询响应数据 +// 包含响应状态码、消息和业务数据 +type QueryLedgerMerResponse struct { + // 响应状态码,000000表示成功 + Code string `json:"code"` + // 响应消息 + Msg string `json:"msg"` + // 响应业务数据,当code为000000时返回 + RespData *QueryLedgerMerRespData `json:"resp_data"` +} + +// QueryLedgerMerRespData 分账商户查询响应业务数据结构体 +// 包含分账商户查询返回的具体业务信息 +type QueryLedgerMerRespData struct { + // 分账商户机构号 + OrgId string `json:"org_id"` + // 分账商户机构名称 + OrgName string `json:"org_name"` + // 拉卡拉内部商户号 + MerInnerNo string `json:"mer_inner_no"` + // 银联商户号 + MerCupNo string `json:"mer_cup_no"` + // 最低分账比例(百分比,支持2位精度),取值说明:70或70.50 + SplitLowestRatio string `json:"split_lowest_ratio"` + // 商户分账状态,取值说明:VALID启用,INVALID禁用 + SplitStatus string `json:"split_status"` + // 分账范围,取值说明:ALL:全部交易分账(商户所有交易默认待分账),MARK:标记交易分账(只有带分账标识交易待分账,其余交易正常结算),默认:MARK + SplitRange string `json:"split_range"` + // 分账依据,取值说明:TR或空:交易分账,BA:余额分账,默认:TR交易分账 + SepFundSource string `json:"sep_fund_source"` + // 平台ID,取值说明:如果商户和绑定平台分账,返回平台ID + PlatformId string `json:"platform_id"` + // 分账发起方式,取值说明:AUTO:自动规则分账,POINTRULE:指定规则分账,MANUAL:手动规则分账 + SplitLaunchMode string `json:"split_launch_mode"` + // 分账规则来源,取值说明:MER:商户分账规则,PLATFORM:平台分账规则 + SplitRuleSource string `json:"split_rule_source"` + // 已绑定接收方列表 + BindRelations []BindRelation `json:"bind_relations"` +} + +// BindRelation 已绑定接收方信息结构体 +// 用于表示分账商户已绑定的接收方信息 +type BindRelation struct { + // 拉卡拉内部商户号 + MerInnerNo string `json:"mer_inner_no"` + // 银联商户号 + MerCupNo string `json:"mer_cup_no"` + // 接收方编号 + ReceiverNo string `json:"receiver_no"` + // 接收方编号名称 + ReceiverName string `json:"receiver_name"` +} + +// SuccessOrFail 判断分账商户查询请求是否成功 +// 成功条件:响应码为000000 +// 返回值:true表示成功,false表示失败 +func (resp *QueryLedgerMerResponse) SuccessOrFail() bool { + return resp.Code == "000000" +} \ No newline at end of file diff --git a/model/refund.go b/model/refund.go new file mode 100644 index 0000000..b16fba7 --- /dev/null +++ b/model/refund.go @@ -0,0 +1,70 @@ +package model + +import "github.com/gogf/gf/v2/os/gtime" + +// Refund 退款请求结构体 +type Refund struct { + ReqTime string `json:"req_time"` // 请求时间,格式:YYYYMMDDHHMMSS + Version string `json:"version"` // 接口版本号,固定值"3.0" + ReqData *RefundReqData `json:"req_data"` // 请求业务数据 +} + +// RefundReqData 退款请求业务数据 +type RefundReqData struct { + MerchantNo string `json:"merchant_no"` // 商户号,拉卡拉分配的商户号,String(32),必填 + TermNo string `json:"term_no"` // 终端号,拉卡拉分配的业务终端号,String(32),必填 + OutTradeNo string `json:"out_trade_no"` // 商户请求流水号,商户系统唯一,String(32),必填 + RefundAmount string `json:"refund_amount"` // 退款金额,单位分,整数型字符,String(12),必填 + RefundAccMode string `json:"refund_acc_mode"` // 退款账户模式,String(2),必填,00-调用户余额 65-调商户余额 66-调终端余额 30-调账户 + LocationInfo struct { + RequestIp string `json:"request_ip"` // 请求方IP地址,请求方的IP地址,存在必填,格式如36.45.36.95,String(64),必填 + } `json:"location_info"` // 地址位置信息,Object,必填 + NotifyUrl string `json:"notify_url"` // 后台通知地址,交易结果通知地址,String(128),选填 + OriginLogNo string `json:"origin_log_no"` // 拉卡拉对账单流水号,正常退款的拉卡拉对账单流水号,String(14),选填 + OriginOutTradeNo string `json:"origin_out_trade_no"` // 原始交易商户流水号,String(32),选填 + OriginTradeNo string `json:"origin_trade_no"` // 原交易拉卡拉交易订单号,String(32),选填 + RefundSplitMsg string `json:"refund_split_msg"` // 退款分账状态,String(2),选填,00-为默认,01-为分账;分账交易退款必须填写。需要退款上送该笔的分账状态,为分账时,是退分账前处理,还是退分账后处理 +} + +// NewRefund 创建退款请求 +func NewRefund(param *RefundReqData) *Refund { + return &Refund{ + ReqTime: gtime.Now().Format("YmdHis"), + Version: "3.0", + ReqData: param, + } +} + +// RefundResponse 退款响应结构体 +type RefundResponse struct { + Code string `json:"code"` // 响应码,"000000"表示成功 + Msg string `json:"msg"` // 响应信息,对响应码的文字描述 + RespData RefundRespData `json:"resp_data"` // 响应业务数据 + RespTime string `json:"resp_time"` // 响应时间 +} + +// RefundRespData 退款响应业务数据 +type RefundRespData struct { + TradeState string `json:"trade_state"` // 交易状态,String(15),必填,INIT-初始化(需商户确认结果);SUCCESS-交易成功;FAIL-交易失败;REFUND-交易退款中(需商户确认结果);PROCESSING-交易处理中(需商户确认结果);TIMEOUT-请求超时(需商户确认结果);EXCEPTION-异常(失败) + RefundType string `json:"refund_type"` // 退款模式,String(20),必填 + MerchantNo string `json:"merchant_no"` // 商户号,拉卡拉分配的商户号,String(32),必填 + OutTradeNo string `json:"out_trade_no"` // 商户请求流水号,请求报文中的商户请求流水号,String(32),必填 + TradeNo string `json:"trade_no"` // 拉卡拉交易流水号,String(32),必填 + LogNo string `json:"log_no"` // 拉卡拉对账单流水号,String(14),必填 + AccType string `json:"acc_type"` // 账户类型,String(32),必填 + TotalAmount string `json:"total_amount"` // 交易金额,单位分,整数型字符,String(12),必填 + RefundAmount string `json:"refund_amount"` // 申请退款金额,单位分,整数型字符,String(12),必填 + PayedAmount string `json:"payed_amount"` // 实际退款金额,单位分,整数型字符,String(12),必填 + TradeTime string `json:"trade_time"` // 退款时间,实际退款时间,格式:yyyyMMddHHmmss,String(14),选填 + OriginLogNo string `json:"origin_log_no"` // 原拉卡拉对账单流水号,原交易的拉卡拉对账单流水号,String(14),选填 + OriginOutTradeNo string `json:"origin_out_trade_no"` // 原商户请求流水号,原交易中的商户请求流水号,String(32),选填 + OriginTradeNo string `json:"origin_trade_no"` // 原交易拉卡拉交易订单号,String(32),选填 + UpCouponInfo string `json:"up_coupon_info"` // 银联优惠券信息,目标字段,单位是银联侧返回的四部分内容:{"fundChannel": "BOC", "amount": "10"},String(500),选填 + TanteInfo string `json:"tante_info"` // 淘方信息,目标字段,数据是淘方侧返回的四部分内容:{"fundChannel": "BOC", "amount": "10"},String(32),选填 + ChannelRetDesc string `json:"channel_ret_desc"` // 渠道返回描述,String,必填,codeMsg: "R000000-成功", "R011122-渠道处理超时" +} + +// SuccessOrFail 判断退款交易是否成功 +func (r *RefundResponse) SuccessOrFail() bool { + return r.Code == "000000" +} \ No newline at end of file diff --git a/model/separate.go b/model/separate.go new file mode 100644 index 0000000..32700de --- /dev/null +++ b/model/separate.go @@ -0,0 +1,43 @@ +package model + +type SeparateRequest struct { + ReqData *SeparateReqData `json:"req_data"` // 请求数据 + Version string `json:"version"` // 版本号 + ReqTime string `json:"req_time"` // 请求时间 +} + +type SeparateReqData struct { + MerchantNo string `json:"merchant_no"` // 商户号 + LogDate string `json:"log_date"` // 交易日期 yyyyMMdd,查清结算用 + LogNo string `json:"log_no"` // 拉卡拉对账单流水号 + OutSeparateNo string `json:"out_separate_no"` // 商户分账指令流水号 + TotalAmt string `json:"total_amt"` // 分账总金额 [单位:分] + LklOrgNo string `json:"lkl_org_no"` // 拉卡拉机构编号 非必填 + CalType string `json:"cal_type"` // 分账计算类型 0- 按照指定金额,1- 按照指定比例。默认 0 非必填 + SeparateType string `json:"separate_type"` + NotifyUrl string `json:"notify_url"` // 回调地址 分账,分账撤销或分账回退时,是异步接口。通过该地址通知商户最终处理结果。不传时,不回调 + RecvDatas []*SeparateRecvDatas `json:"recv_datas"` // 分账接收数据对象 分账接收方编号必须已创建 +} +type SeparateRecvDatas struct { + RecvMerchantNo string `json:"recv_merchant_no,omitempty"` // 接收方商户号 分账接收方商户号,分给自己时使用,需和merchantNo一样,否则检查报错;分账接收方商户号 和 分账接收方 只能填写一个。 + SeparateValue string `json:"separate_value"` // 分账数值 calType为0时,按照固定金额分账,单位:分 calType为1时,按照比例分账,单位:百分比的小数值,比如0.55 (55%) + RecvNo string `json:"recv_no,omitempty"` // 分账接收方编号 分账接收方编号, 分给他人时使用;分账接收方商户号 和 分账接收方 只能填写一个。 +} + +type SeparateResponse struct { + Msg string `json:"msg"` + RespTime string `json:"resp_time"` + Code string `json:"code"` + RespData struct { + TotalAmt string `json:"total_amt"` // 分账总金额 单位:分 + LogDate string `json:"log_date"` // 拉卡拉订单日期 posp日期,yyyyMMdd,查清结算用 + SeparateNo string `json:"separate_no"` // 分账指令流水号 分账系统生成唯一流水 + LogNo string `json:"log_no"` // 拉卡拉对账单流水号 请求透返 + OutSeparateNo string `json:"out_separate_no"` // 商户订单号 请求报文中的商户外部订单号 + Status string `json:"status"` // 分账状态 处理中:PROCESSING, 已受理:ACCEPTED, 成功:SUCCESS, 失败:FAIL + } `json:"resp_data"` +} + +func (s *SeparateResponse) SuccessOrFail() bool { + return s.Code == "SACS0000" +} diff --git a/model/splitBalanceQuery.go b/model/splitBalanceQuery.go new file mode 100644 index 0000000..0c87aca --- /dev/null +++ b/model/splitBalanceQuery.go @@ -0,0 +1,39 @@ +package model + +// SplitBalanceRequest 可分账金额查询请求结构体 +// 用于查询指定交易的可分账金额信息 +// 拉卡拉SDK接口文档:可分账金额查询接口 + +type SplitBalanceRequest struct { + ReqData *SplitBalanceReqData `json:"req_data"` // 请求业务数据 + Version string `json:"version"` // 接口版本号 + ReqTime string `json:"req_time"` // 请求时间,格式为yyyyMMddHHmmss +} + +// SplitBalanceReqData 可分账金额查询请求业务数据结构体 +// 包含可分账金额查询所需的查询参数 + +type SplitBalanceReqData struct { + MerchantNo string `json:"merchant_no"` // 商户号,必传,长度32 + LogDate string `json:"log_date"` // 拉卡拉对账单交易日期,必传,长度8,格式为yyyyMMdd,用于查询结算 + LogNo string `json:"log_no"` // 拉卡拉对账单流水号,必传,长度14,对应pos流水号,用于查询结算 +} +// SplitBalanceResponse 可分账金额查询响应结构体 +// 包含可分账金额查询的结果信息 + +type SplitBalanceResponse struct { + Msg string `json:"msg"` // 响应消息,描述响应结果 + RespTime string `json:"resp_time"` // 响应时间,格式为yyyyMMddHHmmss + Code string `json:"code"` // 响应码,SACS0000表示成功 + RespData struct { + MerchantNo string `json:"merchant_no"` // 商户号,请求返回 + TotalSeparateAmt string `json:"total_separate_amt"` // 总分账金额,必传,长度15,单位为分 + CanSeparateAmt string `json:"can_separate_amt"` // 可分账金额,必传,长度15,单位为分 + LogDate string `json:"log_date"` // 拉卡拉对账单交易日期,必传,长度8,格式为yyyyMMdd + LogNo string `json:"log_no"` // 拉卡拉对账单流水号,必传,长度14,请求返回 + } `json:"resp_data"` // 响应业务数据,当code为SACS0000时返回 +} + +func (s *SplitBalanceResponse) SuccessOrFail() bool { + return s.Code == "SACS0000" +} diff --git a/model/tradeQuery.go b/model/tradeQuery.go new file mode 100644 index 0000000..4514b96 --- /dev/null +++ b/model/tradeQuery.go @@ -0,0 +1,73 @@ +package model + +// TradeQuery 交易查询请求结构体 +type TradeQuery struct { + ReqTime string `json:"req_time"` // 请求时间 + Version string `json:"version"` // API版本号 + OutOrgCode string `json:"out_org_code"` // 外部机构码 + ReqData *TradeQueryReqData `json:"req_data"` // 请求数据 +} + +// TradeQueryReqData 交易查询请求数据结构体 +type TradeQueryReqData struct { + MerchantNo string `json:"merchant_no"` // 商户号,必传 + TermNo string `json:"term_no"` // 终端号,必传 + OutTradeNo string `json:"out_trade_no"` // 商户交易流水号,条件必传,与trade_no必传其一 +} + +// TradeQueryResponse 交易查询响应结构体 +type TradeQueryResponse struct { + Msg string `json:"msg"` // 响应消息 + RespTime string `json:"resp_time"` // 响应时间 + Code string `json:"code"` // 响应码,000000表示成功 + RespData struct { + MerchantNo string `json:"merchant_no"` // 商户号,必传 + OutTradeNo string `json:"out_trade_no"` // 商户请求流水号,必传 + TradeNo string `json:"trade_no"` // 拉卡拉商户订单号,必传 + LogNo string `json:"log_no"` // 拉卡拉对账流水号,必传 + TradeMainType string `json:"trade_main_type"` // 交易大类,条件必传(PREORDER-主扫,MICROPAY-被扫,REFUND-退款,CANCEL-撤销) + SplitAttr string `json:"split_attr"` // 拆单属性,条件必传(M-主单,S-子单) + SplitInfo []struct { + SubTradeNo string `json:"sub_trade_no"` // 子单交易流水号,必传 + SubLogNo string `json:"sub_log_no"` // 子单对账单单流水号,必传 + OutSubTradeNo string `json:"out_sub_trade_no"` // 外部子交易流水号,必传 + MerchantNo string `json:"merchant_no"` // 商户号,必传 + MerchantName string `json:"merchant_name"` // 商户名称,必传 + TermNo string `json:"term_no"` // 终端号,必传 + Amount string `json:"amount"` // 金额,必传(单位分) + } `json:"split_info"` // 拆单信息,条件必传(如果查询订单是主单,则返回) + RefundSplitInfo []struct { + OutSubTradeNo string `json:"out_sub_trade_no"` // 外部子退款交易流水号,必传 + MerchantNo string `json:"merchant_no"` // 商户号,必传 + TermNo string `json:"term_no"` // 终端号,必传 + RefundAmount string `json:"refund_amount"` // 申请退款金额,必传(单位分) + SubTradeNo string `json:"sub_trade_no"` // 拉卡分子交易流水号,条件必传 + SubLogNo string `json:"sub_log_no"` // 对账单子流水号,条件必传 + TradeState string `json:"trade_state"` // 子交易状态,条件必传(SUCCESS-交易成功 FAIL-交易失败) + ResultCode string `json:"result_code"` // 处理结果码,条件必传 + ResultMsg string `json:"result_msg"` // 处理描述,条件必传 + } `json:"refund_split_info"` // 合单退款拆单信息,条件必传(如果查询订单是退款主单,则返回) + AccTradeNo string `json:"acc_trade_no"` // 账户端交易订单号,必传 + AccountType string `json:"account_type"` // 钱包类型,必传(微信: WECHAT 支付宝: ALIPAY 银联: UQRCODEPAY 翼支付: BESTPAY 苏宁支付: SUNING) + TradeState string `json:"trade_state"` // 交易状态,必传(INIT-初始化 CREATE-下单成功 SUCCESS-交易成功 FAIL-交易失败 DEAL-交易处理中 UNKNOWN-未知状态 CLOSE-订单关闭 PART_REFUND-部分退款 REFUND-全部退款) + TradeStateDesc string `json:"trade_state_desc"` // 交易状态描述,条件必传 + TotalAmount string `json:"total_amount"` // 订单金额,必传(单位分) + PayerAmount string `json:"payer_amount"` // 付款人实付金额,条件必传(单位分) + AccSettleAmount string `json:"acc_settle_amount"` // 账户端结算金额,条件必传(单位分) + AccMdiscountAmount string `json:"acc_mdiscount_amount"` // 商户侧优惠金额,条件必传(单位分) + AccDiscountAmount string `json:"acc_discount_amount"` // 账户端优惠金额,条件必传(单位分) + AccOtherDiscountAmount string `json:"acc_other_discount_amount"` // 账户端其它优惠金额,条件必传(单位分) + TradeTime string `json:"trade_time"` // 交易完成时间,条件必传(yyyyMMddHHmmss) + UserId1 string `json:"user_id1"` // 用户标识1,条件必传(微信sub_open_id 支付宝buyer_login_id 买家支付账号) + UserId2 string `json:"user_id2"` // 用户标识2,条件必传(微信open_id 支付宝buyer_user_id 银user_id) + BankType string `json:"bank_type"` // 付款银行,条件必传 + CardType string `json:"card_type"` // 银行卡类型,条件必传(00: 借记卡 01: 贷记卡 02: 微信零钱 03: 支付宝花呗 04: 支付宝其他 05: 数字货币 06: 拉卡拉支付账户 99: 未知) + AccActivityId string `json:"acc_activity_id"` // 活动ID,条件必传(在账户端商户后台配置的批次ID) + TradeReqDate string `json:"trade_req_date"` // 交易请求日期,必传(yyyyMMdd) + AccRespFields map[string]interface{} `json:"acc_resp_fields"` // 账户端返回信息域,条件必传 + } `json:"resp_data"` // 响应数据 +} + +func (t *TradeQueryResponse) SuccessOrFail() bool { + return t.Code == "000000" +} diff --git a/model/uploadFile.go b/model/uploadFile.go new file mode 100644 index 0000000..be9c726 --- /dev/null +++ b/model/uploadFile.go @@ -0,0 +1,36 @@ +package model + +type UploadFileRequest struct { + ReqData *UploadFileReqData `json:"reqData"` + Ver string `json:"ver"` + Timestamp string `json:"timestamp"` + ReqId string `json:"reqId"` +} + +type UploadFileReqData struct { + Version string `json:"version"` + OrderNo string `json:"orderNo"` + AttType string `json:"attType"` + AttExtName string `json:"attExtName"` + AttContext string `json:"attContext"` + OrgCode string `json:"orgCode"` +} + +type UploadFileResponse struct { + CmdRetCode string `json:"cmdRetCode"` + ReqId string `json:"reqId"` + RespData struct { + AttType string `json:"attType"` + OrderNo string `json:"orderNo"` + OrgCode string `json:"orgCode"` + AttFileId string `json:"attFileId"` + } `json:"respData"` + RetCode string `json:"retCode"` + RetMsg string `json:"retMsg"` + Timestamp int64 `json:"timestamp"` + Ver string `json:"ver"` +} + +func (u *UploadFileResponse) SuccessOrFail() bool { + return u.RetCode == "000000" +} diff --git a/model/withdraw.go b/model/withdraw.go new file mode 100644 index 0000000..def784d --- /dev/null +++ b/model/withdraw.go @@ -0,0 +1,67 @@ +package model + +// WithdrawRequest 提现请求结构体 +// 用于向拉卡拉接口发送提现请求 +// 包含请求头信息和业务数据 +type WithdrawRequest struct { + // 请求业务数据 + ReqData *WithdrawReqData `json:"req_data"` + // 接口版本号 + Version string `json:"version"` + // 请求时间,格式为yyyyMMddHHmmss + ReqTime string `json:"req_time"` +} + +// WithdrawReqData 提现请求业务数据结构体 +// 包含提现所需的具体业务参数 +// 用于申请账户资金提现 +type WithdrawReqData struct { + // bmcp机构号,必传,最大长度32 + OrgNo string `json:"org_no"` + // 商户号,必传,最大长度32,822商户号,SR分账接收方编号 + MerchantNo string `json:"merchant_no"` + // 提现金额(单位:元),必传,最大长度32 + DrawAmt string `json:"draw_amt"` + // 通知地址,非必传,最大长度256 + NotifyUrl string `json:"notify_url"` + // 商户订单号(商户系统唯一),非必传,最大长度256 + MerOrderNo string `json:"mer_order_no"` + // 账号(若该参数上送,则payType将无效),非必传,最大长度32 + PayNo string `json:"pay_no"` + // 账号类型(01:收款账户,04:分账接收方账户)未上送则默认01,必传,最大长度32,分账接收方提现时需填04 + PayType string `json:"pay_type"` + // 备注信息,非必传,最大长度64 + Remark string `json:"remark"` + // 摘要,非必传,最大长度64 + Summary string `json:"summary"` + // 结算银行ID,非必传,最大长度32 + BankId string `json:"bank_id"` +} + +// WithdrawResponse 提现响应结构体 +// 拉卡拉接口返回的提现响应数据 +// 包含响应状态码、消息和业务数据 +type WithdrawResponse struct { + // 响应状态码,000000表示成功 + Code string `json:"code"` + // 响应消息 + Msg string `json:"msg"` + // 响应业务数据,当code为000000时返回 + RespData *WithdrawRespData `json:"resp_data"` +} + +// WithdrawRespData 提现响应业务数据结构体 +// 包含提现返回的具体业务信息 +type WithdrawRespData struct { + // 提现流水号,必传 + DrawJnl string `json:"draw_jnl"` + // 商户订单号,必传 + MerOrderNo string `json:"mer_order_no"` +} + +// SuccessOrFail 判断提现请求是否成功 +// 成功条件:响应码为000000 +// 返回值:true表示成功,false表示失败 +func (resp *WithdrawResponse) SuccessOrFail() bool { + return resp.Code == "000000" +} \ No newline at end of file