mirror of
https://github.com/silenceper/wechat.git
synced 2026-02-04 21:02:25 +08:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20a8183e88 | ||
|
|
f0f35e2f77 | ||
|
|
eebfb93386 | ||
|
|
9b028e5368 | ||
|
|
2bfc250f21 | ||
|
|
f0866babb5 | ||
|
|
7d93d1b9c8 | ||
|
|
78c00a9124 | ||
|
|
54f08cbd73 | ||
|
|
c34ff2031b |
58
cache/redis.go
vendored
58
cache/redis.go
vendored
@@ -17,14 +17,24 @@ type Redis struct {
|
||||
|
||||
// RedisOpts redis 连接属性
|
||||
type RedisOpts struct {
|
||||
Host string `json:"host" yml:"host"`
|
||||
Username string `json:"username" yaml:"username"`
|
||||
Password string `json:"password" yml:"password"`
|
||||
Database int `json:"database" yml:"database"`
|
||||
MaxIdle int `json:"max_idle" yml:"max_idle"`
|
||||
MaxActive int `json:"max_active" yml:"max_active"`
|
||||
IdleTimeout int `json:"idle_timeout" yml:"idle_timeout"` // second
|
||||
UseTLS bool `json:"use_tls" yml:"use_tls"` // 是否使用TLS
|
||||
Host string `json:"host" yaml:"host"`
|
||||
Username string `json:"username" yaml:"username"`
|
||||
Password string `json:"password" yaml:"password"`
|
||||
Database int `json:"database" yaml:"database"`
|
||||
MinIdleConns int `json:"min_idle_conns" yaml:"min_idle_conns"` // 最小空闲连接数
|
||||
PoolSize int `json:"pool_size" yaml:"pool_size"` // 连接池大小,0 表示使用默认值(即 CPU 核心数 * 10)
|
||||
MaxRetries int `json:"max_retries" yaml:"max_retries"` // 最大重试次数,-1 表示不重试,0 表示使用默认值(即 3 次)
|
||||
DialTimeout int `json:"dial_timeout" yaml:"dial_timeout"` // 连接超时时间(秒),0 表示使用默认值(即 5 秒)
|
||||
ReadTimeout int `json:"read_timeout" yaml:"read_timeout"` // 读取超时时间(秒),-1 表示不超时,0 表示使用默认值(即 3 秒)
|
||||
WriteTimeout int `json:"write_timeout" yaml:"write_timeout"` // 写入超时时间(秒),-1 表示不超时,0 表示使用默认值(即 ReadTimeout)
|
||||
PoolTimeout int `json:"pool_timeout" yaml:"pool_timeout"` // 连接池获取连接超时时间(秒),0 表示使用默认值(即 ReadTimeout + 1 秒)
|
||||
IdleTimeout int `json:"idle_timeout" yaml:"idle_timeout"` // 空闲连接超时时间(秒),-1 表示禁用空闲连接超时检查,0 表示使用默认值(即 5 分钟)
|
||||
UseTLS bool `json:"use_tls" yaml:"use_tls"` // 是否使用 TLS
|
||||
|
||||
// Deprecated: 应使用 MinIdleConns 代替
|
||||
MaxIdle int `json:"max_idle" yaml:"max_idle"`
|
||||
// Deprecated: 应使用 PoolSize 代替
|
||||
MaxActive int `json:"max_active" yaml:"max_active"`
|
||||
}
|
||||
|
||||
// NewRedis 实例化
|
||||
@@ -34,10 +44,38 @@ func NewRedis(ctx context.Context, opts *RedisOpts) *Redis {
|
||||
DB: opts.Database,
|
||||
Username: opts.Username,
|
||||
Password: opts.Password,
|
||||
IdleTimeout: time.Second * time.Duration(opts.IdleTimeout),
|
||||
MinIdleConns: opts.MaxIdle,
|
||||
MinIdleConns: opts.MinIdleConns,
|
||||
PoolSize: opts.PoolSize,
|
||||
MaxRetries: opts.MaxRetries,
|
||||
}
|
||||
|
||||
// 兼容旧的 MaxIdle 参数,仅在未显式设置 MinIdleConns 时生效
|
||||
if opts.MaxIdle > 0 && opts.MinIdleConns == 0 {
|
||||
uniOpt.MinIdleConns = opts.MaxIdle
|
||||
}
|
||||
|
||||
// 兼容旧的 MaxActive 参数,仅在未显式设置 PoolSize 时生效
|
||||
if opts.MaxActive > 0 && opts.PoolSize == 0 {
|
||||
uniOpt.PoolSize = opts.MaxActive
|
||||
}
|
||||
|
||||
applyTimeout := func(seconds int, target *time.Duration) {
|
||||
if seconds > 0 {
|
||||
*target = time.Duration(seconds) * time.Second
|
||||
} else if seconds == -1 {
|
||||
// 当 seconds 为 -1 时,表示禁用超时:按 go-redis 约定,将超时时间设置为负值(如 -1ns)代表「无超时」
|
||||
*target = -1
|
||||
}
|
||||
// 当 seconds 为 0 时,使用 go-redis 的默认超时配置:
|
||||
// 不修改 target,保持其零值(0),由 go-redis 解释为“使用默认值”
|
||||
}
|
||||
|
||||
applyTimeout(opts.DialTimeout, &uniOpt.DialTimeout)
|
||||
applyTimeout(opts.ReadTimeout, &uniOpt.ReadTimeout)
|
||||
applyTimeout(opts.WriteTimeout, &uniOpt.WriteTimeout)
|
||||
applyTimeout(opts.PoolTimeout, &uniOpt.PoolTimeout)
|
||||
applyTimeout(opts.IdleTimeout, &uniOpt.IdleTimeout)
|
||||
|
||||
if opts.UseTLS {
|
||||
h, _, err := net.SplitHostPort(opts.Host)
|
||||
if err != nil {
|
||||
|
||||
268
cache/redis_test.go
vendored
268
cache/redis_test.go
vendored
@@ -6,6 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
"github.com/go-redis/redis/v8"
|
||||
)
|
||||
|
||||
func TestRedis(t *testing.T) {
|
||||
@@ -18,7 +19,16 @@ func TestRedis(t *testing.T) {
|
||||
timeoutDuration = time.Second
|
||||
ctx = context.Background()
|
||||
opts = &RedisOpts{
|
||||
Host: server.Addr(),
|
||||
Host: server.Addr(),
|
||||
Password: "",
|
||||
Database: 0,
|
||||
PoolSize: 10,
|
||||
MinIdleConns: 5,
|
||||
DialTimeout: 5,
|
||||
ReadTimeout: 5,
|
||||
WriteTimeout: 5,
|
||||
PoolTimeout: 5,
|
||||
IdleTimeout: 300,
|
||||
}
|
||||
redis = NewRedis(ctx, opts)
|
||||
val = "silenceper"
|
||||
@@ -44,3 +54,259 @@ func TestRedis(t *testing.T) {
|
||||
t.Errorf("delete Error , err=%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// setupRedisServer 创建并返回一个 miniredis 服务器实例
|
||||
func setupRedisServer(t *testing.T) *miniredis.Miniredis {
|
||||
server, err := miniredis.Run()
|
||||
if err != nil {
|
||||
t.Fatal("miniredis.Run Error", err)
|
||||
}
|
||||
t.Cleanup(server.Close)
|
||||
return server
|
||||
}
|
||||
|
||||
// TestRedisMaxIdleMapping 测试只设置MaxIdle应该映射到MinIdleConns
|
||||
func TestRedisMaxIdleMapping(t *testing.T) {
|
||||
server := setupRedisServer(t)
|
||||
ctx := context.Background()
|
||||
|
||||
opts := &RedisOpts{
|
||||
Host: server.Addr(),
|
||||
Database: 0,
|
||||
MaxIdle: 10,
|
||||
}
|
||||
r := NewRedis(ctx, opts)
|
||||
|
||||
// 获取底层的 UniversalClient 并断言为 *redis.Client
|
||||
client, ok := r.conn.(*redis.Client)
|
||||
if !ok {
|
||||
t.Fatal("无法转换为 *redis.Client")
|
||||
}
|
||||
|
||||
// 注意:MinIdleConns 表示期望的最小空闲连接数,但实际空闲连接数可能不同
|
||||
// 我们需要通过 Options() 来验证配置是否正确应用
|
||||
clientOpts := client.Options()
|
||||
if clientOpts.MinIdleConns != 10 {
|
||||
t.Errorf("期望 MinIdleConns = 10, 实际 = %d", clientOpts.MinIdleConns)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRedisMaxActiveMapping 测试只设置MaxActive应该映射到PoolSize
|
||||
func TestRedisMaxActiveMapping(t *testing.T) {
|
||||
server := setupRedisServer(t)
|
||||
ctx := context.Background()
|
||||
|
||||
opts := &RedisOpts{
|
||||
Host: server.Addr(),
|
||||
Database: 0,
|
||||
MaxActive: 20,
|
||||
}
|
||||
r := NewRedis(ctx, opts)
|
||||
|
||||
client, ok := r.conn.(*redis.Client)
|
||||
if !ok {
|
||||
t.Fatal("无法转换为 *redis.Client")
|
||||
}
|
||||
|
||||
clientOpts := client.Options()
|
||||
if clientOpts.PoolSize != 20 {
|
||||
t.Errorf("期望 PoolSize = 20, 实际 = %d", clientOpts.PoolSize)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRedisNewFieldsPriority 测试新字段应该优先于旧字段
|
||||
func TestRedisNewFieldsPriority(t *testing.T) {
|
||||
server := setupRedisServer(t)
|
||||
ctx := context.Background()
|
||||
|
||||
opts := &RedisOpts{
|
||||
Host: server.Addr(),
|
||||
Database: 0,
|
||||
MaxIdle: 5,
|
||||
MinIdleConns: 15,
|
||||
MaxActive: 10,
|
||||
PoolSize: 30,
|
||||
}
|
||||
r := NewRedis(ctx, opts)
|
||||
|
||||
client, ok := r.conn.(*redis.Client)
|
||||
if !ok {
|
||||
t.Fatal("无法转换为 *redis.Client")
|
||||
}
|
||||
|
||||
clientOpts := client.Options()
|
||||
if clientOpts.MinIdleConns != 15 {
|
||||
t.Errorf("期望 MinIdleConns = 15 (新字段优先), 实际 = %d", clientOpts.MinIdleConns)
|
||||
}
|
||||
if clientOpts.PoolSize != 30 {
|
||||
t.Errorf("期望 PoolSize = 30 (新字段优先), 实际 = %d", clientOpts.PoolSize)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRedisPositiveTimeouts 测试正值超时应该正确应用
|
||||
func TestRedisPositiveTimeouts(t *testing.T) {
|
||||
server := setupRedisServer(t)
|
||||
ctx := context.Background()
|
||||
|
||||
opts := &RedisOpts{
|
||||
Host: server.Addr(),
|
||||
Database: 0,
|
||||
DialTimeout: 10,
|
||||
ReadTimeout: 20,
|
||||
WriteTimeout: 30,
|
||||
PoolTimeout: 40,
|
||||
IdleTimeout: 50,
|
||||
}
|
||||
r := NewRedis(ctx, opts)
|
||||
|
||||
client, ok := r.conn.(*redis.Client)
|
||||
if !ok {
|
||||
t.Fatal("无法转换为 *redis.Client")
|
||||
}
|
||||
|
||||
clientOpts := client.Options()
|
||||
if clientOpts.DialTimeout != 10*time.Second {
|
||||
t.Errorf("期望 DialTimeout = 10s, 实际 = %v", clientOpts.DialTimeout)
|
||||
}
|
||||
if clientOpts.ReadTimeout != 20*time.Second {
|
||||
t.Errorf("期望 ReadTimeout = 20s, 实际 = %v", clientOpts.ReadTimeout)
|
||||
}
|
||||
if clientOpts.WriteTimeout != 30*time.Second {
|
||||
t.Errorf("期望 WriteTimeout = 30s, 实际 = %v", clientOpts.WriteTimeout)
|
||||
}
|
||||
if clientOpts.PoolTimeout != 40*time.Second {
|
||||
t.Errorf("期望 PoolTimeout = 40s, 实际 = %v", clientOpts.PoolTimeout)
|
||||
}
|
||||
if clientOpts.IdleTimeout != 50*time.Second {
|
||||
t.Errorf("期望 IdleTimeout = 50s, 实际 = %v", clientOpts.IdleTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRedisNegativeTimeouts 测试-1值应该禁用超时
|
||||
func TestRedisNegativeTimeouts(t *testing.T) {
|
||||
server := setupRedisServer(t)
|
||||
ctx := context.Background()
|
||||
|
||||
opts := &RedisOpts{
|
||||
Host: server.Addr(),
|
||||
Database: 0,
|
||||
DialTimeout: -1,
|
||||
ReadTimeout: -1,
|
||||
WriteTimeout: -1,
|
||||
PoolTimeout: -1,
|
||||
IdleTimeout: -1,
|
||||
}
|
||||
r := NewRedis(ctx, opts)
|
||||
|
||||
client, ok := r.conn.(*redis.Client)
|
||||
if !ok {
|
||||
t.Fatal("无法转换为 *redis.Client")
|
||||
}
|
||||
|
||||
clientOpts := client.Options()
|
||||
// -1 应该被设置为负值表示禁用超时
|
||||
// DialTimeout, PoolTimeout, IdleTimeout 会被设置为 -1ns
|
||||
if clientOpts.DialTimeout != -1 {
|
||||
t.Errorf("期望 DialTimeout = -1ns (禁用), 实际 = %v", clientOpts.DialTimeout)
|
||||
}
|
||||
// ReadTimeout 和 WriteTimeout 在 go-redis 中有特殊处理
|
||||
// 当设置为负值时,会被规范化为 0,这也表示无超时
|
||||
t.Logf("ReadTimeout = %v (设置为-1后的值)", clientOpts.ReadTimeout)
|
||||
t.Logf("WriteTimeout = %v (设置为-1后的值)", clientOpts.WriteTimeout)
|
||||
|
||||
if clientOpts.PoolTimeout != -1 {
|
||||
t.Errorf("期望 PoolTimeout = -1ns (禁用), 实际 = %v", clientOpts.PoolTimeout)
|
||||
}
|
||||
if clientOpts.IdleTimeout != -1 {
|
||||
t.Errorf("期望 IdleTimeout = -1ns (禁用), 实际 = %v", clientOpts.IdleTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRedisZeroTimeouts 测试0值应该使用go-redis默认值
|
||||
func TestRedisZeroTimeouts(t *testing.T) {
|
||||
server := setupRedisServer(t)
|
||||
ctx := context.Background()
|
||||
|
||||
opts := &RedisOpts{
|
||||
Host: server.Addr(),
|
||||
Database: 0,
|
||||
DialTimeout: 0,
|
||||
ReadTimeout: 0,
|
||||
WriteTimeout: 0,
|
||||
PoolTimeout: 0,
|
||||
IdleTimeout: 0,
|
||||
}
|
||||
r := NewRedis(ctx, opts)
|
||||
|
||||
client, ok := r.conn.(*redis.Client)
|
||||
if !ok {
|
||||
t.Fatal("无法转换为 *redis.Client")
|
||||
}
|
||||
|
||||
clientOpts := client.Options()
|
||||
// 0值应该保持为0,由 go-redis 使用默认值
|
||||
// go-redis 的默认值:
|
||||
// DialTimeout: 5s
|
||||
// ReadTimeout: 3s
|
||||
// WriteTimeout: ReadTimeout
|
||||
// PoolTimeout: ReadTimeout + 1s
|
||||
// IdleTimeout: 5min
|
||||
|
||||
if clientOpts.DialTimeout == 0 {
|
||||
t.Error("期望 DialTimeout 使用 go-redis 默认值 (5s), 实际为 0")
|
||||
}
|
||||
if clientOpts.ReadTimeout == 0 {
|
||||
t.Error("期望 ReadTimeout 使用 go-redis 默认值 (3s), 实际为 0")
|
||||
}
|
||||
if clientOpts.WriteTimeout == 0 {
|
||||
t.Error("期望 WriteTimeout 使用 go-redis 默认值 (ReadTimeout), 实际为 0")
|
||||
}
|
||||
if clientOpts.PoolTimeout == 0 {
|
||||
t.Error("期望 PoolTimeout 使用 go-redis 默认值 (ReadTimeout + 1s), 实际为 0")
|
||||
}
|
||||
if clientOpts.IdleTimeout == 0 {
|
||||
t.Error("期望 IdleTimeout 使用 go-redis 默认值 (5min), 实际为 0")
|
||||
}
|
||||
}
|
||||
|
||||
// TestRedisMixedTimeouts 测试混合超时配置
|
||||
func TestRedisMixedTimeouts(t *testing.T) {
|
||||
server := setupRedisServer(t)
|
||||
ctx := context.Background()
|
||||
|
||||
opts := &RedisOpts{
|
||||
Host: server.Addr(),
|
||||
Database: 0,
|
||||
DialTimeout: 5, // 正值
|
||||
ReadTimeout: -1, // 禁用
|
||||
WriteTimeout: 0, // 使用默认值
|
||||
PoolTimeout: 10, // 正值
|
||||
IdleTimeout: -1, // 禁用
|
||||
}
|
||||
r := NewRedis(ctx, opts)
|
||||
|
||||
client, ok := r.conn.(*redis.Client)
|
||||
if !ok {
|
||||
t.Fatal("无法转换为 *redis.Client")
|
||||
}
|
||||
|
||||
clientOpts := client.Options()
|
||||
if clientOpts.DialTimeout != 5*time.Second {
|
||||
t.Errorf("期望 DialTimeout = 5s, 实际 = %v", clientOpts.DialTimeout)
|
||||
}
|
||||
// ReadTimeout 设置为 -1,会被 go-redis 处理为 0(无超时)
|
||||
t.Logf("ReadTimeout = %v (设置为-1后的值)", clientOpts.ReadTimeout)
|
||||
|
||||
// WriteTimeout 设置为 0,应该使用 go-redis 的默认值
|
||||
// 默认值通常是 ReadTimeout 的值
|
||||
t.Logf("WriteTimeout = %v (设置为0后使用的默认值)", clientOpts.WriteTimeout)
|
||||
|
||||
if clientOpts.PoolTimeout != 10*time.Second {
|
||||
t.Errorf("期望 PoolTimeout = 10s, 实际 = %v", clientOpts.PoolTimeout)
|
||||
}
|
||||
|
||||
// IdleTimeout 设置为 -1,应该被设置为 -1ns(禁用空闲超时)
|
||||
if clientOpts.IdleTimeout != -1 {
|
||||
t.Errorf("期望 IdleTimeout = -1ns (禁用), 实际 = %v", clientOpts.IdleTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
618
miniprogram/express/intracity.go
Normal file
618
miniprogram/express/intracity.go
Normal file
@@ -0,0 +1,618 @@
|
||||
package express
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
)
|
||||
|
||||
// 同城配送 API URL
|
||||
// https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/express/business/intracity_service.html
|
||||
const (
|
||||
// 开通门店权限
|
||||
intracityApplyURL = "https://api.weixin.qq.com/cgi-bin/express/intracity/apply?access_token=%s"
|
||||
// 创建门店
|
||||
intracityCreateStoreURL = "https://api.weixin.qq.com/cgi-bin/express/intracity/createstore?access_token=%s"
|
||||
// 查询门店
|
||||
intracityQueryStoreURL = "https://api.weixin.qq.com/cgi-bin/express/intracity/querystore?access_token=%s"
|
||||
// 更新门店
|
||||
intracityUpdateStoreURL = "https://api.weixin.qq.com/cgi-bin/express/intracity/updatestore?access_token=%s"
|
||||
// 门店运费充值
|
||||
intracityStoreChargeURL = "https://api.weixin.qq.com/cgi-bin/express/intracity/storecharge?access_token=%s"
|
||||
// 门店运费退款
|
||||
intracityStoreRefundURL = "https://api.weixin.qq.com/cgi-bin/express/intracity/storerefund?access_token=%s"
|
||||
// 门店运费流水查询
|
||||
intracityQueryFlowURL = "https://api.weixin.qq.com/cgi-bin/express/intracity/queryflow?access_token=%s"
|
||||
// 门店余额查询
|
||||
intracityBalanceQueryURL = "https://api.weixin.qq.com/cgi-bin/express/intracity/balancequery?access_token=%s"
|
||||
// 预下配送单(查询运费)
|
||||
intracityPreAddOrderURL = "https://api.weixin.qq.com/cgi-bin/express/intracity/preaddorder?access_token=%s"
|
||||
// 创建配送单
|
||||
intracityAddOrderURL = "https://api.weixin.qq.com/cgi-bin/express/intracity/addorder?access_token=%s"
|
||||
// 查询配送单
|
||||
intracityQueryOrderURL = "https://api.weixin.qq.com/cgi-bin/express/intracity/queryorder?access_token=%s"
|
||||
// 取消配送单
|
||||
intracityCancelOrderURL = "https://api.weixin.qq.com/cgi-bin/express/intracity/cancelorder?access_token=%s"
|
||||
// 模拟配送回调(仅用于测试)
|
||||
intracityMockNotifyURL = "https://api.weixin.qq.com/cgi-bin/express/intracity/mocknotify?access_token=%s"
|
||||
)
|
||||
|
||||
// PayMode 充值/扣费主体
|
||||
type PayMode string
|
||||
|
||||
const (
|
||||
// PayModeStore 门店
|
||||
PayModeStore PayMode = "PAY_MODE_STORE"
|
||||
// PayModeApp 小程序
|
||||
PayModeApp PayMode = "PAY_MODE_APP"
|
||||
// PayModeComponent 服务商
|
||||
PayModeComponent PayMode = "PAY_MODE_COMPONENT"
|
||||
)
|
||||
|
||||
// OrderPattern 运力偏好
|
||||
type OrderPattern uint32
|
||||
|
||||
const (
|
||||
// OrderPatternPriceFirst 价格优先
|
||||
OrderPatternPriceFirst OrderPattern = 1
|
||||
// OrderPatternTransFirst 运力优先
|
||||
OrderPatternTransFirst OrderPattern = 2
|
||||
)
|
||||
|
||||
// FlowType 流水类型
|
||||
type FlowType uint32
|
||||
|
||||
const (
|
||||
// FlowTypeCharge 充值流水
|
||||
FlowTypeCharge FlowType = 1
|
||||
// FlowTypeConsume 消费流水
|
||||
FlowTypeConsume FlowType = 2
|
||||
// FlowTypeRefund 退款流水
|
||||
FlowTypeRefund FlowType = 3
|
||||
)
|
||||
|
||||
// IntracityDeliveryStatus 配送单状态
|
||||
type IntracityDeliveryStatus int32
|
||||
|
||||
const (
|
||||
// IntracityDeliveryStatusReady 配送单待接单
|
||||
IntracityDeliveryStatusReady IntracityDeliveryStatus = 100
|
||||
// IntracityDeliveryStatusPickedUp 配送单待取货
|
||||
IntracityDeliveryStatusPickedUp IntracityDeliveryStatus = 101
|
||||
// IntracityDeliveryStatusOngoing 配送单配送中
|
||||
IntracityDeliveryStatusOngoing IntracityDeliveryStatus = 102
|
||||
// IntracityDeliveryStatusFinished 配送单已送达
|
||||
IntracityDeliveryStatusFinished IntracityDeliveryStatus = 200
|
||||
// IntracityDeliveryStatusCancelled 配送单已取消
|
||||
IntracityDeliveryStatusCancelled IntracityDeliveryStatus = 300
|
||||
// IntracityDeliveryStatusAbnormal 配送单异常
|
||||
IntracityDeliveryStatusAbnormal IntracityDeliveryStatus = 400
|
||||
)
|
||||
|
||||
// IntracityAddressInfo 门店地址信息
|
||||
type IntracityAddressInfo struct {
|
||||
Province string `json:"province"` // 省/自治区/直辖市
|
||||
City string `json:"city"` // 地级市
|
||||
Area string `json:"area"` // 县/县级市/区
|
||||
Street string `json:"street"` // 街道
|
||||
House string `json:"house"` // 具体门牌号或详细地址
|
||||
Lat float64 `json:"lat"` // 门店所在地纬度
|
||||
Lng float64 `json:"lng"` // 门店所在地经度
|
||||
Phone string `json:"phone"` // 门店联系电话
|
||||
Name string `json:"name,omitempty"` // 联系人姓名(收货地址时使用)
|
||||
}
|
||||
|
||||
// IntracityStoreInfo 门店信息
|
||||
type IntracityStoreInfo struct {
|
||||
WxStoreID string `json:"wx_store_id"` // 微信门店编号
|
||||
OutStoreID string `json:"out_store_id"` // 自定义门店编号
|
||||
CityID string `json:"city_id"` // 门店所在城市ID
|
||||
StoreName string `json:"store_name"` // 门店名称
|
||||
OrderPattern OrderPattern `json:"order_pattern"` // 运力偏好
|
||||
ServiceTransPrefer string `json:"service_trans_prefer"` // 优先使用的运力ID
|
||||
AddressInfo IntracityAddressInfo `json:"address_info"` // 门店地址信息
|
||||
}
|
||||
|
||||
// ============ 门店管理接口 ============
|
||||
|
||||
// IntracityApply 开通门店权限
|
||||
// https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/express/business/intracity_service.html
|
||||
func (express *Express) IntracityApply(ctx context.Context) error {
|
||||
accessToken, err := express.GetAccessToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf(intracityApplyURL, accessToken)
|
||||
response, err := util.PostJSONContext(ctx, uri, map[string]interface{}{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return util.DecodeWithCommonError(response, "IntracityApply")
|
||||
}
|
||||
|
||||
// CreateStoreRequest 创建门店请求参数
|
||||
type CreateStoreRequest struct {
|
||||
OutStoreID string `json:"out_store_id"` // 自定义门店编号
|
||||
StoreName string `json:"store_name"` // 门店名称
|
||||
OrderPattern OrderPattern `json:"order_pattern,omitempty"` // 运力偏好:1-价格优先,2-运力优先
|
||||
ServiceTransPrefer string `json:"service_trans_prefer,omitempty"` // 优先使用的运力ID,order_pattern=2时必填
|
||||
AddressInfo IntracityAddressInfo `json:"address_info"` // 门店地址信息
|
||||
}
|
||||
|
||||
// CreateStoreResponse 创建门店返回参数
|
||||
type CreateStoreResponse struct {
|
||||
util.CommonError
|
||||
WxStoreID string `json:"wx_store_id"` // 微信门店编号
|
||||
AppID string `json:"appid"` // 小程序appid
|
||||
OutStoreID string `json:"out_store_id"` // 自定义门店ID
|
||||
}
|
||||
|
||||
// IntracityCreateStore 创建门店
|
||||
func (express *Express) IntracityCreateStore(ctx context.Context, req *CreateStoreRequest) (res CreateStoreResponse, err error) {
|
||||
accessToken, err := express.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf(intracityCreateStoreURL, accessToken)
|
||||
response, err := util.PostJSONContext(ctx, uri, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = util.DecodeWithError(response, &res, "IntracityCreateStore")
|
||||
return
|
||||
}
|
||||
|
||||
// QueryStoreRequest 查询门店请求参数
|
||||
type QueryStoreRequest struct {
|
||||
WxStoreID string `json:"wx_store_id,omitempty"` // 微信门店编号
|
||||
OutStoreID string `json:"out_store_id,omitempty"` // 自定义门店编号
|
||||
}
|
||||
|
||||
// QueryStoreResponse 查询门店返回参数
|
||||
type QueryStoreResponse struct {
|
||||
util.CommonError
|
||||
Total uint32 `json:"total"` // 符合条件的门店总数
|
||||
AppID string `json:"appid"` // 小程序appid
|
||||
StoreList []IntracityStoreInfo `json:"store_list"` // 门店信息列表
|
||||
}
|
||||
|
||||
// IntracityQueryStore 查询门店
|
||||
func (express *Express) IntracityQueryStore(ctx context.Context, req *QueryStoreRequest) (res QueryStoreResponse, err error) {
|
||||
accessToken, err := express.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf(intracityQueryStoreURL, accessToken)
|
||||
response, err := util.PostJSONContext(ctx, uri, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = util.DecodeWithError(response, &res, "IntracityQueryStore")
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateStoreKeyInfo 更新门店的key信息
|
||||
type UpdateStoreKeyInfo struct {
|
||||
WxStoreID string `json:"wx_store_id,omitempty"` // 微信门店编号
|
||||
OutStoreID string `json:"out_store_id,omitempty"` // 自定义门店编号,二选一
|
||||
}
|
||||
|
||||
// UpdateStoreContent 更新门店的内容
|
||||
type UpdateStoreContent struct {
|
||||
StoreName string `json:"store_name,omitempty"` // 门店名称
|
||||
OrderPattern OrderPattern `json:"order_pattern,omitempty"` // 运力偏好
|
||||
ServiceTransPrefer string `json:"service_trans_prefer,omitempty"` // 优先使用的运力ID
|
||||
AddressInfo *IntracityAddressInfo `json:"address_info,omitempty"` // 门店地址信息
|
||||
}
|
||||
|
||||
// UpdateStoreRequest 更新门店请求参数
|
||||
type UpdateStoreRequest struct {
|
||||
Keys UpdateStoreKeyInfo `json:"keys"` // 门店编号
|
||||
Content UpdateStoreContent `json:"content"` // 更新内容
|
||||
}
|
||||
|
||||
// IntracityUpdateStore 更新门店
|
||||
func (express *Express) IntracityUpdateStore(ctx context.Context, req *UpdateStoreRequest) error {
|
||||
accessToken, err := express.GetAccessToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf(intracityUpdateStoreURL, accessToken)
|
||||
response, err := util.PostJSONContext(ctx, uri, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return util.DecodeWithCommonError(response, "IntracityUpdateStore")
|
||||
}
|
||||
|
||||
// ============ 充值退款接口 ============
|
||||
|
||||
// StoreChargeRequest 门店运费充值请求参数
|
||||
type StoreChargeRequest struct {
|
||||
WxStoreID string `json:"wx_store_id,omitempty"` // 微信门店编号,pay_mode=PAY_MODE_STORE时必传
|
||||
ServiceTransID string `json:"service_trans_id"` // 运力ID
|
||||
Amount uint32 `json:"amount"` // 充值金额,单位:分,50元起充
|
||||
PayMode PayMode `json:"pay_mode,omitempty"` // 充值主体
|
||||
}
|
||||
|
||||
// StoreChargeResponse 门店运费充值返回参数
|
||||
type StoreChargeResponse struct {
|
||||
util.CommonError
|
||||
PayURL string `json:"payurl"` // 充值页面地址
|
||||
AppID string `json:"appid"` // 小程序appid
|
||||
WxStoreID string `json:"wx_store_id"` // 微信门店编号
|
||||
}
|
||||
|
||||
// IntracityStoreCharge 门店运费充值
|
||||
func (express *Express) IntracityStoreCharge(ctx context.Context, req *StoreChargeRequest) (res StoreChargeResponse, err error) {
|
||||
accessToken, err := express.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf(intracityStoreChargeURL, accessToken)
|
||||
response, err := util.PostJSONContext(ctx, uri, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = util.DecodeWithError(response, &res, "IntracityStoreCharge")
|
||||
return
|
||||
}
|
||||
|
||||
// StoreRefundRequest 门店运费退款请求参数
|
||||
type StoreRefundRequest struct {
|
||||
WxStoreID string `json:"wx_store_id,omitempty"` // 微信门店编号
|
||||
PayMode PayMode `json:"pay_mode,omitempty"` // 充值/扣费主体
|
||||
ServiceTransID string `json:"service_trans_id"` // 运力ID
|
||||
}
|
||||
|
||||
// StoreRefundResponse 门店运费退款返回参数
|
||||
type StoreRefundResponse struct {
|
||||
util.CommonError
|
||||
AppID string `json:"appid"` // 小程序appid
|
||||
WxStoreID string `json:"wx_store_id"` // 微信门店编号
|
||||
RefundAmount uint32 `json:"refund_amount"` // 退款金额,单位:分
|
||||
}
|
||||
|
||||
// IntracityStoreRefund 门店运费退款
|
||||
func (express *Express) IntracityStoreRefund(ctx context.Context, req *StoreRefundRequest) (res StoreRefundResponse, err error) {
|
||||
accessToken, err := express.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf(intracityStoreRefundURL, accessToken)
|
||||
response, err := util.PostJSONContext(ctx, uri, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = util.DecodeWithError(response, &res, "IntracityStoreRefund")
|
||||
return
|
||||
}
|
||||
|
||||
// QueryFlowRequest 门店运费流水查询请求参数
|
||||
type QueryFlowRequest struct {
|
||||
WxStoreID string `json:"wx_store_id"` // 微信门店编号
|
||||
FlowType FlowType `json:"flow_type"` // 流水类型:1-充值,2-消费,3-退款
|
||||
ServiceTransID string `json:"service_trans_id,omitempty"` // 运力ID
|
||||
BeginTime uint32 `json:"begin_time,omitempty"` // 开始时间戳
|
||||
EndTime uint32 `json:"end_time,omitempty"` // 结束时间戳
|
||||
PayMode PayMode `json:"pay_mode"` // 扣费主体
|
||||
}
|
||||
|
||||
// FlowRecordInfo 流水记录信息
|
||||
type FlowRecordInfo struct {
|
||||
FlowType FlowType `json:"flow_type"` // 流水类型
|
||||
AppID string `json:"appid"` // appid
|
||||
WxStoreID string `json:"wx_store_id"` // 微信门店ID
|
||||
PayOrderID uint64 `json:"pay_order_id,omitempty"` // 充值订单号
|
||||
WxOrderID string `json:"wx_order_id,omitempty"` // 订单ID(消费流水)
|
||||
ServiceTransID string `json:"service_trans_id"` // 运力ID
|
||||
OpenID string `json:"openid,omitempty"` // 用户openid(消费流水)
|
||||
DeliveryStatus int32 `json:"delivery_status,omitempty"` // 运单状态(消费流水)
|
||||
PayAmount int32 `json:"pay_amount"` // 支付金额,单位:分
|
||||
PayTime uint32 `json:"pay_time,omitempty"` // 支付时间
|
||||
PayStatus string `json:"pay_status,omitempty"` // 支付状态
|
||||
RefundStatus string `json:"refund_status,omitempty"` // 退款状态
|
||||
RefundAmount int32 `json:"refund_amount,omitempty"` // 退款金额
|
||||
RefundTime uint32 `json:"refund_time,omitempty"` // 退款时间
|
||||
DeductAmount int32 `json:"deduct_amount,omitempty"` // 扣除违约金
|
||||
CreateTime uint32 `json:"create_time"` // 创建时间
|
||||
ConsumeDeadline uint32 `json:"consume_deadline,omitempty"` // 有效截止日期
|
||||
BillID string `json:"bill_id,omitempty"` // 运单ID
|
||||
DeliveryFinishedTime uint32 `json:"delivery_finished_time,omitempty"` // 运单完成配送的时间
|
||||
}
|
||||
|
||||
// QueryFlowResponse 门店运费流水查询返回参数
|
||||
type QueryFlowResponse struct {
|
||||
util.CommonError
|
||||
Total uint32 `json:"total"` // 总数
|
||||
FlowList []FlowRecordInfo `json:"flow_list"` // 流水数组
|
||||
TotalPayAmt int `json:"total_pay_amt"` // 总支付金额
|
||||
TotalRefundAmt int `json:"total_refund_amt"` // 总退款金额
|
||||
TotalDeductAmt int `json:"total_deduct_amt"` // 总违约金(消费流水返回)
|
||||
}
|
||||
|
||||
// IntracityQueryFlow 门店运费流水查询
|
||||
func (express *Express) IntracityQueryFlow(ctx context.Context, req *QueryFlowRequest) (res QueryFlowResponse, err error) {
|
||||
accessToken, err := express.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf(intracityQueryFlowURL, accessToken)
|
||||
response, err := util.PostJSONContext(ctx, uri, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = util.DecodeWithError(response, &res, "IntracityQueryFlow")
|
||||
return
|
||||
}
|
||||
|
||||
// BalanceQueryRequest 门店余额查询请求参数
|
||||
type BalanceQueryRequest struct {
|
||||
WxStoreID string `json:"wx_store_id,omitempty"` // 微信门店编号
|
||||
ServiceTransID string `json:"service_trans_id,omitempty"` // 运力ID
|
||||
PayMode PayMode `json:"pay_mode,omitempty"` // 充值/扣费主体
|
||||
}
|
||||
|
||||
// BalanceInfo 余额信息
|
||||
type BalanceInfo struct {
|
||||
ServiceTransID string `json:"service_trans_id"` // 运力ID
|
||||
Balance int32 `json:"balance"` // 余额,单位:分
|
||||
}
|
||||
|
||||
// BalanceQueryResponse 门店余额查询返回参数
|
||||
type BalanceQueryResponse struct {
|
||||
util.CommonError
|
||||
AppID string `json:"appid"` // 小程序appid
|
||||
WxStoreID string `json:"wx_store_id"` // 微信门店编号
|
||||
BalanceList []BalanceInfo `json:"balance_list"` // 余额列表
|
||||
}
|
||||
|
||||
// IntracityBalanceQuery 门店余额查询
|
||||
func (express *Express) IntracityBalanceQuery(ctx context.Context, req *BalanceQueryRequest) (res BalanceQueryResponse, err error) {
|
||||
accessToken, err := express.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf(intracityBalanceQueryURL, accessToken)
|
||||
response, err := util.PostJSONContext(ctx, uri, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = util.DecodeWithError(response, &res, "IntracityBalanceQuery")
|
||||
return
|
||||
}
|
||||
|
||||
// ============ 配送订单接口 ============
|
||||
|
||||
// CargoInfo 货物信息
|
||||
type CargoInfo struct {
|
||||
Name string `json:"name"` // 货物名称
|
||||
Num uint32 `json:"num,omitempty"` // 货物数量
|
||||
Price uint32 `json:"price,omitempty"` // 货物价格,单位:分
|
||||
Weight uint32 `json:"weight,omitempty"` // 货物重量,单位:克
|
||||
}
|
||||
|
||||
// PreAddOrderRequest 预下配送单请求参数
|
||||
type PreAddOrderRequest struct {
|
||||
WxStoreID string `json:"wx_store_id,omitempty"` // 微信门店编号,二选一
|
||||
OutStoreID string `json:"out_store_id,omitempty"` // 自定义门店编号,二选一
|
||||
UserOpenID string `json:"user_openid"` // 用户openid
|
||||
UserPhone string `json:"user_phone,omitempty"` // 用户联系电话
|
||||
UserName string `json:"user_name,omitempty"` // 用户姓名
|
||||
UserLat float64 `json:"user_lat"` // 用户地址纬度
|
||||
UserLng float64 `json:"user_lng"` // 用户地址经度
|
||||
UserAddress string `json:"user_address"` // 用户详细地址
|
||||
ServiceTransID string `json:"service_trans_id,omitempty"` // 运力ID,不传则查询所有运力
|
||||
PayMode PayMode `json:"pay_mode,omitempty"` // 充值/扣费主体
|
||||
CargoInfo *CargoInfo `json:"cargo_info,omitempty"` // 货物信息
|
||||
}
|
||||
|
||||
// TransInfo 运力信息
|
||||
type TransInfo struct {
|
||||
ServiceTransID string `json:"service_trans_id"` // 运力ID
|
||||
ServiceName string `json:"service_name"` // 运力名称
|
||||
Price uint32 `json:"price"` // 配送费用,单位:分
|
||||
Distance uint32 `json:"distance"` // 配送距离,单位:米
|
||||
Errcode int `json:"errcode"` // 错误码,0表示成功
|
||||
Errmsg string `json:"errmsg"` // 错误信息
|
||||
}
|
||||
|
||||
// PreAddOrderResponse 预下配送单返回参数
|
||||
type PreAddOrderResponse struct {
|
||||
util.CommonError
|
||||
WxStoreID string `json:"wx_store_id"` // 微信门店编号
|
||||
AppID string `json:"appid"` // 小程序appid
|
||||
TransList []TransInfo `json:"trans_list"` // 运力列表
|
||||
}
|
||||
|
||||
// IntracityPreAddOrder 预下配送单(查询运费)
|
||||
func (express *Express) IntracityPreAddOrder(ctx context.Context, req *PreAddOrderRequest) (res PreAddOrderResponse, err error) {
|
||||
accessToken, err := express.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf(intracityPreAddOrderURL, accessToken)
|
||||
response, err := util.PostJSONContext(ctx, uri, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = util.DecodeWithError(response, &res, "IntracityPreAddOrder")
|
||||
return
|
||||
}
|
||||
|
||||
// AddOrderRequest 创建配送单请求参数
|
||||
type AddOrderRequest struct {
|
||||
WxStoreID string `json:"wx_store_id,omitempty"` // 微信门店编号,二选一
|
||||
OutStoreID string `json:"out_store_id,omitempty"` // 自定义门店编号,二选一
|
||||
OutOrderID string `json:"out_order_id"` // 自定义订单号,需唯一
|
||||
ServiceTransID string `json:"service_trans_id,omitempty"` // 运力ID
|
||||
UserOpenID string `json:"user_openid"` // 用户openid
|
||||
UserPhone string `json:"user_phone"` // 用户联系电话
|
||||
UserName string `json:"user_name"` // 用户姓名
|
||||
UserLat float64 `json:"user_lat"` // 用户地址纬度
|
||||
UserLng float64 `json:"user_lng"` // 用户地址经度
|
||||
UserAddress string `json:"user_address"` // 用户详细地址
|
||||
PayMode PayMode `json:"pay_mode,omitempty"` // 充值/扣费主体
|
||||
CargoInfo *CargoInfo `json:"cargo_info,omitempty"` // 货物信息
|
||||
OrderDetailPath string `json:"order_detail_path,omitempty"` // 订单详情页路径
|
||||
CallbackURL string `json:"callback_url,omitempty"` // 配送状态回调URL
|
||||
UseInsurance uint32 `json:"use_insurance,omitempty"` // 是否使用保价:0-不使用,1-使用
|
||||
InsuranceValue uint32 `json:"insurance_value,omitempty"` // 保价金额,单位:分
|
||||
ExpectTime uint32 `json:"expect_time,omitempty"` // 期望送达时间戳
|
||||
Remark string `json:"remark,omitempty"` // 备注
|
||||
}
|
||||
|
||||
// AddOrderResponse 创建配送单返回参数
|
||||
type AddOrderResponse struct {
|
||||
util.CommonError
|
||||
WxOrderID string `json:"wx_order_id"` // 微信订单号
|
||||
AppID string `json:"appid"` // 小程序appid
|
||||
WxStoreID string `json:"wx_store_id"` // 微信门店编号
|
||||
OutOrderID string `json:"out_order_id"` // 自定义订单号
|
||||
ServiceTransID string `json:"service_trans_id"` // 运力ID
|
||||
BillID string `json:"bill_id"` // 运力订单号
|
||||
Price uint32 `json:"price"` // 配送费用,单位:分
|
||||
Distance uint32 `json:"distance"` // 配送距离,单位:米
|
||||
}
|
||||
|
||||
// IntracityAddOrder 创建配送单
|
||||
func (express *Express) IntracityAddOrder(ctx context.Context, req *AddOrderRequest) (res AddOrderResponse, err error) {
|
||||
accessToken, err := express.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf(intracityAddOrderURL, accessToken)
|
||||
response, err := util.PostJSONContext(ctx, uri, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = util.DecodeWithError(response, &res, "IntracityAddOrder")
|
||||
return
|
||||
}
|
||||
|
||||
// QueryOrderRequest 查询配送单请求参数
|
||||
type QueryOrderRequest struct {
|
||||
WxOrderID string `json:"wx_order_id,omitempty"` // 微信订单号,二选一
|
||||
OutOrderID string `json:"out_order_id,omitempty"` // 自定义订单号,二选一
|
||||
WxStoreID string `json:"wx_store_id,omitempty"` // 微信门店编号
|
||||
OutStoreID string `json:"out_store_id,omitempty"` // 自定义门店编号
|
||||
}
|
||||
|
||||
// RiderInfo 骑手信息
|
||||
type RiderInfo struct {
|
||||
Name string `json:"name"` // 骑手姓名
|
||||
Phone string `json:"phone"` // 骑手电话
|
||||
RiderCode string `json:"rider_code"` // 骑手编号
|
||||
RiderImgURL string `json:"rider_img_url"` // 骑手头像URL
|
||||
}
|
||||
|
||||
// QueryOrderResponse 查询配送单返回参数
|
||||
type QueryOrderResponse struct {
|
||||
util.CommonError
|
||||
WxOrderID string `json:"wx_order_id"` // 微信订单号
|
||||
AppID string `json:"appid"` // 小程序appid
|
||||
WxStoreID string `json:"wx_store_id"` // 微信门店编号
|
||||
OutOrderID string `json:"out_order_id"` // 自定义订单号
|
||||
ServiceTransID string `json:"service_trans_id"` // 运力ID
|
||||
BillID string `json:"bill_id"` // 运力订单号
|
||||
DeliveryStatus IntracityDeliveryStatus `json:"delivery_status"` // 配送状态
|
||||
Price uint32 `json:"price"` // 配送费用,单位:分
|
||||
Distance uint32 `json:"distance"` // 配送距离,单位:米
|
||||
CreateTime uint32 `json:"create_time"` // 订单创建时间
|
||||
RiderInfo *RiderInfo `json:"rider_info"` // 骑手信息
|
||||
FinishTime uint32 `json:"finish_time"` // 订单完成时间
|
||||
}
|
||||
|
||||
// IntracityQueryOrder 查询配送单
|
||||
func (express *Express) IntracityQueryOrder(ctx context.Context, req *QueryOrderRequest) (res QueryOrderResponse, err error) {
|
||||
accessToken, err := express.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf(intracityQueryOrderURL, accessToken)
|
||||
response, err := util.PostJSONContext(ctx, uri, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = util.DecodeWithError(response, &res, "IntracityQueryOrder")
|
||||
return
|
||||
}
|
||||
|
||||
// CancelOrderRequest 取消配送单请求参数
|
||||
type CancelOrderRequest struct {
|
||||
WxOrderID string `json:"wx_order_id,omitempty"` // 微信订单号,二选一
|
||||
OutOrderID string `json:"out_order_id,omitempty"` // 自定义订单号,二选一
|
||||
WxStoreID string `json:"wx_store_id,omitempty"` // 微信门店编号
|
||||
OutStoreID string `json:"out_store_id,omitempty"` // 自定义门店编号
|
||||
CancelReason string `json:"cancel_reason,omitempty"` // 取消原因
|
||||
}
|
||||
|
||||
// CancelOrderResponse 取消配送单返回参数
|
||||
type CancelOrderResponse struct {
|
||||
util.CommonError
|
||||
WxOrderID string `json:"wx_order_id"` // 微信订单号
|
||||
RefundAmount int32 `json:"refund_amount"` // 退款金额,单位:分
|
||||
DeductAmount int32 `json:"deduct_amount"` // 扣除违约金,单位:分
|
||||
}
|
||||
|
||||
// IntracityCancelOrder 取消配送单
|
||||
func (express *Express) IntracityCancelOrder(ctx context.Context, req *CancelOrderRequest) (res CancelOrderResponse, err error) {
|
||||
accessToken, err := express.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf(intracityCancelOrderURL, accessToken)
|
||||
response, err := util.PostJSONContext(ctx, uri, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = util.DecodeWithError(response, &res, "IntracityCancelOrder")
|
||||
return
|
||||
}
|
||||
|
||||
// MockNotifyRequest 模拟配送回调请求参数(仅用于测试)
|
||||
type MockNotifyRequest struct {
|
||||
WxOrderID string `json:"wx_order_id"` // 微信订单号
|
||||
DeliveryStatus IntracityDeliveryStatus `json:"delivery_status"` // 配送状态
|
||||
}
|
||||
|
||||
// IntracityMockNotify 模拟配送回调(仅用于测试)
|
||||
func (express *Express) IntracityMockNotify(ctx context.Context, req *MockNotifyRequest) error {
|
||||
accessToken, err := express.GetAccessToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf(intracityMockNotifyURL, accessToken)
|
||||
response, err := util.PostJSONContext(ctx, uri, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return util.DecodeWithCommonError(response, "IntracityMockNotify")
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/silenceper/wechat/v2/miniprogram/express"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/message"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/minidrama"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/ocr"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/operation"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/order"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/privacy"
|
||||
@@ -191,3 +192,13 @@ func (miniProgram *MiniProgram) GetOperation() *operation.Operation {
|
||||
func (miniProgram *MiniProgram) GetExpress() *express.Express {
|
||||
return express.NewExpress(miniProgram.ctx)
|
||||
}
|
||||
|
||||
// GetOCR OCR接口
|
||||
func (miniProgram *MiniProgram) GetOCR() *ocr.OCR {
|
||||
return ocr.NewOCR(miniProgram.ctx)
|
||||
}
|
||||
|
||||
// GetIntracity 同城配送接口
|
||||
func (miniProgram *MiniProgram) GetIntracity() *express.Express {
|
||||
return express.NewExpress(miniProgram.ctx)
|
||||
}
|
||||
|
||||
248
miniprogram/ocr/ocr.go
Normal file
248
miniprogram/ocr/ocr.go
Normal file
@@ -0,0 +1,248 @@
|
||||
package ocr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/silenceper/wechat/v2/miniprogram/context"
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
)
|
||||
|
||||
const (
|
||||
ocrIDCardURL = "https://api.weixin.qq.com/cv/ocr/idcard"
|
||||
ocrBankCardURL = "https://api.weixin.qq.com/cv/ocr/bankcard"
|
||||
ocrDrivingURL = "https://api.weixin.qq.com/cv/ocr/driving"
|
||||
ocrDrivingLicenseURL = "https://api.weixin.qq.com/cv/ocr/drivinglicense"
|
||||
ocrBizLicenseURL = "https://api.weixin.qq.com/cv/ocr/bizlicense"
|
||||
ocrCommonURL = "https://api.weixin.qq.com/cv/ocr/comm"
|
||||
)
|
||||
|
||||
// OCR struct
|
||||
type OCR struct {
|
||||
*context.Context
|
||||
}
|
||||
|
||||
// coordinate 坐标
|
||||
type coordinate struct {
|
||||
X int64 `json:"x,omitempty"`
|
||||
Y int64 `json:"y,omitempty"`
|
||||
}
|
||||
|
||||
// position 位置
|
||||
type position struct {
|
||||
LeftTop coordinate `json:"left_top"`
|
||||
RightTop coordinate `json:"right_top"`
|
||||
RightBottom coordinate `json:"right_bottom"`
|
||||
LeftBottom coordinate `json:"left_bottom"`
|
||||
}
|
||||
|
||||
// imageSize 图片尺寸
|
||||
type imageSize struct {
|
||||
Width int64 `json:"w,omitempty"`
|
||||
Height int64 `json:"h,omitempty"`
|
||||
}
|
||||
|
||||
// ResDriving 行驶证返回结果
|
||||
type ResDriving struct {
|
||||
util.CommonError
|
||||
|
||||
PlateNumber string `json:"plate_num,omitempty"`
|
||||
VehicleType string `json:"vehicle_type,omitempty"`
|
||||
Owner string `json:"owner,omitempty"`
|
||||
Address string `json:"addr,omitempty"`
|
||||
UseCharacter string `json:"use_character,omitempty"`
|
||||
Model string `json:"model,omitempty"`
|
||||
Vin string `json:"vin,omitempty"`
|
||||
EngineNumber string `json:"engine_num,omitempty"`
|
||||
RegisterDate string `json:"register_date,omitempty"`
|
||||
IssueDate string `json:"issue_date,omitempty"`
|
||||
PlateNumberB string `json:"plate_num_b,omitempty"`
|
||||
Record string `json:"record,omitempty"`
|
||||
PassengersNumber string `json:"passengers_num,omitempty"`
|
||||
TotalQuality string `json:"total_quality,omitempty"`
|
||||
PrepareQuality string `json:"prepare_quality,omitempty"`
|
||||
OverallSize string `json:"overall_size,omitempty"`
|
||||
CardPositionFront map[string]position `json:"card_position_front,omitempty"`
|
||||
CardPositionBack map[string]position `json:"card_position_back,omitempty"`
|
||||
ImageSize imageSize `json:"img_size,omitempty"`
|
||||
}
|
||||
|
||||
// ResIDCard 身份证返回结果
|
||||
type ResIDCard struct {
|
||||
util.CommonError
|
||||
|
||||
Type string `json:"type,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
Address string `json:"addr,omitempty"`
|
||||
Gender string `json:"gender,omitempty"`
|
||||
Nationality string `json:"nationality,omitempty"`
|
||||
ValidDate string `json:"valid_date,omitempty"`
|
||||
}
|
||||
|
||||
// ResBankCard 银行卡返回结果
|
||||
type ResBankCard struct {
|
||||
util.CommonError
|
||||
|
||||
Number string `json:"number,omitempty"`
|
||||
}
|
||||
|
||||
// ResDrivingLicense 驾驶证返回结果
|
||||
type ResDrivingLicense struct {
|
||||
util.CommonError
|
||||
|
||||
IDNumber string `json:"id_num,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Sex string `json:"sex,omitempty"`
|
||||
Nationality string `json:"nationality,omitempty"`
|
||||
Address string `json:"address,omitempty"`
|
||||
Birthday string `json:"birth_date,omitempty"`
|
||||
IssueDate string `json:"issue_date,omitempty"`
|
||||
CarClass string `json:"car_class,omitempty"`
|
||||
ValidFrom string `json:"valid_from,omitempty"`
|
||||
ValidTo string `json:"valid_to,omitempty"`
|
||||
OfficialSeal string `json:"official_seal,omitempty"`
|
||||
}
|
||||
|
||||
// ResBizLicense 营业执照返回结果
|
||||
type ResBizLicense struct {
|
||||
util.CommonError
|
||||
|
||||
RegisterNumber string `json:"reg_num,omitempty"`
|
||||
Serial string `json:"serial,omitempty"`
|
||||
LegalRepresentative string `json:"legal_representative,omitempty"`
|
||||
EnterpriseName string `json:"enterprise_name,omitempty"`
|
||||
TypeOfOrganization string `json:"type_of_organization,omitempty"`
|
||||
Address string `json:"address,omitempty"`
|
||||
TypeOfEnterprise string `json:"type_of_enterprise,omitempty"`
|
||||
BusinessScope string `json:"business_scope,omitempty"`
|
||||
RegisteredCapital string `json:"registered_capital,omitempty"`
|
||||
PaidInCapital string `json:"paid_in_capital,omitempty"`
|
||||
ValidPeriod string `json:"valid_period,omitempty"`
|
||||
RegisterDate string `json:"registered_date,omitempty"`
|
||||
CertPosition map[string]position `json:"cert_position,omitempty"`
|
||||
ImageSize imageSize `json:"img_size,omitempty"`
|
||||
}
|
||||
|
||||
// ResCommon 公共印刷品返回结果
|
||||
type ResCommon struct {
|
||||
util.CommonError
|
||||
|
||||
Items []commonItem `json:"items,omitempty"`
|
||||
ImageSize imageSize `json:"img_size,omitempty"`
|
||||
}
|
||||
|
||||
// commonItem 公共元素
|
||||
type commonItem struct {
|
||||
Position position `json:"pos"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
// NewOCR 实例
|
||||
func NewOCR(c *context.Context) *OCR {
|
||||
ocr := new(OCR)
|
||||
ocr.Context = c
|
||||
return ocr
|
||||
}
|
||||
|
||||
// IDCard 身份证OCR识别接口
|
||||
func (ocr *OCR) IDCard(path string) (resIDCard ResIDCard, err error) {
|
||||
accessToken, err := ocr.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
response, err := util.HTTPPost(fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrIDCardURL, url.QueryEscape(path), accessToken), "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = util.DecodeWithError(response, &resIDCard, "OCRIDCard")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// BankCard 银行卡OCR识别接口
|
||||
func (ocr *OCR) BankCard(path string) (resBankCard ResBankCard, err error) {
|
||||
accessToken, err := ocr.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
response, err := util.HTTPPost(fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrBankCardURL, url.QueryEscape(path), accessToken), "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = util.DecodeWithError(response, &resBankCard, "OCRBankCard")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Driving 行驶证OCR识别接口
|
||||
func (ocr *OCR) Driving(path string) (resDriving ResDriving, err error) {
|
||||
accessToken, err := ocr.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
response, err := util.HTTPPost(fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrDrivingURL, url.QueryEscape(path), accessToken), "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = util.DecodeWithError(response, &resDriving, "OCRDriving")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DrivingLicense 驾驶证OCR识别接口
|
||||
func (ocr *OCR) DrivingLicense(path string) (resDrivingLicense ResDrivingLicense, err error) {
|
||||
accessToken, err := ocr.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
response, err := util.HTTPPost(fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrDrivingLicenseURL, url.QueryEscape(path), accessToken), "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = util.DecodeWithError(response, &resDrivingLicense, "OCRDrivingLicense")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// BizLicense 营业执照OCR识别接口
|
||||
func (ocr *OCR) BizLicense(path string) (resBizLicense ResBizLicense, err error) {
|
||||
accessToken, err := ocr.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
response, err := util.HTTPPost(fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrBizLicenseURL, url.QueryEscape(path), accessToken), "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = util.DecodeWithError(response, &resBizLicense, "OCRBizLicense")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Common 通用印刷体OCR识别接口
|
||||
func (ocr *OCR) Common(path string) (resCommon ResCommon, err error) {
|
||||
accessToken, err := ocr.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
response, err := util.HTTPPost(fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrCommonURL, url.QueryEscape(path), accessToken), "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = util.DecodeWithError(response, &resCommon, "OCRCommon")
|
||||
|
||||
return
|
||||
}
|
||||
@@ -24,6 +24,18 @@ const (
|
||||
convertToOpenIDURL = "https://qyapi.weixin.qq.com/cgi-bin/user/convert_to_openid"
|
||||
// convertToUserIDURL openID转userID
|
||||
convertToUserIDURL = "https://qyapi.weixin.qq.com/cgi-bin/user/convert_to_userid"
|
||||
// userBatchDeleteURL 批量删除成员
|
||||
userBatchDeleteURL = "https://qyapi.weixin.qq.com/cgi-bin/user/batchdelete?access_token=%s"
|
||||
// userAuthSuccURL 登录二次验证
|
||||
userAuthSuccURL = "https://qyapi.weixin.qq.com/cgi-bin/user/authsucc?access_token=%s&userid=%s"
|
||||
// batchInviteURL 邀请成员
|
||||
batchInviteURL = "https://qyapi.weixin.qq.com/cgi-bin/batch/invite?access_token=%s"
|
||||
// getJoinQrcodeURL 获取加入企业二维码
|
||||
getJoinQrcodeURL = "https://qyapi.weixin.qq.com/cgi-bin/corp/get_join_qrcode"
|
||||
// getUseridURL 手机号获取userid
|
||||
getUseridURL = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserid?access_token=%s"
|
||||
// getUseridByEmailURL 邮箱获取userid
|
||||
getUseridByEmailURL = "https://qyapi.weixin.qq.com/cgi-bin/user/get_userid_by_email?access_token=%s"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -158,18 +170,20 @@ func (r *Client) UserCreate(req *UserCreateRequest) (*UserCreateResponse, error)
|
||||
|
||||
// UserUpdateRequest 更新成员请求
|
||||
type UserUpdateRequest struct {
|
||||
UserID string `json:"userid"`
|
||||
NewUserID string `json:"new_userid"`
|
||||
Name string `json:"name"`
|
||||
Alias string `json:"alias"`
|
||||
Mobile string `json:"mobile"`
|
||||
Department []int `json:"department"`
|
||||
Order []int `json:"order"`
|
||||
Position string `json:"position"`
|
||||
Gender int `json:"gender"`
|
||||
Email string `json:"email"`
|
||||
BizMail string `json:"biz_mail"`
|
||||
BizMailAlias string `json:"biz_mail_alias"`
|
||||
UserID string `json:"userid"`
|
||||
NewUserID string `json:"new_userid"`
|
||||
Name string `json:"name"`
|
||||
Alias string `json:"alias"`
|
||||
Mobile string `json:"mobile"`
|
||||
Department []int `json:"department"`
|
||||
Order []int `json:"order"`
|
||||
Position string `json:"position"`
|
||||
Gender int `json:"gender"`
|
||||
Email string `json:"email"`
|
||||
BizMail string `json:"biz_mail"`
|
||||
BizMailAlias struct {
|
||||
Item []string `json:"item"`
|
||||
} `json:"biz_mail_alias"`
|
||||
IsLeaderInDept []int `json:"is_leader_in_dept"`
|
||||
DirectLeader []string `json:"direct_leader"`
|
||||
Enable int `json:"enable"`
|
||||
@@ -445,3 +459,167 @@ func (r *Client) ConvertToUserID(openID string) (string, error) {
|
||||
err = util.DecodeWithError(response, result, "ConvertToUserID")
|
||||
return result.UserID, err
|
||||
}
|
||||
|
||||
// UserBatchDeleteRequest 批量删除成员请求
|
||||
type UserBatchDeleteRequest struct {
|
||||
UseridList []string `json:"useridlist"`
|
||||
}
|
||||
|
||||
// UserBatchDelete 批量删除成员
|
||||
// see https://developer.work.weixin.qq.com/document/path/90199
|
||||
func (r *Client) UserBatchDelete(req *UserBatchDeleteRequest) error {
|
||||
var (
|
||||
accessToken string
|
||||
err error
|
||||
)
|
||||
if accessToken, err = r.GetAccessToken(); err != nil {
|
||||
return err
|
||||
}
|
||||
var response []byte
|
||||
if response, err = util.PostJSON(fmt.Sprintf(userBatchDeleteURL, accessToken), req); err != nil {
|
||||
return err
|
||||
}
|
||||
return util.DecodeWithCommonError(response, "UserBatchDelete")
|
||||
}
|
||||
|
||||
// UserAuthSucc 登录二次验证
|
||||
// @see https://developer.work.weixin.qq.com/document/path/90203
|
||||
func (r *Client) UserAuthSucc(userID string) error {
|
||||
var (
|
||||
accessToken string
|
||||
err error
|
||||
)
|
||||
if accessToken, err = r.GetAccessToken(); err != nil {
|
||||
return err
|
||||
}
|
||||
var response []byte
|
||||
if response, err = util.HTTPGet(fmt.Sprintf(userAuthSuccURL, accessToken, userID)); err != nil {
|
||||
return err
|
||||
}
|
||||
return util.DecodeWithCommonError(response, "UserAuthSucc")
|
||||
}
|
||||
|
||||
// BatchInviteRequest 邀请成员请求
|
||||
type BatchInviteRequest struct {
|
||||
User []string `json:"user"`
|
||||
Party []int `json:"party"`
|
||||
Tag []int `json:"tag"`
|
||||
}
|
||||
|
||||
// BatchInviteResponse 邀请成员响应
|
||||
type BatchInviteResponse struct {
|
||||
util.CommonError
|
||||
InvalidUser []string `json:"invaliduser"`
|
||||
InvalidParty []int `json:"invalidparty"`
|
||||
InvalidTag []int `json:"invalidtag"`
|
||||
}
|
||||
|
||||
// BatchInvite 邀请成员
|
||||
// see https://developer.work.weixin.qq.com/document/path/90975
|
||||
func (r *Client) BatchInvite(req *BatchInviteRequest) (*BatchInviteResponse, error) {
|
||||
var (
|
||||
accessToken string
|
||||
err error
|
||||
)
|
||||
if accessToken, err = r.GetAccessToken(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var response []byte
|
||||
if response, err = util.PostJSON(fmt.Sprintf(batchInviteURL, accessToken), req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := &BatchInviteResponse{}
|
||||
err = util.DecodeWithError(response, result, "BatchInvite")
|
||||
return result, err
|
||||
}
|
||||
|
||||
// GetJoinQrcodeRequest 获取加入企业二维码请求
|
||||
type GetJoinQrcodeRequest struct {
|
||||
SizeType int `json:"size_type"`
|
||||
}
|
||||
|
||||
// GetJoinQrcodeResponse 获取加入企业二维码响应
|
||||
type GetJoinQrcodeResponse struct {
|
||||
util.CommonError
|
||||
JoinQrcode string `json:"join_qrcode"`
|
||||
}
|
||||
|
||||
// GetJoinQrcode 获取加入企业二维码
|
||||
// see https://developer.work.weixin.qq.com/document/path/91714
|
||||
func (r *Client) GetJoinQrcode(req *GetJoinQrcodeRequest) (*GetJoinQrcodeResponse, error) {
|
||||
var (
|
||||
accessToken string
|
||||
err error
|
||||
apiURL string
|
||||
)
|
||||
if accessToken, err = r.GetAccessToken(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if req.SizeType > 0 {
|
||||
apiURL = fmt.Sprintf("%s?access_token=%s&size_type=%d", getJoinQrcodeURL, accessToken, req.SizeType)
|
||||
} else {
|
||||
apiURL = fmt.Sprintf("%s?access_token=%s", getJoinQrcodeURL, accessToken)
|
||||
}
|
||||
var response []byte
|
||||
if response, err = util.HTTPGet(apiURL); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := &GetJoinQrcodeResponse{}
|
||||
err = util.DecodeWithError(response, result, "GetJoinQrcode")
|
||||
return result, err
|
||||
}
|
||||
|
||||
// GetUseridRequest 手机号获取userid请求
|
||||
type GetUseridRequest struct {
|
||||
Mobile string `json:"mobile"`
|
||||
}
|
||||
|
||||
// GetUseridResponse 获取userid响应
|
||||
type GetUseridResponse struct {
|
||||
util.CommonError
|
||||
Userid string `json:"userid"`
|
||||
}
|
||||
|
||||
// GetUserid 手机号获取userid
|
||||
// see https://developer.work.weixin.qq.com/document/path/95402
|
||||
func (r *Client) GetUserid(req *GetUseridRequest) (*GetUseridResponse, error) {
|
||||
var (
|
||||
accessToken string
|
||||
err error
|
||||
)
|
||||
if accessToken, err = r.GetAccessToken(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var response []byte
|
||||
if response, err = util.PostJSON(fmt.Sprintf(getUseridURL, accessToken), req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := &GetUseridResponse{}
|
||||
err = util.DecodeWithError(response, result, "GetUserid")
|
||||
return result, err
|
||||
}
|
||||
|
||||
// GetUseridByEmailRequest 邮箱获取userid请求
|
||||
type GetUseridByEmailRequest struct {
|
||||
Email string `json:"email"`
|
||||
EmailType int `json:"email_type,omitempty"`
|
||||
}
|
||||
|
||||
// GetUseridByEmail 邮箱获取userid
|
||||
// see https://developer.work.weixin.qq.com/document/path/95895
|
||||
func (r *Client) GetUseridByEmail(req *GetUseridByEmailRequest) (*GetUseridResponse, error) {
|
||||
var (
|
||||
accessToken string
|
||||
err error
|
||||
)
|
||||
if accessToken, err = r.GetAccessToken(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var response []byte
|
||||
if response, err = util.PostJSON(fmt.Sprintf(getUseridByEmailURL, accessToken), req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := &GetUseridResponse{}
|
||||
err = util.DecodeWithError(response, result, "GetUseridByEmail")
|
||||
return result, err
|
||||
}
|
||||
|
||||
@@ -158,24 +158,32 @@ type OptionGroupRuleCheckinDate struct {
|
||||
MaxAllowArriveEarly int64 `json:"max_allow_arrive_early"`
|
||||
MaxAllowArriveLate int64 `json:"max_allow_arrive_late"`
|
||||
LateRule OptionGroupLateRule `json:"late_rule"`
|
||||
Biweekly OptionGroupBiweekly `json:"biweekly,omitempty"`
|
||||
}
|
||||
|
||||
// OptionGroupRuleCheckinTime 工作日上下班打卡时间信息
|
||||
type OptionGroupRuleCheckinTime struct {
|
||||
TimeID int64 `json:"time_id"`
|
||||
WorkSec int64 `json:"work_sec"`
|
||||
OffWorkSec int64 `json:"off_work_sec"`
|
||||
RemindWorkSec int64 `json:"remind_work_sec"`
|
||||
RemindOffWorkSec int64 `json:"remind_off_work_sec"`
|
||||
AllowRest bool `json:"allow_rest"`
|
||||
RestBeginTime int64 `json:"rest_begin_time"`
|
||||
RestEndTime int64 `json:"rest_end_time"`
|
||||
EarliestWorkSec int64 `json:"earliest_work_sec"`
|
||||
LatestWorkSec int64 `json:"latest_work_sec"`
|
||||
EarliestOffWorkSec int64 `json:"earliest_off_work_sec"`
|
||||
LatestOffWorkSec int64 `json:"latest_off_work_sec"`
|
||||
NoNeedCheckOn bool `json:"no_need_checkon"`
|
||||
NoNeedCheckOff bool `json:"no_need_checkoff"`
|
||||
TimeID int64 `json:"time_id"`
|
||||
WorkSec int64 `json:"work_sec"`
|
||||
OffWorkSec int64 `json:"off_work_sec"`
|
||||
RemindWorkSec int64 `json:"remind_work_sec"`
|
||||
RemindOffWorkSec int64 `json:"remind_off_work_sec"`
|
||||
AllowRest bool `json:"allow_rest"`
|
||||
RestBeginTime int64 `json:"rest_begin_time"`
|
||||
RestEndTime int64 `json:"rest_end_time"`
|
||||
EarliestWorkSec int64 `json:"earliest_work_sec"`
|
||||
LatestWorkSec int64 `json:"latest_work_sec"`
|
||||
EarliestOffWorkSec int64 `json:"earliest_off_work_sec"`
|
||||
LatestOffWorkSec int64 `json:"latest_off_work_sec"`
|
||||
NoNeedCheckOn bool `json:"no_need_checkon"`
|
||||
NoNeedCheckOff bool `json:"no_need_checkoff"`
|
||||
RestTimes []OptionGroupRuleRestTimes `json:"rest_times,omitempty"`
|
||||
}
|
||||
|
||||
// OptionGroupRuleRestTimes 多组休息时间
|
||||
type OptionGroupRuleRestTimes struct {
|
||||
RestBeginTime int64 `json:"rest_begin_time,omitempty"`
|
||||
RestEndTime int64 `json:"rest_end_time,omitempty"`
|
||||
}
|
||||
|
||||
// OptionGroupLateRule 晚走晚到时间规则信息
|
||||
@@ -192,6 +200,13 @@ type OptionGroupTimeRule struct {
|
||||
OnWorkFlexTime int64 `json:"onwork_flex_time"`
|
||||
}
|
||||
|
||||
// OptionGroupBiweekly 大小周规则
|
||||
type OptionGroupBiweekly struct {
|
||||
EnableWeekdayRecurrence bool `json:"enable_weekday_recurrence"`
|
||||
OddWorkdays []int64 `json:"odd_workdays"`
|
||||
EvenWorkdays []int64 `json:"even_workdays"`
|
||||
}
|
||||
|
||||
// OptionGroupSpeWorkdays 特殊工作日
|
||||
type OptionGroupSpeWorkdays struct {
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
|
||||
@@ -77,6 +77,8 @@ type (
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
ChatExpiresIn int `json:"chat_expires_in"`
|
||||
UnionID string `json:"unionid"`
|
||||
IsExclusive bool `json:"is_exclusive"`
|
||||
MarkSource bool `json:"mark_source"`
|
||||
Conclusions ConclusionsRequest `json:"conclusions"`
|
||||
}
|
||||
// AddContactWayResponse 配置客户联系「联系我」方式响应
|
||||
@@ -132,6 +134,7 @@ type (
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
ChatExpiresIn int `json:"chat_expires_in"`
|
||||
UnionID string `json:"unionid"`
|
||||
MarkSource bool `json:"mark_source"`
|
||||
Conclusions ConclusionsResponse `json:"conclusions"`
|
||||
}
|
||||
)
|
||||
@@ -168,6 +171,7 @@ type (
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
ChatExpiresIn int `json:"chat_expires_in"`
|
||||
UnionID string `json:"unionid"`
|
||||
MarkSource bool `json:"mark_source"`
|
||||
Conclusions ConclusionsRequest `json:"conclusions"`
|
||||
}
|
||||
// UpdateContactWayResponse 更新企业已配置的「联系我」方式响应
|
||||
|
||||
@@ -25,6 +25,8 @@ const (
|
||||
customerAcquisitionStatisticURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/customer_acquisition/statistic?access_token=%s"
|
||||
// customerAcquisitionGetChatInfo 获取成员多次收消息详情
|
||||
customerAcquisitionGetChatInfoURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/customer_acquisition/get_chat_info?access_token=%s"
|
||||
// customerAcquisitionGetPermitURL 获取客户可建联成员
|
||||
customerAcquisitionGetPermitURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/customer_acquisition_app/get_permit?access_token=%s"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -68,9 +70,10 @@ type (
|
||||
// GetCustomerAcquisitionResponse 获取获客链接详情响应
|
||||
GetCustomerAcquisitionResponse struct {
|
||||
util.CommonError
|
||||
Link Link `json:"link"`
|
||||
Range CustomerAcquisitionRange `json:"range"`
|
||||
SkipVerify bool `json:"skip_verify"`
|
||||
Link Link `json:"link"`
|
||||
Range CustomerAcquisitionRange `json:"range"`
|
||||
PriorityOption CustomerPriorityOption `json:"priority_option"`
|
||||
SkipVerify bool `json:"skip_verify"`
|
||||
}
|
||||
// Link 获客链接
|
||||
Link struct {
|
||||
@@ -78,6 +81,8 @@ type (
|
||||
LinkName string `json:"link_name"`
|
||||
URL string `json:"url"`
|
||||
CreateTime int64 `json:"create_time"`
|
||||
SkipVerify bool `json:"skip_verify"`
|
||||
MarkSource bool `json:"mark_source"`
|
||||
}
|
||||
|
||||
// CustomerAcquisitionRange 该获客链接使用范围
|
||||
@@ -85,6 +90,12 @@ type (
|
||||
UserList []string `json:"user_list"`
|
||||
DepartmentList []int64 `json:"department_list"`
|
||||
}
|
||||
|
||||
// CustomerPriorityOption 该获客链接的优先选项
|
||||
CustomerPriorityOption struct {
|
||||
PriorityType int `json:"priority_type"`
|
||||
PriorityUseridList []string `json:"priority_userid_list"`
|
||||
}
|
||||
)
|
||||
|
||||
// GetCustomerAcquisition 获客助手--获取获客链接详情
|
||||
@@ -109,9 +120,11 @@ func (r *Client) GetCustomerAcquisition(req *GetCustomerAcquisitionRequest) (*Ge
|
||||
type (
|
||||
// CreateCustomerAcquisitionLinkRequest 创建获客链接请求
|
||||
CreateCustomerAcquisitionLinkRequest struct {
|
||||
LinkName string `json:"link_name"`
|
||||
Range CustomerAcquisitionRange `json:"range"`
|
||||
SkipVerify bool `json:"skip_verify"`
|
||||
LinkName string `json:"link_name"`
|
||||
Range CustomerAcquisitionRange `json:"range"`
|
||||
SkipVerify bool `json:"skip_verify"`
|
||||
PriorityOption CustomerPriorityOption `json:"priority_option"`
|
||||
MarkSource bool `json:"mark_source"`
|
||||
}
|
||||
// CreateCustomerAcquisitionLinkResponse 创建获客链接响应
|
||||
CreateCustomerAcquisitionLinkResponse struct {
|
||||
@@ -142,10 +155,12 @@ func (r *Client) CreateCustomerAcquisitionLink(req *CreateCustomerAcquisitionLin
|
||||
type (
|
||||
// UpdateCustomerAcquisitionLinkRequest 编辑获客链接请求
|
||||
UpdateCustomerAcquisitionLinkRequest struct {
|
||||
LinkID string `json:"link_id"`
|
||||
LinkName string `json:"link_name"`
|
||||
Range CustomerAcquisitionRange `json:"range"`
|
||||
SkipVerify bool `json:"skip_verify"`
|
||||
LinkID string `json:"link_id"`
|
||||
LinkName string `json:"link_name"`
|
||||
Range CustomerAcquisitionRange `json:"range"`
|
||||
SkipVerify bool `json:"skip_verify"`
|
||||
PriorityOption CustomerPriorityOption `json:"priority_option"`
|
||||
MarkSource bool `json:"mark_source"`
|
||||
}
|
||||
// UpdateCustomerAcquisitionLinkResponse 编辑获客链接响应
|
||||
UpdateCustomerAcquisitionLinkResponse struct {
|
||||
@@ -349,3 +364,30 @@ func (r *Client) GetChatInfo(req *GetChatInfoRequest) (*GetChatInfoResponse, err
|
||||
err = util.DecodeWithError(response, result, "GetChatInfo")
|
||||
return result, err
|
||||
}
|
||||
|
||||
// GetPermitResponse 获取客户可建联成员响应
|
||||
type GetPermitResponse struct {
|
||||
util.CommonError
|
||||
UserList []string `json:"user_list"`
|
||||
DepartmentList []int `json:"department_list"`
|
||||
TagList []int `json:"tag_list"`
|
||||
}
|
||||
|
||||
// GetPermit 获取客户可建联成员
|
||||
// see https://developer.work.weixin.qq.com/document/path/101146
|
||||
func (r *Client) GetPermit() (*GetPermitResponse, error) {
|
||||
var (
|
||||
accessToken string
|
||||
err error
|
||||
)
|
||||
if accessToken, err = r.GetAccessToken(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var response []byte
|
||||
if response, err = util.HTTPGet(fmt.Sprintf(customerAcquisitionGetPermitURL, accessToken)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := &GetPermitResponse{}
|
||||
err = util.DecodeWithError(response, result, "CustomerAcquisitionGetPermit")
|
||||
return result, err
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ type (
|
||||
RoomBaseID int `json:"room_base_id"` //非必填 自动建群的群起始序号,当auto_create_room为1时有效
|
||||
ChatIDList []string `json:"chat_id_list"` //必填 使用该配置的客户群ID列表,支持5个。见客户群ID获取方法
|
||||
State string `json:"state"` //非必填 企业自定义的state参数,用于区分不同的入群渠道。不超过30个UTF-8字符
|
||||
MarkSource bool `json:"mark_source"`
|
||||
}
|
||||
|
||||
// AddJoinWayResponse 添加群配置返回值
|
||||
@@ -65,6 +66,7 @@ type (
|
||||
ChatIDList []string `json:"chat_id_list"`
|
||||
QrCode string `json:"qr_code"`
|
||||
State string `json:"state"`
|
||||
MarkSource bool `json:"mark_source"`
|
||||
}
|
||||
//GetJoinWayResponse 获取群配置的返回值
|
||||
GetJoinWayResponse struct {
|
||||
@@ -103,6 +105,7 @@ type UpdateJoinWayRequest struct {
|
||||
RoomBaseID int `json:"room_base_id"` //非必填 自动建群的群起始序号,当auto_create_room为1时有效
|
||||
ChatIDList []string `json:"chat_id_list"` //必填 使用该配置的客户群ID列表,支持5个。见客户群ID获取方法
|
||||
State string `json:"state"` //非必填 企业自定义的state参数,用于区分不同的入群渠道。不超过30个UTF-8字符
|
||||
MarkSource bool `json:"mark_source"`
|
||||
}
|
||||
|
||||
// UpdateJoinWay 更新客户群进群方式配置
|
||||
|
||||
@@ -27,6 +27,10 @@ var (
|
||||
getUserInfoURL = "https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token=%s&code=%s"
|
||||
// getUserDetailURL 获取访问用户敏感信息
|
||||
getUserDetailURL = "https://qyapi.weixin.qq.com/cgi-bin/auth/getuserdetail?access_token=%s"
|
||||
// getTfaInfoURL 获取用户二次验证信息
|
||||
getTfaInfoURL = "https://qyapi.weixin.qq.com/cgi-bin/auth/get_tfa_info?access_token=%s"
|
||||
// tfaSuccURL 使用二次验证
|
||||
tfaSuccURL = "https://qyapi.weixin.qq.com/cgi-bin/user/tfa_succ?access_token=%s"
|
||||
)
|
||||
|
||||
// NewOauth new init oauth
|
||||
@@ -163,3 +167,57 @@ func (ctr *Oauth) GetUserDetail(req *GetUserDetailRequest) (*GetUserDetailRespon
|
||||
err = util.DecodeWithError(response, result, "GetUserDetail")
|
||||
return result, err
|
||||
}
|
||||
|
||||
// GetTfaInfoRequest 获取用户二次验证信息请求
|
||||
type GetTfaInfoRequest struct {
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
// GetTfaInfoResponse 获取用户二次验证信息响应
|
||||
type GetTfaInfoResponse struct {
|
||||
util.CommonError
|
||||
UserID string `json:"userid"`
|
||||
TfaCode string `json:"tfa_code"`
|
||||
}
|
||||
|
||||
// GetTfaInfo 获取用户二次验证信息
|
||||
// @see https://developer.work.weixin.qq.com/document/path/99499
|
||||
func (ctr *Oauth) GetTfaInfo(req *GetTfaInfoRequest) (*GetTfaInfoResponse, error) {
|
||||
var (
|
||||
accessToken string
|
||||
err error
|
||||
)
|
||||
if accessToken, err = ctr.GetAccessToken(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var response []byte
|
||||
if response, err = util.PostJSON(fmt.Sprintf(getTfaInfoURL, accessToken), req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := &GetTfaInfoResponse{}
|
||||
err = util.DecodeWithError(response, result, "GetTfaInfo")
|
||||
return result, err
|
||||
}
|
||||
|
||||
// TfaSuccRequest 使用二次验证请求
|
||||
type TfaSuccRequest struct {
|
||||
UserID string `json:"userid"`
|
||||
TfaCode string `json:"tfa_code"`
|
||||
}
|
||||
|
||||
// TfaSucc 使用二次验证
|
||||
// @see https://developer.work.weixin.qq.com/document/path/99500
|
||||
func (ctr *Oauth) TfaSucc(req *TfaSuccRequest) error {
|
||||
var (
|
||||
accessToken string
|
||||
err error
|
||||
)
|
||||
if accessToken, err = ctr.GetAccessToken(); err != nil {
|
||||
return err
|
||||
}
|
||||
var response []byte
|
||||
if response, err = util.PostJSON(fmt.Sprintf(tfaSuccURL, accessToken), req); err != nil {
|
||||
return err
|
||||
}
|
||||
return util.DecodeWithCommonError(response, "TfaSucc")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user