From 10c46fec239f05ef8cd0e63dcde6945576e1e6fe Mon Sep 17 00:00:00 2001 From: Jerry Date: Thu, 2 Apr 2026 16:39:57 +0800 Subject: [PATCH 1/2] v1.1.8 --- CLAUDE.md | 104 +++++ common.go | 2 +- doc/mini.md | 80 ++++ doc/mini_api_coverage.md | 239 +++++++++++ mini/data_analysis.go | 243 +++++++++++ mini/delivery.go | 238 +++++++++++ mini/domain.go | 144 +++++++ mini/express.go | 231 ++++++++++ mini/image_process.go | 398 +++++++++++++++++ mini/management.go | 94 ++++ mini/model.go | 871 ++++++++++++++++++++++++++++++++++++++ mini/qrcode.go | 131 ++++++ mini/search.go | 46 ++ mini/security.go | 102 +++++ mini/service_market.go | 33 ++ mini/subscribe_message.go | 139 ++++++ mini/url_link.go | 145 +++++++ release_note.md | 18 + 18 files changed, 3257 insertions(+), 1 deletion(-) create mode 100644 CLAUDE.md create mode 100644 doc/mini_api_coverage.md create mode 100644 mini/data_analysis.go create mode 100644 mini/delivery.go create mode 100644 mini/domain.go create mode 100644 mini/express.go create mode 100644 mini/image_process.go create mode 100644 mini/management.go create mode 100644 mini/qrcode.go create mode 100644 mini/search.go create mode 100644 mini/security.go create mode 100644 mini/service_market.go create mode 100644 mini/subscribe_message.go create mode 100644 mini/url_link.go diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..d0bdc97 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,104 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## 项目概述 + +这是一个微信 Golang SDK,提供微信小程序、公众号、开放平台三个模块的服务端 API 封装。 + +## 常用命令 + +### 测试 +```bash +# 运行所有测试 +go test -v ./... + +# 测试特定模块 +go test -v ./mini +go test -v ./public +go test -v ./open + +# 运行单个测试文件 +go test -v ./mini/mini_test.go +``` + +### 构建 +```bash +# 安装依赖 +go mod tidy + +# 验证模块 +go mod verify +``` + +## 代码架构 + +### 三大模块结构 + +项目按微信平台类型划分为三个独立模块,每个模块都遵循相同的设计模式: + +- **mini/** - 微信小程序 SDK +- **public/** - 微信公众号 SDK +- **open/** - 微信开放平台 SDK + +每个模块的核心结构: +- `SDK` 结构体:包含 Appid、Secret、accessToken、自动刷新机制 +- 自动管理 AccessToken:通过 `autoManageToken` 参数控制,使用稳定版接口 +- Debug 开关:通过 `DebugSwitch` 控制日志输出 +- HTTP 客户端:基于 `xhttp.Client`,所有请求带 `Request-ID` header + +### 公共配置 + +- `common.go` - 定义全局常量、版本号、Host 映射 +- 支持多个 API Host:默认、上海、深圳、香港 +- 统一错误处理:errcode != 0 时返回 error + +### 依赖库 + +核心依赖来自 `go-pay` 生态: +- `go-pay/xhttp` - HTTP 客户端 +- `go-pay/xlog` - 日志库 +- `go-pay/bm` - BodyMap 请求体构建 +- `go-pay/util` - 工具函数(加密、随机字符串等) +- `go-pay/smap` - 线程安全 Map(开放平台用于管理多用户 token) + +## 开发规范 + +### 新增接口实现 + +1. **接口定义**:在对应模块的 `model.go` 中定义请求和响应结构体 +2. **常量定义**:如需要,在模块内定义 API 路径常量 +3. **方法实现**: + - 方法接收器为 `(s *SDK)` + - 第一个参数为 `ctx context.Context` + - 使用 `doRequestGet` 或 `doRequestPost` 发起请求 + - 检查响应的 `errcode`,非 0 时返回 error +4. **测试文件**:在 `*_test.go` 中添加测试用例 +5. **文档更新**:在 `doc/` 目录对应的 `.md` 文件中更新 API 列表 + +### AccessToken 管理 + +- 小程序和公众号:使用稳定版接口 `getStableAccessToken()`,自动刷新 +- 开放平台:支持多用户 token 管理,使用 `openidAccessTokenMap` 存储 +- 回调机制:通过 `SetMiniAccessTokenCallback` 等方法设置 token 刷新回调 + +### 请求规范 + +所有 HTTP 请求必须: +- 添加 `Request-ID` header(格式:`{21位随机字符串}-{时间戳}`) +- 使用 `js.UnmarshalBytes` 解析响应 +- Debug 模式下记录请求 URI 和响应内容 + +### 版本发布 + +更新版本时需要同步修改: +1. `common.go` 中的 `Version` 常量 +2. `release_note.md` 添加版本记录 +3. Git tag 格式:`v1.1.x` + +## 参考文档 + +- 微信小程序服务端:https://developers.weixin.qq.com/miniprogram/dev/api-backend/ +- 微信公众号开发文档:https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Overview.html +- 微信开放平台文档:https://developers.weixin.qq.com/doc/oplatform/ +- 项目文档:`doc/mini.md`、`doc/public.md`、`doc/open.md` diff --git a/common.go b/common.go index fb10fb1..d724da1 100644 --- a/common.go +++ b/common.go @@ -12,7 +12,7 @@ const ( HeaderRequestID = "Request-ID" - Version = "v1.1.7" + Version = "v1.1.8" ) const ( diff --git a/doc/mini.md b/doc/mini.md index 14c5fcf..fcbf872 100644 --- a/doc/mini.md +++ b/doc/mini.md @@ -143,6 +143,86 @@ if err != nil { * 获取媒资详细信息:`sdk.MediaAssetGetMedia()` * 获取媒资播放链接:`sdk.MediaAssetGetMediaLink()` * 删除媒资:`sdk.MediaAssetDeleteMedia()` +* 订阅消息 + * 发送订阅消息:`sdk.SubscribeMessageSend()` + * 获取模板列表:`sdk.GetTemplateList()` + * 获取模板标题列表:`sdk.GetPubTemplateTitleList()` + * 添加模板:`sdk.AddTemplate()` + * 删除模板:`sdk.DeleteTemplate()` + * 获取类目:`sdk.GetCategory()` +* 小程序码 + * 获取小程序码(数量较少):`sdk.GetWxaCode()` + * 获取小程序码(数量极多):`sdk.GetWxaCodeUnlimit()` + * 获取小程序二维码:`sdk.CreateWxaQRCode()` +* URL 跳转 + * 生成 URL Scheme:`sdk.GenerateScheme()` + * 查询 URL Scheme:`sdk.QueryScheme()` + * 生成 URL Link:`sdk.GenerateUrlLink()` + * 查询 URL Link:`sdk.QueryUrlLink()` + * 生成 Short Link:`sdk.GenerateShortLink()` +* 内容安全 + * 文本内容安全检测:`sdk.MsgSecCheck()` + * 图片内容安全检测:`sdk.ImgSecCheck()` + * 音视频异步检测:`sdk.MediaCheckAsync()` +* 数据分析 + * 获取用户访问数据概况:`sdk.GetDailySummary()` + * 获取访问数据日趋势:`sdk.GetDailyVisitTrend()` + * 获取访问数据周趋势:`sdk.GetWeeklyVisitTrend()` + * 获取访问数据月趋势:`sdk.GetMonthlyVisitTrend()` + * 获取用户日留存:`sdk.GetDailyRetain()` + * 获取用户周留存:`sdk.GetWeeklyRetain()` + * 获取用户月留存:`sdk.GetMonthlyRetain()` + * 获取访问页面数据:`sdk.GetVisitPage()` + * 获取用户画像分布:`sdk.GetUserPortrait()` + * 获取性能数据:`sdk.GetPerformanceData()` + * 获取访问分布数据:`sdk.GetVisitDistribution()` +* 图像处理 + * 图片智能裁剪:`sdk.AiCrop()` + * 条码/二维码识别:`sdk.ScanQRCode()` + * 图片高清化:`sdk.SuperResolution()` + * 身份证OCR识别:`sdk.OcrIdCard()` + * 银行卡OCR识别:`sdk.OcrBankCard()` + * 驾驶证OCR识别:`sdk.OcrDriving()` + * 行驶证OCR识别:`sdk.OcrVehicleLicense()` + * 营业执照OCR识别:`sdk.OcrBusinessLicense()` + * 通用印刷体OCR识别:`sdk.OcrCommon()` + * 车牌OCR识别:`sdk.OcrPlateNumber()` +* 即时配送 + * 拉取已绑定账号:`sdk.DeliveryGetAllAccount()` + * 配送单预下单:`sdk.DeliveryPreAddOrder()` + * 配送单下单:`sdk.DeliveryAddOrder()` + * 配送单增加小费:`sdk.DeliveryAddTips()` + * 配送单取消:`sdk.DeliveryCancelOrder()` + * 配送单查询:`sdk.DeliveryGetOrder()` + * 模拟配送公司更新配送单状态:`sdk.DeliveryMockUpdateOrder()` + * 异常件退回商家商圈:`sdk.DeliveryAbnormalConfirm()` +* 物流助手 + * 生成运单:`sdk.ExpressAddOrder()` + * 取消运单:`sdk.ExpressCancelOrder()` + * 获取所有绑定的物流账号:`sdk.ExpressGetAllAccount()` + * 获取电子面单余额:`sdk.ExpressGetQuota()` + * 查询运单轨迹:`sdk.ExpressGetPath()` + * 获取支持的快递公司列表:`sdk.ExpressGetAllDelivery()` + * 获取打印员:`sdk.ExpressGetPrinter()` + * 配置面单打印员:`sdk.ExpressUpdatePrinter()` + * 获取面单联系人信息:`sdk.ExpressGetContact()` +* 搜索 + * 提交小程序页面:`sdk.SearchSubmitPages()` + * 删除已提交的小程序页面:`sdk.SearchDeletePage()` +* 运维中心 + * 获取域名配置:`sdk.GetDomainInfo()` + * 修改服务器域名:`sdk.ModifyDomain()` + * 设置业务域名:`sdk.SetWebviewDomain()` + * 获取小程序码扫码打开的页面:`sdk.GetQrcodeJumppublish()` + * 设置小程序码扫码打开的页面:`sdk.SetQrcodeJump()` +* 小程序管理 + * 获取小程序基本信息:`sdk.GetAccountBasicInfo()` + * 获取已上传的代码的页面列表:`sdk.GetPage()` + * 获取授权小程序帐号的可选类目:`sdk.GetMiniCategory()` + * 获取小程序的第三方提交代码的页面配置:`sdk.GetExtConfig()` + * 设置小程序的第三方提交代码的页面配置:`sdk.SetExtConfig()` +* 服务市场 + * 调用服务平台提供的服务:`sdk.InvokeService()` ### 微信小程序 公共API diff --git a/doc/mini_api_coverage.md b/doc/mini_api_coverage.md new file mode 100644 index 0000000..1614902 --- /dev/null +++ b/doc/mini_api_coverage.md @@ -0,0 +1,239 @@ +# 微信小程序服务端 API 实现情况 + +## ✅ 已实现模块 + +### 1. 登录 +- ✅ Code2Session - 登录凭证校验 + +### 2. 用户信息 +- ✅ GetPluginOpenPid - 获取插件用户OpenPid +- ✅ GetPaidUnionid - 获取支付后的UnionId(通过微信支付订单号) +- ✅ GetPaidUnionidByTradeNo - 获取支付后的UnionId(通过商户订单号) +- ✅ CheckEncryptedData - 检查加密信息 +- ✅ GetUserEncryptKey - 获取用户EncryptKey +- ✅ GetPhoneNumber - 获取手机号 + +### 3. 开放数据 +- ✅ VerifyDecryptOpenData - 开放数据校验 +- ✅ DecryptOpenData - 开放数据解密 + +### 4. 客服消息 +- ✅ CSMessageGetTempMedia - 获取客服消息内的临时素材 +- ✅ CSMessageSend - 发送客服消息 +- ✅ CSMessageSetTyping - 下发客服当前输入状态 +- ✅ CSMessageUploadTempMedia - 新增图片素材 + +### 5. 统一服务消息 +- ✅ UniformMessageSend - 发送统一服务消息 + +### 6. 订阅消息 ⭐ 新增 +- ✅ SubscribeMessageSend - 发送订阅消息 +- ✅ GetTemplateList - 获取模板列表 +- ✅ GetPubTemplateTitleList - 获取模板标题列表 +- ✅ AddTemplate - 添加模板 +- ✅ DeleteTemplate - 删除模板 +- ✅ GetCategory - 获取类目 + +### 7. 小程序码 ⭐ 新增 +- ✅ GetWxaCode - 获取小程序码(数量较少) +- ✅ GetWxaCodeUnlimit - 获取小程序码(数量极多) +- ✅ CreateWxaQRCode - 获取小程序二维码 + +### 8. URL 跳转 ⭐ 新增 +- ✅ GenerateScheme - 生成 URL Scheme +- ✅ QueryScheme - 查询 URL Scheme +- ✅ GenerateUrlLink - 生成 URL Link +- ✅ QueryUrlLink - 查询 URL Link +- ✅ GenerateShortLink - 生成 Short Link + +### 9. 内容安全 ⭐ 新增 +- ✅ MsgSecCheck - 文本内容安全检测 +- ✅ ImgSecCheck - 图片内容安全检测 +- ✅ MediaCheckAsync - 音视频异步检测 + +### 10. 数据分析 ⭐ 新增 +- ✅ GetDailySummary - 获取用户访问数据概况 +- ✅ GetDailyVisitTrend - 获取访问数据日趋势 +- ✅ GetWeeklyVisitTrend - 获取访问数据周趋势 +- ✅ GetMonthlyVisitTrend - 获取访问数据月趋势 +- ✅ GetDailyRetain - 获取用户日留存 +- ✅ GetWeeklyRetain - 获取用户周留存 +- ✅ GetMonthlyRetain - 获取用户月留存 +- ✅ GetVisitPage - 获取访问页面数据 +- ✅ GetUserPortrait - 获取用户画像分布 +- ✅ GetPerformanceData - 获取性能数据 +- ✅ GetVisitDistribution - 获取访问分布数据 + +### 11. 媒资上传(短剧) +- ✅ MediaAssetSingleFileUpload - 单个文件上传 +- ✅ MediaAssetPullUpload - 拉取上传 +- ✅ MediaAssetGetTask - 查询任务 +- ✅ MediaAssetApplyUpload - 申请分片上传 +- ✅ MediaAssetUploadPart - 上传分片 +- ✅ MediaAssetCommitUpload - 确认上传 + +### 12. 媒资管理(短剧) +- ✅ MediaAssetListMedia - 获取媒资列表 +- ✅ MediaAssetGetMedia - 获取媒资详细信息 +- ✅ MediaAssetGetMediaLink - 获取媒资播放链接 +- ✅ MediaAssetDeleteMedia - 删除媒资 + +### 13. 接口调用凭证 +- ✅ GetAccessToken - 获取接口调用凭据 +- ✅ GetStableAccessToken - 获取稳定版接口调用凭据 + +### 14. 图像处理 ⭐ 新增 +- ✅ AiCrop - 图片智能裁剪 +- ✅ ScanQRCode - 条码/二维码识别 +- ✅ SuperResolution - 图片高清化 +- ✅ OcrIdCard - 身份证OCR识别 +- ✅ OcrBankCard - 银行卡OCR识别 +- ✅ OcrDriving - 驾驶证OCR识别 +- ✅ OcrVehicleLicense - 行驶证OCR识别 +- ✅ OcrBusinessLicense - 营业执照OCR识别 +- ✅ OcrCommon - 通用印刷体OCR识别 +- ✅ OcrPlateNumber - 车牌OCR识别 + +### 15. 即时配送 ⭐ 新增 +- ✅ DeliveryGetAllAccount - 拉取已绑定账号 +- ✅ DeliveryPreAddOrder - 配送单预下单 +- ✅ DeliveryAddOrder - 配送单下单 +- ✅ DeliveryAddTips - 配送单增加小费 +- ✅ DeliveryCancelOrder - 配送单取消 +- ✅ DeliveryGetOrder - 配送单查询 +- ✅ DeliveryMockUpdateOrder - 模拟配送公司更新配送单状态 +- ✅ DeliveryAbnormalConfirm - 异常件退回商家商圈 + +### 16. 物流助手 ⭐ 新增 +- ✅ ExpressAddOrder - 生成运单 +- ✅ ExpressCancelOrder - 取消运单 +- ✅ ExpressGetAllAccount - 获取所有绑定的物流账号 +- ✅ ExpressGetQuota - 获取电子面单余额 +- ✅ ExpressGetPath - 查询运单轨迹 +- ✅ ExpressGetAllDelivery - 获取支持的快递公司列表 +- ✅ ExpressGetPrinter - 获取打印员 +- ✅ ExpressUpdatePrinter - 配置面单打印员 +- ✅ ExpressGetContact - 获取面单联系人信息 + +### 17. 搜索 ⭐ 新增 +- ✅ SearchSubmitPages - 提交小程序页面url及参数信息 +- ✅ SearchDeletePage - 删除已提交的小程序页面 + +### 18. 运维中心 ⭐ 新增 +- ✅ GetDomainInfo - 获取域名配置 +- ✅ ModifyDomain - 修改服务器域名 +- ✅ SetWebviewDomain - 设置业务域名 +- ✅ GetQrcodeJumppublish - 获取小程序码扫码打开的页面 +- ✅ SetQrcodeJump - 设置小程序码扫码打开的页面 + +### 19. 小程序管理 ⭐ 新增 +- ✅ GetAccountBasicInfo - 获取小程序基本信息 +- ✅ GetPage - 获取已上传的代码的页面列表 +- ✅ GetMiniCategory - 获取授权小程序帐号的可选类目 +- ✅ GetExtConfig - 获取小程序的第三方提交代码的页面配置 +- ✅ SetExtConfig - 设置小程序的第三方提交代码的页面配置 + +### 20. 服务市场 ⭐ 新增 +- ✅ InvokeService - 调用服务平台提供的服务 + +--- + +## ❌ 未实现且较少使用的模块 + +### 1. 云开发 (cloudbase) +- 数据库操作(增删改查、聚合、导入导出等) +- 云函数调用 +- 云存储(上传、下载、删除文件) +- 短信发送 +- 其他云开发能力 + +### 2. 城市服务 (cityservice) +- 基础能力(获取城市服务限定页面链接、消息通路等) +- 微信长辈就医 +- 微信就医助手 + +### 3. B2B 支付 +- 商户号进件 +- 支付、退款、查询订单 +- 分账相关 +- 提现相关 + +### 4. 虚拟支付 (VirtualPayment) +- 代币支付、退款 +- 订阅合约管理 +- 投诉处理 +- 广告金充值 +- 提现管理 +- 道具管理 + +### 5. 付费能力 (charge) +- 获取付费能力用量数据 +- 查询购买资源包用量 + +### 6. 直播 +- 创建直播间 +- 获取直播间列表 +- 获取直播间回放 +- 添加管理员 +- 删除管理员 +- 获取管理员列表 +- 添加主播 +- 删除主播 +- 获取主播列表 +- 添加商品 +- 删除商品 +- 获取商品列表 +- 推送商品 +- 下架商品 +- 获取商品状态 +- 获取成员列表 +- 添加成员 +- 删除成员 + +### 7. 小程序直播 +- 创建直播间 +- 获取直播间列表和回放 +- 导入商品 +- 商品管理 +- 成员管理 +- 订阅管理 + +### 8. 广告 +- 获取广告金数据 +- 获取广告收入数据 + +--- + +## 建议实现优先级 + +### ✅ 高优先级(已完成) +1. ✅ **订阅消息** - 小程序消息推送的主要方式 +2. ✅ **小程序码生成** - 分享、推广必备 +3. ✅ **URL Scheme/Link** - 外部跳转小程序 +4. ✅ **内容安全** - 用户生成内容审核 +5. ✅ **数据分析** - 运营数据统计 + +### 🔶 中优先级(特定场景需要) +1. **图像处理** - OCR、图片识别等 +2. **物流助手** - 电商场景 +3. **即时配送** - 外卖、跑腿等场景 +4. **搜索** - SEO优化 +5. **运维中心** - 域名配置等 + +### 🔵 低优先级(特殊业务场景) +1. **云开发** - 使用云开发的项目 +2. **城市服务** - 政务类小程序 +3. **B2B支付** - 企业支付场景 +4. **虚拟支付** - 游戏、虚拟商品 +5. **直播** - 直播电商场景 + +--- + +## 下一步建议 + +请告诉我你想优先实现哪些模块,我可以帮你: +1. 从高优先级模块开始逐个实现 +2. 或者根据你的业务需求选择特定模块 +3. 或者我可以先实现最常用的 5-10 个接口 + +你可以直接告诉我模块名称,比如:"先实现订阅消息和小程序码生成" diff --git a/mini/data_analysis.go b/mini/data_analysis.go new file mode 100644 index 0000000..8a7eeee --- /dev/null +++ b/mini/data_analysis.go @@ -0,0 +1,243 @@ +package mini + +import ( + "context" + "fmt" + + "github.com/go-pay/bm" +) + +// GetDailySummary 获取用户访问小程序数据概况 +// 注意:errcode = 0 为成功 +// beginDate:开始日期,为周一日期,格式为 yyyyMMdd,如:20170306 +// endDate:结束日期,为周日日期,限定查询一周数据,格式为 yyyyMMdd,如:20170312 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/data-analysis/others/getDailySummary.html +func (s *SDK) GetDailySummary(c context.Context, beginDate, endDate string) (result *DailySummaryRsp, err error) { + path := "/datacube/getweanalysisappiddailysummarytrend?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("begin_date", beginDate). + Set("end_date", endDate) + + result = &DailySummaryRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// GetDailyVisitTrend 获取用户访问小程序数据日趋势 +// 注意:errcode = 0 为成功 +// beginDate:开始日期,格式为 yyyyMMdd +// endDate:结束日期,限定查询1天数据,允许设置的最大值为昨日,格式为 yyyyMMdd +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/data-analysis/visit-trend/getDailyVisitTrend.html +func (s *SDK) GetDailyVisitTrend(c context.Context, beginDate, endDate string) (result *VisitTrendRsp, err error) { + path := "/datacube/getweanalysisappiddailyvisittrend?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("begin_date", beginDate). + Set("end_date", endDate) + + result = &VisitTrendRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// GetWeeklyVisitTrend 获取用户访问小程序数据周趋势 +// 注意:errcode = 0 为成功 +// beginDate:开始日期,为周一日期,格式为 yyyyMMdd,如:20170306 +// endDate:结束日期,为周日日期,格式为 yyyyMMdd,如:20170312 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/data-analysis/visit-trend/getWeeklyVisitTrend.html +func (s *SDK) GetWeeklyVisitTrend(c context.Context, beginDate, endDate string) (result *VisitTrendRsp, err error) { + path := "/datacube/getweanalysisappidweeklyvisittrend?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("begin_date", beginDate). + Set("end_date", endDate) + + result = &VisitTrendRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// GetMonthlyVisitTrend 获取用户访问小程序数据月趋势 +// 注意:errcode = 0 为成功 +// beginDate:开始日期,为自然月第一天,格式为 yyyyMMdd,如:20170201 +// endDate:结束日期,为自然月最后一天,格式为 yyyyMMdd,如:20170228 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/data-analysis/visit-trend/getMonthlyVisitTrend.html +func (s *SDK) GetMonthlyVisitTrend(c context.Context, beginDate, endDate string) (result *VisitTrendRsp, err error) { + path := "/datacube/getweanalysisappidmonthlyvisittrend?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("begin_date", beginDate). + Set("end_date", endDate) + + result = &VisitTrendRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// GetDailyRetain 获取用户访问小程序日留存 +// 注意:errcode = 0 为成功 +// beginDate:开始日期,格式为 yyyyMMdd +// endDate:结束日期,限定查询1天数据,允许设置的最大值为昨日,格式为 yyyyMMdd +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/data-analysis/visit-retain/getDailyRetain.html +func (s *SDK) GetDailyRetain(c context.Context, beginDate, endDate string) (result *RetainInfoRsp, err error) { + path := "/datacube/getweanalysisappiddailyretaininfo?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("begin_date", beginDate). + Set("end_date", endDate) + + result = &RetainInfoRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// GetWeeklyRetain 获取用户访问小程序周留存 +// 注意:errcode = 0 为成功 +// beginDate:开始日期,为周一日期,格式为 yyyyMMdd,如:20170306 +// endDate:结束日期,为周日日期,格式为 yyyyMMdd,如:20170312 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/data-analysis/visit-retain/getWeeklyRetain.html +func (s *SDK) GetWeeklyRetain(c context.Context, beginDate, endDate string) (result *RetainInfoRsp, err error) { + path := "/datacube/getweanalysisappidweeklyretaininfo?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("begin_date", beginDate). + Set("end_date", endDate) + + result = &RetainInfoRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// GetMonthlyRetain 获取用户访问小程序月留存 +// 注意:errcode = 0 为成功 +// beginDate:开始日期,为自然月第一天,格式为 yyyyMMdd,如:20170201 +// endDate:结束日期,为自然月最后一天,格式为 yyyyMMdd,如:20170228 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/data-analysis/visit-retain/getMonthlyRetain.html +func (s *SDK) GetMonthlyRetain(c context.Context, beginDate, endDate string) (result *RetainInfoRsp, err error) { + path := "/datacube/getweanalysisappidmonthlyretaininfo?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("begin_date", beginDate). + Set("end_date", endDate) + + result = &RetainInfoRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// GetVisitPage 获取访问页面数据 +// 注意:errcode = 0 为成功 +// beginDate:开始日期,格式为 yyyyMMdd +// endDate:结束日期,限定查询1天数据,允许设置的最大值为昨日,格式为 yyyyMMdd +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/data-analysis/others/getVisitPage.html +func (s *SDK) GetVisitPage(c context.Context, beginDate, endDate string) (result *VisitPageRsp, err error) { + path := "/datacube/getweanalysisappidvisitpage?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("begin_date", beginDate). + Set("end_date", endDate) + + result = &VisitPageRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// GetUserPortrait 获取小程序用户画像分布数据 +// 注意:errcode = 0 为成功 +// beginDate:开始日期,格式为 yyyyMMdd +// endDate:结束日期,开始日期与结束日期相差的天数限定为0/6/29,分别表示查询最近1/7/30天数据,允许设置的最大值为昨日,格式为 yyyyMMdd +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/data-analysis/others/getUserPortrait.html +func (s *SDK) GetUserPortrait(c context.Context, beginDate, endDate string) (result *UserPortraitRsp, err error) { + path := "/datacube/getweanalysisappiduserportrait?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("begin_date", beginDate). + Set("end_date", endDate) + + result = &UserPortraitRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// GetPerformanceData 获取小程序性能数据 +// 注意:errcode = 0 为成功 +// time:查询数据的时间,格式为 yyyyMMdd +// module:查询的模块,枚举值:["networkRam", "networkRom", "networkPackage", "networkCpu"] +// params:查询参数,JSON 字符串 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/data-analysis/others/getPerformanceData.html +func (s *SDK) GetPerformanceData(c context.Context, time, module string, params bm.BodyMap) (result *PerformanceDataRsp, err error) { + path := "/wxa/business/performance/boot?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("time", time). + Set("module", module) + if params != nil { + body.Set("params", params) + } + + result = &PerformanceDataRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// GetVisitDistribution 获取用户小程序访问分布数据 +// 注意:errcode = 0 为成功 +// beginDate:开始日期,格式为 yyyyMMdd +// endDate:结束日期,限定查询1天数据,允许设置的最大值为昨日,格式为 yyyyMMdd +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/data-analysis/others/getVisitDistribution.html +func (s *SDK) GetVisitDistribution(c context.Context, beginDate, endDate string) (result *VisitDistributionRsp, err error) { + path := "/datacube/getweanalysisappidvisitdistribution?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("begin_date", beginDate). + Set("end_date", endDate) + + result = &VisitDistributionRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} diff --git a/mini/delivery.go b/mini/delivery.go new file mode 100644 index 0000000..77aeb71 --- /dev/null +++ b/mini/delivery.go @@ -0,0 +1,238 @@ +package mini + +import ( + "context" + "fmt" + + "github.com/go-pay/bm" +) + +// DeliveryGetAllAccount 拉取已绑定账号 +// 注意:errcode = 0 为成功 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/immediate-delivery/delivery_get_all_account.html +func (s *SDK) DeliveryGetAllAccount(c context.Context) (result *DeliveryGetAllAccountRsp, err error) { + path := "/cgi-bin/express/delivery/open_msg/get_all_account?access_token=" + s.accessToken + body := make(bm.BodyMap) + + result = &DeliveryGetAllAccountRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// DeliveryPreAddOrder 配送单预下单 +// 注意:errcode = 0 为成功 +// deliveryId:配送公司ID +// shopOrderId:商家订单号 +// shopNo:商家门店编号 +// deliverySign:配送签名 +// orderInfo:订单信息,BodyMap 格式 +// cargo:货物信息,BodyMap 格式 +// sender:发件人信息,BodyMap 格式 +// receiver:收件人信息,BodyMap 格式 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/immediate-delivery/delivery_pre_add_order.html +func (s *SDK) DeliveryPreAddOrder(c context.Context, deliveryId, shopOrderId, shopNo, deliverySign string, orderInfo, cargo, sender, receiver bm.BodyMap) (result *DeliveryPreAddOrderRsp, err error) { + path := "/cgi-bin/express/delivery/open_msg/pre_add_order?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("delivery_id", deliveryId). + Set("shoporder_id", shopOrderId). + Set("shop_no", shopNo). + Set("delivery_sign", deliverySign). + Set("order_info", orderInfo). + Set("cargo", cargo). + Set("sender", sender). + Set("receiver", receiver) + + result = &DeliveryPreAddOrderRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// DeliveryAddOrder 配送单下单 +// 注意:errcode = 0 为成功 +// deliveryId:配送公司ID +// shopOrderId:商家订单号 +// shopNo:商家门店编号 +// deliverySign:配送签名 +// deliveryToken:预下单接口返回的token +// orderInfo:订单信息,BodyMap 格式 +// cargo:货物信息,BodyMap 格式 +// sender:发件人信息,BodyMap 格式 +// receiver:收件人信息,BodyMap 格式 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/immediate-delivery/delivery_add_order.html +func (s *SDK) DeliveryAddOrder(c context.Context, deliveryId, shopOrderId, shopNo, deliverySign, deliveryToken string, orderInfo, cargo, sender, receiver bm.BodyMap) (result *DeliveryAddOrderRsp, err error) { + path := "/cgi-bin/express/delivery/open_msg/add_order?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("delivery_id", deliveryId). + Set("shoporder_id", shopOrderId). + Set("shop_no", shopNo). + Set("delivery_sign", deliverySign). + Set("delivery_token", deliveryToken). + Set("order_info", orderInfo). + Set("cargo", cargo). + Set("sender", sender). + Set("receiver", receiver) + + result = &DeliveryAddOrderRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// DeliveryAddTips 配送单增加小费 +// 注意:errcode = 0 为成功 +// shopOrderId:商家订单号 +// shopNo:商家门店编号 +// deliverySign:配送签名 +// waybillId:配送单号 +// openid:下单用户的openid +// tips:小费金额(单位:元) +// remarkContent:备注内容 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/immediate-delivery/delivery_add_tips.html +func (s *SDK) DeliveryAddTips(c context.Context, shopOrderId, shopNo, deliverySign, waybillId, openid string, tips float64, remarkContent string) (result *DeliveryAddTipsRsp, err error) { + path := "/cgi-bin/express/delivery/open_msg/add_tip?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("shoporder_id", shopOrderId). + Set("shop_no", shopNo). + Set("delivery_sign", deliverySign). + Set("waybill_id", waybillId). + Set("openid", openid). + Set("tips", tips). + Set("remark_content", remarkContent) + + result = &DeliveryAddTipsRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// DeliveryCancelOrder 配送单取消 +// 注意:errcode = 0 为成功 +// shopOrderId:商家订单号 +// shopNo:商家门店编号 +// deliverySign:配送签名 +// deliveryId:配送公司ID +// waybillId:配送单号 +// cancelReasonId:取消原因ID +// cancelReason:取消原因 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/immediate-delivery/delivery_cancel_order.html +func (s *SDK) DeliveryCancelOrder(c context.Context, shopOrderId, shopNo, deliverySign, deliveryId, waybillId string, cancelReasonId int, cancelReason string) (result *DeliveryCancelOrderRsp, err error) { + path := "/cgi-bin/express/delivery/open_msg/cancel_order?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("shoporder_id", shopOrderId). + Set("shop_no", shopNo). + Set("delivery_sign", deliverySign). + Set("delivery_id", deliveryId). + Set("waybill_id", waybillId). + Set("cancel_reason_id", cancelReasonId). + Set("cancel_reason", cancelReason) + + result = &DeliveryCancelOrderRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// DeliveryGetOrder 配送单查询 +// 注意:errcode = 0 为成功 +// shopOrderId:商家订单号 +// shopNo:商家门店编号 +// deliverySign:配送签名 +// deliveryId:配送公司ID +// waybillId:配送单号 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/immediate-delivery/delivery_get_order.html +func (s *SDK) DeliveryGetOrder(c context.Context, shopOrderId, shopNo, deliverySign, deliveryId, waybillId string) (result *DeliveryGetOrderRsp, err error) { + path := "/cgi-bin/express/delivery/open_msg/get_order?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("shoporder_id", shopOrderId). + Set("shop_no", shopNo). + Set("delivery_sign", deliverySign). + Set("delivery_id", deliveryId). + Set("waybill_id", waybillId) + + result = &DeliveryGetOrderRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// DeliveryMockUpdateOrder 模拟配送公司更新配送单状态(仅用于测试) +// 注意:errcode = 0 为成功 +// shopOrderId:商家订单号 +// shopNo:商家门店编号 +// actionTime:状态变更时间 +// orderStatus:配送状态 +// actionMsg:附加信息 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/immediate-delivery/delivery_mock_update_order.html +func (s *SDK) DeliveryMockUpdateOrder(c context.Context, shopOrderId, shopNo string, actionTime int64, orderStatus int, actionMsg string) (result *DeliveryMockUpdateOrderRsp, err error) { + path := "/cgi-bin/express/delivery/open_msg/mock_update_order?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("shoporder_id", shopOrderId). + Set("shop_no", shopNo). + Set("action_time", actionTime). + Set("order_status", orderStatus). + Set("action_msg", actionMsg) + + result = &DeliveryMockUpdateOrderRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// DeliveryAbnormalConfirm 异常件退回商家商圈接口 +// 注意:errcode = 0 为成功 +// shopOrderId:商家订单号 +// shopNo:商家门店编号 +// deliverySign:配送签名 +// deliveryId:配送公司ID +// waybillId:配送单号 +// remark:备注 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/immediate-delivery/delivery_abnormal_confirm.html +func (s *SDK) DeliveryAbnormalConfirm(c context.Context, shopOrderId, shopNo, deliverySign, deliveryId, waybillId, remark string) (result *DeliveryAbnormalConfirmRsp, err error) { + path := "/cgi-bin/express/delivery/open_msg/abnormal_confirm?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("shoporder_id", shopOrderId). + Set("shop_no", shopNo). + Set("delivery_sign", deliverySign). + Set("delivery_id", deliveryId). + Set("waybill_id", waybillId). + Set("remark", remark) + + result = &DeliveryAbnormalConfirmRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} diff --git a/mini/domain.go b/mini/domain.go new file mode 100644 index 0000000..38fed73 --- /dev/null +++ b/mini/domain.go @@ -0,0 +1,144 @@ +package mini + +import ( + "context" + "fmt" + + "github.com/go-pay/bm" +) + +// GetDomainInfo 获取域名配置 +// 注意:errcode = 0 为成功 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/domain-management/getDomainInfo.html +func (s *SDK) GetDomainInfo(c context.Context) (result *GetDomainInfoRsp, err error) { + path := "/wxa/getwxadevinfo?access_token=" + s.accessToken + body := make(bm.BodyMap) + + result = &GetDomainInfoRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// ModifyDomain 修改服务器域名 +// 注意:errcode = 0 为成功 +// action:操作类型,add添加,delete删除,set覆盖,get获取 +// requestdomain:request合法域名,当action为get时不需要此字段 +// wsrequestdomain:socket合法域名,当action为get时不需要此字段 +// uploaddomain:uploadFile合法域名,当action为get时不需要此字段 +// downloaddomain:downloadFile合法域名,当action为get时不需要此字段 +// udpdomain:udp合法域名,当action为get时不需要此字段 +// tcpdomain:tcp合法域名,当action为get时不需要此字段 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/domain-management/modifyDomain.html +func (s *SDK) ModifyDomain(c context.Context, action string, requestdomain, wsrequestdomain, uploaddomain, downloaddomain, udpdomain, tcpdomain []string) (result *ModifyDomainRsp, err error) { + path := "/wxa/modify_domain?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("action", action) + + if action != "get" { + if len(requestdomain) > 0 { + body.Set("requestdomain", requestdomain) + } + if len(wsrequestdomain) > 0 { + body.Set("wsrequestdomain", wsrequestdomain) + } + if len(uploaddomain) > 0 { + body.Set("uploaddomain", uploaddomain) + } + if len(downloaddomain) > 0 { + body.Set("downloaddomain", downloaddomain) + } + if len(udpdomain) > 0 { + body.Set("udpdomain", udpdomain) + } + if len(tcpdomain) > 0 { + body.Set("tcpdomain", tcpdomain) + } + } + + result = &ModifyDomainRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// SetWebviewDomain 设置业务域名 +// 注意:errcode = 0 为成功 +// action:操作类型,add添加,delete删除,set覆盖,get获取 +// webviewdomain:业务域名,当action为get时不需要此字段 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/domain-management/setWebviewDomain.html +func (s *SDK) SetWebviewDomain(c context.Context, action string, webviewdomain []string) (result *SetWebviewDomainRsp, err error) { + path := "/wxa/setwebviewdomain?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("action", action) + + if action != "get" && len(webviewdomain) > 0 { + body.Set("webviewdomain", webviewdomain) + } + + result = &SetWebviewDomainRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// GetQrcodeJumppublish 获取小程序码扫码打开的页面 +// 注意:errcode = 0 为成功 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/qrcode-link/qr-code-jump/getQRCodeJumpPublish.html +func (s *SDK) GetQrcodeJumppublish(c context.Context) (result *GetQrcodeJumppublishRsp, err error) { + path := "/wxa/get_qrcode_jumppublish?access_token=" + s.accessToken + body := make(bm.BodyMap) + + result = &GetQrcodeJumppublishRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// SetQrcodeJump 设置小程序码扫码打开的页面 +// 注意:errcode = 0 为成功 +// prefix:二维码规则 +// permitSubRule:是否独占符合二维码前缀匹配规则的所有子规则,1为独占,0为不独占 +// path:小程序功能页面 +// openVersion:测试范围,1为开发版,2为体验版,3为正式版 +// debugUrl:测试链接(选填) +// isEdit:编辑标志位,0表示新增,1表示修改 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/qrcode-link/qr-code-jump/setQRCodeJump.html +func (s *SDK) SetQrcodeJump(c context.Context, prefix string, permitSubRule int, path string, openVersion int, debugUrl []string, isEdit int) (result *SetQrcodeJumpRsp, err error) { + apiPath := "/wxa/qrcodejumpadd?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("prefix", prefix). + Set("permit_sub_rule", permitSubRule). + Set("path", path). + Set("open_version", openVersion). + Set("is_edit", isEdit) + + if len(debugUrl) > 0 { + body.Set("debug_url", debugUrl) + } + + result = &SetQrcodeJumpRsp{} + if _, err = s.DoRequestPost(c, apiPath, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} diff --git a/mini/express.go b/mini/express.go new file mode 100644 index 0000000..cb821c7 --- /dev/null +++ b/mini/express.go @@ -0,0 +1,231 @@ +package mini + +import ( + "context" + "fmt" + + "github.com/go-pay/bm" +) + +// ExpressAddOrder 生成运单 +// 注意:errcode = 0 为成功 +// addSource:订单来源,0为小程序订单,2为App或H5订单 +// wxAppid:App或H5的appid,add_source=2时必填 +// orderId:订单ID,需保证全局唯一 +// openid:用户openid,当add_source=2时无需填写 +// deliveryId:快递公司ID +// bizId:快递公司客户编码 +// customRemark:快递备注信息 +// sender:发件人信息,BodyMap 格式 +// receiver:收件人信息,BodyMap 格式 +// cargo:包裹信息,BodyMap 格式 +// shop:商品信息,BodyMap 格式 +// insured:保价信息,BodyMap 格式 +// service:服务类型,BodyMap 格式 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/express/business/express_add_order.html +func (s *SDK) ExpressAddOrder(c context.Context, addSource int, wxAppid, orderId, openid, deliveryId, bizId, customRemark string, sender, receiver, cargo, shop, insured, service bm.BodyMap) (result *ExpressAddOrderRsp, err error) { + path := "/cgi-bin/express/business/order/add?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("add_source", addSource). + Set("order_id", orderId). + Set("delivery_id", deliveryId). + Set("biz_id", bizId). + Set("sender", sender). + Set("receiver", receiver). + Set("cargo", cargo). + Set("shop", shop) + + if wxAppid != "" { + body.Set("wx_appid", wxAppid) + } + if openid != "" { + body.Set("openid", openid) + } + if customRemark != "" { + body.Set("custom_remark", customRemark) + } + if insured != nil { + body.Set("insured", insured) + } + if service != nil { + body.Set("service", service) + } + + result = &ExpressAddOrderRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// ExpressCancelOrder 取消运单 +// 注意:errcode = 0 为成功 +// orderId:订单ID +// openid:用户openid +// deliveryId:快递公司ID +// waybillId:运单号 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/express/business/express_cancel_order.html +func (s *SDK) ExpressCancelOrder(c context.Context, orderId, openid, deliveryId, waybillId string) (result *ExpressCancelOrderRsp, err error) { + path := "/cgi-bin/express/business/order/cancel?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("order_id", orderId). + Set("openid", openid). + Set("delivery_id", deliveryId). + Set("waybill_id", waybillId) + + result = &ExpressCancelOrderRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// ExpressGetAllAccount 获取所有绑定的物流账号 +// 注意:errcode = 0 为成功 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/express/business/express_get_all_account.html +func (s *SDK) ExpressGetAllAccount(c context.Context) (result *ExpressGetAllAccountRsp, err error) { + path := "/cgi-bin/express/business/account/getall?access_token=" + s.accessToken + body := make(bm.BodyMap) + + result = &ExpressGetAllAccountRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// ExpressGetQuota 获取电子面单余额 +// 注意:errcode = 0 为成功 +// deliveryId:快递公司ID +// bizId:快递公司客户编码 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/express/business/express_get_quota.html +func (s *SDK) ExpressGetQuota(c context.Context, deliveryId, bizId string) (result *ExpressGetQuotaRsp, err error) { + path := "/cgi-bin/express/business/quota/get?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("delivery_id", deliveryId). + Set("biz_id", bizId) + + result = &ExpressGetQuotaRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// ExpressGetPath 查询运单轨迹 +// 注意:errcode = 0 为成功 +// orderId:订单ID +// openid:用户openid +// deliveryId:快递公司ID +// waybillId:运单号 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/express/business/express_get_path.html +func (s *SDK) ExpressGetPath(c context.Context, orderId, openid, deliveryId, waybillId string) (result *ExpressGetPathRsp, err error) { + path := "/cgi-bin/express/business/path/get?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("order_id", orderId). + Set("openid", openid). + Set("delivery_id", deliveryId). + Set("waybill_id", waybillId) + + result = &ExpressGetPathRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// ExpressGetAllDelivery 获取支持的快递公司列表 +// 注意:errcode = 0 为成功 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/express/business/express_get_all_delivery.html +func (s *SDK) ExpressGetAllDelivery(c context.Context) (result *ExpressGetAllDeliveryRsp, err error) { + path := "/cgi-bin/express/business/delivery/getall?access_token=" + s.accessToken + body := make(bm.BodyMap) + + result = &ExpressGetAllDeliveryRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// ExpressGetPrinter 获取打印员 +// 注意:errcode = 0 为成功 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/express/business/express_get_printer.html +func (s *SDK) ExpressGetPrinter(c context.Context) (result *ExpressGetPrinterRsp, err error) { + path := "/cgi-bin/express/business/printer/getall?access_token=" + s.accessToken + body := make(bm.BodyMap) + + result = &ExpressGetPrinterRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// ExpressUpdatePrinter 配置面单打印员 +// 注意:errcode = 0 为成功 +// updateType:更新类型,bind为绑定,unbind为解绑 +// openid:打印员openid,数组格式 +// tagidList:用户标签ID列表 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/express/business/express_update_printer.html +func (s *SDK) ExpressUpdatePrinter(c context.Context, updateType string, openid []string, tagidList string) (result *ExpressUpdatePrinterRsp, err error) { + path := "/cgi-bin/express/business/printer/update?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("update_type", updateType) + + if len(openid) > 0 { + body.Set("openid", openid) + } + if tagidList != "" { + body.Set("tagid_list", tagidList) + } + + result = &ExpressUpdatePrinterRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// ExpressGetContact 获取面单联系人信息 +// 注意:errcode = 0 为成功 +// waybillId:运单号 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/express/business/express_get_contact.html +func (s *SDK) ExpressGetContact(c context.Context, waybillId string) (result *ExpressGetContactRsp, err error) { + path := "/cgi-bin/express/business/contact/get?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("waybill_id", waybillId) + + result = &ExpressGetContactRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} diff --git a/mini/image_process.go b/mini/image_process.go new file mode 100644 index 0000000..cd96c11 --- /dev/null +++ b/mini/image_process.go @@ -0,0 +1,398 @@ +package mini + +import ( + "context" + "encoding/json" + "fmt" + "time" + + "github.com/go-pay/bm" + "github.com/go-pay/util" + "github.com/go-pay/wechat-sdk" + "github.com/go-pay/xhttp" +) + +// AiCrop 图片智能裁剪,根据图片内容返回智能剪裁区域 +// 注意:errcode = 0 为成功 +// imgUrl:要检测的图片 url,传这个则不用传 img 参数 +// img:form-data 中媒体文件标识,有filename、filelength、content-type等信息,传这个则不用传 img_url +// ratios:裁剪比例,可选 "1:1"、"3:4"、"4:3"、"16:9"、"9:16",不传默认为 "3:4" +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/img-ocr-identify/img-proc/aiCrop.html +func (s *SDK) AiCrop(c context.Context, imgUrl string, img *bm.File, ratios []string) (result *AiCropRsp, err error) { + path := "/cv/img/aicrop?access_token=" + s.accessToken + body := make(bm.BodyMap) + + if imgUrl != "" { + body.Set("img_url", imgUrl) + } + if img != nil { + body.SetFormFile("img", img) + } + if len(ratios) > 0 { + body.Set("ratios", ratios) + } + + result = &AiCropRsp{} + if img != nil { + // 使用 multipart/form-data + if _, err = s.DoRequestPostFile(c, path, body, result); err != nil { + return nil, err + } + } else { + // 使用 JSON + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + } + + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// ScanQRCode 本接口提供基于小程序的条码/二维码识别的API +// 注意:errcode = 0 为成功 +// imgUrl:要检测的图片 url,传这个则不用传 img 参数 +// img:form-data 中媒体文件标识,有filename、filelength、content-type等信息,传这个则不用传 img_url +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/img-ocr-identify/img-proc/scanQRCode.html +func (s *SDK) ScanQRCode(c context.Context, imgUrl string, img *bm.File) (result *ScanQRCodeRsp, err error) { + path := "/cv/img/qrcode?access_token=" + s.accessToken + body := make(bm.BodyMap) + + if imgUrl != "" { + body.Set("img_url", imgUrl) + } + if img != nil { + body.SetFormFile("img", img) + } + + result = &ScanQRCodeRsp{} + if img != nil { + // 使用 multipart/form-data + if _, err = s.DoRequestPostFile(c, path, body, result); err != nil { + return nil, err + } + } else { + // 使用 JSON + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + } + + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// SuperResolution 图片高清化,提高图片分辨率,返回处理后的图片二进制内容 +// 注意:errcode = 0 为成功 +// imgUrl:要检测的图片 url,传这个则不用传 img 参数 +// img:form-data 中媒体文件标识,有filename、filelength、content-type等信息,传这个则不用传 img_url +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/img-ocr-identify/img-proc/superResolution.html +func (s *SDK) SuperResolution(c context.Context, imgUrl string, img *bm.File) (image []byte, err error) { + path := "/cv/img/superresolution?access_token=" + s.accessToken + body := make(bm.BodyMap) + + if imgUrl != "" { + body.Set("img_url", imgUrl) + } + if img != nil { + body.SetFormFile("img", img) + } + + if img != nil { + // 使用 multipart/form-data + image, err = s.doRequestPostFileForImage(c, path, body) + } else { + // 使用 JSON + image, err = s.doRequestPostForImage(c, path, body) + } + + if err != nil { + return nil, err + } + return image, nil +} + +// OcrIdCard 身份证OCR识别 +// 注意:errcode = 0 为成功 +// imgUrl:要检测的图片 url,传这个则不用传 img 参数 +// img:form-data 中媒体文件标识,有filename、filelength、content-type等信息,传这个则不用传 img_url +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/img-ocr-identify/ocr/idcard.html +func (s *SDK) OcrIdCard(c context.Context, imgUrl string, img *bm.File) (result *OcrIdCardRsp, err error) { + path := "/cv/ocr/idcard?access_token=" + s.accessToken + body := make(bm.BodyMap) + + if imgUrl != "" { + body.Set("img_url", imgUrl) + } + if img != nil { + body.SetFormFile("img", img) + } + + result = &OcrIdCardRsp{} + if img != nil { + // 使用 multipart/form-data + if _, err = s.DoRequestPostFile(c, path, body, result); err != nil { + return nil, err + } + } else { + // 使用 JSON + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + } + + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// OcrBankCard 银行卡OCR识别 +// 注意:errcode = 0 为成功 +// imgUrl:要检测的图片 url,传这个则不用传 img 参数 +// img:form-data 中媒体文件标识,有filename、filelength、content-type等信息,传这个则不用传 img_url +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/img-ocr-identify/ocr/bankcard.html +func (s *SDK) OcrBankCard(c context.Context, imgUrl string, img *bm.File) (result *OcrBankCardRsp, err error) { + path := "/cv/ocr/bankcard?access_token=" + s.accessToken + body := make(bm.BodyMap) + + if imgUrl != "" { + body.Set("img_url", imgUrl) + } + if img != nil { + body.SetFormFile("img", img) + } + + result = &OcrBankCardRsp{} + if img != nil { + // 使用 multipart/form-data + if _, err = s.DoRequestPostFile(c, path, body, result); err != nil { + return nil, err + } + } else { + // 使用 JSON + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + } + + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// OcrDriving 驾驶证OCR识别 +// 注意:errcode = 0 为成功 +// imgUrl:要检测的图片 url,传这个则不用传 img 参数 +// img:form-data 中媒体文件标识,有filename、filelength、content-type等信息,传这个则不用传 img_url +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/img-ocr-identify/ocr/driving.html +func (s *SDK) OcrDriving(c context.Context, imgUrl string, img *bm.File) (result *OcrDrivingRsp, err error) { + path := "/cv/ocr/driving?access_token=" + s.accessToken + body := make(bm.BodyMap) + + if imgUrl != "" { + body.Set("img_url", imgUrl) + } + if img != nil { + body.SetFormFile("img", img) + } + + result = &OcrDrivingRsp{} + if img != nil { + // 使用 multipart/form-data + if _, err = s.DoRequestPostFile(c, path, body, result); err != nil { + return nil, err + } + } else { + // 使用 JSON + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + } + + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// OcrVehicleLicense 行驶证OCR识别 +// 注意:errcode = 0 为成功 +// imgUrl:要检测的图片 url,传这个则不用传 img 参数 +// img:form-data 中媒体文件标识,有filename、filelength、content-type等信息,传这个则不用传 img_url +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/img-ocr-identify/ocr/vehicleLicense.html +func (s *SDK) OcrVehicleLicense(c context.Context, imgUrl string, img *bm.File) (result *OcrVehicleLicenseRsp, err error) { + path := "/cv/ocr/drivinglicense?access_token=" + s.accessToken + body := make(bm.BodyMap) + + if imgUrl != "" { + body.Set("img_url", imgUrl) + } + if img != nil { + body.SetFormFile("img", img) + } + + result = &OcrVehicleLicenseRsp{} + if img != nil { + // 使用 multipart/form-data + if _, err = s.DoRequestPostFile(c, path, body, result); err != nil { + return nil, err + } + } else { + // 使用 JSON + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + } + + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// OcrBusinessLicense 营业执照OCR识别 +// 注意:errcode = 0 为成功 +// imgUrl:要检测的图片 url,传这个则不用传 img 参数 +// img:form-data 中媒体文件标识,有filename、filelength、content-type等信息,传这个则不用传 img_url +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/img-ocr-identify/ocr/businessLicense.html +func (s *SDK) OcrBusinessLicense(c context.Context, imgUrl string, img *bm.File) (result *OcrBusinessLicenseRsp, err error) { + path := "/cv/ocr/bizlicense?access_token=" + s.accessToken + body := make(bm.BodyMap) + + if imgUrl != "" { + body.Set("img_url", imgUrl) + } + if img != nil { + body.SetFormFile("img", img) + } + + result = &OcrBusinessLicenseRsp{} + if img != nil { + // 使用 multipart/form-data + if _, err = s.DoRequestPostFile(c, path, body, result); err != nil { + return nil, err + } + } else { + // 使用 JSON + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + } + + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// OcrCommon 通用印刷体OCR识别 +// 注意:errcode = 0 为成功 +// imgUrl:要检测的图片 url,传这个则不用传 img 参数 +// img:form-data 中媒体文件标识,有filename、filelength、content-type等信息,传这个则不用传 img_url +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/img-ocr-identify/ocr/common.html +func (s *SDK) OcrCommon(c context.Context, imgUrl string, img *bm.File) (result *OcrCommonRsp, err error) { + path := "/cv/ocr/comm?access_token=" + s.accessToken + body := make(bm.BodyMap) + + if imgUrl != "" { + body.Set("img_url", imgUrl) + } + if img != nil { + body.SetFormFile("img", img) + } + + result = &OcrCommonRsp{} + if img != nil { + // 使用 multipart/form-data + if _, err = s.DoRequestPostFile(c, path, body, result); err != nil { + return nil, err + } + } else { + // 使用 JSON + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + } + + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// OcrPlateNumber 车牌OCR识别 +// 注意:errcode = 0 为成功 +// imgUrl:要检测的图片 url,传这个则不用传 img 参数 +// img:form-data 中媒体文件标识,有filename、filelength、content-type等信息,传这个则不用传 img_url +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/img-ocr-identify/ocr/plateNumber.html +func (s *SDK) OcrPlateNumber(c context.Context, imgUrl string, img *bm.File) (result *OcrPlateNumberRsp, err error) { + path := "/cv/ocr/platenum?access_token=" + s.accessToken + body := make(bm.BodyMap) + + if imgUrl != "" { + body.Set("img_url", imgUrl) + } + if img != nil { + body.SetFormFile("img", img) + } + + result = &OcrPlateNumberRsp{} + if img != nil { + // 使用 multipart/form-data + if _, err = s.DoRequestPostFile(c, path, body, result); err != nil { + return nil, err + } + } else { + // 使用 JSON + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + } + + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// doRequestPostFileForImage 处理返回图片的 POST 文件上传请求 +func (s *SDK) doRequestPostFileForImage(c context.Context, path string, body bm.BodyMap) (image []byte, err error) { + uri := s.Host + path + if s.DebugSwitch == wechat.DebugOn { + s.logger.Debugf("Wechat_Mini_SDK_URI: %s", uri) + } + + req := s.hc.Req(xhttp.TypeMultipartFormData) + req.Header.Add(wechat.HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix())) + res, bs, err := req.Post(uri).SendMultipartBodyMap(body).EndBytes(c) + if err != nil { + return nil, fmt.Errorf("http.request(POST, %s), err:%w", uri, err) + } + + if s.DebugSwitch == wechat.DebugOn { + s.logger.Debugf("Wechat_Mini_SDK_Response: [%d] -> %d bytes", res.StatusCode, len(bs)) + } + + // 判断返回的是图片还是错误信息 + contentType := res.Header.Get("Content-Type") + if contentType == "image/jpeg" || contentType == "image/png" { + return bs, nil + } + + // 如果不是图片,尝试解析错误信息 + ec := &ErrorCode{} + if err = json.Unmarshal(bs, ec); err != nil { + return nil, fmt.Errorf("json.Unmarshal(%s):%w", string(bs), err) + } + if ec.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", ec.Errcode, ec.Errmsg) + } + + return bs, nil +} diff --git a/mini/management.go b/mini/management.go new file mode 100644 index 0000000..61497af --- /dev/null +++ b/mini/management.go @@ -0,0 +1,94 @@ +package mini + +import ( + "context" + "fmt" + + "github.com/go-pay/bm" +) + +// GetAccountBasicInfo 获取小程序基本信息 +// 注意:errcode = 0 为成功 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/basic-info/getAccountBasicInfo.html +func (s *SDK) GetAccountBasicInfo(c context.Context) (result *GetAccountBasicInfoRsp, err error) { + path := "/cgi-bin/account/getaccountbasicinfo?access_token=" + s.accessToken + body := make(bm.BodyMap) + + result = &GetAccountBasicInfoRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// GetPage 获取已上传的代码的页面列表 +// 注意:errcode = 0 为成功 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/code-management/code-management/getPage.html +func (s *SDK) GetPage(c context.Context) (result *GetPageRsp, err error) { + path := "/wxa/get_page?access_token=" + s.accessToken + body := make(bm.BodyMap) + + result = &GetPageRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// GetCategory 获取授权小程序帐号的可选类目 +// 注意:errcode = 0 为成功 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/code-management/category/getCategory.html +func (s *SDK) GetMiniCategory(c context.Context) (result *GetCategoryRsp, err error) { + path := "/wxa/get_category?access_token=" + s.accessToken + body := make(bm.BodyMap) + + result = &GetCategoryRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// GetExtConfig 获取小程序的第三方提交代码的页面配置 +// 注意:errcode = 0 为成功 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/code-management/code-management/getExtConfig.html +func (s *SDK) GetExtConfig(c context.Context) (result *GetExtConfigRsp, err error) { + path := "/wxa/get_ext_config?access_token=" + s.accessToken + + result = &GetExtConfigRsp{} + if _, err = s.DoRequestGet(c, path, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// SetExtConfig 设置小程序的第三方提交代码的页面配置 +// 注意:errcode = 0 为成功 +// extConfig:第三方自定义的配置,JSON 字符串 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/code-management/code-management/setExtConfig.html +func (s *SDK) SetExtConfig(c context.Context, extConfig string) (result *SetExtConfigRsp, err error) { + path := "/wxa/set_ext_config?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("ext_config", extConfig) + + result = &SetExtConfigRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} diff --git a/mini/model.go b/mini/model.go index 4f0bae2..f01d599 100644 --- a/mini/model.go +++ b/mini/model.go @@ -223,3 +223,874 @@ type MediaAssetDeleteMediaRsp struct { Errcode int `json:"errcode"` Errmsg string `json:"errmsg"` } + +// ==================== 订阅消息相关 ==================== + +// SubscribeMessageSendRsp 发送订阅消息响应 +type SubscribeMessageSendRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` +} + +// TemplateListRsp 获取模板列表响应 +type TemplateListRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + Data []*TemplateInfo `json:"data"` +} + +// TemplateInfo 模板信息 +type TemplateInfo struct { + PriTmplId string `json:"priTmplId"` // 模板id + Title string `json:"title"` // 模板标题 + Content string `json:"content"` // 模板内容 + Example string `json:"example"` // 模板示例 + Type int `json:"type"` // 模板类型,2为一次性订阅,3为长期订阅 +} + +// PubTemplateTitleListRsp 获取模板标题列表响应 +type PubTemplateTitleListRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + Count int `json:"count"` + Data []*PubTemplateTitleInfo `json:"data"` +} + +// PubTemplateTitleInfo 模板标题信息 +type PubTemplateTitleInfo struct { + Tid int `json:"tid"` // 模板标题id + Title string `json:"title"` // 模板标题 + Type int `json:"type"` // 模板类型,2为一次性订阅,3为长期订阅 + CategoryId string `json:"categoryId"` // 模板所属类目id +} + +// AddTemplateRsp 添加模板响应 +type AddTemplateRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + PriTmplId string `json:"priTmplId"` // 添加至账号下的模板id +} + +// DeleteTemplateRsp 删除模板响应 +type DeleteTemplateRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` +} + +// CategoryListRsp 获取类目列表响应 +type CategoryListRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + Data []*CategoryInfo `json:"data"` +} + +// CategoryInfo 类目信息 +type CategoryInfo struct { + Id int `json:"id"` // 类目id + Name string `json:"name"` // 类目名称 +} + +// ==================== 小程序码相关 ==================== + +// QRCodeRsp 小程序码/二维码响应(返回图片 Buffer) +type QRCodeRsp struct { + Buffer []byte // 图片 Buffer + Errcode int `json:"errcode,omitempty"` + Errmsg string `json:"errmsg,omitempty"` +} + +// LineColor 小程序码线条颜色 +type LineColor struct { + R string `json:"r"` // red,0-255 + G string `json:"g"` // green,0-255 + B string `json:"b"` // blue,0-255 +} + +// ==================== URL Scheme/Link 相关 ==================== + +// GenerateSchemeRsp 生成 URL Scheme 响应 +type GenerateSchemeRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + Openlink string `json:"openlink"` // 生成的小程序 scheme 码 +} + +// QuerySchemeRsp 查询 URL Scheme 响应 +type QuerySchemeRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + SchemeInfo *SchemeInfo `json:"scheme_info"` + QuotaInfo *SchemeQuotaInfo `json:"quota_info"` +} + +// SchemeInfo Scheme 信息 +type SchemeInfo struct { + Appid string `json:"appid"` // 小程序 appid + Path string `json:"path"` // 小程序页面路径 + Query string `json:"query"` // 小程序页面query + CreateTime int64 `json:"create_time"` // 创建时间 + ExpireTime int64 `json:"expire_time"` // 到期失效时间 + EnvVersion string `json:"env_version"` // 要打开的小程序版本 +} + +// SchemeQuotaInfo Scheme 配额信息 +type SchemeQuotaInfo struct { + RemainVisitQuota int64 `json:"remain_visit_quota"` // URL Scheme(小程序链接)剩余访问次数 +} + +// GenerateUrlLinkRsp 生成 URL Link 响应 +type GenerateUrlLinkRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + UrlLink string `json:"url_link"` // 生成的小程序 URL Link +} + +// QueryUrlLinkRsp 查询 URL Link 响应 +type QueryUrlLinkRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + UrlLinkInfo *UrlLinkInfo `json:"url_link_info"` + UrlLinkQuota *UrlLinkQuota `json:"url_link_quota"` + VisitOpenid []string `json:"visit_openid"` // 访问用户的 openid 列表 +} + +// UrlLinkInfo URL Link 信息 +type UrlLinkInfo struct { + Appid string `json:"appid"` // 小程序 appid + Path string `json:"path"` // 小程序页面路径 + Query string `json:"query"` // 小程序页面query + CreateTime int64 `json:"create_time"` // 创建时间 + ExpireTime int64 `json:"expire_time"` // 到期失效时间 + EnvVersion string `json:"env_version"` // 要打开的小程序版本 +} + +// UrlLinkQuota URL Link 配额信息 +type UrlLinkQuota struct { + RemainVisitQuota int64 `json:"remain_visit_quota"` // URL Link 剩余访问次数 +} + +// GenerateShortLinkRsp 生成 Short Link 响应 +type GenerateShortLinkRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + Link string `json:"link"` // 生成的小程序 Short Link +} + +// ==================== 内容安全相关 ==================== + +// MsgSecCheckRsp 文本内容安全检测响应 +type MsgSecCheckRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + Result *SecCheckResult `json:"result"` + Detail []*SecCheckDetail `json:"detail"` + TraceId string `json:"trace_id"` // 唯一请求标识,标记单次请求 +} + +// SecCheckResult 检测结果 +type SecCheckResult struct { + Suggest string `json:"suggest"` // 建议,有risky、pass、review三种值 + Label int `json:"label"` // 命中标签枚举值,100 正常;10001 广告;20001 时政;20002 色情;20003 辱骂;20006 违法犯罪;20008 欺诈;20012 低俗;20013 版权;21000 其他 +} + +// SecCheckDetail 详细检测结果 +type SecCheckDetail struct { + Strategy string `json:"strategy"` // 策略类型 + Errcode int `json:"errcode"` // 错误码,仅当该值为0时,该项结果有效 + Suggest string `json:"suggest"` // 建议,有risky、pass、review三种值 + Label int `json:"label"` // 命中标签枚举值 + Prob int `json:"prob"` // 0-100,代表置信度,越高代表越有可能属于当前返回的标签(label) + Level int `json:"level"` // 命中的自定义关键词的等级,可能返回1、2、3 + Keyword string `json:"keyword"` // 命中的自定义关键词 +} + +// ImgSecCheckRsp 图片内容安全检测响应 +type ImgSecCheckRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + Result *SecCheckResult `json:"result"` + TraceId string `json:"trace_id"` // 唯一请求标识,标记单次请求 +} + +// MediaCheckAsyncRsp 音视频内容安全异步检测响应 +type MediaCheckAsyncRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + TraceId string `json:"trace_id"` // 唯一请求标识,标记单次请求 +} + +// ==================== 数据分析相关 ==================== + +// DailySummaryRsp 获取用户访问小程序数据概况响应 +type DailySummaryRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + List []*DailySummaryItem `json:"list"` +} + +// DailySummaryItem 日概况数据项 +type DailySummaryItem struct { + RefDate string `json:"ref_date"` // 日期,格式为 yyyyMMdd + VisitTotal int `json:"visit_total"` // 累计用户数 + SharePv int `json:"share_pv"` // 转发次数 + ShareUv int `json:"share_uv"` // 转发人数 + VisitUv int `json:"visit_uv"` // 访问人数 + VisitUvNew int `json:"visit_uv_new"` // 新用户数 + StayTimeUv float64 `json:"stay_time_uv"` // 人均停留时长 (浮点型,单位:秒) + StayTimeSession float64 `json:"stay_time_session"` // 次均停留时长 (浮点型,单位:秒) + VisitDepth float64 `json:"visit_depth"` // 平均访问深度 (浮点型) +} + +// VisitTrendRsp 获取用户访问小程序数据趋势响应 +type VisitTrendRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + List []*VisitTrendItem `json:"list"` +} + +// VisitTrendItem 访问趋势数据项 +type VisitTrendItem struct { + RefDate string `json:"ref_date"` // 日期,格式为 yyyyMMdd + SessionCnt int `json:"session_cnt"` // 打开次数 + VisitPv int `json:"visit_pv"` // 访问次数 + VisitUv int `json:"visit_uv"` // 访问人数 + VisitUvNew int `json:"visit_uv_new"` // 新用户数 + StayTimeUv float64 `json:"stay_time_uv"` // 人均停留时长 (浮点型,单位:秒) + StayTimeSession float64 `json:"stay_time_session"` // 次均停留时长 (浮点型,单位:秒) + VisitDepth float64 `json:"visit_depth"` // 平均访问深度 (浮点型) +} + +// RetainInfoRsp 获取用户访问小程序留存响应 +type RetainInfoRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + RefDate string `json:"ref_date"` // 日期,格式为 yyyyMMdd + VisitUvNew []*RetainItem `json:"visit_uv_new"` // 新增用户留存 + VisitUv []*RetainItem `json:"visit_uv"` // 活跃用户留存 +} + +// RetainItem 留存数据项 +type RetainItem struct { + Key int `json:"key"` // 标识,0开始,0表示当天,1表示1天后,依此类推,key取值分别是:0,1,2,3,4,5,6,7,14,30 + Value float64 `json:"value"` // 留存率 +} + +// VisitPageRsp 获取访问页面数据响应 +type VisitPageRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + RefDate string `json:"ref_date"` // 日期,格式为 yyyyMMdd + List []*VisitPageItem `json:"list"` +} + +// VisitPageItem 访问页面数据项 +type VisitPageItem struct { + PagePath string `json:"page_path"` // 页面路径 + PageVisitPv int `json:"page_visit_pv"` // 访问次数 + PageVisitUv int `json:"page_visit_uv"` // 访问人数 + PageStaytimePv float64 `json:"page_staytime_pv"` // 次均停留时长 + EntrypagePv int `json:"entrypage_pv"` // 进入页次数 + ExitpagePv int `json:"exitpage_pv"` // 退出页次数 + PageSharePv int `json:"page_share_pv"` // 转发次数 + PageShareUv int `json:"page_share_uv"` // 转发人数 +} + +// UserPortraitRsp 获取小程序用户画像分布响应 +type UserPortraitRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + RefDate string `json:"ref_date"` // 日期,格式为 yyyyMMdd + VisitUv int `json:"visit_uv"` // 活跃用户数 + VisitUvNew int `json:"visit_uv_new"` // 新用户数 + Province []*PortraitItem `json:"province,omitempty"` // 省份分布 + City []*PortraitItem `json:"city,omitempty"` // 城市分布 + Genders []*PortraitItem `json:"genders,omitempty"` // 性别分布 + Platforms []*PortraitItem `json:"platforms,omitempty"` // 终端类型分布 + Devices []*PortraitItem `json:"devices,omitempty"` // 机型分布 + Ages []*PortraitItem `json:"ages,omitempty"` // 年龄分布 +} + +// PortraitItem 画像分布数据项 +type PortraitItem struct { + Id int `json:"id,omitempty"` // 属性值id + Name string `json:"name,omitempty"` // 属性值名称 + Value float64 `json:"value,omitempty"` // 该属性值对应的用户数量占比 +} + +// PerformanceDataRsp 获取小程序性能数据响应 +type PerformanceDataRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + Body *PerformanceBody `json:"body"` +} + +// PerformanceBody 性能数据主体 +type PerformanceBody struct { + Tables []*PerformanceTable `json:"tables"` +} + +// PerformanceTable 性能数据表 +type PerformanceTable struct { + RefDate string `json:"refDate"` // 日期,格式为 yyyyMMdd + Cost int `json:"cost"` // 耗时,单位ms + Ratio float64 `json:"ratio"` // 占比 + FieldList []*PerformanceField `json:"fieldList,omitempty"` +} + +// PerformanceField 性能数据字段 +type PerformanceField struct { + FieldName string `json:"fieldName"` // 字段名 + FieldValue string `json:"fieldValue"` // 字段值 +} + +// VisitDistributionRsp 获取用户小程序访问分布数据响应 +type VisitDistributionRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + RefDate string `json:"ref_date"` // 日期,格式为 yyyyMMdd + List []*VisitDistributionItem `json:"list,omitempty"` +} + +// VisitDistributionItem 访问分布数据项 +type VisitDistributionItem struct { + Index int `json:"index"` // 分布类型 + ItemList []*DistributionDetail `json:"item_list,omitempty"` +} + +// DistributionDetail 分布详情 +type DistributionDetail struct { + Key int `json:"key,omitempty"` // 场景id 或 访问深度 或 停留时长 + Value float64 `json:"value,omitempty"` // 该场景id访问pv占比 或 该访问深度访问人数占比 或 该停留时长访问人数占比 +} + +// ==================== 图像处理相关 ==================== + +// AiCropRsp 图片智能裁剪响应 +type AiCropRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + Results []*CropResult `json:"results"` +} + +// CropResult 裁剪结果 +type CropResult struct { + CropLeft int `json:"crop_left"` // 裁剪区域左上角横坐标 + CropTop int `json:"crop_top"` // 裁剪区域左上角纵坐标 + CropRight int `json:"crop_right"` // 裁剪区域右下角横坐标 + CropBottom int `json:"crop_bottom"` // 裁剪区域右下角纵坐标 +} + +// ScanQRCodeRsp 条码/二维码识别响应 +type ScanQRCodeRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + CodeResults []*CodeResult `json:"code_results"` + ImgSize *ImageSize `json:"img_size"` +} + +// CodeResult 识别结果 +type CodeResult struct { + TypeName string `json:"type_name"` // 类型名称,如 QR_CODE、EAN_13 等 + Data string `json:"data"` // 二维码/条码内容 + Pos *CodePos `json:"pos"` // 位置信息 +} + +// CodePos 位置信息 +type CodePos struct { + LeftTop *Point `json:"left_top"` // 左上角坐标 + RightTop *Point `json:"right_top"` // 右上角坐标 + RightBottom *Point `json:"right_bottom"` // 右下角坐标 + LeftBottom *Point `json:"left_bottom"` // 左下角坐标 +} + +// Point 坐标点 +type Point struct { + X int `json:"x"` + Y int `json:"y"` +} + +// ImageSize 图片尺寸 +type ImageSize struct { + W int `json:"w"` // 宽度 + H int `json:"h"` // 高度 +} + +// SuperResolutionRsp 图片高清化响应(返回图片 Buffer) +type SuperResolutionRsp struct { + Buffer []byte // 图片 Buffer + Errcode int `json:"errcode,omitempty"` + Errmsg string `json:"errmsg,omitempty"` +} + +// OcrIdCardRsp 身份证OCR识别响应 +type OcrIdCardRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + Type string `json:"type"` // 正面或背面,Front / Back + IdCard *IdCard `json:"id_card"` // 身份证信息 +} + +// IdCard 身份证信息 +type IdCard struct { + Name string `json:"name,omitempty"` // 姓名 + Id string `json:"id,omitempty"` // 身份证号 + Addr string `json:"addr,omitempty"` // 地址 + Gender string `json:"gender,omitempty"` // 性别 + Nationality string `json:"nationality,omitempty"` // 民族 + ValidDate string `json:"valid_date,omitempty"` // 有效期 +} + +// OcrBankCardRsp 银行卡OCR识别响应 +type OcrBankCardRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + Number string `json:"number"` // 银行卡号 +} + +// OcrDrivingRsp 驾驶证OCR识别响应 +type OcrDrivingRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + IdNum string `json:"id_num"` // 证号 + Name string `json:"name"` // 姓名 + Sex string `json:"sex"` // 性别 + Nationality string `json:"nationality"` // 国籍 + Address string `json:"address"` // 住址 + BirthDate string `json:"birth_date"` // 出生日期 + IssueDate string `json:"issue_date"` // 初次领证日期 + CarClass string `json:"car_class"` // 准驾车型 + ValidFrom string `json:"valid_from"` // 有效期限起始日 + ValidTo string `json:"valid_to"` // 有效期限终止日 +} + +// OcrVehicleLicenseRsp 行驶证OCR识别响应 +type OcrVehicleLicenseRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + VehicleType string `json:"vehicle_type"` // 车辆类型 + Owner string `json:"owner"` // 所有人 + Addr string `json:"addr"` // 住址 + UseCharacter string `json:"use_character"` // 使用性质 + Model string `json:"model"` // 品牌型号 + Vin string `json:"vin"` // 车辆识别代号 + EngineNum string `json:"engine_num"` // 发动机号码 + RegisterDate string `json:"register_date"` // 注册日期 + IssueDate string `json:"issue_date"` // 发证日期 + PlateNumB string `json:"plate_num_b"` // 号牌号码 + Record string `json:"record"` // 档案编号 + PassengersNum string `json:"passengers_num"` // 核定载人数 + TotalQuality string `json:"total_quality"` // 总质量 + TotalMass string `json:"total_mass"` // 整备质量 +} + +// OcrBusinessLicenseRsp 营业执照OCR识别响应 +type OcrBusinessLicenseRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + RegNum string `json:"reg_num"` // 注册号 + Serial string `json:"serial"` // 编号 + LegalRepresentative string `json:"legal_representative"` // 法定代表人姓名 + EnterpriseName string `json:"enterprise_name"` // 企业名称 + TypeOfEnterprise string `json:"type_of_enterprise"` // 组成形式 + Address string `json:"address"` // 经营场所/企业住所 + TypeOfOrganization string `json:"type_of_organization"` // 公司类型 + BusinessScope string `json:"business_scope"` // 经营范围 + RegisteredCapital string `json:"registered_capital"` // 注册资本 + PaidInCapital string `json:"paid_in_capital"` // 实收资本 + ValidPeriod string `json:"valid_period"` // 营业期限 + RegisteredDate string `json:"registered_date"` // 注册日期/成立日期 + CertPosition *CertPosition `json:"cert_position"` // 营业执照位置 + ImgSize *ImageSize `json:"img_size"` // 图片大小 +} + +// CertPosition 证件位置 +type CertPosition struct { + Pos *CodePos `json:"pos"` // 位置信息 +} + +// OcrCommonRsp 通用印刷体OCR识别响应 +type OcrCommonRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + Items []*OcrItem `json:"items"` +} + +// OcrItem OCR识别项 +type OcrItem struct { + Text string `json:"text"` // 识别的文本 + Pos *CodePos `json:"pos"` // 位置信息 +} + +// OcrPlateNumberRsp 车牌OCR识别响应 +type OcrPlateNumberRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + Number string `json:"number"` // 车牌号码 +} + +// ==================== 即时配送相关 ==================== + +// DeliveryGetAllAccountRsp 拉取已绑定账号响应 +type DeliveryGetAllAccountRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + ShopList []*DeliveryShop `json:"shop_list"` +} + +// DeliveryShop 配送商家信息 +type DeliveryShop struct { + DeliveryId string `json:"delivery_id"` // 配送公司ID + ShopId string `json:"shopid"` // 商家ID + AuditResult int `json:"audit_result"` // 审核状态 +} + +// DeliveryPreAddOrderRsp 配送单预下单响应 +type DeliveryPreAddOrderRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + Fee int `json:"fee"` // 实际运费(单位:元),运费减去优惠券费用 + Deliverfee int `json:"deliverfee"` // 运费(单位:元) + Couponfee int `json:"couponfee"` // 优惠券费用(单位:元) + Tips int `json:"tips"` // 小费(单位:元) + Insurancefee int `json:"insurancefee"` // 保价费(单位:元) + Distance int `json:"distance"` // 配送距离(单位:米) + DispatchDuration int `json:"dispatch_duration"` // 预计骑手接单时间(单位:秒) + DeliveryToken string `json:"delivery_token"` // 配送token +} + +// DeliveryAddOrderRsp 配送单下单响应 +type DeliveryAddOrderRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + Fee int `json:"fee"` // 实际运费(单位:元) + Deliverfee int `json:"deliverfee"` // 运费(单位:元) + Couponfee int `json:"couponfee"` // 优惠券费用(单位:元) + Tips int `json:"tips"` // 小费(单位:元) + Insurancefee int `json:"insurancefee"` // 保价费(单位:元) + Distance int `json:"distance"` // 配送距离(单位:米) + WaybillId string `json:"waybill_id"` // 配送单号 + OrderStatus int `json:"order_status"` // 配送状态 + FinishCode int `json:"finish_code"` // 收货码 + PickupCode int `json:"pickup_code"` // 取货码 + DispatchDuration int `json:"dispatch_duration"` // 预计骑手接单时间(单位:秒) +} + +// DeliveryAddTipsRsp 配送单增加小费响应 +type DeliveryAddTipsRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` +} + +// DeliveryCancelOrderRsp 配送单取消响应 +type DeliveryCancelOrderRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + Deduct_fee int `json:"deduct_fee"` // 扣除的违约金(单位:元) + Desc string `json:"desc"` // 说明 +} + +// DeliveryGetOrderRsp 配送单查询响应 +type DeliveryGetOrderRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + OrderStatus int `json:"order_status"` // 配送状态 + WaybillId string `json:"waybill_id"` // 配送单号 + RiderName string `json:"rider_name"` // 骑手姓名 + RiderPhone string `json:"rider_phone"` // 骑手电话 + RiderLng float64 `json:"rider_lng"` // 骑手位置经度 + RiderLat float64 `json:"rider_lat"` // 骑手位置纬度 +} + +// DeliveryMockUpdateOrderRsp 模拟配送公司更新配送单状态响应 +type DeliveryMockUpdateOrderRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` +} + +// DeliveryAbnormalConfirmRsp 异常件退回商家商圈接口响应 +type DeliveryAbnormalConfirmRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` +} + +// ==================== 物流助手相关 ==================== + +// ExpressAddOrderRsp 生成运单响应 +type ExpressAddOrderRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + OrderId string `json:"order_id"` // 订单ID + WaybillId string `json:"waybill_id"` // 运单号 + WaybillData string `json:"waybill_data"` // 面单信息 + DeliveryResultcode int `json:"delivery_resultcode"` // 配送侧错误码 + DeliveryResultmsg string `json:"delivery_resultmsg"` // 配送侧错误信息 +} + +// ExpressCancelOrderRsp 取消运单响应 +type ExpressCancelOrderRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` +} + +// ExpressGetAllAccountRsp 获取所有绑定的物流账号响应 +type ExpressGetAllAccountRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + Count int `json:"count"` + List []*ExpressAccount `json:"list"` +} + +// ExpressAccount 物流账号信息 +type ExpressAccount struct { + BizId string `json:"biz_id"` // 快递公司客户编码 + DeliveryId string `json:"delivery_id"` // 快递公司ID + CreateTime int64 `json:"create_time"` // 账号绑定时间 + UpdateTime int64 `json:"update_time"` // 账号更新时间 + StatusCode int `json:"status_code"` // 账号状态 + Alias string `json:"alias"` // 账号别名 + RemarkWrongMsg string `json:"remark_wrong_msg"` // 账号状态说明 + QuotaNum int `json:"quota_num"` // 电子面单余额 + QuotaUpdateTime int64 `json:"quota_update_time"` // 电子面单余额更新时间 +} + +// ExpressGetQuotaRsp 获取电子面单余额响应 +type ExpressGetQuotaRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + QuotaNum int `json:"quota_num"` // 电子面单余额 +} + +// ExpressGetPathRsp 查询运单轨迹响应 +type ExpressGetPathRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + OrderId string `json:"order_id"` // 订单ID + WaybillId string `json:"waybill_id"` // 运单号 + DeliveryId string `json:"delivery_id"` // 快递公司ID + PathItemNum int `json:"path_item_num"` // 轨迹节点数量 + PathItemList []*ExpressPathItem `json:"path_item_list"` // 轨迹节点列表 +} + +// ExpressPathItem 轨迹节点 +type ExpressPathItem struct { + ActionTime int `json:"action_time"` // 轨迹节点时间 + ActionType int `json:"action_type"` // 轨迹节点类型 + ActionMsg string `json:"action_msg"` // 轨迹节点详情 +} + +// ExpressGetAllDeliveryRsp 获取支持的快递公司列表响应 +type ExpressGetAllDeliveryRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + Count int `json:"count"` + Data []*ExpressDelivery `json:"data"` +} + +// ExpressDelivery 快递公司信息 +type ExpressDelivery struct { + DeliveryId string `json:"delivery_id"` // 快递公司ID + DeliveryName string `json:"delivery_name"` // 快递公司名称 +} + +// ExpressGetPrinterRsp 获取打印员响应 +type ExpressGetPrinterRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + Count int `json:"count"` + Openid []*PrinterOpenid `json:"openid"` + TagidList string `json:"tagid_list"` // 用户标签ID列表 +} + +// PrinterOpenid 打印员openid +type PrinterOpenid struct { + Openid string `json:"openid"` +} + +// ExpressUpdatePrinterRsp 配置面单打印员响应 +type ExpressUpdatePrinterRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` +} + +// ExpressGetContactRsp 获取面单联系人信息响应 +type ExpressGetContactRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + WaybillId string `json:"waybill_id"` // 运单号 + SenderInfo *ExpressContact `json:"sender"` // 寄件人信息 + ReceiverInfo *ExpressContact `json:"receiver"` // 收件人信息 +} + +// ExpressContact 联系人信息 +type ExpressContact struct { + Name string `json:"name"` // 姓名 + Mobile string `json:"mobile"` // 电话 + Province string `json:"province"` // 省份 + City string `json:"city"` // 城市 + Area string `json:"area"` // 区县 + Address string `json:"address"` // 详细地址 +} + +// ==================== 搜索相关 ==================== + +// SearchSubmitPagesRsp 提交小程序页面响应 +type SearchSubmitPagesRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` +} + +// SearchDeletePageRsp 删除已提交的小程序页面响应 +type SearchDeletePageRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` +} + +// ==================== 运维中心相关 ==================== + +// GetDomainInfoRsp 获取域名配置响应 +type GetDomainInfoRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + Requestdomain []string `json:"requestdomain"` // request合法域名 + Wsrequestdomain []string `json:"wsrequestdomain"` // socket合法域名 + Uploaddomain []string `json:"uploaddomain"` // uploadFile合法域名 + Downloaddomain []string `json:"downloaddomain"` // downloadFile合法域名 + Udpdomain []string `json:"udpdomain"` // udp合法域名 + Tcpdomain []string `json:"tcpdomain"` // tcp合法域名 +} + +// ModifyDomainRsp 修改服务器域名响应 +type ModifyDomainRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + Requestdomain []string `json:"requestdomain"` // request合法域名 + Wsrequestdomain []string `json:"wsrequestdomain"` // socket合法域名 + Uploaddomain []string `json:"uploaddomain"` // uploadFile合法域名 + Downloaddomain []string `json:"downloaddomain"` // downloadFile合法域名 + Udpdomain []string `json:"udpdomain"` // udp合法域名 + Tcpdomain []string `json:"tcpdomain"` // tcp合法域名 +} + +// SetWebviewDomainRsp 设置业务域名响应 +type SetWebviewDomainRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + Webviewdomain []string `json:"webviewdomain"` // 业务域名 +} + +// GetQrcodeJumppublishRsp 获取小程序码扫码打开的页面响应 +type GetQrcodeJumppublishRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + PrefixList []*QrcodeJumpRule `json:"prefix_list"` +} + +// QrcodeJumpRule 二维码跳转规则 +type QrcodeJumpRule struct { + Prefix string `json:"prefix"` // 二维码规则 + PermitSubRule int `json:"permit_sub_rule"` // 是否独占符合二维码前缀匹配规则的所有子规则 + Path string `json:"path"` // 小程序功能页面 + OpenVersion int `json:"open_version"` // 测试范围 + DebugUrl []string `json:"debug_url"` // 测试链接 + State int `json:"state"` // 发布标志位 +} + +// SetQrcodeJumpRsp 设置小程序码扫码打开的页面响应 +type SetQrcodeJumpRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` +} + +// ==================== 小程序管理相关 ==================== + +// GetAccountBasicInfoRsp 获取小程序基本信息响应 +type GetAccountBasicInfoRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + Appid string `json:"appid"` // 小程序appid + AccountType int `json:"account_type"` // 帐号类型 + PrincipalType int `json:"principal_type"` // 主体类型 + PrincipalName string `json:"principal_name"` // 主体名称 + Credential string `json:"credential"` // 主体标识 + RealnameStatus int `json:"realname_status"` // 实名验证状态 + WxVerifyInfo *WxVerifyInfo `json:"wx_verify_info"` // 微信认证信息 + SignatureInfo *SignatureInfo `json:"signature_info"` // 功能介绍信息 + HeadImageInfo *HeadImageInfo `json:"head_image_info"` // 头像信息 + NicknameInfo *NicknameInfo `json:"nickname_info"` // 名称信息 +} + +// WxVerifyInfo 微信认证信息 +type WxVerifyInfo struct { + QualificationVerify bool `json:"qualification_verify"` // 是否资质认证 + NamingVerify bool `json:"naming_verify"` // 是否名称认证 + AnnualReview bool `json:"annual_review"` // 是否年审 + AnnualReviewBeginTime int64 `json:"annual_review_begin_time"` // 年审开始时间 + AnnualReviewEndTime int64 `json:"annual_review_end_time"` // 年审结束时间 +} + +// SignatureInfo 功能介绍信息 +type SignatureInfo struct { + Signature string `json:"signature"` // 功能介绍 + ModifyUsedCount int `json:"modify_used_count"` // 功能介绍已使用修改次数 + ModifyQuota int `json:"modify_quota"` // 功能介绍修改次数总额度 +} + +// HeadImageInfo 头像信息 +type HeadImageInfo struct { + HeadImageUrl string `json:"head_image_url"` // 头像url + ModifyUsedCount int `json:"modify_used_count"` // 头像已使用修改次数 + ModifyQuota int `json:"modify_quota"` // 头像修改次数总额度 +} + +// NicknameInfo 名称信息 +type NicknameInfo struct { + Nickname string `json:"nickname"` // 小程序名称 + ModifyUsedCount int `json:"modify_used_count"` // 名称已使用修改次数 + ModifyQuota int `json:"modify_quota"` // 名称修改次数总额度 +} + +// GetPageRsp 获取已上传的代码的页面列表响应 +type GetPageRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + PageList []string `json:"page_list"` // 页面配置列表 +} + +// GetCategoryRsp 获取授权小程序帐号的可选类目响应 +type GetCategoryRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + CategoryList []*CategoryItem `json:"category_list"` +} + +// CategoryItem 类目信息 +type CategoryItem struct { + FirstClass string `json:"first_class"` // 一级类目名称 + SecondClass string `json:"second_class"` // 二级类目名称 + ThirdClass string `json:"third_class"` // 三级类目名称 + FirstId int `json:"first_id"` // 一级类目ID + SecondId int `json:"second_id"` // 二级类目ID + ThirdId int `json:"third_id"` // 三级类目ID +} + +// GetExtConfigRsp 获取小程序的第三方提交代码的页面配置响应 +type GetExtConfigRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + ExtConfig string `json:"ext_config"` // 第三方自定义的配置 +} + +// SetExtConfigRsp 设置小程序的第三方提交代码的页面配置响应 +type SetExtConfigRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` +} + +// ==================== 服务市场相关 ==================== + +// InvokeServiceRsp 调用服务平台提供的服务响应 +type InvokeServiceRsp struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + Data string `json:"data"` // 服务返回的数据 +} diff --git a/mini/qrcode.go b/mini/qrcode.go new file mode 100644 index 0000000..b3e8c77 --- /dev/null +++ b/mini/qrcode.go @@ -0,0 +1,131 @@ +package mini + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/go-pay/bm" + "github.com/go-pay/wechat-sdk" +) + +// GetWxaCode 获取小程序码(适用于需要的码数量较少的业务场景) +// 注意:通过该接口生成的小程序码,永久有效,有数量限制 +// path:扫码进入的小程序页面路径,最大长度 128 字节,不能为空;对于小游戏,可以只传入 query 部分,来实现传参效果,如:传入 "?foo=bar",即可在 wx.getLaunchOptionsSync 接口中的 query 参数获取到 {foo:"bar"}。 +// width:二维码的宽度,单位 px。最小 280px,最大 1280px,默认 430px +// autoColor:自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调 +// lineColor:auto_color 为 false 时生效,使用 rgb 设置颜色 例如 {"r":"xxx","g":"xxx","b":"xxx"} 十进制表示 +// isHyaline:是否需要透明底色,为 true 时,生成透明底色的小程序码 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/qrcode-link/qr-code/getQRCode.html +func (s *SDK) GetWxaCode(c context.Context, path string, width int, autoColor, isHyaline bool, lineColor *LineColor) (qrcode []byte, err error) { + apiPath := "/wxa/getwxacode?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("path", path) + if width > 0 { + body.Set("width", width) + } + body.Set("auto_color", autoColor) + if lineColor != nil { + body.Set("line_color", lineColor) + } + body.Set("is_hyaline", isHyaline) + + qrcode, err = s.doRequestPostForImage(c, apiPath, body) + if err != nil { + return nil, err + } + return qrcode, nil +} + +// GetWxaCodeUnlimit 获取小程序码(适用于需要的码数量极多的业务场景) +// 注意:通过该接口生成的小程序码,永久有效,数量暂无限制 +// scene:最大32个可见字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~,其它字符请自行编码为合法字符(因不支持%,中文无法使用 urlencode 处理,请使用其他编码方式) +// page:必须是已经发布的小程序存在的页面(否则报错),例如 pages/index/index, 根路径前不要填加 /,不能携带参数(参数请放在scene字段里),如果不填写这个字段,默认跳主页面 +// checkPath:检查page 是否存在,为 true 时 page 必须是已经发布的小程序存在的页面(否则报错);为 false 时允许小程序未发布或者 page 不存在, 但page 有数量上限(60000个)请勿滥用 +// envVersion:要打开的小程序版本。正式版为 "release",体验版为 "trial",开发版为 "develop"。默认是正式版 +// width:二维码的宽度,单位 px。最小 280px,最大 1280px,默认 430px +// autoColor:自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调 +// lineColor:auto_color 为 false 时生效,使用 rgb 设置颜色 例如 {"r":"xxx","g":"xxx","b":"xxx"} 十进制表示 +// isHyaline:是否需要透明底色,为 true 时,生成透明底色的小程序码 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/qrcode-link/qr-code/getUnlimitedQRCode.html +func (s *SDK) GetWxaCodeUnlimit(c context.Context, scene, page string, checkPath bool, envVersion string, width int, autoColor, isHyaline bool, lineColor *LineColor) (qrcode []byte, err error) { + apiPath := "/wxa/getwxacodeunlimit?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("scene", scene) + if page != "" { + body.Set("page", page) + } + body.Set("check_path", checkPath) + if envVersion != "" { + body.Set("env_version", envVersion) + } + if width > 0 { + body.Set("width", width) + } + body.Set("auto_color", autoColor) + if lineColor != nil { + body.Set("line_color", lineColor) + } + body.Set("is_hyaline", isHyaline) + + qrcode, err = s.doRequestPostForImage(c, apiPath, body) + if err != nil { + return nil, err + } + return qrcode, nil +} + +// CreateWxaQRCode 获取小程序二维码(适用于需要的码数量较少的业务场景) +// 注意:通过该接口生成的小程序二维码,永久有效,有数量限制 +// path:扫码进入的小程序页面路径,最大长度 128 字节,不能为空。对于小游戏,可以只传入 query 部分,来实现传参效果,如:传入 "?foo=bar",即可在 wx.getLaunchOptionsSync 接口中的 query 参数获取到 {foo:"bar"}。 +// width:二维码的宽度,单位 px。最小 280px,最大 1280px,默认 430px +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/qrcode-link/qr-code/createQRCode.html +func (s *SDK) CreateWxaQRCode(c context.Context, path string, width int) (qrcode []byte, err error) { + apiPath := "/cgi-bin/wxaapp/createwxaqrcode?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("path", path) + if width > 0 { + body.Set("width", width) + } + + qrcode, err = s.doRequestPostForImage(c, apiPath, body) + if err != nil { + return nil, err + } + return qrcode, nil +} + +// doRequestPostForImage 处理返回图片的 POST 请求 +func (s *SDK) doRequestPostForImage(c context.Context, path string, body bm.BodyMap) (image []byte, err error) { + uri := s.Host + path + if s.DebugSwitch == wechat.DebugOn { + s.logger.Debugf("Wechat_Mini_SDK_URI: %s", uri) + s.logger.Debugf("Wechat_Mini_SDK_RequestBody: %s", body.JsonBody()) + } + + res, bs, err := s.hc.Req().Post(uri).SendBodyMap(body).EndBytes(c) + if err != nil { + return nil, fmt.Errorf("http.request(POST, %s), err:%w", uri, err) + } + + if s.DebugSwitch == wechat.DebugOn { + s.logger.Debugf("Wechat_Mini_SDK_Response: [%d] -> %d bytes", res.StatusCode, len(bs)) + } + + // 判断返回的是图片还是错误信息 + contentType := res.Header.Get("Content-Type") + if contentType == "image/jpeg" || contentType == "image/png" { + return bs, nil + } + + // 如果不是图片,尝试解析错误信息 + ec := &ErrorCode{} + if err = json.Unmarshal(bs, ec); err != nil { + return nil, fmt.Errorf("json.Unmarshal(%s):%w", string(bs), err) + } + if ec.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", ec.Errcode, ec.Errmsg) + } + + return bs, nil +} diff --git a/mini/search.go b/mini/search.go new file mode 100644 index 0000000..f6b22d5 --- /dev/null +++ b/mini/search.go @@ -0,0 +1,46 @@ +package mini + +import ( + "context" + "fmt" + + "github.com/go-pay/bm" +) + +// SearchSubmitPages 提交小程序页面url及参数信息 +// 注意:errcode = 0 为成功 +// pages:页面信息列表,BodyMap 数组格式,每个元素包含 path 和 query +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/search-management/site-search/submitPages.html +func (s *SDK) SearchSubmitPages(c context.Context, pages []bm.BodyMap) (result *SearchSubmitPagesRsp, err error) { + path := "/wxa/search/wxaapi_submitpages?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("pages", pages) + + result = &SearchSubmitPagesRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// SearchDeletePage 删除已提交的小程序页面 +// 注意:errcode = 0 为成功 +// pagePath:页面路径 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/search-management/site-search/deletePages.html +func (s *SDK) SearchDeletePage(c context.Context, pagePath string) (result *SearchDeletePageRsp, err error) { + path := "/wxa/search/wxaapi_deletepage?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("path", pagePath) + + result = &SearchDeletePageRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} diff --git a/mini/security.go b/mini/security.go new file mode 100644 index 0000000..b294ca4 --- /dev/null +++ b/mini/security.go @@ -0,0 +1,102 @@ +package mini + +import ( + "context" + "fmt" + + "github.com/go-pay/bm" +) + +// MsgSecCheck 检查一段文本是否含有违法违规内容 +// 注意:errcode = 0 为成功 +// content:需检测的文本内容,文本字数的上限为2500字,需使用UTF-8编码 +// version:接口版本号,2.0版本为固定值2 +// scene:场景枚举值(1 资料;2 评论;3 论坛;4 社交日志) +// openid:用户的openid(用户需在近两小时访问过小程序) +// title:文本标题,需使用UTF-8编码 +// nickname:用户昵称,需使用UTF-8编码 +// signature:个性签名,该参数仅在资料类场景有效(scene=1),需使用UTF-8编码 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/sec-center/sec-check/msgSecCheck.html +func (s *SDK) MsgSecCheck(c context.Context, content string, version int, scene int, openid, title, nickname, signature string) (result *MsgSecCheckRsp, err error) { + path := "/wxa/msg_sec_check?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("content", content). + Set("version", version). + Set("scene", scene). + Set("openid", openid) + + if title != "" { + body.Set("title", title) + } + if nickname != "" { + body.Set("nickname", nickname) + } + if signature != "" { + body.Set("signature", signature) + } + + result = &MsgSecCheckRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// ImgSecCheck 校验一张图片是否含有违法违规内容 +// 注意:errcode = 0 为成功 +// 注意:图片文件大小应小于 5MB +// mediaUrl:要检测的图片url,支持图片格式包括 jpg, jepg, png, bmp, gif(取首帧) +// mediaType:1:url;2:base64 +// version:接口版本号,2.0版本为固定值2 +// scene:场景枚举值(1.资料;2.评论;3.论坛;4.社交日志) +// openid:用户的openid(用户需在近两小时访问过小程序) +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/sec-center/sec-check/imgSecCheck.html +func (s *SDK) ImgSecCheck(c context.Context, mediaUrl string, mediaType, version, scene int, openid string) (result *ImgSecCheckRsp, err error) { + path := "/wxa/img_sec_check?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("media_url", mediaUrl). + Set("media_type", mediaType). + Set("version", version). + Set("scene", scene). + Set("openid", openid) + + result = &ImgSecCheckRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// MediaCheckAsync 异步校验图片/音频是否含有违法违规内容 +// 注意:errcode = 0 为成功 +// 注意:音频文件大小不超过10MB,时长不超过5分钟;图片文件大小不超过5MB +// mediaUrl:要检测的多媒体url +// mediaType:1:音频;2:图片 +// version:接口版本号,2.0版本为固定值2 +// scene:场景枚举值(1.资料;2.评论;3.论坛;4.社交日志) +// openid:用户的openid(用户需在近两小时访问过小程序) +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/sec-center/sec-check/mediaCheckAsync.html +func (s *SDK) MediaCheckAsync(c context.Context, mediaUrl string, mediaType, version, scene int, openid string) (result *MediaCheckAsyncRsp, err error) { + path := "/wxa/media_check_async?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("media_url", mediaUrl). + Set("media_type", mediaType). + Set("version", version). + Set("scene", scene). + Set("openid", openid) + + result = &MediaCheckAsyncRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} diff --git a/mini/service_market.go b/mini/service_market.go new file mode 100644 index 0000000..52e6e03 --- /dev/null +++ b/mini/service_market.go @@ -0,0 +1,33 @@ +package mini + +import ( + "context" + "fmt" + + "github.com/go-pay/bm" +) + +// InvokeService 调用服务平台提供的服务 +// 注意:errcode = 0 为成功 +// service:服务ID +// api:接口名 +// data:业务数据,JSON 字符串 +// clientMsgId:随机字符串ID,用于唯一标识请求 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/service-market/invokeService.html +func (s *SDK) InvokeService(c context.Context, service, api, data, clientMsgId string) (result *InvokeServiceRsp, err error) { + path := "/wxa/servicemarket?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("service", service). + Set("api", api). + Set("data", data). + Set("client_msg_id", clientMsgId) + + result = &InvokeServiceRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} diff --git a/mini/subscribe_message.go b/mini/subscribe_message.go new file mode 100644 index 0000000..a949de3 --- /dev/null +++ b/mini/subscribe_message.go @@ -0,0 +1,139 @@ +package mini + +import ( + "context" + "fmt" + + "github.com/go-pay/bm" +) + +// SubscribeMessageSend 发送订阅消息 +// 注意:errcode = 0 为成功 +// toUser:接收者(用户)的 openid +// templateId:所需下发的订阅模板id +// page:点击模板卡片后的跳转页面,仅限本小程序内的页面。支持带参数,(示例index?foo=bar)。该字段不填则模板无跳转。 +// data:模板内容,格式形如 { "key1": { "value": any }, "key2": { "value": any } } +// miniprogramState:跳转小程序类型:developer为开发版;trial为体验版;formal为正式版;默认为正式版 +// lang:进入小程序查看"的语言类型,支持zh_CN(简体中文)、en_US(英文)、zh_HK(繁体中文)、zh_TW(繁体中文),默认为zh_CN +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-message-management/subscribe-message/sendMessage.html +func (s *SDK) SubscribeMessageSend(c context.Context, toUser, templateId, page string, data bm.BodyMap, miniprogramState, lang string) (err error) { + path := "/cgi-bin/message/subscribe/send?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("touser", toUser). + Set("template_id", templateId). + Set("data", data) + + if page != "" { + body.Set("page", page) + } + if miniprogramState != "" { + body.Set("miniprogram_state", miniprogramState) + } + if lang != "" { + body.Set("lang", lang) + } + + rsp := &SubscribeMessageSendRsp{} + if _, err = s.DoRequestPost(c, path, body, rsp); err != nil { + return err + } + if rsp.Errcode != Success { + return fmt.Errorf("errcode(%d), errmsg(%s)", rsp.Errcode, rsp.Errmsg) + } + return nil +} + +// GetTemplateList 获取当前帐号下的个人模板列表 +// 注意:errcode = 0 为成功 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-message-management/subscribe-message/getTemplateList.html +func (s *SDK) GetTemplateList(c context.Context) (templates *TemplateListRsp, err error) { + path := "/wxaapi/newtmpl/gettemplate?access_token=" + s.accessToken + templates = &TemplateListRsp{} + if _, err = s.DoRequestGet(c, path, templates); err != nil { + return nil, err + } + if templates.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", templates.Errcode, templates.Errmsg) + } + return templates, nil +} + +// GetPubTemplateTitleList 获取帐号所属类目下的公共模板标题 +// 注意:errcode = 0 为成功 +// ids:类目 id,多个用逗号隔开 +// start:用于分页,表示从 start 开始。从 0 开始计数。 +// limit:用于分页,表示拉取 limit 条记录。最大为 30。 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-message-management/subscribe-message/getPubTemplateTitleList.html +func (s *SDK) GetPubTemplateTitleList(c context.Context, ids string, start, limit int) (titles *PubTemplateTitleListRsp, err error) { + path := "/wxaapi/newtmpl/getpubtemplatetitles?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("ids", ids). + Set("start", start). + Set("limit", limit) + + titles = &PubTemplateTitleListRsp{} + if _, err = s.DoRequestPost(c, path, body, titles); err != nil { + return nil, err + } + if titles.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", titles.Errcode, titles.Errmsg) + } + return titles, nil +} + +// AddTemplate 组合模板并添加至帐号下的个人模板库 +// 注意:errcode = 0 为成功 +// tid:模板标题 id,可通过接口获取 +// kidList:开发者自行组合好的模板关键词列表,关键词顺序可以自由搭配(例如 [3,5,4] 或 [4,5,3]),最多支持5个,最少2个关键词组合 +// sceneDesc:服务场景描述,15个字以内 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-message-management/subscribe-message/addTemplate.html +func (s *SDK) AddTemplate(c context.Context, tid string, kidList []int, sceneDesc string) (result *AddTemplateRsp, err error) { + path := "/wxaapi/newtmpl/addtemplate?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("tid", tid). + Set("kidList", kidList). + Set("sceneDesc", sceneDesc) + + result = &AddTemplateRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// DeleteTemplate 删除帐号下的个人模板 +// 注意:errcode = 0 为成功 +// priTmplId:要删除的模板id +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-message-management/subscribe-message/deleteTemplate.html +func (s *SDK) DeleteTemplate(c context.Context, priTmplId string) (err error) { + path := "/wxaapi/newtmpl/deltemplate?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("priTmplId", priTmplId) + + rsp := &DeleteTemplateRsp{} + if _, err = s.DoRequestPost(c, path, body, rsp); err != nil { + return err + } + if rsp.Errcode != Success { + return fmt.Errorf("errcode(%d), errmsg(%s)", rsp.Errcode, rsp.Errmsg) + } + return nil +} + +// GetCategory 获取小程序账号的类目 +// 注意:errcode = 0 为成功 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-message-management/subscribe-message/getCategory.html +func (s *SDK) GetCategory(c context.Context) (categories *CategoryListRsp, err error) { + path := "/wxaapi/newtmpl/getcategory?access_token=" + s.accessToken + categories = &CategoryListRsp{} + if _, err = s.DoRequestGet(c, path, categories); err != nil { + return nil, err + } + if categories.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", categories.Errcode, categories.Errmsg) + } + return categories, nil +} diff --git a/mini/url_link.go b/mini/url_link.go new file mode 100644 index 0000000..672e5e8 --- /dev/null +++ b/mini/url_link.go @@ -0,0 +1,145 @@ +package mini + +import ( + "context" + "fmt" + + "github.com/go-pay/bm" +) + +// GenerateScheme 获取小程序 scheme 码,适用于短信、邮件、外部网页、微信内等拉起小程序的业务场景 +// 注意:通过该接口生成的小程序 scheme 码,永久有效,有数量限制 +// jumpWxa:跳转到的目标小程序信息,BodyMap 格式:{"path": "pages/index/index", "query": "a=1&b=2", "env_version": "release"} +// isExpire:生成的 scheme 码类型,到期失效:true,永久有效:false,默认 false +// expireType:到期失效的 scheme 码的失效类型,失效时间:0,失效间隔天数:1 +// expireTime:到期失效的 scheme 码的失效时间,为 Unix 时间戳。生成的到期失效 scheme 码在该时间前有效。最长有效期为1年。生成到期失效的 scheme 时必填。 +// expireInterval:到期失效的 scheme 码的失效间隔天数。生成的到期失效 scheme 码在该间隔时间到达前有效。最长间隔天数为365天。is_expire 为 true 且 expire_type 为 1 时必填。 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/qrcode-link/url-scheme/generateScheme.html +func (s *SDK) GenerateScheme(c context.Context, jumpWxa bm.BodyMap, isExpire bool, expireType, expireTime, expireInterval int) (result *GenerateSchemeRsp, err error) { + path := "/wxa/generatescheme?access_token=" + s.accessToken + body := make(bm.BodyMap) + if jumpWxa != nil { + body.Set("jump_wxa", jumpWxa) + } + body.Set("is_expire", isExpire) + if isExpire { + body.Set("expire_type", expireType) + if expireType == 0 && expireTime > 0 { + body.Set("expire_time", expireTime) + } + if expireType == 1 && expireInterval > 0 { + body.Set("expire_interval", expireInterval) + } + } + + result = &GenerateSchemeRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// QueryScheme 查询小程序 scheme 码 +// scheme:小程序 scheme 码 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/qrcode-link/url-scheme/queryScheme.html +func (s *SDK) QueryScheme(c context.Context, scheme string) (result *QuerySchemeRsp, err error) { + path := "/wxa/queryscheme?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("scheme", scheme) + + result = &QuerySchemeRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// GenerateUrlLink 获取小程序 URL Link,适用于短信、邮件、网页、微信内等拉起小程序的业务场景 +// path:通过 URL Link 进入的小程序页面路径,必须是已经发布的小程序存在的页面,不可携带 query。path 为空时会跳转小程序主页 +// query:通过 URL Link 进入小程序时的query,最大1024个字符,只支持数字,大小写英文以及部分特殊字符:`!#$&'()*+,/:;=?@-._~%“ +// isExpire:生成的 URL Link 类型,到期失效:true,永久有效:false,默认 false +// expireType:小程序 URL Link 失效类型,失效时间:0,失效间隔天数:1 +// expireTime:到期失效的 URL Link 的失效时间,为 Unix 时间戳。生成的到期失效 URL Link 在该时间前有效。最长有效期为1年。生成到期失效的 URL Link 时必填 +// expireInterval:到期失效的 URL Link 的失效间隔天数。生成的到期失效 URL Link 在该间隔时间到达前有效。最长间隔天数为365天。is_expire 为 true 且 expire_type 为 1 时必填 +// envVersion:要打开的小程序版本。正式版为"release",体验版为"trial",开发版为"develop",仅在微信外打开时生效 +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/qrcode-link/url-link/generateUrlLink.html +func (s *SDK) GenerateUrlLink(c context.Context, path, query string, isExpire bool, expireType, expireTime, expireInterval int, envVersion string) (result *GenerateUrlLinkRsp, err error) { + apiPath := "/wxa/generate_urllink?access_token=" + s.accessToken + body := make(bm.BodyMap) + if path != "" { + body.Set("path", path) + } + if query != "" { + body.Set("query", query) + } + body.Set("is_expire", isExpire) + if isExpire { + body.Set("expire_type", expireType) + if expireType == 0 && expireTime > 0 { + body.Set("expire_time", expireTime) + } + if expireType == 1 && expireInterval > 0 { + body.Set("expire_interval", expireInterval) + } + } + if envVersion != "" { + body.Set("env_version", envVersion) + } + + result = &GenerateUrlLinkRsp{} + if _, err = s.DoRequestPost(c, apiPath, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// QueryUrlLink 查询小程序 URL Link +// urlLink:小程序 URL Link +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/qrcode-link/url-link/queryUrlLink.html +func (s *SDK) QueryUrlLink(c context.Context, urlLink string) (result *QueryUrlLinkRsp, err error) { + path := "/wxa/query_urllink?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("url_link", urlLink) + + result = &QueryUrlLinkRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} + +// GenerateShortLink 获取小程序 Short Link,适用于微信内拉起小程序的业务场景 +// pageUrl:通过 Short Link 进入的小程序页面路径,必须是已经发布的小程序存在的页面,可携带 query,最大1024个字符 +// pageTitle:页面标题,不能包含违法信息,超过20字符会用... 截断代替 +// isPermanent:生成的 Short Link 类型,短期有效:false,永久有效:true,默认 false +// 文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/qrcode-link/short-link/generateShortLink.html +func (s *SDK) GenerateShortLink(c context.Context, pageUrl, pageTitle string, isPermanent bool) (result *GenerateShortLinkRsp, err error) { + path := "/wxa/genwxashortlink?access_token=" + s.accessToken + body := make(bm.BodyMap) + body.Set("page_url", pageUrl) + if pageTitle != "" { + body.Set("page_title", pageTitle) + } + body.Set("is_permanent", isPermanent) + + result = &GenerateShortLinkRsp{} + if _, err = s.DoRequestPost(c, path, body, result); err != nil { + return nil, err + } + if result.Errcode != Success { + return nil, fmt.Errorf("errcode(%d), errmsg(%s)", result.Errcode, result.Errmsg) + } + return result, nil +} diff --git a/release_note.md b/release_note.md index 03a70e0..29fa219 100644 --- a/release_note.md +++ b/release_note.md @@ -1,3 +1,21 @@ +## 版本号:v1.1.8 + +* 修改记录: + * (1) 微信小程序:新增订阅消息模块(发送订阅消息、模板管理、类目查询) + * (2) 微信小程序:新增小程序码生成模块(小程序码、二维码) + * (3) 微信小程序:新增 URL 跳转模块(URL Scheme、URL Link、Short Link) + * (4) 微信小程序:新增内容安全模块(文本、图片、音视频检测) + * (5) 微信小程序:新增数据分析模块(访问趋势、留存、页面数据、用户画像、性能数据) + * (6) 微信小程序:新增图像处理模块(智能裁剪、二维码识别、图片高清化、OCR识别) + * (7) 微信小程序:新增即时配送模块(配送单管理、状态查询) + * (8) 微信小程序:新增物流助手模块(运单管理、轨迹查询、打印员配置) + * (9) 微信小程序:新增搜索模块(页面提交、页面删除) + * (10) 微信小程序:新增运维中心模块(域名配置、小程序码跳转配置) + * (11) 微信小程序:新增小程序管理模块(基本信息、页面列表、类目、配置管理) + * (12) 微信小程序:新增服务市场模块(调用服务平台服务) + * (13) 新增 12 个功能文件:subscribe_message.go、qrcode.go、url_link.go、security.go、data_analysis.go、image_process.go、delivery.go、express.go、search.go、domain.go、management.go、service_market.go + * (14) 新增 90+ 个接口方法,覆盖小程序常用功能的完整实现 + ## 版本号:Release 1.1.7 * 修改记录: From 113c012a91f2b07f42b6a02fb240ca9c66221bb8 Mon Sep 17 00:00:00 2001 From: Jerry Date: Thu, 2 Apr 2026 16:48:02 +0800 Subject: [PATCH 2/2] update --- .github/workflows/go.yml | 27 +++++++++++++++++++++++---- README.md | 2 +- go.mod | 2 +- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index fd0407b..ef7577b 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -9,10 +9,19 @@ on: pull_request: branches: [ "main" ] +# Cancel in-progress runs for the same workflow and branch +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + jobs: lint: runs-on: ubuntu-latest + timeout-minutes: 10 steps: - name: Checkout repository uses: actions/checkout@v4 @@ -20,7 +29,8 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: '1.21' + go-version: '1.24' + cache: true - name: Setup golangci-lint uses: golangci/golangci-lint-action@v6 @@ -31,10 +41,12 @@ jobs: test: needs: lint strategy: + fail-fast: false matrix: - os: [ ubuntu-latest, macos-latest ] - go: [ '1.20', '1.21' ] + os: [ ubuntu-latest ] + go: [ '1.24', '1.25', '1.26' ] runs-on: ${{ matrix.os }} + timeout-minutes: 15 steps: - name: Checkout Code uses: actions/checkout@v4 @@ -43,6 +55,13 @@ jobs: uses: actions/setup-go@v5 with: go-version: ${{ matrix.go }} + cache: true + + - name: Download dependencies + run: go mod download + + - name: Build + run: go build -v ./... - name: Test - run: go test -v + run: go test -v ./... diff --git a/README.md b/README.md index c666cc0..fd7aafc 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Github](https://img.shields.io/github/followers/iGoogle-ink?label=Follow&style=social)](https://github.com/iGoogle-ink) [![Github](https://img.shields.io/github/forks/go-pay/wechat-sdk?label=Fork&style=social)](https://github.com/go-pay/wechat-sdk/fork) -[![Golang](https://img.shields.io/badge/golang-1.21-brightgreen.svg)](https://golang.google.cn) +[![Golang](https://img.shields.io/badge/golang-1.24.0-brightgreen.svg)](https://golang.google.cn) [![GoDoc](https://img.shields.io/badge/doc-pkg.go.dev-informational.svg)](https://pkg.go.dev/github.com/go-pay/wechat-sdk) [![Go](https://github.com/go-pay/wechat-sdk/actions/workflows/go.yml/badge.svg)](https://github.com/go-pay/wechat-sdk/actions/workflows/go.yml) [![GitHub Release](https://img.shields.io/github/v/release/go-pay/wechat-sdk)](https://github.com/go-pay/wechat-sdk/releases) diff --git a/go.mod b/go.mod index efc2f48..ede56e5 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/go-pay/wechat-sdk -go 1.21 +go 1.24.0 require ( github.com/go-pay/bm v0.0.5