mirror of
https://github.com/silenceper/wechat.git
synced 2026-02-06 13:42:26 +08:00
Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d91e82c183 | ||
|
|
3111e12c00 | ||
|
|
9c8717afba | ||
|
|
d7f371cb65 | ||
|
|
39e81b45bf | ||
|
|
733c53a044 | ||
|
|
5617c9512d | ||
|
|
d806d1c968 | ||
|
|
254ac9d7a6 | ||
|
|
19e3174107 | ||
|
|
0dbc292f44 | ||
|
|
33d00f45c5 | ||
|
|
c4a361bbf6 | ||
|
|
f4e58b0712 | ||
|
|
5b307df969 | ||
|
|
b16d231a29 | ||
|
|
c30319c74c | ||
|
|
76c1832798 | ||
|
|
f6d07aa714 | ||
|
|
1475417a64 | ||
|
|
bf456aa77b | ||
|
|
934ca61b3b | ||
|
|
00ca733814 | ||
|
|
2e796b21d3 | ||
|
|
412c2f0ea9 | ||
|
|
8cb724ece0 | ||
|
|
13facb6df8 | ||
|
|
55615762eb | ||
|
|
1fc4cc70ec | ||
|
|
e09031b58c | ||
|
|
279ff79406 | ||
|
|
7bde39a634 | ||
|
|
1711aeb46d | ||
|
|
87470f143d | ||
|
|
900a54ee06 | ||
|
|
7170f6ef32 | ||
|
|
0c9dd16f1f | ||
|
|
f68f9d6f5e | ||
|
|
09dabb232d | ||
|
|
3ea624f832 | ||
|
|
546dce2396 | ||
|
|
abd7f512ba | ||
|
|
b8239ef9a9 | ||
|
|
0dffcde475 | ||
|
|
4e6fd625da | ||
|
|
453089e83e | ||
|
|
bb97bddc08 | ||
|
|
54e2c82fff | ||
|
|
fab09a0bbe | ||
|
|
f8ab592606 |
12
.github/FUNDING.yml
vendored
Normal file
12
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||||
|
patreon: # Replace with a single Patreon username
|
||||||
|
open_collective: # Replace with a single Open Collective username
|
||||||
|
ko_fi: # Replace with a single Ko-fi username
|
||||||
|
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||||
|
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||||
|
liberapay: # Replace with a single Liberapay username
|
||||||
|
issuehunt: # Replace with a single IssueHunt username
|
||||||
|
otechie: # Replace with a single Otechie username
|
||||||
|
custom: https://silenceper.com/img/wechat-pay.jpeg
|
||||||
2
.github/ISSUE_TEMPLATE.md
vendored
Normal file
2
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
## 问题及现象
|
||||||
|
<!-- 描述你的问题现象,报错**贴截图**粘贴或者贴具体信息,提供**必要的代码段**
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
|
- 1.13.x
|
||||||
- 1.12.x
|
- 1.12.x
|
||||||
- 1.11.x
|
- 1.11.x
|
||||||
- 1.10.x
|
- 1.10.x
|
||||||
|
|||||||
@@ -283,8 +283,8 @@ type Reply struct {
|
|||||||
#### 回复图片消息
|
#### 回复图片消息
|
||||||
```go
|
```go
|
||||||
//mediaID 可通过素材管理-上上传多媒体文件获得
|
//mediaID 可通过素材管理-上上传多媒体文件获得
|
||||||
image :=message.NewVideo("mediaID")
|
image :=message.NewImage("mediaID")
|
||||||
return &message.Reply{message.MsgTypeVideo, image}
|
return &message.Reply{message.MsgTypeImage, image}
|
||||||
```
|
```
|
||||||
#### 回复视频消息
|
#### 回复视频消息
|
||||||
```go
|
```go
|
||||||
|
|||||||
116
device/authorize.go
Normal file
116
device/authorize.go
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/silenceper/wechat/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DeviceAdd 添加设备标识
|
||||||
|
DeviceAdd = iota
|
||||||
|
// DeviceUpgrade 更新设备标识
|
||||||
|
DeviceUpgrade
|
||||||
|
)
|
||||||
|
|
||||||
|
type reqDeviceAuthorize struct {
|
||||||
|
// 设备id的个数
|
||||||
|
DeviceNum string `json:"device_num"`
|
||||||
|
// 设备id的列表,json的array格式,其size必须等于device_num
|
||||||
|
DeviceList []ReqDevice `json:"device_list"`
|
||||||
|
// 请求操作的类型,限定取值为:0:设备授权(缺省值为0) 1:设备更新(更新已授权设备的各属性值)
|
||||||
|
OpType string `json:"op_type,omitempty"`
|
||||||
|
// 设备的产品编号(由微信硬件平台分配)。可在公众号设备功能管理页面查询。
|
||||||
|
//当 op_type 为‘0’,product_id 为‘1’时,不要填写 product_id 字段(会引起不必要错误);
|
||||||
|
//当 op_typy 为‘0’,product_id 不为‘1’时,必须填写 product_id 字段;
|
||||||
|
//当 op_type 为 1 时,不要填写 product_id 字段。
|
||||||
|
ProductID string `json:"product_id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//ReqDevice 设备授权实体
|
||||||
|
type ReqDevice struct {
|
||||||
|
// 设备的 device id
|
||||||
|
ID string `json:"id"`
|
||||||
|
// 设备的mac地址 格式采用16进制串的方式(长度为12字节),
|
||||||
|
// 不需要0X前缀,如: 1234567890AB
|
||||||
|
Mac string `json:"mac"`
|
||||||
|
// 支持以下四种连接协议:
|
||||||
|
// android classic bluetooth – 1
|
||||||
|
// ios classic bluetooth – 2
|
||||||
|
// ble – 3
|
||||||
|
// wifi -- 4
|
||||||
|
// 一个设备可以支持多种连接类型,用符号"|"做分割,客户端优先选择靠前的连接方式(优先级按|关系的排序依次降低),举例:
|
||||||
|
// 1:表示设备仅支持andiod classic bluetooth 1|2:表示设备支持andiod 和ios 两种classic bluetooth,但是客户端优先选择andriod classic bluetooth 协议,如果andriod classic bluetooth协议连接失败,再选择ios classic bluetooth协议进行连接
|
||||||
|
// (注:安卓平台不同时支持BLE和classic类型)
|
||||||
|
ConnectProtocol string `json:"connect_protocol"`
|
||||||
|
//auth及通信的加密key,第三方需要将key烧制在设备上(128bit),格式采用16进制串的方式(长度为32字节),不需要0X前缀,如: 1234567890ABCDEF1234567890ABCDEF
|
||||||
|
AuthKey string `json:"auth_key"`
|
||||||
|
// 断开策略,目前支持: 1:退出公众号页面时即断开连接 2:退出公众号之后保持连接不断开
|
||||||
|
CloseStrategy string `json:"close_strategy"`
|
||||||
|
//连接策略,32位整型,按bit位置位,目前仅第1bit和第3bit位有效(bit置0为无效,1为有效;第2bit已被废弃),且bit位可以按或置位(如1|4=5),各bit置位含义说明如下:
|
||||||
|
//1:(第1bit置位)在公众号对话页面,不停的尝试连接设备
|
||||||
|
//4:(第3bit置位)处于非公众号页面(如主界面等),微信自动连接。当用户切换微信到前台时,可能尝试去连接设备,连上后一定时间会断开
|
||||||
|
ConnStrategy string `json:"conn_strategy"`
|
||||||
|
// auth version,设备和微信进行auth时,会根据该版本号来确认auth buf和auth key的格式(各version对应的auth buf及key的具体格式可以参看“客户端蓝牙外设协议”),该字段目前支持取值:
|
||||||
|
// 0:不加密的version
|
||||||
|
// 1:version 1
|
||||||
|
AuthVer string `json:"auth_ver"`
|
||||||
|
// 表示mac地址在厂商广播manufature data里含有mac地址的偏移,取值如下:
|
||||||
|
// -1:在尾部、
|
||||||
|
// -2:表示不包含mac地址 其他:非法偏移
|
||||||
|
ManuMacPos string `json:"manu_mac_pos"`
|
||||||
|
// 表示mac地址在厂商serial number里含有mac地址的偏移,取值如下:
|
||||||
|
// -1:表示在尾部
|
||||||
|
// -2:表示不包含mac地址 其他:非法偏移
|
||||||
|
SerMacPost string `json:"ser_mac_post"`
|
||||||
|
// 精简协议类型,取值如下:计步设备精简协议:1 (若该字段填1,connect_protocol 必须包括3。非精简协议设备切勿填写该字段)
|
||||||
|
BleSimpleProtocol string `json:"ble_simple_protocol,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//ResBaseInfo 授权回调实体
|
||||||
|
type ResBaseInfo struct {
|
||||||
|
BaseInfo struct {
|
||||||
|
DeviceType string `json:"device_type"`
|
||||||
|
DeviceID string `json:"device_id"`
|
||||||
|
} `json:"base_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 授权回调根信息
|
||||||
|
type resDeviceAuthorize struct {
|
||||||
|
util.CommonError
|
||||||
|
Resp []ResBaseInfo `json:"resp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeviceAuthorize 设备授权
|
||||||
|
func (d *Device) DeviceAuthorize(devices []ReqDevice, opType int, product string) (res []ResBaseInfo, err error) {
|
||||||
|
var accessToken string
|
||||||
|
accessToken, err = d.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("%s?access_token=%s", uriAuthorize, accessToken)
|
||||||
|
req := reqDeviceAuthorize{
|
||||||
|
DeviceNum: fmt.Sprintf("%d", len(devices)),
|
||||||
|
DeviceList: devices,
|
||||||
|
OpType: fmt.Sprintf("%d", opType),
|
||||||
|
ProductID: product,
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
response, err = util.PostJSON(uri, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var result resDeviceAuthorize
|
||||||
|
err = json.Unmarshal(response, &result)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if result.ErrCode != 0 {
|
||||||
|
err = fmt.Errorf("DeviceAuthorize Error , errcode=%d , errmsg=%s", result.ErrCode, result.ErrMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res = result.Resp
|
||||||
|
return
|
||||||
|
}
|
||||||
106
device/bind.go
Normal file
106
device/bind.go
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/silenceper/wechat/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReqBind 设备绑定解绑共通实体
|
||||||
|
type ReqBind struct {
|
||||||
|
Ticket string `json:"ticket,omitempty"`
|
||||||
|
DeviceID string `json:"device_id"`
|
||||||
|
OpenID string `json:"openid"`
|
||||||
|
}
|
||||||
|
type resBind struct {
|
||||||
|
BaseResp util.CommonError `json:"base_resp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind 设备绑定
|
||||||
|
func (d *Device) Bind(req ReqBind) (err error) {
|
||||||
|
var accessToken string
|
||||||
|
if accessToken, err = d.GetAccessToken(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uri := fmt.Sprintf("%s?access_token=%s", uriBind, accessToken)
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.PostJSON(uri, req); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var result resBind
|
||||||
|
if err = json.Unmarshal(response, &result); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if result.BaseResp.ErrCode != 0 {
|
||||||
|
err = fmt.Errorf("DeviceBind Error , errcode=%d , errmsg=%s", result.BaseResp.ErrCode, result.BaseResp.ErrMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unbind 设备解绑
|
||||||
|
func (d *Device) Unbind(req ReqBind) (err error) {
|
||||||
|
var accessToken string
|
||||||
|
if accessToken, err = d.GetAccessToken(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uri := fmt.Sprintf("%s?access_token=%s", uriUnbind, accessToken)
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.PostJSON(uri, req); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var result resBind
|
||||||
|
if err = json.Unmarshal(response, &result); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if result.BaseResp.ErrCode != 0 {
|
||||||
|
err = fmt.Errorf("DeviceBind Error , errcode=%d , errmsg=%s", result.BaseResp.ErrCode, result.BaseResp.ErrMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompelBind 强制绑定用户和设备
|
||||||
|
func (d *Device) CompelBind(req ReqBind) (err error) {
|
||||||
|
var accessToken string
|
||||||
|
if accessToken, err = d.GetAccessToken(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uri := fmt.Sprintf("%s?access_token=%s", uriCompelBind, accessToken)
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.PostJSON(uri, req); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var result resBind
|
||||||
|
if err = json.Unmarshal(response, &result); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if result.BaseResp.ErrCode != 0 {
|
||||||
|
err = fmt.Errorf("DeviceBind Error , errcode=%d , errmsg=%s", result.BaseResp.ErrCode, result.BaseResp.ErrMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompelUnbind 强制解绑用户和设备
|
||||||
|
func (d *Device) CompelUnbind(req ReqBind) (err error) {
|
||||||
|
var accessToken string
|
||||||
|
if accessToken, err = d.GetAccessToken(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uri := fmt.Sprintf("%s?access_token=%s", uriCompelUnbind, accessToken)
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.PostJSON(uri, req); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var result resBind
|
||||||
|
if err = json.Unmarshal(response, &result); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if result.BaseResp.ErrCode != 0 {
|
||||||
|
err = fmt.Errorf("DeviceBind Error , errcode=%d , errmsg=%s", result.BaseResp.ErrCode, result.BaseResp.ErrMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
60
device/device.go
Normal file
60
device/device.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/silenceper/wechat/context"
|
||||||
|
"github.com/silenceper/wechat/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
uriAuthorize = "https://api.weixin.qq.com/device/authorize_device"
|
||||||
|
uriQRCode = "https://api.weixin.qq.com/device/create_qrcode"
|
||||||
|
uriVerifyQRCode = "https://api.weixin.qq.com/device/verify_qrcode"
|
||||||
|
uriBind = "https://api.weixin.qq.com/device/bind"
|
||||||
|
uriUnbind = "https://api.weixin.qq.com/device/unbind"
|
||||||
|
uriCompelBind = "https://api.weixin.qq.com/device/compel_bind"
|
||||||
|
uriCompelUnbind = "https://api.weixin.qq.com/device/compel_unbind"
|
||||||
|
uriState = "https://api.weixin.qq.com/device/get_stat"
|
||||||
|
)
|
||||||
|
|
||||||
|
//Device struct
|
||||||
|
type Device struct {
|
||||||
|
*context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewDevice 实例
|
||||||
|
func NewDevice(context *context.Context) *Device {
|
||||||
|
device := new(Device)
|
||||||
|
device.Context = context
|
||||||
|
return device
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResDeviceState 设备状态响应实体
|
||||||
|
type ResDeviceState struct {
|
||||||
|
util.CommonError
|
||||||
|
Status int `json:"status"`
|
||||||
|
StatusInfo string `json:"status_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// State 设备状态查询
|
||||||
|
func (d *Device) State(device string) (res ResDeviceState, err error) {
|
||||||
|
var accessToken string
|
||||||
|
if accessToken, err = d.GetAccessToken(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uri := fmt.Sprintf("%s?access_token=%s&device_id=%s", uriState, accessToken, device)
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.HTTPGet(uri); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(response, &res); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if res.ErrCode != 0 {
|
||||||
|
err = fmt.Errorf("DeviceState Error , errcode=%d , errmsg=%s", res.ErrCode, res.ErrMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
9
device/message.go
Normal file
9
device/message.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package device
|
||||||
|
|
||||||
|
//MsgDevice 设备消息响应
|
||||||
|
type MsgDevice struct {
|
||||||
|
DeviceType string
|
||||||
|
DeviceID string
|
||||||
|
SessionID string
|
||||||
|
OpenID string
|
||||||
|
}
|
||||||
76
device/qrcode.go
Normal file
76
device/qrcode.go
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/silenceper/wechat/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
//ResCreateQRCode 获取二维码的返回实体
|
||||||
|
type ResCreateQRCode struct {
|
||||||
|
util.CommonError
|
||||||
|
DeviceNum int `json:"device_num"`
|
||||||
|
CodeList []struct {
|
||||||
|
DeviceID string `json:"device_id"`
|
||||||
|
Ticket string `json:"ticket"`
|
||||||
|
} `json:"code_list"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateQRCode 获取设备二维码
|
||||||
|
func (d *Device) CreateQRCode(devices []string) (res ResCreateQRCode, err error) {
|
||||||
|
var accessToken string
|
||||||
|
if accessToken, err = d.GetAccessToken(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uri := fmt.Sprintf("%s?access_token=%s", uriQRCode, accessToken)
|
||||||
|
req := map[string]interface{}{
|
||||||
|
"device_num": len(devices),
|
||||||
|
"device_id_list": devices,
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.PostJSON(uri, req); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(response, &res); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if res.ErrCode != 0 {
|
||||||
|
err = fmt.Errorf("DeviceCreateQRCode Error , errcode=%d , errmsg=%s", res.ErrCode, res.ErrMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//ResVerifyQRCode 验证授权结果实体
|
||||||
|
type ResVerifyQRCode struct {
|
||||||
|
util.CommonError
|
||||||
|
DeviceType string `json:"device_type"`
|
||||||
|
DeviceID string `json:"device_id"`
|
||||||
|
Mac string `json:"mac"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyQRCode 验证设备二维码
|
||||||
|
func (d *Device) VerifyQRCode(ticket string) (res ResVerifyQRCode, err error) {
|
||||||
|
var accessToken string
|
||||||
|
if accessToken, err = d.GetAccessToken(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uri := fmt.Sprintf("%s?access_token=%s", uriVerifyQRCode, accessToken)
|
||||||
|
req := map[string]interface{}{
|
||||||
|
"ticket": ticket,
|
||||||
|
}
|
||||||
|
fmt.Println(req)
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.PostJSON(uri, req); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(response, &res); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if res.ErrCode != 0 {
|
||||||
|
err = fmt.Errorf("DeviceCreateQRCode Error , errcode=%d , errmsg=%s", res.ErrCode, res.ErrMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ const (
|
|||||||
addNewsURL = "https://api.weixin.qq.com/cgi-bin/material/add_news"
|
addNewsURL = "https://api.weixin.qq.com/cgi-bin/material/add_news"
|
||||||
addMaterialURL = "https://api.weixin.qq.com/cgi-bin/material/add_material"
|
addMaterialURL = "https://api.weixin.qq.com/cgi-bin/material/add_material"
|
||||||
delMaterialURL = "https://api.weixin.qq.com/cgi-bin/material/del_material"
|
delMaterialURL = "https://api.weixin.qq.com/cgi-bin/material/del_material"
|
||||||
|
getMaterialURL = "https://api.weixin.qq.com/cgi-bin/material/get_material"
|
||||||
)
|
)
|
||||||
|
|
||||||
//Material 素材管理
|
//Material 素材管理
|
||||||
@@ -36,6 +37,33 @@ type Article struct {
|
|||||||
ShowCoverPic int `json:"show_cover_pic"`
|
ShowCoverPic int `json:"show_cover_pic"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
ContentSourceURL string `json:"content_source_url"`
|
ContentSourceURL string `json:"content_source_url"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
DownURL string `json:"down_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNews 获取/下载永久素材
|
||||||
|
func (material *Material) GetNews(id string) ([]*Article, error) {
|
||||||
|
accessToken, err := material.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
uri := fmt.Sprintf("%s?access_token=%s", getMaterialURL, accessToken)
|
||||||
|
|
||||||
|
var req struct {
|
||||||
|
MediaID string `json:"media_id"`
|
||||||
|
}
|
||||||
|
req.MediaID = id
|
||||||
|
responseBytes, err := util.PostJSON(uri, req)
|
||||||
|
|
||||||
|
var res struct {
|
||||||
|
NewsItem []*Article `json:"news_item"`
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(responseBytes, &res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.NewsItem, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//reqArticles 永久性图文素材请求信息
|
//reqArticles 永久性图文素材请求信息
|
||||||
@@ -138,11 +166,11 @@ func (material *Material) AddVideo(filename, title, introduction string) (mediaI
|
|||||||
fields := []util.MultipartFormField{
|
fields := []util.MultipartFormField{
|
||||||
{
|
{
|
||||||
IsFile: true,
|
IsFile: true,
|
||||||
Fieldname: "video",
|
Fieldname: "media",
|
||||||
Filename: filename,
|
Filename: filename,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
IsFile: true,
|
IsFile: false,
|
||||||
Fieldname: "description",
|
Fieldname: "description",
|
||||||
Value: fieldValue,
|
Value: fieldValue,
|
||||||
},
|
},
|
||||||
|
|||||||
160
message/customer_message.go
Normal file
160
message/customer_message.go
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
package message
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/silenceper/wechat/context"
|
||||||
|
"github.com/silenceper/wechat/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
customerSendMessage = "https://api.weixin.qq.com/cgi-bin/message/custom/send"
|
||||||
|
)
|
||||||
|
|
||||||
|
//Manager 消息管理者,可以发送消息
|
||||||
|
type Manager struct {
|
||||||
|
*context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewMessageManager 实例化消息管理者
|
||||||
|
func NewMessageManager(context *context.Context) *Manager {
|
||||||
|
return &Manager{
|
||||||
|
context,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//CustomerMessage 客服消息
|
||||||
|
type CustomerMessage struct {
|
||||||
|
ToUser string `json:"touser"` //接受者OpenID
|
||||||
|
Msgtype MsgType `json:"msgtype"` //客服消息类型
|
||||||
|
Text *MediaText `json:"text,omitempty"` //可选
|
||||||
|
Image *MediaResource `json:"image,omitempty"` //可选
|
||||||
|
Voice *MediaResource `json:"voice,omitempty"` //可选
|
||||||
|
Video *MediaVideo `json:"video,omitempty"` //可选
|
||||||
|
Music *MediaMusic `json:"music,omitempty"` //可选
|
||||||
|
News *MediaNews `json:"news,omitempty"` //可选
|
||||||
|
Mpnews *MediaResource `json:"mpnews,omitempty"` //可选
|
||||||
|
Wxcard *MediaWxcard `json:"wxcard,omitempty"` //可选
|
||||||
|
Msgmenu *MediaMsgmenu `json:"msgmenu,omitempty"` //可选
|
||||||
|
Miniprogrampage *MediaMiniprogrampage `json:"miniprogrampage,omitempty"` //可选
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewCustomerTextMessage 文本消息结构体构造方法
|
||||||
|
func NewCustomerTextMessage(toUser, text string) *CustomerMessage {
|
||||||
|
return &CustomerMessage{
|
||||||
|
ToUser: toUser,
|
||||||
|
Msgtype: MsgTypeText,
|
||||||
|
Text: &MediaText{
|
||||||
|
text,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewCustomerImgMessage 图片消息的构造方法
|
||||||
|
func NewCustomerImgMessage(toUser, mediaID string) *CustomerMessage {
|
||||||
|
return &CustomerMessage{
|
||||||
|
ToUser: toUser,
|
||||||
|
Msgtype: MsgTypeImage,
|
||||||
|
Image: &MediaResource{
|
||||||
|
mediaID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewCustomerVoiceMessage 语音消息的构造方法
|
||||||
|
func NewCustomerVoiceMessage(toUser, mediaID string) *CustomerMessage {
|
||||||
|
return &CustomerMessage{
|
||||||
|
ToUser: toUser,
|
||||||
|
Msgtype: MsgTypeVoice,
|
||||||
|
Voice: &MediaResource{
|
||||||
|
mediaID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//MediaText 文本消息的文字
|
||||||
|
type MediaText struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//MediaResource 消息使用的永久素材id
|
||||||
|
type MediaResource struct {
|
||||||
|
MediaID string `json:"media_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//MediaVideo 视频消息包含的内容
|
||||||
|
type MediaVideo struct {
|
||||||
|
MediaID string `json:"media_id"`
|
||||||
|
ThumbMediaID string `json:"thumb_media_id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//MediaMusic 音乐消息包括的内容
|
||||||
|
type MediaMusic struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Musicurl string `json:"musicurl"`
|
||||||
|
Hqmusicurl string `json:"hqmusicurl"`
|
||||||
|
ThumbMediaID string `json:"thumb_media_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//MediaNews 图文消息的内容
|
||||||
|
type MediaNews struct {
|
||||||
|
Articles []MediaArticles `json:"articles"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//MediaArticles 图文消息的内容的文章列表中的单独一条
|
||||||
|
type MediaArticles struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Picurl string `json:"picurl"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//MediaMsgmenu 菜单消息的内容
|
||||||
|
type MediaMsgmenu struct {
|
||||||
|
HeadContent string `json:"head_content"`
|
||||||
|
List []MsgmenuItem `json:"list"`
|
||||||
|
TailContent string `json:"tail_content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//MsgmenuItem 菜单消息的菜单按钮
|
||||||
|
type MsgmenuItem struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//MediaWxcard 卡券的id
|
||||||
|
type MediaWxcard struct {
|
||||||
|
CardID string `json:"card_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//MediaMiniprogrampage 小程序消息
|
||||||
|
type MediaMiniprogrampage struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Appid string `json:"appid"`
|
||||||
|
Pagepath string `json:"pagepath"`
|
||||||
|
ThumbMediaID string `json:"thumb_media_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//Send 发送客服消息
|
||||||
|
func (manager *Manager) Send(msg *CustomerMessage) error {
|
||||||
|
accessToken, err := manager.Context.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
uri := fmt.Sprintf("%s?access_token=%s", customerSendMessage, accessToken)
|
||||||
|
response, err := util.PostJSON(uri, msg)
|
||||||
|
var result util.CommonError
|
||||||
|
err = json.Unmarshal(response, &result)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if result.ErrCode != 0 {
|
||||||
|
err = fmt.Errorf("customer msg send error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
package message
|
package message
|
||||||
|
|
||||||
import "encoding/xml"
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
|
||||||
|
"github.com/silenceper/wechat/device"
|
||||||
|
)
|
||||||
|
|
||||||
// MsgType 基本消息类型
|
// MsgType 基本消息类型
|
||||||
type MsgType string
|
type MsgType string
|
||||||
@@ -63,6 +67,8 @@ const (
|
|||||||
EventLocationSelect = "location_select"
|
EventLocationSelect = "location_select"
|
||||||
//EventTemplateSendJobFinish 发送模板消息推送通知
|
//EventTemplateSendJobFinish 发送模板消息推送通知
|
||||||
EventTemplateSendJobFinish = "TEMPLATESENDJOBFINISH"
|
EventTemplateSendJobFinish = "TEMPLATESENDJOBFINISH"
|
||||||
|
//EventWxaMediaCheck 异步校验图片/音频是否含有违法违规内容推送事件
|
||||||
|
EventWxaMediaCheck = "wxa_media_check"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -144,6 +150,15 @@ type MixMessage struct {
|
|||||||
OuterStr string `xml:"OuterStr"`
|
OuterStr string `xml:"OuterStr"`
|
||||||
IsRestoreMemberCard int32 `xml:"IsRestoreMemberCard"`
|
IsRestoreMemberCard int32 `xml:"IsRestoreMemberCard"`
|
||||||
UnionID string `xml:"UnionId"`
|
UnionID string `xml:"UnionId"`
|
||||||
|
|
||||||
|
// 内容审核相关
|
||||||
|
IsRisky bool `xml:"isrisky"`
|
||||||
|
ExtraInfoJSON string `xml:"extra_info_json"`
|
||||||
|
TraceID string `xml:"trace_id"`
|
||||||
|
StatusCode int `xml:"status_code"`
|
||||||
|
|
||||||
|
//设备相关
|
||||||
|
device.MsgDevice
|
||||||
}
|
}
|
||||||
|
|
||||||
//EventPic 发图事件推送
|
//EventPic 发图事件推送
|
||||||
@@ -167,22 +182,32 @@ type ResponseEncryptedXMLMsg struct {
|
|||||||
Nonce string `xml:"Nonce" json:"Nonce"`
|
Nonce string `xml:"Nonce" json:"Nonce"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CDATA 使用该类型,在序列化为 xml 文本时文本会被解析器忽略
|
||||||
|
type CDATA string
|
||||||
|
|
||||||
|
// MarshalXML 实现自己的序列化方法
|
||||||
|
func (c CDATA) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||||
|
return e.EncodeElement(struct {
|
||||||
|
string `xml:",cdata"`
|
||||||
|
}{string(c)}, start)
|
||||||
|
}
|
||||||
|
|
||||||
// CommonToken 消息中通用的结构
|
// CommonToken 消息中通用的结构
|
||||||
type CommonToken struct {
|
type CommonToken struct {
|
||||||
XMLName xml.Name `xml:"xml"`
|
XMLName xml.Name `xml:"xml"`
|
||||||
ToUserName string `xml:"ToUserName"`
|
ToUserName CDATA `xml:"ToUserName"`
|
||||||
FromUserName string `xml:"FromUserName"`
|
FromUserName CDATA `xml:"FromUserName"`
|
||||||
CreateTime int64 `xml:"CreateTime"`
|
CreateTime int64 `xml:"CreateTime"`
|
||||||
MsgType MsgType `xml:"MsgType"`
|
MsgType MsgType `xml:"MsgType"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//SetToUserName set ToUserName
|
//SetToUserName set ToUserName
|
||||||
func (msg *CommonToken) SetToUserName(toUserName string) {
|
func (msg *CommonToken) SetToUserName(toUserName CDATA) {
|
||||||
msg.ToUserName = toUserName
|
msg.ToUserName = toUserName
|
||||||
}
|
}
|
||||||
|
|
||||||
//SetFromUserName set FromUserName
|
//SetFromUserName set FromUserName
|
||||||
func (msg *CommonToken) SetFromUserName(fromUserName string) {
|
func (msg *CommonToken) SetFromUserName(fromUserName CDATA) {
|
||||||
msg.FromUserName = fromUserName
|
msg.FromUserName = fromUserName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ package message
|
|||||||
//Text 文本消息
|
//Text 文本消息
|
||||||
type Text struct {
|
type Text struct {
|
||||||
CommonToken
|
CommonToken
|
||||||
Content string `xml:"Content"`
|
Content CDATA `xml:"Content"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//NewText 初始化文本消息
|
//NewText 初始化文本消息
|
||||||
func NewText(content string) *Text {
|
func NewText(content string) *Text {
|
||||||
text := new(Text)
|
text := new(Text)
|
||||||
text.Content = content
|
text.Content = CDATA(content)
|
||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,17 @@ type UserInfo struct {
|
|||||||
} `json:"watermark"`
|
} `json:"watermark"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PhoneInfo 用户手机号
|
||||||
|
type PhoneInfo struct {
|
||||||
|
PhoneNumber string `json:"phoneNumber"`
|
||||||
|
PurePhoneNumber string `json:"purePhoneNumber"`
|
||||||
|
CountryCode string `json:"countryCode"`
|
||||||
|
Watermark struct {
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
AppID string `json:"appid"`
|
||||||
|
} `json:"watermark"`
|
||||||
|
}
|
||||||
|
|
||||||
// pkcs7Unpad returns slice of the original data without padding
|
// pkcs7Unpad returns slice of the original data without padding
|
||||||
func pkcs7Unpad(data []byte, blockSize int) ([]byte, error) {
|
func pkcs7Unpad(data []byte, blockSize int) ([]byte, error) {
|
||||||
if blockSize <= 0 {
|
if blockSize <= 0 {
|
||||||
@@ -57,8 +68,8 @@ func pkcs7Unpad(data []byte, blockSize int) ([]byte, error) {
|
|||||||
return data[:len(data)-n], nil
|
return data[:len(data)-n], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrypt 解密数据
|
// getCipherText returns slice of the cipher text
|
||||||
func (wxa *MiniProgram) Decrypt(sessionKey, encryptedData, iv string) (*UserInfo, error) {
|
func getCipherText(sessionKey, encryptedData, iv string) ([]byte, error) {
|
||||||
aesKey, err := base64.StdEncoding.DecodeString(sessionKey)
|
aesKey, err := base64.StdEncoding.DecodeString(sessionKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -81,6 +92,15 @@ func (wxa *MiniProgram) Decrypt(sessionKey, encryptedData, iv string) (*UserInfo
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return cipherText, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt 解密数据
|
||||||
|
func (wxa *MiniProgram) Decrypt(sessionKey, encryptedData, iv string) (*UserInfo, error) {
|
||||||
|
cipherText, err := getCipherText(sessionKey, encryptedData, iv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
var userInfo UserInfo
|
var userInfo UserInfo
|
||||||
err = json.Unmarshal(cipherText, &userInfo)
|
err = json.Unmarshal(cipherText, &userInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -91,3 +111,20 @@ func (wxa *MiniProgram) Decrypt(sessionKey, encryptedData, iv string) (*UserInfo
|
|||||||
}
|
}
|
||||||
return &userInfo, nil
|
return &userInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DecryptPhone 解密数据(手机)
|
||||||
|
func (wxa *MiniProgram) DecryptPhone(sessionKey, encryptedData, iv string) (*PhoneInfo, error) {
|
||||||
|
cipherText, err := getCipherText(sessionKey, encryptedData, iv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var phoneInfo PhoneInfo
|
||||||
|
err = json.Unmarshal(cipherText, &phoneInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if phoneInfo.Watermark.AppID != wxa.AppID {
|
||||||
|
return nil, ErrAppIDNotMatch
|
||||||
|
}
|
||||||
|
return &phoneInfo, nil
|
||||||
|
}
|
||||||
|
|||||||
84
pay/notify_result.go
Normal file
84
pay/notify_result.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package pay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/silenceper/wechat/util"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Base 公用参数
|
||||||
|
type Base struct {
|
||||||
|
AppID string `xml:"appid"`
|
||||||
|
MchID string `xml:"mch_id"`
|
||||||
|
NonceStr string `xml:"nonce_str"`
|
||||||
|
Sign string `xml:"sign"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyResult 下单回调
|
||||||
|
type NotifyResult struct {
|
||||||
|
Base
|
||||||
|
ReturnCode string `xml:"return_code"`
|
||||||
|
ReturnMsg string `xml:"return_msg"`
|
||||||
|
ResultCode string `xml:"result_code"`
|
||||||
|
OpenID string `xml:"openid"`
|
||||||
|
IsSubscribe string `xml:"is_subscribe"`
|
||||||
|
TradeType string `xml:"trade_type"`
|
||||||
|
BankType string `xml:"bank_type"`
|
||||||
|
TotalFee int `xml:"total_fee"`
|
||||||
|
FeeType string `xml:"fee_type"`
|
||||||
|
CashFee int `xml:"cash_fee"`
|
||||||
|
CashFeeType string `xml:"cash_fee_type"`
|
||||||
|
TransactionID string `xml:"transaction_id"`
|
||||||
|
OutTradeNo string `xml:"out_trade_no"`
|
||||||
|
Attach string `xml:"attach"`
|
||||||
|
TimeEnd string `xml:"time_end"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyResp 消息通知返回
|
||||||
|
type NotifyResp struct {
|
||||||
|
ReturnCode string `xml:"return_code"`
|
||||||
|
ReturnMsg string `xml:"return_msg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifySign 验签
|
||||||
|
func (pcf *Pay) VerifySign(notifyRes NotifyResult) bool {
|
||||||
|
// 封装map 请求过来的 map
|
||||||
|
resMap := make(map[string]interface{})
|
||||||
|
resMap["appid"] = notifyRes.AppID
|
||||||
|
resMap["bank_type"] = notifyRes.BankType
|
||||||
|
resMap["cash_fee"] = notifyRes.CashFee
|
||||||
|
resMap["fee_type"] = notifyRes.FeeType
|
||||||
|
resMap["is_subscribe"] = notifyRes.IsSubscribe
|
||||||
|
resMap["mch_id"] = notifyRes.MchID
|
||||||
|
resMap["nonce_str"] = notifyRes.NonceStr
|
||||||
|
resMap["openid"] = notifyRes.OpenID
|
||||||
|
resMap["out_trade_no"] = notifyRes.OutTradeNo
|
||||||
|
resMap["result_code"] = notifyRes.ResultCode
|
||||||
|
resMap["return_code"] = notifyRes.ReturnCode
|
||||||
|
resMap["time_end"] = notifyRes.TimeEnd
|
||||||
|
resMap["total_fee"] = notifyRes.TotalFee
|
||||||
|
resMap["trade_type"] = notifyRes.TradeType
|
||||||
|
resMap["transaction_id"] = notifyRes.TransactionID
|
||||||
|
// 支付key
|
||||||
|
sortedKeys := make([]string, 0, len(resMap))
|
||||||
|
for k := range resMap {
|
||||||
|
sortedKeys = append(sortedKeys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(sortedKeys)
|
||||||
|
// STEP2, 对key=value的键值对用&连接起来,略过空值
|
||||||
|
var signStrings string
|
||||||
|
for _, k := range sortedKeys {
|
||||||
|
value := fmt.Sprintf("%v", resMap[k])
|
||||||
|
if value != "" {
|
||||||
|
signStrings = signStrings + k + "=" + value + "&"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// STEP3, 在键值对的最后加上key=API_KEY
|
||||||
|
signStrings = signStrings + "key=" + pcf.PayKey
|
||||||
|
// STEP4, 进行MD5签名并且将所有字符转为大写.
|
||||||
|
sign := util.MD5Sum(signStrings)
|
||||||
|
if sign != notifyRes.Sign {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
115
pay/pay.go
115
pay/pay.go
@@ -2,10 +2,17 @@ package pay
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"errors"
|
"errors"
|
||||||
|
"hash"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/silenceper/wechat/context"
|
"github.com/silenceper/wechat/context"
|
||||||
"github.com/silenceper/wechat/util"
|
"github.com/silenceper/wechat/util"
|
||||||
@@ -27,15 +34,21 @@ type Params struct {
|
|||||||
OutTradeNo string
|
OutTradeNo string
|
||||||
OpenID string
|
OpenID string
|
||||||
TradeType string
|
TradeType string
|
||||||
|
SignType string
|
||||||
|
Detail string
|
||||||
|
Attach string
|
||||||
|
GoodsTag string
|
||||||
|
NotifyURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config 是传出用于 jsdk 用的参数
|
// Config 是传出用于 js sdk 用的参数
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Timestamp int64
|
Timestamp string `json:"timestamp"`
|
||||||
NonceStr string
|
NonceStr string `json:"nonceStr"`
|
||||||
PrePayID string
|
PrePayID string `json:"prePayId"`
|
||||||
SignType string
|
SignType string `json:"signType"`
|
||||||
Sign string
|
Package string `json:"package"`
|
||||||
|
PaySign string `json:"paySign"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PreOrder 是 unifie order 接口的返回
|
// PreOrder 是 unifie order 接口的返回
|
||||||
@@ -54,7 +67,7 @@ type PreOrder struct {
|
|||||||
ErrCodeDes string `xml:"err_code_des,omitempty"`
|
ErrCodeDes string `xml:"err_code_des,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//payRequest 接口请求参数
|
// payRequest 接口请求参数
|
||||||
type payRequest struct {
|
type payRequest struct {
|
||||||
AppID string `xml:"appid"`
|
AppID string `xml:"appid"`
|
||||||
MchID string `xml:"mch_id"`
|
MchID string `xml:"mch_id"`
|
||||||
@@ -64,20 +77,20 @@ type payRequest struct {
|
|||||||
SignType string `xml:"sign_type,omitempty"`
|
SignType string `xml:"sign_type,omitempty"`
|
||||||
Body string `xml:"body"`
|
Body string `xml:"body"`
|
||||||
Detail string `xml:"detail,omitempty"`
|
Detail string `xml:"detail,omitempty"`
|
||||||
Attach string `xml:"attach,omitempty"` //附加数据
|
Attach string `xml:"attach,omitempty"` // 附加数据
|
||||||
OutTradeNo string `xml:"out_trade_no"` //商户订单号
|
OutTradeNo string `xml:"out_trade_no"` // 商户订单号
|
||||||
FeeType string `xml:"fee_type,omitempty"` //标价币种
|
FeeType string `xml:"fee_type,omitempty"` // 标价币种
|
||||||
TotalFee string `xml:"total_fee"` //标价金额
|
TotalFee string `xml:"total_fee"` // 标价金额
|
||||||
SpbillCreateIP string `xml:"spbill_create_ip"` //终端IP
|
SpbillCreateIP string `xml:"spbill_create_ip"` // 终端IP
|
||||||
TimeStart string `xml:"time_start,omitempty"` //交易起始时间
|
TimeStart string `xml:"time_start,omitempty"` // 交易起始时间
|
||||||
TimeExpire string `xml:"time_expire,omitempty"` //交易结束时间
|
TimeExpire string `xml:"time_expire,omitempty"` // 交易结束时间
|
||||||
GoodsTag string `xml:"goods_tag,omitempty"` //订单优惠标记
|
GoodsTag string `xml:"goods_tag,omitempty"` // 订单优惠标记
|
||||||
NotifyURL string `xml:"notify_url"` //通知地址
|
NotifyURL string `xml:"notify_url"` // 通知地址
|
||||||
TradeType string `xml:"trade_type"` //交易类型
|
TradeType string `xml:"trade_type"` // 交易类型
|
||||||
ProductID string `xml:"product_id,omitempty"` //商品ID
|
ProductID string `xml:"product_id,omitempty"` // 商品ID
|
||||||
LimitPay string `xml:"limit_pay,omitempty"` //
|
LimitPay string `xml:"limit_pay,omitempty"` //
|
||||||
OpenID string `xml:"openid,omitempty"` //用户标识
|
OpenID string `xml:"openid,omitempty"` // 用户标识
|
||||||
SceneInfo string `xml:"scene_info,omitempty"` //场景信息
|
SceneInfo string `xml:"scene_info,omitempty"` // 场景信息
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPay return an instance of Pay package
|
// NewPay return an instance of Pay package
|
||||||
@@ -86,20 +99,72 @@ func NewPay(ctx *context.Context) *Pay {
|
|||||||
return &pay
|
return &pay
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BridgeConfig get js bridge config
|
||||||
|
func (pcf *Pay) BridgeConfig(p *Params) (cfg Config, err error) {
|
||||||
|
var (
|
||||||
|
buffer strings.Builder
|
||||||
|
h hash.Hash
|
||||||
|
timestamp = strconv.FormatInt(time.Now().Unix(), 10)
|
||||||
|
)
|
||||||
|
order, err := pcf.PrePayOrder(p)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buffer.WriteString("appId=")
|
||||||
|
buffer.WriteString(order.AppID)
|
||||||
|
buffer.WriteString("&nonceStr=")
|
||||||
|
buffer.WriteString(order.NonceStr)
|
||||||
|
buffer.WriteString("&package=")
|
||||||
|
buffer.WriteString("prepay_id=" + order.PrePayID)
|
||||||
|
buffer.WriteString("&signType=")
|
||||||
|
buffer.WriteString(p.SignType)
|
||||||
|
buffer.WriteString("&timeStamp=")
|
||||||
|
buffer.WriteString(timestamp)
|
||||||
|
buffer.WriteString("&key=")
|
||||||
|
buffer.WriteString(pcf.PayKey)
|
||||||
|
if p.SignType == "MD5" {
|
||||||
|
h = md5.New()
|
||||||
|
} else {
|
||||||
|
h = hmac.New(sha256.New, []byte(pcf.PayKey))
|
||||||
|
}
|
||||||
|
h.Write([]byte(buffer.String()))
|
||||||
|
// 签名
|
||||||
|
cfg.PaySign = strings.ToUpper(hex.EncodeToString(h.Sum(nil)))
|
||||||
|
cfg.NonceStr = order.NonceStr
|
||||||
|
cfg.Timestamp = timestamp
|
||||||
|
cfg.PrePayID = order.PrePayID
|
||||||
|
cfg.SignType = p.SignType
|
||||||
|
cfg.Package = "prepay_id=" + order.PrePayID
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// PrePayOrder return data for invoke wechat payment
|
// PrePayOrder return data for invoke wechat payment
|
||||||
func (pcf *Pay) PrePayOrder(p *Params) (payOrder PreOrder, err error) {
|
func (pcf *Pay) PrePayOrder(p *Params) (payOrder PreOrder, err error) {
|
||||||
nonceStr := util.RandomStr(32)
|
nonceStr := util.RandomStr(32)
|
||||||
|
notifyURL := pcf.PayNotifyURL
|
||||||
|
// 签名类型
|
||||||
|
if p.SignType == "" {
|
||||||
|
p.SignType = "MD5"
|
||||||
|
}
|
||||||
|
// 通知地址
|
||||||
|
if p.NotifyURL != "" {
|
||||||
|
notifyURL = p.NotifyURL
|
||||||
|
}
|
||||||
param := make(map[string]interface{})
|
param := make(map[string]interface{})
|
||||||
param["appid"] = pcf.AppID
|
param["appid"] = pcf.AppID
|
||||||
param["body"] = p.Body
|
param["body"] = p.Body
|
||||||
param["mch_id"] = pcf.PayMchID
|
param["mch_id"] = pcf.PayMchID
|
||||||
param["nonce_str"] = nonceStr
|
param["nonce_str"] = nonceStr
|
||||||
param["notify_url"] = pcf.PayNotifyURL
|
|
||||||
param["out_trade_no"] = p.OutTradeNo
|
param["out_trade_no"] = p.OutTradeNo
|
||||||
param["spbill_create_ip"] = p.CreateIP
|
param["spbill_create_ip"] = p.CreateIP
|
||||||
param["total_fee"] = p.TotalFee
|
param["total_fee"] = p.TotalFee
|
||||||
param["trade_type"] = p.TradeType
|
param["trade_type"] = p.TradeType
|
||||||
param["openid"] = p.OpenID
|
param["openid"] = p.OpenID
|
||||||
|
param["sign_type"] = p.SignType
|
||||||
|
param["detail"] = p.Detail
|
||||||
|
param["attach"] = p.Attach
|
||||||
|
param["goods_tag"] = p.GoodsTag
|
||||||
|
param["notify_url"] = notifyURL
|
||||||
|
|
||||||
bizKey := "&key=" + pcf.PayKey
|
bizKey := "&key=" + pcf.PayKey
|
||||||
str := orderParam(param, bizKey)
|
str := orderParam(param, bizKey)
|
||||||
@@ -113,9 +178,13 @@ func (pcf *Pay) PrePayOrder(p *Params) (payOrder PreOrder, err error) {
|
|||||||
OutTradeNo: p.OutTradeNo,
|
OutTradeNo: p.OutTradeNo,
|
||||||
TotalFee: p.TotalFee,
|
TotalFee: p.TotalFee,
|
||||||
SpbillCreateIP: p.CreateIP,
|
SpbillCreateIP: p.CreateIP,
|
||||||
NotifyURL: pcf.PayNotifyURL,
|
NotifyURL: notifyURL,
|
||||||
TradeType: p.TradeType,
|
TradeType: p.TradeType,
|
||||||
OpenID: p.OpenID,
|
OpenID: p.OpenID,
|
||||||
|
SignType: p.SignType,
|
||||||
|
Detail: p.Detail,
|
||||||
|
Attach: p.Attach,
|
||||||
|
GoodsTag: p.GoodsTag,
|
||||||
}
|
}
|
||||||
rawRet, err := util.PostXML(payGateway, request)
|
rawRet, err := util.PostXML(payGateway, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -126,7 +195,7 @@ func (pcf *Pay) PrePayOrder(p *Params) (payOrder PreOrder, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if payOrder.ReturnCode == "SUCCESS" {
|
if payOrder.ReturnCode == "SUCCESS" {
|
||||||
//pay success
|
// pay success
|
||||||
if payOrder.ResultCode == "SUCCESS" {
|
if payOrder.ResultCode == "SUCCESS" {
|
||||||
err = nil
|
err = nil
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -65,7 +65,9 @@ func (srv *Server) Serve() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//debug
|
//debug
|
||||||
//fmt.Println("request msg = ", string(srv.requestRawXMLMsg))
|
if srv.debug {
|
||||||
|
fmt.Println("request msg = ", string(srv.requestRawXMLMsg))
|
||||||
|
}
|
||||||
|
|
||||||
return srv.buildResponse(response)
|
return srv.buildResponse(response)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package wechat
|
package wechat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/silenceper/wechat/device"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@@ -111,3 +112,8 @@ func (wc *Wechat) GetQR() *qr.QR {
|
|||||||
func (wc *Wechat) GetMiniProgram() *miniprogram.MiniProgram {
|
func (wc *Wechat) GetMiniProgram() *miniprogram.MiniProgram {
|
||||||
return miniprogram.NewMiniProgram(wc.Context)
|
return miniprogram.NewMiniProgram(wc.Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDevice 获取智能设备的实例
|
||||||
|
func (wc *Wechat) GetDevice() *device.Device {
|
||||||
|
return device.NewDevice(wc.Context)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user