增加vendor
@@ -0,0 +1,40 @@
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: gopay
|
||||
|
||||
clone:
|
||||
depth: 1
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: helloworld
|
||||
pull: if-not-exists
|
||||
image: hello-world
|
||||
|
||||
- name: ci_1.16
|
||||
pull: if-not-exists
|
||||
image: golang:1.16
|
||||
environment:
|
||||
GO111MODULE: "on"
|
||||
GOPROXY: "https://goproxy.cn,direct"
|
||||
GOSUMDB: "off"
|
||||
CGO_ENABLED: "0"
|
||||
GOOS: "linux"
|
||||
depends_on:
|
||||
- helloworld
|
||||
commands:
|
||||
- go version
|
||||
- go env
|
||||
- go mod tidy
|
||||
- go test ./...
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- main
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
||||
- tag
|
||||
@@ -0,0 +1,7 @@
|
||||
/.idea
|
||||
|
||||
.vscode
|
||||
|
||||
settings.json
|
||||
|
||||
vendor
|
||||
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2019 Jerry
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,108 @@
|
||||
<div align=center><img width="240" height="240" alt="Logo was Loading Faild!" src="https://raw.githubusercontent.com/go-pay/gopay/main/logo.png"/></div>
|
||||
|
||||
# GoPay
|
||||
|
||||
### 微信、支付宝、PayPal、QQ 的 Golang 版本SDK
|
||||
|
||||
[](https://github.com/iGoogle-ink)
|
||||
[](https://github.com/go-pay/gopay/fork)
|
||||
|
||||
[](https://golang.google.cn)
|
||||
[](https://pkg.go.dev/github.com/go-pay/gopay)
|
||||
[](https://cloud.drone.io/go-pay/gopay)
|
||||
[](https://github.com/go-pay/gopay/releases)
|
||||
[](https://www.apache.org/licenses/LICENSE-2.0)
|
||||
[](https://github.com/go-pay/gopay)
|
||||
|
||||
---
|
||||
|
||||
- #### 近期计划:
|
||||
|
||||
> 将 gopay 库中,非支付相关的一些接口方法独立出去另外的 sdk 库,在 go-pay 组织下新建 `wechat-sdk` 和 `alipay-sdk` 两个项目,分别实现各个平台相关接口方法,优先进行 `wechat-sdk` 开发。
|
||||
|
||||
> 微信小程序或公众号相关接口方法:已从 `微信v2` 移步替换成 `github.com/go-pay/wechat-sdk`
|
||||
|
||||
<br>
|
||||
|
||||
# 一、安装
|
||||
|
||||
```bash
|
||||
go get -u github.com/go-pay/gopay
|
||||
```
|
||||
|
||||
#### 查看 GoPay 版本
|
||||
|
||||
[版本更新记录](https://github.com/go-pay/gopay/blob/main/release_note.txt)
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/pkg/xlog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
xlog.Info("GoPay Version: ", gopay.Version)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
<br>
|
||||
|
||||
# 二、文档目录
|
||||
|
||||
> ### 点击查看不同支付方式的使用文档。方便的话,请留下您认可的小星星,十分感谢!
|
||||
|
||||
* #### [Alipay](https://github.com/go-pay/gopay/blob/main/doc/alipay.md)
|
||||
* #### [Wechat](https://github.com/go-pay/gopay/blob/main/doc/wechat_v3.md)
|
||||
* #### [QQ](https://github.com/go-pay/gopay/blob/main/doc/qq.md)
|
||||
* #### [Paypal](https://github.com/go-pay/gopay/blob/main/doc/paypal.md)
|
||||
* #### [Apple](https://github.com/go-pay/gopay/blob/main/doc/apple.md)
|
||||
|
||||
---
|
||||
|
||||
<br>
|
||||
|
||||
# 三、其他说明
|
||||
|
||||
* 各支付方式接入,请仔细查看 `xxx_test.go` 使用方式
|
||||
* `gopay/wechat/v3/client_test.go`
|
||||
* `gopay/alipay/client_test.go`
|
||||
* `gopay/qq/client_test.go`
|
||||
* `gopay/paypal/client_test.go`
|
||||
* `gopay/apple/verify_test.go`
|
||||
* 或 examples
|
||||
* 有问题请加QQ群(加群验证答案:gopay),或加微信好友拉群。在此,非常感谢提出宝贵意见和反馈问题的同志们!
|
||||
* 开发过程中,请尽量使用正式环境,1分钱测试法!
|
||||
|
||||
QQ群:
|
||||
<img width="280" height="280" src="https://raw.githubusercontent.com/go-pay/gopay/main/qq_gopay.png"/>
|
||||
加微信拉群:
|
||||
<img width="280" height="280" src="https://raw.githubusercontent.com/go-pay/gopay/main/wechat_jerry.png"/>
|
||||
|
||||
---
|
||||
|
||||
<br>
|
||||
|
||||
## 赞赏多少是您的心意,感谢支持!
|
||||
|
||||
微信赞赏码: <img width="200" height="200" src="https://raw.githubusercontent.com/go-pay/gopay/main/zanshang.png"/>
|
||||
支付宝赞助码: <img width="200" height="200" src="https://raw.githubusercontent.com/go-pay/gopay/main/zanshang_zfb.png"/>
|
||||
|
||||
## License
|
||||
|
||||
```
|
||||
Copyright 2019 Jerry
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
```
|
||||
|
After Width: | Height: | Size: 34 KiB |
@@ -0,0 +1,242 @@
|
||||
package gopay
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/go-pay/gopay/pkg/util"
|
||||
)
|
||||
|
||||
type BodyMap map[string]interface{}
|
||||
|
||||
type xmlMapMarshal struct {
|
||||
XMLName xml.Name
|
||||
Value interface{} `xml:",cdata"`
|
||||
}
|
||||
|
||||
type xmlMapUnmarshal struct {
|
||||
XMLName xml.Name
|
||||
Value string `xml:",cdata"`
|
||||
}
|
||||
|
||||
// 设置参数
|
||||
func (bm BodyMap) Set(key string, value interface{}) BodyMap {
|
||||
bm[key] = value
|
||||
return bm
|
||||
}
|
||||
|
||||
func (bm BodyMap) SetBodyMap(key string, value func(b BodyMap)) BodyMap {
|
||||
_bm := make(BodyMap)
|
||||
value(_bm)
|
||||
bm[key] = _bm
|
||||
return bm
|
||||
}
|
||||
|
||||
// 设置 FormFile
|
||||
func (bm BodyMap) SetFormFile(key string, file *util.File) BodyMap {
|
||||
bm[key] = file
|
||||
return bm
|
||||
}
|
||||
|
||||
// 获取参数,同 GetString()
|
||||
func (bm BodyMap) Get(key string) string {
|
||||
return bm.GetString(key)
|
||||
}
|
||||
|
||||
// 获取参数转换string
|
||||
func (bm BodyMap) GetString(key string) string {
|
||||
if bm == nil {
|
||||
return NULL
|
||||
}
|
||||
value, ok := bm[key]
|
||||
if !ok {
|
||||
return NULL
|
||||
}
|
||||
v, ok := value.(string)
|
||||
if !ok {
|
||||
return convertToString(value)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// 获取原始参数
|
||||
func (bm BodyMap) GetInterface(key string) interface{} {
|
||||
if bm == nil {
|
||||
return nil
|
||||
}
|
||||
return bm[key]
|
||||
}
|
||||
|
||||
// 删除参数
|
||||
func (bm BodyMap) Remove(key string) {
|
||||
delete(bm, key)
|
||||
}
|
||||
|
||||
// 置空BodyMap
|
||||
func (bm BodyMap) Reset() {
|
||||
for k := range bm {
|
||||
delete(bm, k)
|
||||
}
|
||||
}
|
||||
|
||||
func (bm BodyMap) JsonBody() (jb string) {
|
||||
bs, err := json.Marshal(bm)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
jb = string(bs)
|
||||
return jb
|
||||
}
|
||||
|
||||
// Unmarshal to struct or slice point
|
||||
func (bm BodyMap) Unmarshal(ptr interface{}) (err error) {
|
||||
bs, err := json.Marshal(bm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(bs, ptr)
|
||||
}
|
||||
|
||||
func (bm BodyMap) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) {
|
||||
if len(bm) == 0 {
|
||||
return nil
|
||||
}
|
||||
start.Name = xml.Name{Space: NULL, Local: "xml"}
|
||||
if err = e.EncodeToken(start); err != nil {
|
||||
return
|
||||
}
|
||||
for k := range bm {
|
||||
if v := bm.GetString(k); v != NULL {
|
||||
e.Encode(xmlMapMarshal{XMLName: xml.Name{Local: k}, Value: v})
|
||||
}
|
||||
}
|
||||
return e.EncodeToken(start.End())
|
||||
}
|
||||
|
||||
func (bm *BodyMap) UnmarshalXML(d *xml.Decoder, start xml.StartElement) (err error) {
|
||||
for {
|
||||
var e xmlMapUnmarshal
|
||||
err = d.Decode(&e)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
bm.Set(e.XMLName.Local, e.Value)
|
||||
}
|
||||
}
|
||||
|
||||
// ("bar=baz&foo=quux") sorted by key.
|
||||
func (bm BodyMap) EncodeWeChatSignParams(apiKey string) string {
|
||||
if bm == nil {
|
||||
return NULL
|
||||
}
|
||||
var (
|
||||
buf strings.Builder
|
||||
keyList []string
|
||||
)
|
||||
for k := range bm {
|
||||
keyList = append(keyList, k)
|
||||
}
|
||||
sort.Strings(keyList)
|
||||
for _, k := range keyList {
|
||||
if v := bm.GetString(k); v != NULL {
|
||||
buf.WriteString(k)
|
||||
buf.WriteByte('=')
|
||||
buf.WriteString(v)
|
||||
buf.WriteByte('&')
|
||||
}
|
||||
}
|
||||
buf.WriteString("key")
|
||||
buf.WriteByte('=')
|
||||
buf.WriteString(apiKey)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// ("bar=baz&foo=quux") sorted by key.
|
||||
func (bm BodyMap) EncodeAliPaySignParams() string {
|
||||
if bm == nil {
|
||||
return NULL
|
||||
}
|
||||
var (
|
||||
buf strings.Builder
|
||||
keyList []string
|
||||
)
|
||||
for k := range bm {
|
||||
keyList = append(keyList, k)
|
||||
}
|
||||
sort.Strings(keyList)
|
||||
for _, k := range keyList {
|
||||
if v := bm.GetString(k); v != NULL {
|
||||
buf.WriteString(k)
|
||||
buf.WriteByte('=')
|
||||
buf.WriteString(v)
|
||||
buf.WriteByte('&')
|
||||
}
|
||||
}
|
||||
if buf.Len() <= 0 {
|
||||
return NULL
|
||||
}
|
||||
return buf.String()[:buf.Len()-1]
|
||||
}
|
||||
|
||||
// ("bar=baz&foo=quux") sorted by key.
|
||||
func (bm BodyMap) EncodeURLParams() string {
|
||||
if bm == nil {
|
||||
return NULL
|
||||
}
|
||||
var (
|
||||
buf strings.Builder
|
||||
keys []string
|
||||
)
|
||||
for k := range bm {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
if v := bm.GetString(k); v != NULL {
|
||||
buf.WriteString(url.QueryEscape(k))
|
||||
buf.WriteByte('=')
|
||||
buf.WriteString(url.QueryEscape(v))
|
||||
buf.WriteByte('&')
|
||||
}
|
||||
}
|
||||
if buf.Len() <= 0 {
|
||||
return NULL
|
||||
}
|
||||
return buf.String()[:buf.Len()-1]
|
||||
}
|
||||
|
||||
func (bm BodyMap) CheckEmptyError(keys ...string) error {
|
||||
var emptyKeys []string
|
||||
for _, k := range keys {
|
||||
if v := bm.GetString(k); v == NULL {
|
||||
emptyKeys = append(emptyKeys, k)
|
||||
}
|
||||
}
|
||||
if len(emptyKeys) > 0 {
|
||||
return fmt.Errorf("[%w], %v", MissParamErr, strings.Join(emptyKeys, ", "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertToString(v interface{}) (str string) {
|
||||
if v == nil {
|
||||
return NULL
|
||||
}
|
||||
var (
|
||||
bs []byte
|
||||
err error
|
||||
)
|
||||
if bs, err = json.Marshal(v); err != nil {
|
||||
return NULL
|
||||
}
|
||||
str = string(bs)
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package gopay
|
||||
|
||||
const (
|
||||
NULL = ""
|
||||
SUCCESS = "SUCCESS"
|
||||
FAIL = "FAIL"
|
||||
OK = "OK"
|
||||
DebugOff = 0
|
||||
DebugOn = 1
|
||||
Version = "1.5.78"
|
||||
)
|
||||
|
||||
type DebugSwitch int8
|
||||
@@ -0,0 +1,16 @@
|
||||
package gopay
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
MissWechatInitParamErr = errors.New("missing wechat init parameter")
|
||||
MissAlipayInitParamErr = errors.New("missing alipay init parameter")
|
||||
MissPayPalInitParamErr = errors.New("missing paypal init parameter")
|
||||
MissParamErr = errors.New("missing required parameter")
|
||||
MarshalErr = errors.New("marshal error")
|
||||
UnmarshalErr = errors.New("unmarshal error")
|
||||
SignatureErr = errors.New("signature error")
|
||||
VerifySignatureErr = errors.New("verify signature error")
|
||||
CertNotMatchErr = errors.New("cert not match error")
|
||||
GetSignDataErr = errors.New("get signature data error")
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
module github.com/go-pay/gopay
|
||||
|
||||
go 1.16
|
||||
|
||||
require golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29
|
||||
@@ -0,0 +1,9 @@
|
||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o=
|
||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
After Width: | Height: | Size: 32 KiB |
@@ -0,0 +1,43 @@
|
||||
package aes
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// AES-CBC 加密数据
|
||||
func CBCEncrypt(originData, key, iv []byte) ([]byte, error) {
|
||||
return cbcEncrypt(originData, key, iv)
|
||||
}
|
||||
|
||||
// AES-CBC 解密数据
|
||||
func CBCDecrypt(secretData, key, iv []byte) ([]byte, error) {
|
||||
return cbcDecrypt(secretData, key, iv)
|
||||
}
|
||||
|
||||
func cbcEncrypt(originData, key, iv []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
originData = PKCS7Padding(originData, block.BlockSize())
|
||||
secretData := make([]byte, len(originData))
|
||||
blockMode := cipher.NewCBCEncrypter(block, iv[:block.BlockSize()])
|
||||
blockMode.CryptBlocks(secretData, originData)
|
||||
return secretData, nil
|
||||
}
|
||||
|
||||
func cbcDecrypt(secretData, key, iv []byte) (originByte []byte, err error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
originByte = make([]byte, len(secretData))
|
||||
blockMode := cipher.NewCBCDecrypter(block, iv[:block.BlockSize()])
|
||||
blockMode.CryptBlocks(originByte, secretData)
|
||||
if len(originByte) == 0 {
|
||||
return nil, errors.New("blockMode.CryptBlocks error")
|
||||
}
|
||||
return PKCS7UnPadding(originByte), nil
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package aes
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// AES-ECB 加密数据
|
||||
func ECBEncrypt(originData, key []byte) ([]byte, error) {
|
||||
return ecbEncrypt(originData, key)
|
||||
}
|
||||
|
||||
// AES-ECB 解密数据
|
||||
func ECBDecrypt(secretData, key []byte) ([]byte, error) {
|
||||
return ecbDecrypt(secretData, key)
|
||||
}
|
||||
|
||||
func ecbEncrypt(originData, key []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
originData = PKCS7Padding(originData, block.BlockSize())
|
||||
secretData := make([]byte, len(originData))
|
||||
blockMode := newECBEncrypter(block)
|
||||
blockMode.CryptBlocks(secretData, originData)
|
||||
return secretData, nil
|
||||
}
|
||||
|
||||
func ecbDecrypt(secretData, key []byte) (originByte []byte, err error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blockMode := newECBDecrypter(block)
|
||||
originByte = make([]byte, len(secretData))
|
||||
blockMode.CryptBlocks(originByte, secretData)
|
||||
if len(originByte) == 0 {
|
||||
return nil, errors.New("blockMode.CryptBlocks error")
|
||||
}
|
||||
return PKCS7UnPadding(originByte), nil
|
||||
}
|
||||
|
||||
// ===========
|
||||
|
||||
type ecb struct {
|
||||
b cipher.Block
|
||||
blockSize int
|
||||
}
|
||||
|
||||
func newECB(b cipher.Block) *ecb {
|
||||
return &ecb{
|
||||
b: b,
|
||||
blockSize: b.BlockSize(),
|
||||
}
|
||||
}
|
||||
|
||||
type ecbEncrypter ecb
|
||||
|
||||
// newECBEncrypter returns a BlockMode which encrypts in electronic code book
|
||||
// mode, using the given Block.
|
||||
func newECBEncrypter(b cipher.Block) cipher.BlockMode {
|
||||
return (*ecbEncrypter)(newECB(b))
|
||||
}
|
||||
|
||||
func (x *ecbEncrypter) BlockSize() int { return x.blockSize }
|
||||
|
||||
func (x *ecbEncrypter) CryptBlocks(dst, src []byte) {
|
||||
if len(src)%x.blockSize != 0 {
|
||||
panic("crypto/cipher: input not full blocks")
|
||||
}
|
||||
if len(dst) < len(src) {
|
||||
panic("crypto/cipher: output smaller than input")
|
||||
}
|
||||
for len(src) > 0 {
|
||||
x.b.Encrypt(dst, src[:x.blockSize])
|
||||
src = src[x.blockSize:]
|
||||
dst = dst[x.blockSize:]
|
||||
}
|
||||
}
|
||||
|
||||
type ecbDecrypter ecb
|
||||
|
||||
// newECBDecrypter returns a BlockMode which decrypts in electronic code book
|
||||
// mode, using the given Block.
|
||||
func newECBDecrypter(b cipher.Block) cipher.BlockMode {
|
||||
return (*ecbDecrypter)(newECB(b))
|
||||
}
|
||||
|
||||
func (x *ecbDecrypter) BlockSize() int { return x.blockSize }
|
||||
|
||||
func (x *ecbDecrypter) CryptBlocks(dst, src []byte) {
|
||||
if len(src)%x.blockSize != 0 {
|
||||
panic("crypto/cipher: input not full blocks")
|
||||
}
|
||||
if len(dst) < len(src) {
|
||||
panic("crypto/cipher: output smaller than input")
|
||||
}
|
||||
for len(src) > 0 {
|
||||
x.b.Decrypt(dst, src[:x.blockSize])
|
||||
src = src[x.blockSize:]
|
||||
dst = dst[x.blockSize:]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package aes
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-pay/gopay/pkg/util"
|
||||
)
|
||||
|
||||
// AES-GCM 加密数据
|
||||
func GCMEncrypt(originText, additional, key []byte) (nonce []byte, cipherText []byte, err error) {
|
||||
return gcmEncrypt(originText, additional, key)
|
||||
}
|
||||
|
||||
// AES-GCM 解密数据
|
||||
func GCMDecrypt(cipherText, nonce, additional, key []byte) ([]byte, error) {
|
||||
return gcmDecrypt(cipherText, nonce, additional, key)
|
||||
}
|
||||
|
||||
func gcmDecrypt(secretData, nonce, additional, key []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cipher.NewGCM(),error:%w", err)
|
||||
}
|
||||
originByte, err := gcm.Open(nil, nonce, secretData, additional)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return originByte, nil
|
||||
}
|
||||
|
||||
func gcmEncrypt(originText, additional, key []byte) ([]byte, []byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
nonce := []byte(util.RandomString(12))
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("cipher.NewGCM(),error:%w", err)
|
||||
}
|
||||
cipherBytes := gcm.Seal(nil, nonce, originText, additional)
|
||||
return nonce, cipherBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package aes
|
||||
|
||||
import "bytes"
|
||||
|
||||
// 加密填充模式(添加补全码) PKCS5Padding
|
||||
// 加密时,如果加密bytes的length不是blockSize的整数倍,需要在最后面添加填充byte
|
||||
func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
|
||||
paddingCount := blockSize - len(ciphertext)%blockSize //需要padding的数目
|
||||
paddingBytes := []byte{byte(paddingCount)}
|
||||
padtext := bytes.Repeat(paddingBytes, paddingCount) //生成填充的文本
|
||||
return append(ciphertext, padtext...)
|
||||
}
|
||||
|
||||
// 解密填充模式(去除补全码) PKCS5UnPadding
|
||||
// 解密时,需要在最后面去掉加密时添加的填充byte
|
||||
func PKCS5UnPadding(origData []byte) []byte {
|
||||
length := len(origData)
|
||||
unpadding := int(origData[length-1]) //找到Byte数组最后的填充byte
|
||||
return origData[:(length - unpadding)] //只截取返回有效数字内的byte数组
|
||||
}
|
||||
|
||||
// 加密填充模式(添加补全码) PKCS5Padding
|
||||
// 加密时,如果加密bytes的length不是blockSize的整数倍,需要在最后面添加填充byte
|
||||
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
|
||||
paddingCount := blockSize - len(ciphertext)%blockSize //需要padding的数目
|
||||
paddingBytes := []byte{byte(paddingCount)}
|
||||
padtext := bytes.Repeat(paddingBytes, paddingCount) //生成填充的文本
|
||||
return append(ciphertext, padtext...)
|
||||
}
|
||||
|
||||
// 解密填充模式(去除补全码) PKCS7UnPadding
|
||||
// 解密时,需要在最后面去掉加密时添加的填充byte
|
||||
func PKCS7UnPadding(origData []byte) (bs []byte) {
|
||||
length := len(origData)
|
||||
unPaddingNumber := int(origData[length-1]) // 找到Byte数组最后的填充byte 数字
|
||||
if unPaddingNumber <= 16 {
|
||||
bs = origData[:(length - unPaddingNumber)] // 只截取返回有效数字内的byte数组
|
||||
} else {
|
||||
bs = origData
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package errgroup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A Group is a collection of goroutines working on subtasks that are part of
|
||||
// the same overall task.
|
||||
//
|
||||
// A zero Group is valid and does not cancel on error.
|
||||
type Group struct {
|
||||
err error
|
||||
wg sync.WaitGroup
|
||||
errOnce sync.Once
|
||||
|
||||
workerOnce sync.Once
|
||||
ch chan func(ctx context.Context) error
|
||||
chs []func(ctx context.Context) error
|
||||
workerNum int
|
||||
|
||||
ctx context.Context
|
||||
cancel func()
|
||||
}
|
||||
|
||||
// WithContext create a Group.
|
||||
// given function from Go will receive this context,
|
||||
func WithContext(ctx context.Context) *Group {
|
||||
return &Group{ctx: ctx}
|
||||
}
|
||||
|
||||
// WithCancel create a new Group and an associated Context derived from ctx.
|
||||
//
|
||||
// given function from Go will receive context derived from this ctx,
|
||||
// The derived Context is canceled the first time a function passed to Go
|
||||
// returns a non-nil error or the first time Wait returns, whichever occurs
|
||||
// first.
|
||||
func WithCancel(ctx context.Context) *Group {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
return &Group{ctx: ctx, cancel: cancel}
|
||||
}
|
||||
|
||||
func (g *Group) do(f func(ctx context.Context) error) {
|
||||
ctx := g.ctx
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
var err error
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
buf := make([]byte, 64<<10)
|
||||
buf = buf[:runtime.Stack(buf, false)]
|
||||
err = fmt.Errorf("errgroup: panic recovered: %s\n%s", r, buf)
|
||||
}
|
||||
if err != nil {
|
||||
g.errOnce.Do(func() {
|
||||
g.err = err
|
||||
if g.cancel != nil {
|
||||
g.cancel()
|
||||
}
|
||||
})
|
||||
}
|
||||
g.wg.Done()
|
||||
}()
|
||||
err = f(ctx)
|
||||
}
|
||||
|
||||
// GOMAXPROCS set max goroutine to work.
|
||||
func (g *Group) GOMAXPROCS(n int) {
|
||||
if n <= 0 {
|
||||
panic("errgroup: GOMAXPROCS must great than 0")
|
||||
}
|
||||
g.workerOnce.Do(func() {
|
||||
g.ch = make(chan func(context.Context) error, n)
|
||||
for i := 0; i < n; i++ {
|
||||
go func() {
|
||||
for f := range g.ch {
|
||||
g.do(f)
|
||||
}
|
||||
}()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Go calls the given function in a new goroutine.
|
||||
//
|
||||
// The first call to return a non-nil error cancels the group; its error will be
|
||||
// returned by Wait.
|
||||
func (g *Group) Go(f func(ctx context.Context) error) {
|
||||
g.wg.Add(1)
|
||||
g.workerNum++
|
||||
if g.ch != nil {
|
||||
select {
|
||||
case g.ch <- f:
|
||||
default:
|
||||
g.chs = append(g.chs, f)
|
||||
}
|
||||
return
|
||||
}
|
||||
go g.do(f)
|
||||
}
|
||||
|
||||
// Wait blocks until all function calls from the Go method have returned, then
|
||||
// returns the first non-nil error (if any) from them.
|
||||
func (g *Group) Wait() error {
|
||||
if g.ch != nil {
|
||||
for _, f := range g.chs {
|
||||
g.ch <- f
|
||||
}
|
||||
}
|
||||
g.wg.Wait()
|
||||
if g.ch != nil {
|
||||
close(g.ch) // let all receiver exit
|
||||
}
|
||||
if g.cancel != nil {
|
||||
g.cancel()
|
||||
}
|
||||
g.workerNum = 0
|
||||
return g.err
|
||||
}
|
||||
|
||||
func (g *Group) WorkNum() int {
|
||||
return g.workerNum
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package util
|
||||
|
||||
const (
|
||||
TimeLayout = "2006-01-02 15:04:05"
|
||||
TimeLayout_2 = "20060102150405"
|
||||
DateLayout = "2006-01-02"
|
||||
NULL = ""
|
||||
)
|
||||
|
||||
type File struct {
|
||||
Name string `json:"name"`
|
||||
Content []byte `json:"content"`
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// 字符串转Int
|
||||
// intStr:数字的字符串
|
||||
func String2Int(intStr string) (intNum int) {
|
||||
intNum, _ = strconv.Atoi(intStr)
|
||||
return
|
||||
}
|
||||
|
||||
// 字符串转Int64
|
||||
// intStr:数字的字符串
|
||||
func String2Int64(intStr string) (int64Num int64) {
|
||||
intNum, _ := strconv.Atoi(intStr)
|
||||
int64Num = int64(intNum)
|
||||
return
|
||||
}
|
||||
|
||||
// 字符串转Float64
|
||||
// floatStr:小数点数字的字符串
|
||||
func String2Float64(floatStr string) (floatNum float64) {
|
||||
floatNum, _ = strconv.ParseFloat(floatStr, 64)
|
||||
return
|
||||
}
|
||||
|
||||
// 字符串转Float32
|
||||
// floatStr:小数点数字的字符串
|
||||
func String2Float32(floatStr string) (floatNum float32) {
|
||||
floatNum64, _ := strconv.ParseFloat(floatStr, 32)
|
||||
floatNum = float32(floatNum64)
|
||||
return
|
||||
}
|
||||
|
||||
// Int转字符串
|
||||
// intNum:数字字符串
|
||||
func Int2String(intNum int) (intStr string) {
|
||||
intStr = strconv.Itoa(intNum)
|
||||
return
|
||||
}
|
||||
|
||||
// Int64转字符串
|
||||
// intNum:数字字符串
|
||||
func Int642String(intNum int64) (int64Str string) {
|
||||
//10, 代表10进制
|
||||
int64Str = strconv.FormatInt(intNum, 10)
|
||||
return
|
||||
}
|
||||
|
||||
// Float64转字符串
|
||||
// floatNum:float64数字
|
||||
// prec:精度位数(不传则默认float数字精度)
|
||||
func Float64ToString(floatNum float64, prec ...int) (floatStr string) {
|
||||
if len(prec) > 0 {
|
||||
floatStr = strconv.FormatFloat(floatNum, 'f', prec[0], 64)
|
||||
return
|
||||
}
|
||||
floatStr = strconv.FormatFloat(floatNum, 'f', -1, 64)
|
||||
return
|
||||
}
|
||||
|
||||
// Float32转字符串
|
||||
// floatNum:float32数字
|
||||
// prec:精度位数(不传则默认float数字精度)
|
||||
func Float32ToString(floatNum float32, prec ...int) (floatStr string) {
|
||||
if len(prec) > 0 {
|
||||
floatStr = strconv.FormatFloat(float64(floatNum), 'f', prec[0], 32)
|
||||
return
|
||||
}
|
||||
floatStr = strconv.FormatFloat(float64(floatNum), 'f', -1, 32)
|
||||
return
|
||||
}
|
||||
|
||||
// 二进制转10进制
|
||||
func BinaryToDecimal(bit string) (num int) {
|
||||
fields := strings.Split(bit, "")
|
||||
lens := len(fields)
|
||||
var tempF float64 = 0
|
||||
for i := 0; i < lens; i++ {
|
||||
floatNum := String2Float64(fields[i])
|
||||
tempF += floatNum * math.Pow(2, float64(lens-i-1))
|
||||
}
|
||||
num = int(tempF)
|
||||
return
|
||||
}
|
||||
|
||||
// BytesToString 0 拷贝转换 slice byte 为 string
|
||||
func BytesToString(b []byte) (s string) {
|
||||
_bptr := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||||
_sptr := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
||||
_sptr.Data = _bptr.Data
|
||||
_sptr.Len = _bptr.Len
|
||||
return s
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
//随机生成字符串
|
||||
func RandomString(l int) string {
|
||||
str := "0123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz"
|
||||
bytes := []byte(str)
|
||||
var result []byte = make([]byte, 0, l)
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
for i := 0; i < l; i++ {
|
||||
result = append(result, bytes[r.Intn(len(bytes))])
|
||||
}
|
||||
return BytesToString(result)
|
||||
}
|
||||
|
||||
//随机生成纯字符串
|
||||
func RandomPureString(l int) string {
|
||||
str := "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz"
|
||||
bytes := []byte(str)
|
||||
var result []byte = make([]byte, 0, l)
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
for i := 0; i < l; i++ {
|
||||
result = append(result, bytes[r.Intn(len(bytes))])
|
||||
}
|
||||
return BytesToString(result)
|
||||
}
|
||||
|
||||
//随机生成数字字符串
|
||||
func RandomNumber(l int) string {
|
||||
str := "0123456789"
|
||||
bytes := []byte(str)
|
||||
var result []byte
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
for i := 0; i < l; i++ {
|
||||
result = append(result, bytes[r.Intn(len(bytes))])
|
||||
}
|
||||
return BytesToString(result)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package util
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
func ConvertToString(v interface{}) (str string) {
|
||||
if v == nil {
|
||||
return NULL
|
||||
}
|
||||
var (
|
||||
bs []byte
|
||||
err error
|
||||
)
|
||||
if bs, err = json.Marshal(v); err != nil {
|
||||
return NULL
|
||||
}
|
||||
str = string(bs)
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
package xhttp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/pkg/util"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
HttpClient *http.Client
|
||||
Transport *http.Transport
|
||||
Header http.Header
|
||||
Timeout time.Duration
|
||||
url string
|
||||
Host string
|
||||
method string
|
||||
requestType RequestType
|
||||
FormString string
|
||||
ContentType string
|
||||
unmarshalType string
|
||||
multipartBodyMap map[string]interface{}
|
||||
jsonByte []byte
|
||||
err error
|
||||
}
|
||||
|
||||
// NewClient , default tls.Config{InsecureSkipVerify: true}
|
||||
func NewClient() (client *Client) {
|
||||
client = &Client{
|
||||
HttpClient: &http.Client{
|
||||
Timeout: 60 * time.Second,
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
DisableKeepAlives: true,
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
},
|
||||
},
|
||||
Transport: nil,
|
||||
Header: make(http.Header),
|
||||
requestType: TypeJSON,
|
||||
unmarshalType: string(TypeJSON),
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
func (c *Client) SetTransport(transport *http.Transport) (client *Client) {
|
||||
c.Transport = transport
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) SetTLSConfig(tlsCfg *tls.Config) (client *Client) {
|
||||
c.Transport = &http.Transport{TLSClientConfig: tlsCfg, DisableKeepAlives: true, Proxy: http.ProxyFromEnvironment}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) SetTimeout(timeout time.Duration) (client *Client) {
|
||||
c.Timeout = timeout
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) SetHost(host string) (client *Client) {
|
||||
c.Host = host
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) Type(typeStr RequestType) (client *Client) {
|
||||
if _, ok := types[typeStr]; ok {
|
||||
c.requestType = typeStr
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) Get(url string) (client *Client) {
|
||||
c.method = GET
|
||||
c.url = url
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) Post(url string) (client *Client) {
|
||||
c.method = POST
|
||||
c.url = url
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) Put(url string) (client *Client) {
|
||||
c.method = PUT
|
||||
c.url = url
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) Delete(url string) (client *Client) {
|
||||
c.method = DELETE
|
||||
c.url = url
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) Patch(url string) (client *Client) {
|
||||
c.method = PATCH
|
||||
c.url = url
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) SendStruct(v interface{}) (client *Client) {
|
||||
if v == nil {
|
||||
return c
|
||||
}
|
||||
bs, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
c.err = fmt.Errorf("[%w]: %v, value: %v", gopay.MarshalErr, err, v)
|
||||
return c
|
||||
}
|
||||
switch c.requestType {
|
||||
case TypeJSON:
|
||||
c.jsonByte = bs
|
||||
case TypeXML, TypeUrlencoded, TypeForm, TypeFormData:
|
||||
body := make(map[string]interface{})
|
||||
if err = json.Unmarshal(bs, &body); err != nil {
|
||||
c.err = fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
return c
|
||||
}
|
||||
c.FormString = FormatURLParam(body)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) SendBodyMap(bm map[string]interface{}) (client *Client) {
|
||||
if bm == nil {
|
||||
return c
|
||||
}
|
||||
switch c.requestType {
|
||||
case TypeJSON:
|
||||
bs, err := json.Marshal(bm)
|
||||
if err != nil {
|
||||
c.err = fmt.Errorf("[%w]: %v, value: %v", gopay.MarshalErr, err, bm)
|
||||
return c
|
||||
}
|
||||
c.jsonByte = bs
|
||||
case TypeXML, TypeUrlencoded, TypeForm, TypeFormData:
|
||||
c.FormString = FormatURLParam(bm)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) SendMultipartBodyMap(bm map[string]interface{}) (client *Client) {
|
||||
if bm == nil {
|
||||
return c
|
||||
}
|
||||
switch c.requestType {
|
||||
case TypeJSON:
|
||||
bs, err := json.Marshal(bm)
|
||||
if err != nil {
|
||||
c.err = fmt.Errorf("[%w]: %v, value: %v", gopay.MarshalErr, err, bm)
|
||||
return c
|
||||
}
|
||||
c.jsonByte = bs
|
||||
case TypeXML, TypeUrlencoded, TypeForm, TypeFormData:
|
||||
c.FormString = FormatURLParam(bm)
|
||||
case TypeMultipartFormData:
|
||||
c.multipartBodyMap = bm
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// encodeStr: url.Values.Encode() or jsonBody
|
||||
func (c *Client) SendString(encodeStr string) (client *Client) {
|
||||
switch c.requestType {
|
||||
case TypeJSON:
|
||||
c.jsonByte = []byte(encodeStr)
|
||||
case TypeXML, TypeUrlencoded, TypeForm, TypeFormData:
|
||||
c.FormString = encodeStr
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) EndStruct(ctx context.Context, v interface{}) (res *http.Response, err error) {
|
||||
res, bs, err := c.EndBytes(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return res, fmt.Errorf("StatusCode(%d) != 200", res.StatusCode)
|
||||
}
|
||||
|
||||
switch c.unmarshalType {
|
||||
case string(TypeJSON):
|
||||
err = json.Unmarshal(bs, &v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
return res, nil
|
||||
case string(TypeXML):
|
||||
err = xml.Unmarshal(bs, &v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
return res, nil
|
||||
default:
|
||||
return nil, errors.New("unmarshalType Type Wrong")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) EndBytes(ctx context.Context) (res *http.Response, bs []byte, err error) {
|
||||
if c.err != nil {
|
||||
return nil, nil, c.err
|
||||
}
|
||||
var (
|
||||
body io.Reader
|
||||
bw *multipart.Writer
|
||||
)
|
||||
// multipart-form-data
|
||||
if c.requestType == TypeMultipartFormData {
|
||||
body = &bytes.Buffer{}
|
||||
bw = multipart.NewWriter(body.(io.Writer))
|
||||
}
|
||||
|
||||
reqFunc := func() (err error) {
|
||||
switch c.method {
|
||||
case GET:
|
||||
switch c.requestType {
|
||||
case TypeJSON:
|
||||
c.ContentType = types[TypeJSON]
|
||||
case TypeForm, TypeFormData, TypeUrlencoded:
|
||||
c.ContentType = types[TypeForm]
|
||||
case TypeMultipartFormData:
|
||||
c.ContentType = bw.FormDataContentType()
|
||||
case TypeXML:
|
||||
c.ContentType = types[TypeXML]
|
||||
c.unmarshalType = string(TypeXML)
|
||||
default:
|
||||
return errors.New("Request type Error ")
|
||||
}
|
||||
case POST, PUT, DELETE, PATCH:
|
||||
switch c.requestType {
|
||||
case TypeJSON:
|
||||
if c.jsonByte != nil {
|
||||
body = strings.NewReader(string(c.jsonByte))
|
||||
}
|
||||
c.ContentType = types[TypeJSON]
|
||||
case TypeForm, TypeFormData, TypeUrlencoded:
|
||||
body = strings.NewReader(c.FormString)
|
||||
c.ContentType = types[TypeForm]
|
||||
case TypeMultipartFormData:
|
||||
for k, v := range c.multipartBodyMap {
|
||||
// file 参数
|
||||
if file, ok := v.(*util.File); ok {
|
||||
fw, err := bw.CreateFormFile(k, file.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = fw.Write(file.Content)
|
||||
continue
|
||||
}
|
||||
// text 参数
|
||||
vs, ok2 := v.(string)
|
||||
if ok2 {
|
||||
_ = bw.WriteField(k, vs)
|
||||
} else if ss := util.ConvertToString(v); ss != "" {
|
||||
_ = bw.WriteField(k, ss)
|
||||
}
|
||||
}
|
||||
_ = bw.Close()
|
||||
c.ContentType = bw.FormDataContentType()
|
||||
case TypeXML:
|
||||
body = strings.NewReader(c.FormString)
|
||||
c.ContentType = types[TypeXML]
|
||||
c.unmarshalType = string(TypeXML)
|
||||
default:
|
||||
return errors.New("Request type Error ")
|
||||
}
|
||||
default:
|
||||
return errors.New("Only support GET and POST and PUT and DELETE ")
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, c.method, c.url, body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header = c.Header
|
||||
req.Header.Set("Content-Type", c.ContentType)
|
||||
if c.Transport != nil {
|
||||
c.HttpClient.Transport = c.Transport
|
||||
}
|
||||
if c.Host != "" {
|
||||
req.Host = c.Host
|
||||
}
|
||||
if c.Timeout > 0 {
|
||||
c.HttpClient.Timeout = c.Timeout
|
||||
}
|
||||
res, err = c.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
bs, err = ioutil.ReadAll(io.LimitReader(res.Body, int64(5<<20))) // default 5MB change the size you want
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = reqFunc(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return res, bs, nil
|
||||
}
|
||||
|
||||
func FormatURLParam(body map[string]interface{}) (urlParam string) {
|
||||
var (
|
||||
buf strings.Builder
|
||||
keys []string
|
||||
)
|
||||
for k := range body {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
v, ok := body[k].(string)
|
||||
if !ok {
|
||||
v = convertToString(body[k])
|
||||
}
|
||||
if v != "" {
|
||||
buf.WriteString(url.QueryEscape(k))
|
||||
buf.WriteByte('=')
|
||||
buf.WriteString(url.QueryEscape(v))
|
||||
buf.WriteByte('&')
|
||||
}
|
||||
}
|
||||
if buf.Len() <= 0 {
|
||||
return ""
|
||||
}
|
||||
return buf.String()[:buf.Len()-1]
|
||||
}
|
||||
|
||||
func convertToString(v interface{}) (str string) {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
var (
|
||||
bs []byte
|
||||
err error
|
||||
)
|
||||
if bs, err = json.Marshal(v); err != nil {
|
||||
return ""
|
||||
}
|
||||
str = string(bs)
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package xhttp
|
||||
|
||||
type RequestType string
|
||||
|
||||
const (
|
||||
GET = "GET"
|
||||
POST = "POST"
|
||||
PUT = "PUT"
|
||||
DELETE = "DELETE"
|
||||
PATCH = "PATCH"
|
||||
TypeJSON RequestType = "json"
|
||||
TypeXML RequestType = "xml"
|
||||
TypeUrlencoded RequestType = "urlencoded"
|
||||
TypeForm RequestType = "form"
|
||||
TypeFormData RequestType = "form-data"
|
||||
TypeMultipartFormData RequestType = "multipart-form-data"
|
||||
)
|
||||
|
||||
var types = map[RequestType]string{
|
||||
TypeJSON: "application/json",
|
||||
TypeXML: "application/xml",
|
||||
TypeUrlencoded: "application/x-www-form-urlencoded",
|
||||
TypeForm: "application/x-www-form-urlencoded",
|
||||
TypeFormData: "application/x-www-form-urlencoded",
|
||||
TypeMultipartFormData: "multipart/form-data",
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package xlog
|
||||
|
||||
type ColorType string
|
||||
|
||||
var (
|
||||
Reset = ColorType([]byte{27, 91, 48, 109})
|
||||
// 标准
|
||||
White = ColorType([]byte{27, 91, 51, 48, 109}) // 白色
|
||||
Red = ColorType([]byte{27, 91, 51, 49, 109}) // 红色
|
||||
Green = ColorType([]byte{27, 91, 51, 50, 109}) // 绿色
|
||||
Yellow = ColorType([]byte{27, 91, 51, 51, 109}) // 黄色
|
||||
Blue = ColorType([]byte{27, 91, 51, 52, 109}) // 蓝色
|
||||
Magenta = ColorType([]byte{27, 91, 51, 53, 109}) // 紫色
|
||||
Cyan = ColorType([]byte{27, 91, 51, 54, 109}) // 青色
|
||||
// 高亮
|
||||
WhiteBright = ColorType([]byte{27, 91, 49, 59, 51, 48, 109})
|
||||
RedBright = ColorType([]byte{27, 91, 49, 59, 51, 49, 109})
|
||||
GreenBright = ColorType([]byte{27, 91, 49, 59, 51, 50, 109})
|
||||
YellowBright = ColorType([]byte{27, 91, 49, 59, 51, 51, 109})
|
||||
BlueBright = ColorType([]byte{27, 91, 49, 59, 51, 52, 109})
|
||||
MagentaBright = ColorType([]byte{27, 91, 49, 59, 51, 53, 109})
|
||||
CyanBright = ColorType([]byte{27, 91, 49, 59, 51, 54, 109})
|
||||
// 斜体
|
||||
WhiteBevel = ColorType([]byte{27, 91, 51, 59, 51, 48, 109})
|
||||
RedBevel = ColorType([]byte{27, 91, 51, 59, 51, 49, 109})
|
||||
GreenBevel = ColorType([]byte{27, 91, 51, 59, 51, 50, 109})
|
||||
YellowBevel = ColorType([]byte{27, 91, 51, 59, 51, 51, 109})
|
||||
BlueBevel = ColorType([]byte{27, 91, 51, 59, 51, 52, 109})
|
||||
MagentaBevel = ColorType([]byte{27, 91, 51, 59, 51, 53, 109})
|
||||
CyanBevel = ColorType([]byte{27, 91, 51, 59, 51, 54, 109})
|
||||
// 下划线
|
||||
WhiteUnderLine = ColorType([]byte{27, 91, 52, 59, 51, 48, 109})
|
||||
RedUnderLine = ColorType([]byte{27, 91, 52, 59, 51, 49, 109})
|
||||
GreenUnderLine = ColorType([]byte{27, 91, 52, 59, 51, 50, 109})
|
||||
YellowUnderLine = ColorType([]byte{27, 91, 52, 59, 51, 51, 109})
|
||||
BlueUnderLine = ColorType([]byte{27, 91, 52, 59, 51, 52, 109})
|
||||
MagentaUnderLine = ColorType([]byte{27, 91, 52, 59, 51, 53, 109})
|
||||
CyanUnderLine = ColorType([]byte{27, 91, 52, 59, 51, 54, 109})
|
||||
// 背景色
|
||||
WhiteBg = ColorType([]byte{27, 91, 55, 59, 51, 48, 109})
|
||||
RedBg = ColorType([]byte{27, 91, 55, 59, 51, 49, 109})
|
||||
GreenBg = ColorType([]byte{27, 91, 55, 59, 51, 50, 109})
|
||||
YellowBg = ColorType([]byte{27, 91, 55, 59, 51, 51, 109})
|
||||
BlueBg = ColorType([]byte{27, 91, 55, 59, 51, 52, 109})
|
||||
MagentaBg = ColorType([]byte{27, 91, 55, 59, 51, 53, 109})
|
||||
CyanBg = ColorType([]byte{27, 91, 55, 59, 51, 54, 109})
|
||||
// 删除线
|
||||
WhiteDelLine = ColorType([]byte{27, 91, 57, 59, 51, 48, 109})
|
||||
RedDelLine = ColorType([]byte{27, 91, 57, 59, 51, 49, 109})
|
||||
GreenDelLine = ColorType([]byte{27, 91, 57, 59, 51, 50, 109})
|
||||
YellowDelLine = ColorType([]byte{27, 91, 57, 59, 51, 51, 109})
|
||||
BlueDelLine = ColorType([]byte{27, 91, 57, 59, 51, 52, 109})
|
||||
MagentaDelLine = ColorType([]byte{27, 91, 57, 59, 51, 53, 109})
|
||||
CyanDelLine = ColorType([]byte{27, 91, 57, 59, 51, 54, 109})
|
||||
)
|
||||
|
||||
var cl *ColorLogger
|
||||
|
||||
type ColorLogger struct {
|
||||
Color ColorType
|
||||
i *InfoLogger
|
||||
d *DebugLogger
|
||||
w *WarnLogger
|
||||
e *ErrorLogger
|
||||
}
|
||||
|
||||
func Color(color ColorType) *ColorLogger {
|
||||
if cl == nil {
|
||||
cl = &ColorLogger{
|
||||
Color: color,
|
||||
i: &InfoLogger{},
|
||||
d: &DebugLogger{},
|
||||
w: &WarnLogger{},
|
||||
e: &ErrorLogger{},
|
||||
}
|
||||
return cl
|
||||
}
|
||||
cl.Color = color
|
||||
return cl
|
||||
}
|
||||
|
||||
func (l *ColorLogger) Info(args ...interface{}) {
|
||||
l.i.LogOut(&l.Color, nil, args...)
|
||||
}
|
||||
|
||||
func (l *ColorLogger) Infof(format string, args ...interface{}) {
|
||||
l.i.LogOut(&l.Color, &format, args...)
|
||||
}
|
||||
|
||||
func (l *ColorLogger) Debug(args ...interface{}) {
|
||||
l.d.LogOut(&l.Color, nil, args...)
|
||||
}
|
||||
|
||||
func (l *ColorLogger) Debugf(format string, args ...interface{}) {
|
||||
l.d.LogOut(&l.Color, &format, args...)
|
||||
}
|
||||
|
||||
func (l *ColorLogger) Warn(args ...interface{}) {
|
||||
l.w.LogOut(&l.Color, nil, args...)
|
||||
}
|
||||
|
||||
func (l *ColorLogger) Warnf(format string, args ...interface{}) {
|
||||
l.w.LogOut(&l.Color, &format, args...)
|
||||
}
|
||||
|
||||
func (l *ColorLogger) Error(args ...interface{}) {
|
||||
l.e.LogOut(&l.Color, nil, args...)
|
||||
}
|
||||
|
||||
func (l *ColorLogger) Errorf(format string, args ...interface{}) {
|
||||
l.e.LogOut(&l.Color, &format, args...)
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package xlog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type DebugLogger struct {
|
||||
logger *log.Logger
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func (i *DebugLogger) LogOut(col *ColorType, format *string, v ...interface{}) {
|
||||
i.once.Do(func() {
|
||||
i.init()
|
||||
})
|
||||
if Level >= DebugLevel {
|
||||
if col != nil {
|
||||
if format != nil {
|
||||
i.logger.Output(3, string(*col)+fmt.Sprintf(*format, v...)+string(Reset))
|
||||
return
|
||||
}
|
||||
i.logger.Output(3, string(*col)+fmt.Sprintln(v...)+string(Reset))
|
||||
return
|
||||
}
|
||||
if format != nil {
|
||||
i.logger.Output(3, fmt.Sprintf(*format, v...))
|
||||
return
|
||||
}
|
||||
i.logger.Output(3, fmt.Sprintln(v...))
|
||||
}
|
||||
}
|
||||
|
||||
func (i *DebugLogger) init() {
|
||||
if Level == 0 {
|
||||
Level = DebugLevel
|
||||
}
|
||||
i.logger = log.New(os.Stdout, "[DEBUG] >> ", log.Lmsgprefix|log.Lshortfile|log.Ldate|log.Lmicroseconds)
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package xlog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type ErrorLogger struct {
|
||||
logger *log.Logger
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func (e *ErrorLogger) LogOut(col *ColorType, format *string, v ...interface{}) {
|
||||
e.once.Do(func() {
|
||||
e.init()
|
||||
})
|
||||
if Level >= ErrorLevel {
|
||||
if col != nil {
|
||||
if format != nil {
|
||||
e.logger.Output(3, string(*col)+fmt.Sprintf(*format, v...)+string(Reset))
|
||||
return
|
||||
}
|
||||
e.logger.Output(3, string(*col)+fmt.Sprintln(v...)+string(Reset))
|
||||
return
|
||||
}
|
||||
if format != nil {
|
||||
e.logger.Output(3, fmt.Sprintf(*format, v...))
|
||||
return
|
||||
}
|
||||
e.logger.Output(3, fmt.Sprintln(v...))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *ErrorLogger) init() {
|
||||
if Level == 0 {
|
||||
Level = DebugLevel
|
||||
}
|
||||
e.logger = log.New(os.Stdout, "[ERROR] >> ", log.Lmsgprefix|log.Lshortfile|log.Ldate|log.Lmicroseconds)
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package xlog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type InfoLogger struct {
|
||||
logger *log.Logger
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func (i *InfoLogger) LogOut(col *ColorType, format *string, v ...interface{}) {
|
||||
i.once.Do(func() {
|
||||
i.init()
|
||||
})
|
||||
if Level >= InfoLevel {
|
||||
if col != nil {
|
||||
if format != nil {
|
||||
i.logger.Output(3, string(*col)+fmt.Sprintf(*format, v...)+string(Reset))
|
||||
return
|
||||
}
|
||||
i.logger.Output(3, string(*col)+fmt.Sprintln(v...)+string(Reset))
|
||||
return
|
||||
}
|
||||
if format != nil {
|
||||
i.logger.Output(3, fmt.Sprintf(*format, v...))
|
||||
return
|
||||
}
|
||||
i.logger.Output(3, fmt.Sprintln(v...))
|
||||
}
|
||||
}
|
||||
|
||||
func (i *InfoLogger) init() {
|
||||
if Level == 0 {
|
||||
Level = DebugLevel
|
||||
}
|
||||
i.logger = log.New(os.Stdout, "[INFO] >> ", log.Lmsgprefix|log.Lshortfile|log.Ldate|log.Lmicroseconds)
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package xlog
|
||||
|
||||
const (
|
||||
ErrorLevel LogLevel = iota + 1
|
||||
WarnLevel
|
||||
InfoLevel
|
||||
DebugLevel
|
||||
)
|
||||
|
||||
type LogLevel int
|
||||
|
||||
var (
|
||||
debugLog XLogger = &DebugLogger{}
|
||||
infoLog XLogger = &InfoLogger{}
|
||||
warnLog XLogger = &WarnLogger{}
|
||||
errLog XLogger = &ErrorLogger{}
|
||||
|
||||
Level LogLevel
|
||||
)
|
||||
|
||||
type XLogger interface {
|
||||
LogOut(col *ColorType, format *string, args ...interface{})
|
||||
}
|
||||
|
||||
func Info(args ...interface{}) {
|
||||
infoLog.LogOut(nil, nil, args...)
|
||||
}
|
||||
|
||||
func Infof(format string, args ...interface{}) {
|
||||
infoLog.LogOut(nil, &format, args...)
|
||||
}
|
||||
|
||||
func Debug(args ...interface{}) {
|
||||
debugLog.LogOut(nil, nil, args...)
|
||||
}
|
||||
|
||||
func Debugf(format string, args ...interface{}) {
|
||||
debugLog.LogOut(nil, &format, args...)
|
||||
}
|
||||
|
||||
func Warn(args ...interface{}) {
|
||||
warnLog.LogOut(nil, nil, args...)
|
||||
}
|
||||
|
||||
func Warnf(format string, args ...interface{}) {
|
||||
warnLog.LogOut(nil, &format, args...)
|
||||
}
|
||||
|
||||
func Error(args ...interface{}) {
|
||||
errLog.LogOut(nil, nil, args...)
|
||||
}
|
||||
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
errLog.LogOut(nil, &format, args...)
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package xlog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type WarnLogger struct {
|
||||
logger *log.Logger
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func (i *WarnLogger) LogOut(col *ColorType, format *string, v ...interface{}) {
|
||||
i.once.Do(func() {
|
||||
i.init()
|
||||
})
|
||||
if Level >= WarnLevel {
|
||||
if col != nil {
|
||||
if format != nil {
|
||||
i.logger.Output(3, string(*col)+fmt.Sprintf(*format, v...)+string(Reset))
|
||||
return
|
||||
}
|
||||
i.logger.Output(3, string(*col)+fmt.Sprintln(v...)+string(Reset))
|
||||
return
|
||||
}
|
||||
if format != nil {
|
||||
i.logger.Output(3, fmt.Sprintf(*format, v...))
|
||||
return
|
||||
}
|
||||
i.logger.Output(3, fmt.Sprintln(v...))
|
||||
}
|
||||
}
|
||||
|
||||
func (i *WarnLogger) init() {
|
||||
if Level == 0 {
|
||||
Level = DebugLevel
|
||||
}
|
||||
i.logger = log.New(os.Stdout, "[WARN] >> ", log.Lmsgprefix|log.Lshortfile|log.Ldate|log.Lmicroseconds)
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package xpem
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func DecodePublicKey(pemContent []byte) (publicKey *rsa.PublicKey, err error) {
|
||||
block, _ := pem.Decode(pemContent)
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("pem.Decode(%s):pemContent decode error", pemContent)
|
||||
}
|
||||
switch block.Type {
|
||||
case "CERTIFICATE":
|
||||
pubKeyCert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("x509.ParseCertificate(%s):%w", pemContent, err)
|
||||
}
|
||||
pubKey, ok := pubKeyCert.PublicKey.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("公钥证书提取公钥出错 [%s]", pemContent)
|
||||
}
|
||||
publicKey = pubKey
|
||||
case "PUBLIC KEY":
|
||||
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("x509.ParsePKIXPublicKey(%s),err:%w", pemContent, err)
|
||||
}
|
||||
pubKey, ok := pub.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("公钥解析出错 [%s]", pemContent)
|
||||
}
|
||||
publicKey = pubKey
|
||||
case "RSA PUBLIC KEY":
|
||||
pubKey, err := x509.ParsePKCS1PublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("x509.ParsePKCS1PublicKey(%s):%w", pemContent, err)
|
||||
}
|
||||
publicKey = pubKey
|
||||
}
|
||||
return publicKey, nil
|
||||
}
|
||||
|
||||
func DecodePrivateKey(pemContent []byte) (privateKey *rsa.PrivateKey, err error) {
|
||||
block, _ := pem.Decode(pemContent)
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("pem.Decode(%s):pemContent decode error", pemContent)
|
||||
}
|
||||
privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
pk8, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("私钥解析出错 [%s]", pemContent)
|
||||
}
|
||||
var ok bool
|
||||
privateKey, ok = pk8.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("私钥解析出错 [%s]", pemContent)
|
||||
}
|
||||
}
|
||||
return privateKey, nil
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package xtime
|
||||
|
||||
const (
|
||||
TimeLayout = "2006-01-02 15:04:05"
|
||||
TimeLayout_1 = "2006-01-02 15:04:05.000"
|
||||
TimeLayout_2 = "20060102150405"
|
||||
DateLayout = "2006-01-02"
|
||||
)
|
||||
@@ -0,0 +1,99 @@
|
||||
package xtime
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var daysBefore = [...]int32{
|
||||
0,
|
||||
31,
|
||||
31 + 28,
|
||||
31 + 28 + 31,
|
||||
31 + 28 + 31 + 30,
|
||||
31 + 28 + 31 + 30 + 31,
|
||||
31 + 28 + 31 + 30 + 31 + 30,
|
||||
31 + 28 + 31 + 30 + 31 + 30 + 31,
|
||||
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31,
|
||||
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30,
|
||||
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31,
|
||||
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,
|
||||
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31,
|
||||
}
|
||||
|
||||
func MonthDays(m time.Month, year int) int {
|
||||
if m == time.February && isLeap(year) {
|
||||
return 29
|
||||
}
|
||||
return int(daysBefore[m] - daysBefore[m-1])
|
||||
}
|
||||
|
||||
func isLeap(year int) bool {
|
||||
return year%4 == 0 && (year%100 != 0 || year%400 == 0)
|
||||
}
|
||||
|
||||
// 获取最近 7天 日期
|
||||
func GetRecentSevenDay() (sevenDays []string) {
|
||||
now := time.Now()
|
||||
nowDay := time.Date(now.Year(), now.Month(), now.Day()-7, 0, 0, 0, 0, time.Local)
|
||||
for i := 0; i < 7; i++ {
|
||||
date := nowDay.AddDate(0, 0, i)
|
||||
sevenDays = append(sevenDays, date.Format(DateLayout))
|
||||
}
|
||||
return sevenDays
|
||||
}
|
||||
|
||||
// 获取最近 30天 日期
|
||||
func GetRecentThirtyDay() (thirtyDays []string) {
|
||||
now := time.Now()
|
||||
nowDay := time.Date(now.Year(), now.Month(), now.Day()-30, 0, 0, 0, 0, time.Local)
|
||||
for i := 0; i < 30; i++ {
|
||||
date := nowDay.AddDate(0, 0, i)
|
||||
thirtyDays = append(thirtyDays, date.Format(DateLayout))
|
||||
}
|
||||
return thirtyDays
|
||||
}
|
||||
|
||||
// 获取本周 7天 日期
|
||||
func GetCurWeekDays() (curWeekDays []string) {
|
||||
now := time.Now()
|
||||
offset := int(time.Monday - now.Weekday())
|
||||
if offset > 0 {
|
||||
offset = -6
|
||||
}
|
||||
weekStartDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, offset)
|
||||
for i := 0; i < 7; i++ {
|
||||
date := weekStartDate.AddDate(0, 0, i)
|
||||
curWeekDays = append(curWeekDays, date.Format(DateLayout))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 获取本月 日期
|
||||
func GetCurMonthDays() (curMonthDays []string) {
|
||||
now := time.Now()
|
||||
year := now.Year()
|
||||
month := now.Month()
|
||||
days := MonthDays(month, year)
|
||||
|
||||
monthFirstDay := time.Date(year, month, 01, 0, 0, 0, 0, time.Local)
|
||||
for i := 0; i < days; i++ {
|
||||
date := monthFirstDay.AddDate(0, 0, i)
|
||||
curMonthDays = append(curMonthDays, date.Format(DateLayout))
|
||||
}
|
||||
return curMonthDays
|
||||
}
|
||||
|
||||
// 获取上一个月 日期
|
||||
func GetLastMonthDays() (monthDays []string) {
|
||||
now := time.Now()
|
||||
year := now.Year()
|
||||
month := now.Month()
|
||||
days := MonthDays(month-1, year)
|
||||
|
||||
monthFirstDay := time.Date(year, month-1, 01, 0, 0, 0, 0, time.Local)
|
||||
for i := 0; i < days; i++ {
|
||||
date := monthFirstDay.AddDate(0, 0, i)
|
||||
monthDays = append(monthDays, date.Format(DateLayout))
|
||||
}
|
||||
return monthDays
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package xtime
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-pay/gopay/pkg/util"
|
||||
)
|
||||
|
||||
//解析时间
|
||||
// 时间字符串格式:2006-01-02 15:04:05
|
||||
func ParseDateTime(timeStr string) (datetime time.Time) {
|
||||
datetime, _ = time.ParseInLocation(TimeLayout, timeStr, time.Local)
|
||||
return
|
||||
}
|
||||
|
||||
//解析日期
|
||||
// 日期字符串格式:2006-01-02
|
||||
func ParseDate(timeStr string) (date time.Time) {
|
||||
date, _ = time.ParseInLocation(DateLayout, timeStr, time.Local)
|
||||
return
|
||||
}
|
||||
|
||||
//格式化Datetime字符串
|
||||
// 格式化前输入样式:2019-01-04T15:40:00Z 或 2019-01-04T15:40:00+08:00
|
||||
// 格式化后返回样式:2019-01-04 15:40:00
|
||||
func FormatDateTime(timeStr string) (formatTime string) {
|
||||
if timeStr == "" {
|
||||
return ""
|
||||
}
|
||||
replace := strings.Replace(timeStr, "T", " ", 1)
|
||||
formatTime = replace[:19]
|
||||
return
|
||||
}
|
||||
|
||||
//格式化Date成字符串
|
||||
// 格式化前输入样式:2019-01-04T15:40:00Z 或 2019-01-04T15:40:00+08:00
|
||||
// 格式化后返回样式:2019-01-04
|
||||
func FormatDate(dateStr string) (formatDate string) {
|
||||
if dateStr == "" {
|
||||
return ""
|
||||
}
|
||||
split := strings.Split(dateStr, "T")
|
||||
formatDate = split[0]
|
||||
return
|
||||
}
|
||||
|
||||
func DurationToUnit(duration time.Duration) string {
|
||||
var (
|
||||
t string
|
||||
intNs = int64(duration)
|
||||
)
|
||||
if intNs >= 0 && intNs < int64(time.Second) {
|
||||
t = util.Int642String(intNs/int64(time.Millisecond)) + "ms"
|
||||
}
|
||||
|
||||
// 大于等于 1秒,小于 1分钟
|
||||
if intNs >= int64(time.Second) && intNs < int64(time.Minute) {
|
||||
s := intNs / int64(time.Second)
|
||||
ms := (intNs - s*int64(time.Second)) / int64(time.Millisecond)
|
||||
t = util.Int642String(s) + "s"
|
||||
if ms > 0 {
|
||||
t += util.Int642String(ms) + "ms"
|
||||
}
|
||||
}
|
||||
// 大于等于 1分钟,小于 1小时
|
||||
if intNs >= int64(time.Minute) && intNs < int64(time.Hour) {
|
||||
m := intNs / int64(time.Minute)
|
||||
s := (intNs - m*int64(time.Minute)) / int64(time.Second)
|
||||
t = util.Int642String(m) + "m"
|
||||
if s > 0 {
|
||||
t += util.Int642String(s) + "s"
|
||||
}
|
||||
}
|
||||
// 大于等于 1小时,小于 1天
|
||||
if intNs >= int64(time.Hour) && intNs < 24*int64(time.Hour) {
|
||||
h := intNs / int64(time.Hour)
|
||||
m := (intNs - h*int64(time.Hour)) / int64(time.Minute)
|
||||
s := (intNs - h*int64(time.Hour) - m*int64(time.Minute)) / int64(time.Second)
|
||||
t = util.Int642String(h) + "h"
|
||||
if m > 0 {
|
||||
t += util.Int642String(m) + "m"
|
||||
}
|
||||
if s > 0 {
|
||||
t += util.Int642String(s) + "s"
|
||||
}
|
||||
}
|
||||
// 大于等于 1天
|
||||
if intNs >= 24*int64(time.Hour) {
|
||||
d := intNs / (24 * int64(time.Hour))
|
||||
h := (intNs - d*24*int64(time.Hour)) / int64(time.Hour)
|
||||
m := (intNs - d*24*int64(time.Hour) - h*int64(time.Hour)) / int64(time.Minute)
|
||||
s := ((intNs - m*int64(time.Minute)) % int64(time.Minute)) / int64(time.Second)
|
||||
|
||||
t = util.Int642String(d) + "d"
|
||||
if h > 0 {
|
||||
t += util.Int642String(h) + "h"
|
||||
}
|
||||
if m > 0 {
|
||||
t += util.Int642String(m) + "m"
|
||||
}
|
||||
if s > 0 {
|
||||
t += util.Int642String(s) + "s"
|
||||
}
|
||||
}
|
||||
return t
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package xtime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql/driver"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Time be used to MySql timestamp converting.
|
||||
type Time int64
|
||||
|
||||
// Scan scan time.
|
||||
func (t *Time) Scan(src interface{}) (err error) {
|
||||
switch sc := src.(type) {
|
||||
case time.Time:
|
||||
if sc.IsZero() {
|
||||
return
|
||||
}
|
||||
*t = Time(sc.Unix())
|
||||
case string:
|
||||
var i int64
|
||||
i, err = strconv.ParseInt(sc, 10, 64)
|
||||
*t = Time(i)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Value get time value.
|
||||
func (t Time) Value() (driver.Value, error) {
|
||||
return time.Unix(int64(t), 0), nil
|
||||
}
|
||||
|
||||
// Time get time.
|
||||
func (t Time) Time() time.Time {
|
||||
return time.Unix(int64(t), 0)
|
||||
}
|
||||
|
||||
func (t *Time) FromDB(bs []byte) error {
|
||||
timeStr := string(bs)
|
||||
ti, err := time.ParseInLocation("2006-01-02T15:04:05", timeStr[:19], time.Local)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ti.IsZero() {
|
||||
return nil
|
||||
}
|
||||
*t = Time(ti.Unix())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t Time) ToDB() ([]byte, error) {
|
||||
unix := time.Unix(int64(t), 0)
|
||||
return []byte(unix.String()), nil
|
||||
}
|
||||
|
||||
// Duration be used json unmarshal string time, like 1s, 500ms.
|
||||
type Duration time.Duration
|
||||
|
||||
// UnmarshalText unmarshal text to duration.
|
||||
func (d *Duration) UnmarshalText(text []byte) error {
|
||||
tmp, err := time.ParseDuration(string(text))
|
||||
if err == nil {
|
||||
*d = Duration(tmp)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// UnitTime duration parse to unit, such as "300ms", "1h30m" or "2h10s".
|
||||
func (d *Duration) UnitTime() string {
|
||||
return DurationToUnit(time.Duration(*d))
|
||||
}
|
||||
|
||||
// Shrink will decrease the duration by comparing with context's timeout duration and return new timeout\context\CancelFunc.
|
||||
func (d Duration) Shrink(c context.Context) (Duration, context.Context, context.CancelFunc) {
|
||||
if deadline, ok := c.Deadline(); ok {
|
||||
if ctimeout := time.Until(deadline); ctimeout < time.Duration(d) {
|
||||
// deliver small timeout
|
||||
return Duration(ctimeout), c, func() {}
|
||||
}
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(c, time.Duration(d))
|
||||
return d, ctx, cancel
|
||||
}
|
||||
|
After Width: | Height: | Size: 37 KiB |
@@ -0,0 +1,670 @@
|
||||
版本号:Release 1.5.78
|
||||
修改记录:
|
||||
(1) 微信V3:V3EcommerceBalance() 缺失参数补充
|
||||
(2) 微信V3:新增 client.V3EcommerceDayBalance() 方法,电商平台预约提现
|
||||
(3) 微信V3:修复 银行列表获取相关接口,路由修正
|
||||
(4) 微信V3:修复 client.V3BankSearchBank() 接口,私钥解密出错的问题
|
||||
|
||||
版本号:Release 1.5.77
|
||||
修改记录:
|
||||
(1) 微信V3:V3EcommerceRefundQueryById()、V3EcommerceRefundQueryByNo(),缺失参数补充
|
||||
(2) 微信V3:新增 client.V3EcommerceWithdraw() 方法,电商平台预约提现
|
||||
(3) 微信V3:新增 client.V3EcommerceWithdrawStatus() 方法,电商平台查询预约提现状态
|
||||
|
||||
版本号:Release 1.5.76
|
||||
修改记录:
|
||||
(1) gopay:大量优化error处理和返回,统一部分通用错误到 error.go 中
|
||||
(2) 支付宝:新增 alipay.IsBizError(),判断并捕获业务错误
|
||||
(3) 微信、支付宝:优化部分 error 返回格式以及透传,优化参数校验返回
|
||||
|
||||
版本号:Release 1.5.75
|
||||
修改记录:
|
||||
(1) 微信V3:client.V3Apply4SubModifySettlement(),sub_mchid 问题处理
|
||||
(2) 微信V3:微信分账接收方,model参数补充添加 detail_id
|
||||
(3) PayPal:注释中接口文档地址更新
|
||||
(4) PayPal:新增 client.OrderConfirm(),订单确认
|
||||
(5) PayPal:OrderDetail、Capture、Payer、Name 等结构体,遗漏参数补充
|
||||
(6) gopay:pkg/xtime/parse_format.go,优化 DurationToUnit() 方法,int -> int64
|
||||
|
||||
版本号:Release 1.5.74
|
||||
修改记录:
|
||||
(1) gopay:一些小改动,util.GetRandomString() -> util.RandomString()
|
||||
(2) gopay:升级 xlog
|
||||
|
||||
版本号:Release 1.5.73
|
||||
修改记录:
|
||||
(1) Apple:新增内购支付通知V2解析
|
||||
|
||||
版本号:Release 1.5.72
|
||||
修改记录:
|
||||
(1) Apple:返回参数类型错误修复,pending_renewal_info -> 数组类型
|
||||
(2) QQ:获取 AccessToken 结果 expires_in 类型修复,expires_in -> 字符串类型
|
||||
(3) 微信V3:证书相关代码优化
|
||||
|
||||
版本号:Release 1.5.71
|
||||
修改记录:
|
||||
(1) 微信V2:去除所有微信小程序、公众号相关接口,请使用 wechat-sdk
|
||||
(2) 支付宝:client.UserCertdocCertverifyConsult() 方法,增加 authToken 参数
|
||||
(2) 微信V3:新增 银行组件(服务商) 相关接口,详情查看v3文档最下方的接口列表
|
||||
|
||||
版本号:Release 1.5.69
|
||||
修改记录:
|
||||
(1) 微信V3:修改 client.V3RefundQuery()、增加入参参数,适配 服务商 模式
|
||||
(2) 微信V3:修复 client.V3Apply4SubSubmit(),接口路由修复
|
||||
(3) gopay:BodyMap 新增 Unmarshal() 方法,解析数据到结构体、数组指针
|
||||
|
||||
版本号:Release 1.5.68
|
||||
修改记录:
|
||||
(1) 微信V3:修复 client.V3ComplaintResponse()、client.V3ComplaintComplete(), complaintId 参数类型错误问题
|
||||
(2) 微信V3:新增 电商收付通(分账)相关接口,详情查看v3文档最下方的接口列表
|
||||
(3) 微信V3:新增 电商收付通(补差)相关接口,详情查看v3文档最下方的接口列表
|
||||
(4) 微信V3:新增 电商收付通(退款)相关接口,详情查看v3文档最下方的接口列表
|
||||
(5) 微信V3:返回参数中字段,ID写法全部改写为Id写法
|
||||
|
||||
版本号:Release 1.5.67
|
||||
修改记录:
|
||||
(1) 微信V3:配合微信文档修改,拆分服务商 批量转账 相关接口,接口如下:
|
||||
(2) 微信V3:新增 client.V3PartnerTransfer()
|
||||
(3) 微信V3:新增 client.V3PartnerTransferQuery()
|
||||
(4) 微信V3:新增 client.V3PartnerTransferDetail()
|
||||
(5) 微信V3:新增 client.V3PartnerTransferMerchantQuery()
|
||||
(6) 微信V3:新增 client.V3PartnerTransferMerchantDetail()
|
||||
(7) 微信V3:新增 client.V3Withdraw()
|
||||
(8) 微信V3:新增 client.V3WithdrawStatus()
|
||||
(9) 微信V3:新增 client.V3WithdrawDownloadErrBill()
|
||||
(10) 微信V3:修改 V3TransferDetailQuery() => V3TransferDetail()
|
||||
(11) 微信V3:修改 V3TransferMerchantDetailQuery() => V3TransferMerchantDetail()
|
||||
|
||||
版本号:Release 1.5.66
|
||||
修改记录:
|
||||
(1) 微信V3:fix bug that `{"code":"PARAM_ERROR","message":"平台证书序列号Wechatpay-Serial错误"}`
|
||||
|
||||
版本号:Release 1.5.65
|
||||
修改记录:
|
||||
(1) 微信V3:新增 client.V3EcommerceApply(),二级商户进件
|
||||
(2) 微信V3:新增 client.V3EcommerceApplyStatus(),查询申请状态
|
||||
(3) 微信V3:新增 client.V3GoldPlanManage(),点金计划管理
|
||||
(4) 微信V3:新增 client.V3GoldPlanBillManage(),商家小票管理
|
||||
(5) 微信V3:新增 client.V3GoldPlanFilterManage(),同业过滤标签管理
|
||||
(6) 微信V3:新增 client.V3GoldPlanOpenAdShow(),开通广告展示
|
||||
(7) 微信V3:新增 client.V3GoldPlanCloseAdShow(),关闭广告展示
|
||||
(8) 微信V3:公有化 wechat.GetReleaseSign()、wechat.GetSandBoxSign() 方法
|
||||
(9) 微信V3:修改 client.V3PartnerCloseOrder() 入参参数
|
||||
(10) GoPay:一些小修改优化
|
||||
|
||||
版本号:Release 1.5.64
|
||||
修改记录:
|
||||
(1) xhttp:恢复 xhttp
|
||||
|
||||
版本号:Release 1.5.63
|
||||
修改记录:
|
||||
(1) GoPay:部分代码优化
|
||||
(2) xhttp:xhttp client 优化,支持自定义client,默认还是使用标准 http.Client
|
||||
|
||||
版本号:Release 1.5.62
|
||||
修改记录:
|
||||
(1) 微信V3:client 内 WxSerialNo、ApiV3Key 公有化
|
||||
(2) 微信V3:client 提供新方法 client.WxPublicKey() 直接获取 微信平台公钥
|
||||
(3) 微信V3:wechat 提供新方法 wechat.V3VerifySignByPK(),不再推荐使用 wechat.V3VerifySign()
|
||||
(4) 微信V3:V3NotifyReq 提供新方法 notify.VerifySignByPK(),不再推荐使用 notify.VerifySign()
|
||||
(5) 微信V3:整理微信v3说明文档
|
||||
|
||||
版本号:Release 1.5.61
|
||||
修改记录:
|
||||
(1) gopay:更新 xhttp pkg, 方法全部增加 context 传递
|
||||
|
||||
版本号:Release 1.5.60
|
||||
修改记录:
|
||||
(1) 微信V3:不再推荐使用 client.SetPlatformCert() 方法
|
||||
(2) 微信V3:新增 client.GetAndSelectNewestCert() 方法
|
||||
(3) 微信V3:重构 client.AutoVerifySign() 方法
|
||||
(4) QQ:新增 qq.GetAccessToken() 方法
|
||||
(5) QQ:新增 qq.GetOpenId() 方法
|
||||
(6) QQ:新增 qq.GetUserInfo() 方法
|
||||
|
||||
版本号:Release 1.5.59
|
||||
修改记录:
|
||||
(1) 微信V3:证书获取方法返回结构体,去除 SignInfo 字段
|
||||
(2) gopay:BodyMap,EncodeURLParams 方法稍作调整
|
||||
(3) PayPal:PayPal支付能力接入(订单、支付)
|
||||
|
||||
版本号:Release 1.5.58
|
||||
修改记录:
|
||||
(1) 微信V3:新增 client.V3FavorMediaUploadImage() 图片上传(营销专用)
|
||||
(2) 微信V3:新增 client.V3EcommerceIncomeRecord() 特约商户银行来账查询
|
||||
(3) 微信V3:新增 client.V3EcommerceBalance() 查询特约商户账户实时余额
|
||||
(4) 微信V3:新增 client.V3BusiFavorSend() 发放消费卡
|
||||
(5) 微信V3:新增 client.V3PartnershipsBuild() 建立合作关系
|
||||
(6) 微信V3:新增 client.V3PartnershipsTerminate() 终止合作关系
|
||||
(7) 微信V3:新增 client.V3PartnershipsList() 查询合作关系列表
|
||||
(8) 微信V3:修改 client.V3PartnerQueryOrder() 入参参数调整
|
||||
(9) 微信V3:修改 client.V3BillLevel2FundFlowBill() => client.V3BillEcommerceFundFlowBill() 申请特约商户资金账单
|
||||
(10) 支付宝:按照支付宝更新后的文档,修改大量接口返回参数结构体字段
|
||||
|
||||
版本号:Release 1.5.57
|
||||
修改记录:
|
||||
(1) 微信V3:修复一些已知问题
|
||||
(2) 支付宝:一些细小的修复,部分参数类型更正
|
||||
|
||||
版本号:Release 1.5.56
|
||||
修改记录:
|
||||
(1) 微信V3:修改 client.V3ProfitShareReturnResult() 接口入参,适配服务商模式
|
||||
(2) 微信V3:部分接口参数需要加密,修复 V3EncryptText() 和 V3DecryptText() 方法
|
||||
(3) 支付宝:修改 alipay.NewClient(),增加error返回值,去除Client内部分字段
|
||||
(4) Apple:新增apple pay的 apple.VerifyReceipt() 校验收据API
|
||||
(5) 优化代码中所有有关证书的解析操作
|
||||
|
||||
版本号:Release 1.5.55
|
||||
修改记录:
|
||||
(1) 微信V3:wechat.NewClientV3(),去掉初始化参数 appid,所以方法中需要 appid 或sp_appid 的,需要自行传参
|
||||
(2) 微信V3:新增 代金券 相关接口
|
||||
(3) 微信V3:新增 商家券 相关接口
|
||||
(4) 微信V2、V3:修复部分接口发现的Bug
|
||||
|
||||
版本号:Release 1.5.54
|
||||
修改记录:
|
||||
(1) 微信V3:新增微信支付分回调参数解密方法 notifyReq.DecryptScoreCipherText()
|
||||
(2) 微信V3:新增分账接口 client.V3ProfitShareMerchantConfigs()
|
||||
(3) Readme:更新Readme
|
||||
|
||||
版本号:Release 1.5.53
|
||||
修改记录:
|
||||
(1) 支付宝:补充接口
|
||||
(2) 微信V3:修改支付分相关接口的返回参数字段, out_trade_no 为 out_order_no
|
||||
|
||||
版本号:Release 1.5.52
|
||||
修改记录:
|
||||
(1) 支付宝:补充 支付API 相关接口
|
||||
(2) pkg:xhttp.Client 的 Transport 默认配置:Proxy: http.ProxyFromEnvironment
|
||||
|
||||
版本号:Release 1.5.51
|
||||
修改记录:
|
||||
(1) 微信:新增 特约商户进件(服务商平台) 相关接口
|
||||
(2) 支付宝:补充完整 芝麻分 相关接口
|
||||
(3) 支付宝:补充 会员API 相关接口
|
||||
|
||||
版本号:Release 1.5.50
|
||||
修改记录:
|
||||
(1) 支付宝:新增 芝麻分 相关接口
|
||||
(2) 支付宝:当判断 Response 中 code!="10000" 时,不再返回nil,而是返回 aliRsp 结果
|
||||
|
||||
版本号:Release 1.5.49
|
||||
修改记录:
|
||||
(1) 微信V3:新增 wechat.GetPlatformCerts(),无需初始化V3client,直接获取微信平台证书和序列号等信息
|
||||
(2) gopay:更新 go mod version
|
||||
(3) 微信V2:新增 client.CustomsDeclareOrder(),订单附加信息提交(正式环境)
|
||||
(4) 微信V2:新增 client.CustomsDeclareQuery(),订单附加信息查询(正式环境)
|
||||
(5) 微信V2:新增 client.CustomsReDeclareOrder(),订单附加信息重推(正式环境)
|
||||
(6) 支付宝:新增 client.TradeCustomsDeclare(),统一收单报关接口(正式环境)
|
||||
(7) 支付宝:新增 client.AcquireCustoms(),报关接口(正式环境),未经测试
|
||||
(8) 支付宝:新增 client.AcquireCustomsQuery(),报关查询接口(正式环境),未经测试
|
||||
|
||||
版本号:Release 1.5.48
|
||||
修改记录:
|
||||
(1) 微信V3:修复 平台证书序列号Wechatpay-Serial错误 问题
|
||||
(2) 微信V3:新增 client.SetPlatformCert(),设置 微信支付平台证书 和 证书序列号 方法
|
||||
(3) 微信V3:新增 client.V3EncryptText(),请求参数 敏感信息 加密方法
|
||||
(4) 微信V3:新增 client.V3DecryptText(),返回参数 敏感信息 解密方法
|
||||
(5) 微信V3:修改 client.AutoVerifySign() 方法无需传参,但需要提前调用 client.SetPlatformCert() 设置 微信支付平台证书 和 证书序列号
|
||||
|
||||
版本号:Release 1.5.47
|
||||
修改记录:
|
||||
(1) 微信V3:新增 转账相关 相关接口
|
||||
(2) 微信V3:新增 账户余额查询 相关接口
|
||||
(3) 微信V3:新增 来账识别 相关接口
|
||||
|
||||
版本号:Release 1.5.46
|
||||
修改记录:
|
||||
(1) 微信V3:新增敏感信息加解密方法,wechat.V3EncryptText() 加密数据,wechat.V3DecryptText() 解密数据
|
||||
(2) 微信V3:新增 微信先享卡 相关接口
|
||||
(3) 微信V3:新增 支付即服务 相关接口
|
||||
(4) 微信V3:新增 智慧商圈 相关接口
|
||||
|
||||
版本号:Release 1.5.45
|
||||
修改记录:
|
||||
(1) 支付宝:优化现有代码,修复公钥证书模式下,同步验签失败的问题
|
||||
(2) 支付宝:新增 client.AutoVerifySign(),自动同步验签设置(公钥证书模式)
|
||||
(3) 支付宝:新增 client.PublicCertDownload(),应用支付宝公钥证书下载
|
||||
(4) 支付宝:新增 client.FundTransPayeeBindQuery(),资金收款账号绑定关系查询
|
||||
(5) 支付宝:新增 client.OpenAppQrcodeCreate(),小程序生成推广二维码接口
|
||||
(6) 支付宝:新增 client.UserAgreementPageSign(),支付宝个人协议页面签约接口
|
||||
(7) 支付宝:新增 client.UserAgreementPageUnSign(),支付宝个人代扣协议解约接口
|
||||
(8) 支付宝:新增 client.UserAgreementQuery(),支付宝个人代扣协议查询接口
|
||||
(9) 支付宝:新增 client.TradeOrderInfoSync(),支付宝订单信息同步接口
|
||||
(10) 支付宝:新增 client.TradeAdvanceConsult(),订单咨询服务
|
||||
|
||||
版本号:Release 1.5.44
|
||||
修改记录:
|
||||
(1) 微信V3:新增 图片上传 接口
|
||||
(2) 微信V3:新增 视频上传 接口
|
||||
(3) 微信V3:修复消费者投诉接口中的图片上传失败问题
|
||||
|
||||
版本号:Release 1.5.42
|
||||
修改记录:
|
||||
(1) 迁移新仓库 https://github.com/go-pay/gopay
|
||||
|
||||
版本号:Release 1.5.41
|
||||
修改记录:
|
||||
(1) 微信V3:新增 消费者投诉2.0 相关接口
|
||||
(2) 微信V3:新增 分账 相关接口
|
||||
|
||||
版本号:Release 1.5.40
|
||||
修改记录:
|
||||
(1) 微信V3:新增微信支付分(免确认模式)相关接口
|
||||
(2) 微信V3:新增微信支付分(免确认预授权模式)相关接口
|
||||
|
||||
版本号:Release 1.5.39
|
||||
修改记录:
|
||||
(1) 微信V3:新增微信支付分(公共API)相关接口
|
||||
|
||||
版本号:Release 1.5.38
|
||||
修改记录:
|
||||
(1) 微信:去掉所以微信 client 方法中需要传证书的参数,请统一在初始化client时,添加证书
|
||||
(2) 微信:使用方法请参考 wechat/client_test.go 下的初始化,以及各个方法使用
|
||||
|
||||
版本号:Release 1.5.37
|
||||
修改记录:
|
||||
(1) 支付宝:修改 client.FundAuthOrderAppFreeze() 接口返回参数
|
||||
(2) 支付宝:新增 client.GetRequestSignParam(),获取已签名的完整请求参数
|
||||
(3) 微信V3:增加 client.GetPlatformCerts(),获取微信平台证书公钥,增加注释说明
|
||||
(4) 支付宝:拆分 _test.go 文件
|
||||
|
||||
版本号:Release 1.5.36
|
||||
修改记录:
|
||||
(1) 支付宝:新增 资金API 类别 接口实现
|
||||
(2) 微信V3:修复银行转账接口,银行卡号和收款人的 RSA 加密bug
|
||||
(3) 一些其他小修复调整
|
||||
|
||||
版本号:Release 1.5.35
|
||||
修改记录:
|
||||
(1) 微信V3:修复 普通支付回调通知 解密后的结构体问题
|
||||
(2) 微信V3:新增 合单支付回调通知
|
||||
(3) 微信V3:修复 退款回调通知 解密后的结构体问题
|
||||
|
||||
版本号:Release 1.5.34
|
||||
修改记录:
|
||||
(1) 微信V3:修复 client.GetPlatformCerts() 的返回值 code 问题
|
||||
(2) ReadMe:补充 README.md 说明
|
||||
(3) 微信V3:修复 PaySignOfApp() 签名出错的问题
|
||||
(4) 微信V2:部分文件调整
|
||||
|
||||
版本号:Release 1.5.32
|
||||
修改记录:
|
||||
(1) xhttp:Fix bug about Transport
|
||||
|
||||
版本号:Release 1.5.31
|
||||
修改记录:
|
||||
(1) 微信:新增服务商支付接口
|
||||
|
||||
版本号:Release 1.5.30
|
||||
修改记录:
|
||||
(1) BodyMap:恢复 bm.Get() 方法获取的是string类型,增加 bm.GetInterface()
|
||||
(2) 微信:新增V3版本退款查询接口
|
||||
|
||||
版本号:Release 1.5.29
|
||||
发布时间:2021/02/27 22:49
|
||||
修改记录:
|
||||
(1) 支付宝:新增 client.PostAliPayAPISelfV2(),比非V2版本更灵活化,具体参考 client_test.go 内的 TestClient_PostAliPayAPISelfV2() 方法
|
||||
(2) BodyMap:新增 bm.SetFormFile() 的部分方法,修改 bm.Get() 方法,新增bm.GetString() 方法
|
||||
(3) xHttp:更新 httpClient, httpClient.Type() 支持 TypeMultipartFormData 类型
|
||||
(4) go mod 版本改为 1.14
|
||||
|
||||
版本号:Release 1.5.28
|
||||
发布时间:2021/02/19 18:48
|
||||
修改记录:
|
||||
(1) QQ:新增 client.AddCertFileContent(),解决无证书文件,只有证书内容的问题
|
||||
(2) 支付宝:新增 alipay.VerifySyncSignWithCert(),同步证书验签
|
||||
(3) 支付宝:新增 client.SetCertSnByContent(),通过应用公钥证书内容设置 app_cert_sn、alipay_root_cert_sn、alipay_cert_sn
|
||||
(4) 支付宝:删除废弃接口 client.FundTransToaccountTransfer()
|
||||
(5) fix BodyMap 的部分方法
|
||||
|
||||
版本号:Release 1.5.27
|
||||
发布时间:2021/02/03 18:50
|
||||
修改记录:
|
||||
(1) GoPay:去掉对 gotil 的强依赖
|
||||
|
||||
版本号:Release 1.5.26
|
||||
发布时间:2021/01/29 19:38
|
||||
修改记录:
|
||||
(1) 微信:重新整理文件分级,商户分账模块增加test方法说明
|
||||
(2) BodyMap: 去除 GetArrayBodyMap()、GetBodyMap() 方法
|
||||
|
||||
版本号:Release 1.5.25
|
||||
发布时间:2020/12/31 18:38
|
||||
修改记录:
|
||||
(1) 微信:v3 基础支付接口完成,使用请参考:gopay/wechat/v3/client_test.go
|
||||
|
||||
版本号:Release 1.5.24
|
||||
发布时间:2020/12/21 18:58
|
||||
修改记录:
|
||||
(1) 微信:证书支持二选一,只传 apiclient_cert.pem 和 apiclient_key.pem 或者只传 apiclient_cert.p12
|
||||
|
||||
版本号:Release 1.5.23
|
||||
发布时间:2020/12/15 17:58
|
||||
修改记录:
|
||||
(1) 微信:新增 client.AddCertFileContent(),解决无证书文件,只有证书内容的问题
|
||||
|
||||
版本号:Release 1.5.22
|
||||
发布时间:2020/12/04 02:58
|
||||
修改记录:
|
||||
(1) 更新 gotil,修复xlog导致 go 1.14 以下版本报bug问题
|
||||
(2) 采纳 WenyXu 的意见,优化BodyMap的用法
|
||||
|
||||
版本号:Release 1.5.20
|
||||
发布时间:2020/09/30 23:58
|
||||
修改记录:
|
||||
(1) 微信:client 添加 DebugSwitch 开关,默认关闭,不输出 请求参数和返回参数,通过 client.DebugSwitch = gopay.DebugOn 打开
|
||||
(2) 支付宝:client 添加 DebugSwitch 开关,默认关闭,不输出 请求参数和返回参数,通过 client.DebugSwitch = gopay.DebugOn 打开
|
||||
(3) QQ:client 添加 DebugSwitch 开关,默认关闭,不输出 请求参数和返回参数,通过 client.DebugSwitch = gopay.DebugOn 打开
|
||||
(4) 更新 Gotil
|
||||
|
||||
版本号:Release 1.5.19
|
||||
发布时间:2020/09/20 23:58
|
||||
修改记录:
|
||||
(1) 微信:修复 client.ProfitSharingQuery() 接口的Bug,https://github.com/iGoogle-ink/gopay/issues/68
|
||||
(2) 微信:优化 client.doProdPost()
|
||||
(3) 支付宝:优化 client.doAliPay()
|
||||
(4) 微信:项目文件区分改动,开放平台接口和微信公众号区分
|
||||
(5) 微信:替换 wechat.GetAppLoginAccessToken() = > wechat.GetOauth2AccessToken()
|
||||
(6) 微信:替换 wechat.RefreshAppLoginAccessToken() = > wechat.RefreshOauth2AccessToken()
|
||||
(7) 微信:替换 wechat.GetUserInfoOpen() = > wechat.GetOauth2UserInfo()
|
||||
(8) 微信:替换 wechat.GetUserInfo() = > wechat.GetPublicUserInfo()
|
||||
(9) 微信:新增 wechat.CheckOauth2AccessToken() 检验授权凭证(access_token)是否有效
|
||||
(10) 微信:新增 wechat.GetPublicUserInfoBatch() 批量获取用户基本信息(微信公众号)
|
||||
(11) 微信:新增 client.SendCashRed() 发放现金红包
|
||||
(12) 微信:新增 client.SendGroupCashRed() 发放现金裂变红包
|
||||
(13) 微信:新增 client.SendAppletRed() 发放小程序红包
|
||||
(14) 微信:新增 client.QueryRedRecord() 查询红包记录
|
||||
(15) QQ:新增 client.SendCashRed() 创建现金红包,(未经测试,有条件的帮忙测一下吧,有问题提PR)
|
||||
(16) QQ:新增 client.DownloadRedListFile() 对账单下载,(未经测试,有条件的帮忙测一下吧,有问题提PR)
|
||||
(17) QQ:新增 client.QueryRedInfo() 查询红包详情,(未经测试,有条件的帮忙测一下吧,有问题提PR)
|
||||
|
||||
版本号:Release 1.5.18
|
||||
发布时间:2020/08/29 18:30
|
||||
修改记录:
|
||||
(1) 微信:修复 client.AddCertFilePath() 无效的Bug
|
||||
(2) QQ:修复 client.AddCertFilePath() 无效的Bug
|
||||
(3) Gotil:升级 gotil 到 v1.0.7-beta2 版本
|
||||
(4) 支付宝:OpenAuthTokenAppResponse 结构体中 ExpiresIn、ReExpiresIn 字段改为int64(有用户反馈返回的是int类型,但文档写的是string),如果此处有问题,请立马联系改回去。
|
||||
|
||||
版本号:Release 1.5.17
|
||||
发布时间:2020/08/23 15:30
|
||||
修改记录:
|
||||
(1) 微信:Response model 增加字段
|
||||
(2) ReadMe:修改部分遗留未更改的文档内容
|
||||
(3) 支付宝:添加证书由只支持证书路径,改为支持证书路径或者这书Byte数组
|
||||
(4) 支付宝:修复SystemOauthToken()方法未添加 AppCertSN 和 AliPayRootCertSN 的问题
|
||||
|
||||
版本号:Release 1.5.16
|
||||
发布时间:2020/07/29 18:30
|
||||
修改记录:
|
||||
(1) 微信:新增公共方法:wechat.GetUserInfoOpen(),微信开放平台:获取用户个人信息(UnionID机制)
|
||||
(2) Gotil:升级 gotil 到 v1.0.4 版本
|
||||
(3) 微信:新增ReadMe说明,微信支付下单等操作可用沙箱环境测试是否成功,但真正支付时,请使用正式环境,isProd = true,不然会报错
|
||||
|
||||
版本号:Release 1.5.15
|
||||
发布时间:2020/07/09 18:30
|
||||
修改记录:
|
||||
(1) 微信:新增client方法:client.ProfitSharing(),请求单次分账
|
||||
(2) 微信:新增client方法:client.MultiProfitSharing(),请求多次分账
|
||||
(3) 微信:新增client方法:client.ProfitSharingQuery(),查询分账结果
|
||||
(4) 微信:新增client方法:client.ProfitSharingAddReceiver(),添加分账接收方
|
||||
(5) 微信:新增client方法:client.ProfitSharingRemoveReceiver(),删除分账接收方
|
||||
(6) 微信:新增client方法:client.ProfitSharingFinish(),完结分账
|
||||
(7) 微信:新增client方法:client.ProfitSharingReturn(),分账回退
|
||||
(8) 微信:新增client方法:client.ProfitSharingReturnQuery(),分账回退结果查询
|
||||
(9) 微信:新增client方法:client.PayBank(),企业付款到银行卡API
|
||||
(10) 微信:新增client方法:client.QueryBank(),查询企业付款到银行卡API
|
||||
(11) 微信:新增client方法:client.GetRSAPublicKey(),获取RSA加密公钥API
|
||||
(12) 微信:修改client方法名:client.PostRequest() -> client.PostWeChatAPISelf()
|
||||
(13) QQ:修改client方法名:client.PostRequest() -> client.PostQQAPISelf()
|
||||
(14) 说明:方法未经严格测试,还请开发者在开始使用时确认是否正常使用,有问题请提 issue
|
||||
|
||||
版本号:Release 1.5.14
|
||||
发布时间:2020/06/27 3:30
|
||||
修改记录:
|
||||
(1) 引入 github.com/iGoogle-ink/gotil 包
|
||||
(2) 替换 log 输出样式
|
||||
(3) 支付宝:新增client方法:client.PostAliPayAPISelf(),支付宝接口自行实现方法
|
||||
|
||||
版本号:Release 1.5.12
|
||||
发布时间:2020/05/20 02:10
|
||||
修改记录:
|
||||
(1) http_client:增加默认请求的超时时间 60s,增加 SetTimeout() 方法,可自定义超时时间
|
||||
(2) 微信:修改 申请退款、退款查询、订单查询 接口的返回结构体,增加带下标的部分字段
|
||||
(3) 微信:增加 申请退款、退款查询、订单查询 接口的返回值,新增了 resBm BodyMap 类型,方便接收结构体中未定义到的下标字段
|
||||
(4) 微信:新增client方法:client.GetTransferInfo(),查询企业退款,此方法实际暂未测试,请自行测试,有问题提issue
|
||||
|
||||
版本号:Release 1.5.11
|
||||
发布时间:2020/05/13 17:15
|
||||
修改记录:
|
||||
(1) 支付宝:修复rsp解析出错的问题 client.SystemOauthToken()
|
||||
(2) 微信:修改部分公共方法Rsp结构体参数问题,同步微信文档
|
||||
|
||||
版本号:Release 1.5.10
|
||||
发布时间:2020/05/06 20:15
|
||||
修改记录:
|
||||
(1) 微信:修改部分公共方法返回值结构体字段类型
|
||||
(2) drone fix
|
||||
|
||||
版本号:Release 1.5.9
|
||||
发布时间:2020/04/25 15:32
|
||||
修改记录:
|
||||
(1) 支付宝:异步验签,推荐使用 alipay.ParseNotifyToBodyMap(),解析参数后参数在Verify验签时,推荐传入参数BodyMap bm。
|
||||
(2) 支付宝:修改公共方法:alipay.ParseNotifyResultToBodyMap() 为 alipay.ParseNotifyToBodyMap()
|
||||
(3) 支付宝:修改公共方法:alipay.ParseNotifyResultByURLValues() 为 alipay.ParseNotifyByURLValues()
|
||||
(4) 支付宝:废弃公共方法:alipay.ParseNotifyResult(),因为异步通知有参数因为支付接口不同,返回的字段不同,无法使用结构体全部定义好
|
||||
(5) 支付宝:调整了部分接口的文档地址
|
||||
(6) 微信:修改公共方法:wechat.ParseNotifyResultToBodyMap() 为 wechat.ParseNotifyToBodyMap()
|
||||
(7) 微信:修改公共方法:wechat.ParseNotifyResult() 为 wechat.ParseNotify()
|
||||
(8) 微信:修改公告方法:wechat.ParseRefundNotifyResult() 为 wechat.ParseRefundNotify()
|
||||
|
||||
版本号:Release 1.5.8
|
||||
发布时间:2020/04/18 21:32
|
||||
修改记录:
|
||||
(1) 微信:新增Client方法:client.PostRequest(),向微信发送Post请求,对于本库未提供的微信API,可自行实现,通过此方法发送请求
|
||||
(2) 微信:微信同步返回结构体类型全部修改为string类型,验签出错的问题
|
||||
(3) 微信:Client方法,需要传证书的接口方法,入参类型统一改为interface{},无需传证书地址时,由 "" 改为 nil
|
||||
(4) QQ:同微信改动
|
||||
(5) 支付宝:model结构体参数全部修改为string类型
|
||||
|
||||
版本号:Release 1.5.7
|
||||
发布时间:2020/03/25 20:32
|
||||
修改记录:
|
||||
(1) 支付宝:修改 client.UserCertifyOpenQuery() 方法的返回值解析类型报错问题,官方文档类型实例有误
|
||||
|
||||
版本号:Release 1.5.6
|
||||
发布时间:2020/03/06 17:32
|
||||
修改记录:
|
||||
(1) 支付宝:新增Client方法:client.SetPrivateKeyType(),设置 支付宝 私钥类型,alipay.PKCS1 或 alipay.PKCS8,默认 PKCS1。
|
||||
(2) 支付宝:修改公共方法:alipay.GetRsaSign(),增加了私钥类型参数,并将私钥的格式化操作,移动到该方法内,传入的私钥无需事先格式化。
|
||||
|
||||
版本号:Release 1.5.5
|
||||
发布时间:2020/03/05 18:32
|
||||
修改记录:
|
||||
(1) 支付宝:新增Client方法:client.DataBillBalanceQuery(),支付宝商家账户当前余额查询。
|
||||
(2) 支付宝:新增Client方法:client.DataBillDownloadUrlQuery(),查询对账单下载地址。
|
||||
(3) 支付宝:开放公共方法:alipay.GetRsaSign(),获取支付宝参数签名(参数sign值)。
|
||||
(4) 支付宝:开放公共方法:alipay.FormatURLParam(),格式化支付宝请求URL参数。
|
||||
(5) 支付宝:新增公共方法:alipay.ParseNotifyResultByURLValues(),通过 url.Values 解析支付宝支付异步通知的参数到Struct。
|
||||
|
||||
版本号:Release 1.5.4
|
||||
发布时间:2020/02/29 14:32
|
||||
修改记录:
|
||||
(1) 支付宝:新增Client方法:client.UserInfoAuth(),用户登陆授权。(方法未测试通过,待有测试条件的同学测试一下吧)
|
||||
(2) 支付宝:新增公共方法:alipay.MonitorHeartbeatSyn(),验签接口。(方法未测试通过,待有测试条件的同学测试一下吧)
|
||||
|
||||
版本号:Release 1.5.3
|
||||
发布时间:2020/02/19 11:32
|
||||
修改记录:
|
||||
(1) 支付宝:修改公共方法:SystemOauthToken(),添加参数 signType
|
||||
|
||||
版本号:Release 1.5.2
|
||||
发布时间:2020/02/14 13:32
|
||||
修改记录:
|
||||
(1) 支付宝:官方单笔转账接口更新,新增Client方法:client.FundTransUniTransfer(),单笔转账接口
|
||||
(2) 支付宝:新增Client方法:client.FundTransCommonQuery(),转账业务单据查询接口
|
||||
(3) 支付宝:新增Client放大:client.FundAccountQuery(),支付宝资金账户资产查询接口
|
||||
(3) 支付宝:Client的方法,必选参数校验
|
||||
|
||||
版本号:Release 1.5.1
|
||||
发布时间:2020/01/03 17:32
|
||||
修改记录:
|
||||
(1) 由于下载包需要 /v2 的问题,替换版本号到 1.x,代码不变,只改变版本号记录
|
||||
|
||||
版本号:Release 2.0.5
|
||||
发布时间:2020/01/01 22:55
|
||||
修改记录:
|
||||
(1) 添加一些函数参数判空操作,避免Panic
|
||||
(2) 去掉不用的结构体 ReturnMessage
|
||||
(3) 去掉 go mod v1.4.8版本的依赖
|
||||
|
||||
版本号:Release 2.0.4
|
||||
发布时间:2019/12/24 14:29
|
||||
修改记录:
|
||||
(1) 支付宝:新增支付宝公钥文件验证签方法(公钥证书模式):client.VerifySignWithCert()
|
||||
|
||||
版本号:Release 2.0.3
|
||||
发布时间:2019/12/18 19:25
|
||||
修改记录:
|
||||
(1) 微信:新增Client方法:client.AuthCodeToOpenId(),授权码查询OpenId(正式)
|
||||
(2) 微信:新增Client方法:client.Report(),交易保障
|
||||
(3) 微信:新增Client方法:client.EntrustPublic(),公众号纯签约(正式)
|
||||
(4) 微信:新增Client方法:client.EntrustAppPre(),APP纯签约-预签约接口-获取预签约ID(正式)
|
||||
(5) 微信:新增Client方法:client.EntrustH5(),H5纯签约(正式)
|
||||
(5) 微信:新增Client方法:client.EntrustPaying(),支付中签约(正式)
|
||||
|
||||
版本号:Release 2.0.2
|
||||
发布时间:2019/12/13 23:25
|
||||
修改记录:
|
||||
(1) 微信:删除Client方法:client.AddCertFileByte()
|
||||
(2) 版本限制 golang 1.13,fmt.Errorf() 使用 %w 格式化 error,1.13新特性
|
||||
|
||||
版本号:Release 2.0.1
|
||||
发布时间:2019/12/13 17:20
|
||||
修改记录:
|
||||
(1) 处理 go mod 包,go get github.com/iGoogle-ink/gopay/v2
|
||||
|
||||
版本号:Release 2.0.0
|
||||
发布时间:2019/12/12 18:22
|
||||
修改记录:
|
||||
(1) 按支付渠道模块分包大调整
|
||||
(2) 一大推细小改动,不一一描述了
|
||||
(3) 支付宝:修改公共API方法:alipay.GetCertSN(),不再支持支付宝根证书的SN获取
|
||||
(4) 支付宝:新增公共API方法:alipay.GetRootCertSN(),获取root证书序列号SN
|
||||
(5) 支付宝:新增Client方法:alipay.SetLocation(),设置时区,不设置或出错均为默认服务器时间
|
||||
|
||||
版本号:Release 1.4.8
|
||||
发布时间:2019/12/11 16:40
|
||||
修改记录:
|
||||
(1) 1.几 最后一个版本
|
||||
(2) 修复一些问题
|
||||
(3) 支付宝:修改公共API方法:gopay.GetCertSN(),不再支持支付宝根证书的SN获取
|
||||
(4) 支付宝:新增公共API方法:gopay.GetRootCertSN(),获取root证书序列号SN
|
||||
(5) 微信:修改公共API方法:gopay.GetWeChatSanBoxParamSign(),修复 沙箱验签出错问题
|
||||
|
||||
版本号:Release 1.4.6
|
||||
发布时间:2019/12/09 18:37
|
||||
修改记录:
|
||||
(1) 移除第三方http请求库,自己封装了一个请求库使用,解决不会设置 goproxy 无法下载包的问题
|
||||
(2) 使用中如有问题,请微信联系作者处理 或 提issue
|
||||
|
||||
版本号:Release 1.4.5
|
||||
发布时间:2019/12/07 21:56
|
||||
修改记录:
|
||||
(1) 支付宝:修复 公钥证书模式 下,同步返回参数接收问题,返回接收结构体增加参数 alipay_cert_sn,同步返回证书模式验签,暂时未解决
|
||||
(2) 支付宝:修改公共API方法:gopay.VerifyAliPaySign(),不再支持同步验签,只做异步通知验签
|
||||
(3) 支付宝:新增公共API方法:gopay.VerifyAliPaySyncSign(),支付宝同步返回验签
|
||||
(4) 支付宝:新增Client方法:client.SetAliPayPublicCertSN(),设置 支付宝公钥证书SN,通过 gopay.GetCertSN() 获取 alipay_cert_sn
|
||||
(5) 支付宝:新增Client方法:client.SetAppCertSnByPath(),设置 app_cert_sn 通过应用公钥证书路径
|
||||
(6) 支付宝:新增Client方法:client.SetAliPayPublicCertSnByPath(),设置 alipay_cert_sn 通过 支付宝公钥证书文件路径
|
||||
(7) 支付宝:新增Client方法:client.SetAliPayRootCertSnByPath(),设置 alipay_root_cert_sn 通过支付宝CA根证书文件路径
|
||||
|
||||
版本号:Release 1.4.4
|
||||
发布时间:2019/11/16 15:56
|
||||
修改记录:
|
||||
(1) 支付宝:新增公共API方法:gopay.ParseAliPayNotifyResultToBodyMap(),解析支付宝支付异步通知的参数到BodyMap
|
||||
(2) 支付宝:修改公共API方法:gopay.VerifyAliPaySign(),支付宝异步验签支持传入 BodyMap
|
||||
(3) 微信:新增Client方法:client.AddCertFileByte(),添加微信证书 Byte 数组
|
||||
(4) 微信:新增Client方法:client.AddCertFilePath(),添加微信证书 Path 路径
|
||||
(5) 微信:微信Client需要证书的方法,如已使用client.AddCertFilePath()或client.AddCertFileByte()添加过证书,参数certFilePath、keyFilePath、pkcs12FilePath全传空字符串 "",否则,3证书Path均不可空
|
||||
(6) BodyMap 的Set方法去掉switch判断,直接赋值
|
||||
(7) WeChatClient、AliPayClient 加锁
|
||||
(8) 修改部分小问题和部分样式
|
||||
|
||||
版本号:Release 1.4.3
|
||||
发布时间:2019/11/12 01:15
|
||||
修改记录:
|
||||
(1) 微信:新增公共API方法:gopay.ParseWeChatRefundNotifyResult(),解析微信退款异步通知的请求参数
|
||||
(2) 微信:新增公共API方法:gopay.DecryptRefundNotifyReqInfo(),解密微信退款异步通知的加密数据
|
||||
(3) 支付宝:修改Client方法:client.AliPayUserCertifyOpenCertify(),身份认证开始认证(获取认证链接)
|
||||
(4) 修改部分小问题
|
||||
|
||||
版本号:Release 1.4.2
|
||||
发布时间:2019/11/11 16:43
|
||||
修改记录:
|
||||
(1) 支付宝:新增Client方法:client.AliPayUserCertifyOpenInit(),身份认证初始化服务
|
||||
(2) 支付宝:新增Client方法:client.AliPayUserCertifyOpenCertify(),身份认证开始认证
|
||||
(3) 支付宝:新增Client方法:client.AliPayUserCertifyOpenQuery(),身份认证记录查询
|
||||
(4) 支付宝:所有方法的返回结构体下的 XxxResponse 字段,统一修改为 Response,如:
|
||||
type AliPayTradeCreateResponse struct {
|
||||
Response createResponse `json:"alipay_trade_create_response,omitempty"`
|
||||
SignData string `json:"-"`
|
||||
Sign string `json:"sign"`
|
||||
}
|
||||
(5) 支付宝:修改一些代码格式问题
|
||||
(6) 支付宝:client.AlipayOpenAuthTokenApp() 修改为 client.AliPayOpenAuthTokenApp()
|
||||
|
||||
版本号:Release 1.4.1
|
||||
发布时间:2019/11/04 14:28
|
||||
修改记录:
|
||||
(1) 支付宝:修改公共API方法:GetCertSN(),修复获取支付宝根证书获取 sn 的问题(wziww)
|
||||
(2) 支付宝:新增Client方法:client.SetAppCertSN(),可自行获取公钥 sn 并赋值
|
||||
(3) 支付宝:新增Client方法:client.SetAliPayRootCertSN(),可自行获取根证书 sn 并赋值
|
||||
|
||||
版本号:Release 1.4.0
|
||||
发布时间:2019/10/10 13:51
|
||||
修改记录:
|
||||
(1) AliPayNotifyRequest 结构体,新增加两个字段:method、timestamp,修复电脑网站支付,配置 return_url 支付成功后,支付宝请求该 return_url 返回参数验签失败的问题
|
||||
(2) 去除支付宝老验签方法 VerifyAliPayResultSign()
|
||||
(3) 去除微信老验签方法 VerifyWeChatResultSign()
|
||||
|
||||
版本号:Release 1.3.9
|
||||
发布时间:2019/09/30 00:01
|
||||
修改记录:
|
||||
(1) 修复支付宝支付验签出错的问题!
|
||||
|
||||
版本号:Release 1.3.8
|
||||
发布时间:2019/09/24 17:51
|
||||
修改记录:
|
||||
(1) 代码风格修改更新
|
||||
|
||||
版本号:Release 1.3.7
|
||||
发布时间:2019/09/22 11:41
|
||||
修改记录:
|
||||
(1) README 增加 go mod 安装gopay的方法指导
|
||||
|
||||
版本号:Release 1.3.6
|
||||
发布时间:2019/09/09 23:51
|
||||
修改记录:
|
||||
(1) 新增支付宝Client方法:client.AlipayUserInfoShare() => 支付宝会员授权信息查询接口(App支付宝登录)
|
||||
|
||||
版本号:Release 1.3.6
|
||||
发布时间:2019/09/05 02:55
|
||||
修改记录:
|
||||
(1) 更改微信公共API方法名称:gopay.GetAccessToken() to gopay.GetWeChatAppletAccessToken() => 获取微信小程序全局唯一后台接口调用凭据
|
||||
(2) 更改微信公共API方法名称:gopay.GetPaidUnionId() to gopay.GetWeChatAppletPaidUnionId() => 微信小程序用户支付完成后,获取该用户的 UnionId,无需用户授权
|
||||
(3) 新增微信公共API方法:gopay.GetAppWeChatLoginAccessToken() => App应用微信第三方登录,code换取access_token
|
||||
(4) 新增微信公共API方法:gopay.RefreshAppWeChatLoginAccessToken() => 刷新App应用微信第三方登录后,获取的 access_token
|
||||
|
||||
版本号:Release 1.3.5
|
||||
发布时间:2019/09/05 02:10
|
||||
修改记录:
|
||||
(1) 支付宝、微信Client 由私有改为公有
|
||||
|
||||
版本号:Release 1.3.4
|
||||
发布时间:2019/09/03 19:26
|
||||
修改记录:
|
||||
(1) 新增支付宝公共API方法:gopay.GetCertSN() => 获取证书SN号(app_cert_sn、alipay_root_cert_sn、alipay_cert_sn)
|
||||
(2) 新增支付宝Client方法:client.SetAliPayRootCertSN() => 设置支付宝根证书SN,通过 gopay.GetCertSN() 获取
|
||||
(3) 新增支付宝Client方法:client.SetAppCertSN() => 设置应用公钥证书SN,通过 gopay.GetCertSN() 获取
|
||||
@@ -0,0 +1,13 @@
|
||||
赞助者皆为 微信 或 支付宝 用户名
|
||||
|
||||
平台 ---- 用户 ---- 金额
|
||||
|
||||
微信 ---- 燃烧的发丝 ---- 50
|
||||
微信 ---- 三五五七ᴳᴼ ---- 100
|
||||
微信 ---- 沐修 ---- 50
|
||||
微信 ---- 深度学习 ---- 100
|
||||
支付宝 ---- **旺 ---- 50
|
||||
微信 ---- 开博黄工 ---- 88
|
||||
微信 ---- 每天都有新旋律 ---- 100
|
||||
微信 ---- *冉 ---- 50
|
||||
微信 ---- *萧 ---- 80
|
||||
@@ -0,0 +1,146 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
)
|
||||
|
||||
// 提交申请单API
|
||||
// 注意:本接口会提交一些敏感信息,需调用 client.V3EncryptText() 进行加密
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter11_1_1.shtml
|
||||
func (c *ClientV3) V3Apply4SubSubmit(ctx context.Context, bm gopay.BodyMap) (*Apply4SubSubmitRsp, error) {
|
||||
if err := bm.CheckEmptyError("business_code", "contact_info", "subject_info", "business_info", "settlement_info", "bank_account_info"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authorization, err := c.authorization(MethodPost, v3Apply4SubSubmit, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3Apply4SubSubmit, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp := &Apply4SubSubmitRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(Apply4SubSubmit)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 通过业务申请编号查询申请状态API
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter11_1_2.shtml
|
||||
func (c *ClientV3) V3Apply4SubQueryByBusinessCode(ctx context.Context, businessCode string) (*Apply4SubQueryRsp, error) {
|
||||
uri := fmt.Sprintf(v3Apply4SubQueryByBusinessCode, businessCode)
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp := &Apply4SubQueryRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(Apply4SubQuery)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 通过申请单号查询申请状态API
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter11_1_2.shtml
|
||||
func (c *ClientV3) V3Apply4SubQueryByApplyId(ctx context.Context, applyId string) (*Apply4SubQueryRsp, error) {
|
||||
uri := fmt.Sprintf(v3Apply4SubQueryByApplyId, applyId)
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &Apply4SubQueryRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(Apply4SubQuery)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 修改结算账号 API
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter11_1_3.shtml
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_1_4.shtml
|
||||
func (c *ClientV3) V3Apply4SubModifySettlement(ctx context.Context, bm gopay.BodyMap) (*EmptyRsp, error) {
|
||||
if err := bm.CheckEmptyError("sub_mchid", "account_type", "account_bank", "account_number"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
postUrl := fmt.Sprintf(v3Apply4SubModifySettlement, bm["sub_mchid"])
|
||||
bm.Remove("sub_mchid")
|
||||
authorization, err := c.authorization(MethodPost, postUrl, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, postUrl, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp := &EmptyRsp{Code: Success, SignInfo: si}
|
||||
if res.StatusCode != http.StatusNoContent {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询结算账户 API
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter11_1_4.shtml
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_1_5.shtml
|
||||
func (c *ClientV3) V3Apply4SubQuerySettlement(ctx context.Context, subMchId string) (*Apply4SubQuerySettlementRsp, error) {
|
||||
uri := fmt.Sprintf(v3Apply4SubQuerySettlement, subMchId)
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp := &Apply4SubQuerySettlementRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(Apply4SubQuerySettlement)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/pkg/util"
|
||||
)
|
||||
|
||||
// 获取对私银行卡号开户银行
|
||||
// 注意:accountNo 需此方法加密:client.V3EncryptText()
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter11_2_1.shtml
|
||||
func (c *ClientV3) V3BankSearchBank(ctx context.Context, accountNo string) (wxRsp *BankSearchBankRsp, err error) {
|
||||
uri := v3BankSearchBank + "?account_number=" + url.QueryEscape(accountNo)
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &BankSearchBankRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(BankSearchBank)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询支持个人业务的银行列表
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter11_2_2.shtml
|
||||
func (c *ClientV3) V3BankSearchPersonalList(ctx context.Context, limit, offset int) (wxRsp *BankSearchPersonalListRsp, err error) {
|
||||
if limit == 0 {
|
||||
limit = 20
|
||||
}
|
||||
uri := v3BankSearchPersonalList + "?limit=" + util.Int2String(limit) + "&offset=" + util.Int2String(offset)
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &BankSearchPersonalListRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(BankSearchList)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询支持对公业务的银行列表
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter11_2_3.shtml
|
||||
func (c *ClientV3) V3BankSearchCorporateList(ctx context.Context, limit, offset int) (wxRsp *BankSearchCorporateListRsp, err error) {
|
||||
if limit == 0 {
|
||||
limit = 20
|
||||
}
|
||||
uri := v3BankSearchCorporateList + "?limit=" + util.Int2String(limit) + "&offset=" + util.Int2String(offset)
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &BankSearchCorporateListRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(BankSearchList)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询省份列表
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter11_2_4.shtml
|
||||
func (c *ClientV3) V3BankSearchProvinceList(ctx context.Context) (wxRsp *BankSearchProvinceListRsp, err error) {
|
||||
authorization, err := c.authorization(MethodGet, v3BankSearchProvinceList, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, v3BankSearchProvinceList, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &BankSearchProvinceListRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(BankSearchProvince)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询城市列表
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter11_2_5.shtml
|
||||
func (c *ClientV3) V3BankSearchCityList(ctx context.Context, provinceCode int) (wxRsp *BankSearchCityListRsp, err error) {
|
||||
url := fmt.Sprintf(v3BankSearchCityList, provinceCode)
|
||||
authorization, err := c.authorization(MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &BankSearchCityListRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(BankSearchCity)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询支行列表
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter11_2_6.shtml
|
||||
func (c *ClientV3) V3BankSearchBranchList(ctx context.Context, bankAliasCode string, cityCode, limit, offset int) (wxRsp *BankSearchBranchListRsp, err error) {
|
||||
if limit == 0 {
|
||||
limit = 20
|
||||
}
|
||||
uri := fmt.Sprintf(v3BankSearchBranchList, bankAliasCode) + "?city_code=" + util.Int2String(cityCode) + "&limit=" + util.Int2String(limit) + "&offset=" + util.Int2String(offset)
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &BankSearchBranchListRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(BankSearchBranch)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/pkg/util"
|
||||
)
|
||||
|
||||
// 申请交易账单API
|
||||
// 注意:如 bill_date 为空,默认查前一天的
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_6.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_6.shtml
|
||||
func (c *ClientV3) V3BillTradeBill(ctx context.Context, bm gopay.BodyMap) (wxRsp *BillRsp, err error) {
|
||||
if bm != nil {
|
||||
if bm.GetString("bill_date") == util.NULL {
|
||||
now := time.Now()
|
||||
yesterday := time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, time.Local).Format(util.DateLayout)
|
||||
bm.Set("bill_date", yesterday)
|
||||
}
|
||||
}
|
||||
uri := v3TradeBill + "?" + bm.EncodeURLParams()
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp = &BillRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(TradeBill)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 申请资金账单API
|
||||
// 注意:如 bill_date 为空,默认查前一天的
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_7.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_7.shtml
|
||||
func (c *ClientV3) V3BillFundFlowBill(ctx context.Context, bm gopay.BodyMap) (wxRsp *BillRsp, err error) {
|
||||
if bm != nil {
|
||||
if bm.GetString("bill_date") == util.NULL {
|
||||
now := time.Now()
|
||||
yesterday := time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, time.Local).Format(util.DateLayout)
|
||||
bm.Set("bill_date", yesterday)
|
||||
}
|
||||
}
|
||||
uri := v3FundFlowBill + "?" + bm.EncodeURLParams()
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp = &BillRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(TradeBill)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 申请特约商户资金账单API
|
||||
// 注意:如 bill_date 为空,默认查前一天的
|
||||
// Code = 0 is success
|
||||
// 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter7_2.shtml
|
||||
func (c *ClientV3) V3BillEcommerceFundFlowBill(ctx context.Context, bm gopay.BodyMap) (wxRsp *EcommerceFundFlowBillRsp, err error) {
|
||||
if bm != nil {
|
||||
if bm.GetString("bill_date") == util.NULL {
|
||||
now := time.Now()
|
||||
yesterday := time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, time.Local).Format(util.DateLayout)
|
||||
bm.Set("bill_date", yesterday)
|
||||
}
|
||||
if bm.GetString("account_type") == util.NULL {
|
||||
bm.Set("account_type", "ALL")
|
||||
}
|
||||
if bm.GetString("algorithm") == util.NULL {
|
||||
bm.Set("algorithm", "AEAD_AES_256_GCM")
|
||||
}
|
||||
}
|
||||
uri := v3EcommerceFundFlowBill + "?" + bm.EncodeURLParams()
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp = &EcommerceFundFlowBillRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(DownloadBill)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 申请单个子商户资金账单API
|
||||
// 注意:如 bill_date 为空,默认查前一天的
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_12.shtml
|
||||
func (c *ClientV3) V3BillSubFundFlowBill(ctx context.Context, bm gopay.BodyMap) (wxRsp *BillRsp, err error) {
|
||||
if bm != nil {
|
||||
if bm.GetString("bill_date") == util.NULL {
|
||||
now := time.Now()
|
||||
yesterday := time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, time.Local).Format(util.DateLayout)
|
||||
bm.Set("bill_date", yesterday)
|
||||
}
|
||||
}
|
||||
uri := v3SubFundFlowBill + "?" + bm.EncodeURLParams()
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp = &BillRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(TradeBill)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 下载账单API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_8.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_8.shtml
|
||||
func (c *ClientV3) V3BillDownLoadBill(ctx context.Context, downloadUrl string) (fileBytes []byte, err error) {
|
||||
if downloadUrl == gopay.NULL {
|
||||
return nil, errors.New("invalid download url")
|
||||
}
|
||||
split := strings.Split(downloadUrl, ".com")
|
||||
if len(split) != 2 {
|
||||
return nil, errors.New("invalid download url")
|
||||
}
|
||||
authorization, err := c.authorization(MethodGet, split[1], nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, _, bs, err := c.doProdGet(ctx, split[1], authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, errors.New(string(bs))
|
||||
}
|
||||
return bs, nil
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
)
|
||||
|
||||
// 商圈积分同步
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_6_2.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_6_2.shtml
|
||||
func (c *ClientV3) V3BusinessPointsSync(ctx context.Context, bm gopay.BodyMap) (wxRsp *EmptyRsp, err error) {
|
||||
authorization, err := c.authorization(MethodPost, v3BusinessPointsSync, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3BusinessPointsSync, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &EmptyRsp{Code: Success, SignInfo: si}
|
||||
if res.StatusCode != http.StatusNoContent {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 商圈积分授权查询
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_6_4.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_6_4.shtml
|
||||
func (c *ClientV3) V3BusinessAuthPointsQuery(ctx context.Context, appid, openid string) (*BusinessAuthPointsQueryRsp, error) {
|
||||
uri := fmt.Sprintf(v3BusinessAuthPointsQuery, openid) + "?appid=" + appid
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp := &BusinessAuthPointsQueryRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(BusinessAuthPointsQuery)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/pkg/aes"
|
||||
"github.com/go-pay/gopay/pkg/errgroup"
|
||||
"github.com/go-pay/gopay/pkg/util"
|
||||
"github.com/go-pay/gopay/pkg/xhttp"
|
||||
"github.com/go-pay/gopay/pkg/xlog"
|
||||
"github.com/go-pay/gopay/pkg/xpem"
|
||||
"github.com/go-pay/gopay/pkg/xtime"
|
||||
)
|
||||
|
||||
// 获取微信平台证书公钥(获取后自行保存使用,如需定期刷新功能,自行实现)
|
||||
// 注意事项
|
||||
// 如果自行实现验证平台签名逻辑的话,需要注意以下事项:
|
||||
// - 程序实现定期更新平台证书的逻辑,不要硬编码验证应答消息签名的平台证书
|
||||
// - 定期调用该接口,间隔时间小于12小时
|
||||
// - 加密请求消息中的敏感信息时,使用最新的平台证书(即:证书启用时间较晚的证书)
|
||||
// 文档说明:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay5_1.shtml
|
||||
func GetPlatformCerts(ctx context.Context, mchid, apiV3Key, serialNo, privateKey string) (certs *PlatformCertRsp, err error) {
|
||||
var (
|
||||
eg = new(errgroup.Group)
|
||||
mu sync.Mutex
|
||||
jb = ""
|
||||
)
|
||||
// Prepare
|
||||
priKey, err := xpem.DecodePrivateKey([]byte(privateKey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
timestamp := time.Now().Unix()
|
||||
nonceStr := util.RandomString(32)
|
||||
ts := util.Int642String(timestamp)
|
||||
_str := MethodGet + "\n" + v3GetCerts + "\n" + ts + "\n" + nonceStr + "\n" + jb + "\n"
|
||||
// Sign
|
||||
h := sha256.New()
|
||||
h.Write([]byte(_str))
|
||||
result, err := rsa.SignPKCS1v15(rand.Reader, priKey, crypto.SHA256, h.Sum(nil))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %+v", gopay.SignatureErr, err)
|
||||
}
|
||||
sign := base64.StdEncoding.EncodeToString(result)
|
||||
// Authorization
|
||||
authorization := Authorization + ` mchid="` + mchid + `",nonce_str="` + nonceStr + `",timestamp="` + ts + `",serial_no="` + serialNo + `",signature="` + sign + `"`
|
||||
// Request
|
||||
var url = v3BaseUrlCh + v3GetCerts
|
||||
httpClient := xhttp.NewClient()
|
||||
httpClient.Header.Add(HeaderAuthorization, authorization)
|
||||
httpClient.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix()))
|
||||
httpClient.Header.Add(HeaderSerial, serialNo)
|
||||
httpClient.Header.Add("Accept", "*/*")
|
||||
res, bs, err := httpClient.Type(xhttp.TypeJSON).Get(url).EndBytes(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certs = &PlatformCertRsp{Code: Success}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
certs.Code = res.StatusCode
|
||||
certs.Error = string(bs)
|
||||
return certs, nil
|
||||
}
|
||||
// Parse
|
||||
certRsp := new(PlatformCert)
|
||||
if err = json.Unmarshal(bs, certRsp); err != nil {
|
||||
return nil, fmt.Errorf("json.Unmarshal(%s):%+v", string(bs), err)
|
||||
}
|
||||
for _, v := range certRsp.Data {
|
||||
cert := v
|
||||
if cert.EncryptCertificate != nil {
|
||||
ec := cert.EncryptCertificate
|
||||
eg.Go(func(ctx context.Context) error {
|
||||
cipherBytes, _ := base64.StdEncoding.DecodeString(ec.Ciphertext)
|
||||
pubKeyBytes, err := aes.GCMDecrypt(cipherBytes, []byte(ec.Nonce), []byte(ec.AssociatedData), []byte(apiV3Key))
|
||||
if err != nil {
|
||||
return fmt.Errorf("aes.GCMDecrypt, err:%+v", err)
|
||||
}
|
||||
pci := &PlatformCertItem{
|
||||
EffectiveTime: cert.EffectiveTime,
|
||||
ExpireTime: cert.ExpireTime,
|
||||
PublicKey: string(pubKeyBytes),
|
||||
SerialNo: cert.SerialNo,
|
||||
}
|
||||
mu.Lock()
|
||||
certs.Certs = append(certs.Certs, pci)
|
||||
mu.Unlock()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
if err = eg.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return certs, nil
|
||||
}
|
||||
|
||||
// 设置 微信支付平台证书 和 证书序列号
|
||||
// 注意1:如已开启自动验签功能 client.AutoVerifySign(),无需再调用此方法设置
|
||||
// 注意2:请预先通过 wechat.GetPlatformCerts() 获取 微信平台公钥证书 和 证书序列号
|
||||
// 部分接口请求参数中敏感信息加密,使用此 微信支付平台公钥 和 证书序列号
|
||||
func (c *ClientV3) SetPlatformCert(wxPublicKeyContent []byte, wxSerialNo string) (client *ClientV3) {
|
||||
pubKey, err := xpem.DecodePublicKey(wxPublicKeyContent)
|
||||
if err != nil {
|
||||
xlog.Errorf("SetPlatformCert(%s),err:%+v", wxPublicKeyContent, err)
|
||||
}
|
||||
if pubKey != nil {
|
||||
c.wxPublicKey = pubKey
|
||||
}
|
||||
c.WxSerialNo = wxSerialNo
|
||||
return c
|
||||
}
|
||||
|
||||
// 获取 微信平台证书(readonly)
|
||||
func (c *ClientV3) WxPublicKey() (wxPublicKey *rsa.PublicKey) {
|
||||
wxPublicKey = c.wxPublicKey
|
||||
return
|
||||
}
|
||||
|
||||
// 获取并选择最新的有效证书
|
||||
func (c *ClientV3) GetAndSelectNewestCert() (cert, serialNo string, err error) {
|
||||
certs, err := c.getPlatformCerts()
|
||||
if err != nil {
|
||||
return gopay.NULL, gopay.NULL, err
|
||||
}
|
||||
if certs.Code == Success && len(certs.Certs) > 0 {
|
||||
// only one
|
||||
if len(certs.Certs) == 1 {
|
||||
formatExpire := xtime.FormatDateTime(certs.Certs[0].ExpireTime)
|
||||
expireTime, err := time.ParseInLocation(xtime.TimeLayout, formatExpire, time.Local)
|
||||
if err != nil {
|
||||
return gopay.NULL, gopay.NULL, fmt.Errorf("time.ParseInLocation(%s, %s),err:%w", xtime.TimeLayout, formatExpire, err)
|
||||
}
|
||||
if time.Now().Unix() >= expireTime.Unix() {
|
||||
// 过期了
|
||||
return gopay.NULL, gopay.NULL, fmt.Errorf("wechat platform API cert expired, expired time: %s", formatExpire)
|
||||
}
|
||||
return certs.Certs[0].PublicKey, certs.Certs[0].SerialNo, nil
|
||||
}
|
||||
// more one
|
||||
var (
|
||||
effectiveTs []int
|
||||
certMap = make(map[int]*PlatformCertItem)
|
||||
)
|
||||
for _, v := range certs.Certs {
|
||||
formatEffective := xtime.FormatDateTime(v.EffectiveTime)
|
||||
effectiveTime, err := time.ParseInLocation(xtime.TimeLayout, formatEffective, time.Local)
|
||||
if err != nil {
|
||||
return gopay.NULL, gopay.NULL, fmt.Errorf("time.ParseInLocation(%s, %s),err:%w", xtime.TimeLayout, formatEffective, err)
|
||||
}
|
||||
eu := int(effectiveTime.Unix())
|
||||
effectiveTs = append(effectiveTs, eu)
|
||||
certMap[eu] = v
|
||||
}
|
||||
sort.Ints(effectiveTs)
|
||||
// newest cert
|
||||
newestCert := certMap[effectiveTs[len(effectiveTs)-1]]
|
||||
formatExpire := xtime.FormatDateTime(newestCert.ExpireTime)
|
||||
expireTime, err := time.ParseInLocation(xtime.TimeLayout, formatExpire, time.Local)
|
||||
if err != nil {
|
||||
return gopay.NULL, gopay.NULL, fmt.Errorf("time.ParseInLocation(%s, %s),err:%w", xtime.TimeLayout, formatExpire, err)
|
||||
}
|
||||
if time.Now().Unix() >= expireTime.Unix() {
|
||||
// 过期了
|
||||
return gopay.NULL, gopay.NULL, fmt.Errorf("wechat platform API cert expired, expired time: %s", formatExpire)
|
||||
}
|
||||
return newestCert.PublicKey, newestCert.SerialNo, nil
|
||||
}
|
||||
// failed
|
||||
return gopay.NULL, gopay.NULL, fmt.Errorf("GetAndSelectNewestCert() failed or certs is nil: %+v", certs)
|
||||
}
|
||||
|
||||
// 推荐直接使用 client.GetAndSelectNewestCert() 方法
|
||||
// 获取微信平台证书公钥(获取后自行保存使用,如需定期刷新功能,自行实现)
|
||||
// 注意事项
|
||||
// 如果自行实现验证平台签名逻辑的话,需要注意以下事项:
|
||||
// - 程序实现定期更新平台证书的逻辑,不要硬编码验证应答消息签名的平台证书
|
||||
// - 定期调用该接口,间隔时间小于12小时
|
||||
// - 加密请求消息中的敏感信息时,使用最新的平台证书(即:证书启用时间较晚的证书)
|
||||
// 文档说明:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay5_1.shtml
|
||||
func (c *ClientV3) getPlatformCerts() (certs *PlatformCertRsp, err error) {
|
||||
var (
|
||||
eg = new(errgroup.Group)
|
||||
mu sync.Mutex
|
||||
)
|
||||
|
||||
authorization, err := c.authorization(MethodGet, v3GetCerts, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, _, bs, err := c.doProdGet(c.ctx, v3GetCerts, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certs = &PlatformCertRsp{Code: Success}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
certs.Code = res.StatusCode
|
||||
certs.Error = string(bs)
|
||||
return certs, nil
|
||||
}
|
||||
certRsp := new(PlatformCert)
|
||||
if err = json.Unmarshal(bs, certRsp); err != nil {
|
||||
return nil, fmt.Errorf("json.Unmarshal(%s):%+v", string(bs), err)
|
||||
}
|
||||
for _, v := range certRsp.Data {
|
||||
cert := v
|
||||
if cert.EncryptCertificate != nil {
|
||||
ec := cert.EncryptCertificate
|
||||
eg.Go(func(ctx context.Context) error {
|
||||
pubKey, err := c.decryptCerts(ec.Ciphertext, ec.Nonce, ec.AssociatedData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pci := &PlatformCertItem{
|
||||
EffectiveTime: cert.EffectiveTime,
|
||||
ExpireTime: cert.ExpireTime,
|
||||
PublicKey: pubKey,
|
||||
SerialNo: cert.SerialNo,
|
||||
}
|
||||
mu.Lock()
|
||||
certs.Certs = append(certs.Certs, pci)
|
||||
mu.Unlock()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
if err = eg.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return certs, nil
|
||||
}
|
||||
|
||||
// 解密加密的证书
|
||||
func (c *ClientV3) decryptCerts(ciphertext, nonce, additional string) (wxCerts string, err error) {
|
||||
cipherBytes, _ := base64.StdEncoding.DecodeString(ciphertext)
|
||||
decrypt, err := aes.GCMDecrypt(cipherBytes, []byte(nonce), []byte(additional), c.ApiV3Key)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("aes.GCMDecrypt, err:%w", err)
|
||||
}
|
||||
return string(decrypt), nil
|
||||
}
|
||||
|
||||
func (c *ClientV3) autoCheckCertProc() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
buf := make([]byte, 64<<10)
|
||||
buf = buf[:runtime.Stack(buf, false)]
|
||||
xlog.Errorf("autoCheckCertProc: panic recovered: %s\n%s", r, buf)
|
||||
// 重启
|
||||
c.autoCheckCertProc()
|
||||
}
|
||||
}()
|
||||
for {
|
||||
time.Sleep(time.Hour * 12)
|
||||
wxPk, wxSerialNo, err := c.GetAndSelectNewestCert()
|
||||
if err != nil {
|
||||
xlog.Errorf("c.GetAndSelectNewestCert(),err:%+v", err)
|
||||
continue
|
||||
}
|
||||
// decode cert
|
||||
pubKey, err := xpem.DecodePublicKey([]byte(wxPk))
|
||||
if err != nil {
|
||||
xlog.Errorf("xpem.DecodePublicKey(%s),err:%+v", wxPk, err)
|
||||
continue
|
||||
}
|
||||
c.wxPublicKey = pubKey
|
||||
c.WxSerialNo = wxSerialNo
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/pkg/util"
|
||||
"github.com/go-pay/gopay/pkg/xhttp"
|
||||
"github.com/go-pay/gopay/pkg/xlog"
|
||||
"github.com/go-pay/gopay/pkg/xpem"
|
||||
)
|
||||
|
||||
// ClientV3 微信支付 V3
|
||||
type ClientV3 struct {
|
||||
Mchid string
|
||||
ApiV3Key []byte
|
||||
SerialNo string
|
||||
WxSerialNo string
|
||||
autoSign bool
|
||||
privateKey *rsa.PrivateKey
|
||||
wxPublicKey *rsa.PublicKey
|
||||
ctx context.Context
|
||||
DebugSwitch gopay.DebugSwitch
|
||||
}
|
||||
|
||||
// NewClientV3 初始化微信客户端 V3
|
||||
// mchid:商户ID 或者服务商模式的 sp_mchid
|
||||
// serialNo:商户API证书的证书序列号
|
||||
// apiV3Key:APIv3Key,商户平台获取
|
||||
// privateKey:商户API证书下载后,私钥 apiclient_key.pem 读取后的字符串内容
|
||||
func NewClientV3(mchid, serialNo, apiV3Key, privateKey string) (client *ClientV3, err error) {
|
||||
if mchid == util.NULL || serialNo == util.NULL || apiV3Key == util.NULL || privateKey == util.NULL {
|
||||
return nil, gopay.MissWechatInitParamErr
|
||||
}
|
||||
priKey, err := xpem.DecodePrivateKey([]byte(privateKey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client = &ClientV3{
|
||||
Mchid: mchid,
|
||||
SerialNo: serialNo,
|
||||
ApiV3Key: []byte(apiV3Key),
|
||||
privateKey: priKey,
|
||||
ctx: context.Background(),
|
||||
DebugSwitch: gopay.DebugOff,
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// AutoVerifySign 开启请求完自动验签功能(默认不开启,推荐开启)
|
||||
// 开启自动验签,自动开启每12小时一次轮询,请求最新证书操作
|
||||
func (c *ClientV3) AutoVerifySign() (err error) {
|
||||
wxPk, wxSerialNo, err := c.GetAndSelectNewestCert()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// decode cert
|
||||
pubKey, err := xpem.DecodePublicKey([]byte(wxPk))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.wxPublicKey = pubKey
|
||||
c.WxSerialNo = wxSerialNo
|
||||
c.autoSign = true
|
||||
go c.autoCheckCertProc()
|
||||
return
|
||||
}
|
||||
|
||||
func (c *ClientV3) doProdPostWithHeader(ctx context.Context, headerMap map[string]string, bm gopay.BodyMap, path, authorization string) (res *http.Response, si *SignInfo, bs []byte, err error) {
|
||||
var url = v3BaseUrlCh + path
|
||||
httpClient := xhttp.NewClient()
|
||||
if c.DebugSwitch == gopay.DebugOn {
|
||||
xlog.Debugf("Wechat_V3_RequestBody: %s", bm.JsonBody())
|
||||
xlog.Debugf("Wechat_V3_Authorization: %s", authorization)
|
||||
}
|
||||
for k, v := range headerMap {
|
||||
httpClient.Header.Add(k, v)
|
||||
}
|
||||
httpClient.Header.Add(HeaderAuthorization, authorization)
|
||||
httpClient.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix()))
|
||||
httpClient.Header.Add(HeaderSerial, c.WxSerialNo)
|
||||
httpClient.Header.Add("Accept", "*/*")
|
||||
res, bs, err = httpClient.Type(xhttp.TypeJSON).Post(url).SendBodyMap(bm).EndBytes(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
si = &SignInfo{
|
||||
HeaderTimestamp: res.Header.Get(HeaderTimestamp),
|
||||
HeaderNonce: res.Header.Get(HeaderNonce),
|
||||
HeaderSignature: res.Header.Get(HeaderSignature),
|
||||
HeaderSerial: res.Header.Get(HeaderSerial),
|
||||
SignBody: string(bs),
|
||||
}
|
||||
if c.DebugSwitch == gopay.DebugOn {
|
||||
xlog.Debugf("Wechat_Response: %d > %s", res.StatusCode, string(bs))
|
||||
xlog.Debugf("Wechat_Headers: %#v", res.Header)
|
||||
xlog.Debugf("Wechat_SignInfo: %#v", si)
|
||||
}
|
||||
return res, si, bs, nil
|
||||
}
|
||||
|
||||
func (c *ClientV3) doProdPost(ctx context.Context, bm gopay.BodyMap, path, authorization string) (res *http.Response, si *SignInfo, bs []byte, err error) {
|
||||
var url = v3BaseUrlCh + path
|
||||
httpClient := xhttp.NewClient()
|
||||
if c.DebugSwitch == gopay.DebugOn {
|
||||
xlog.Debugf("Wechat_V3_RequestBody: %s", bm.JsonBody())
|
||||
xlog.Debugf("Wechat_V3_Authorization: %s", authorization)
|
||||
}
|
||||
httpClient.Header.Add(HeaderAuthorization, authorization)
|
||||
httpClient.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix()))
|
||||
httpClient.Header.Add(HeaderSerial, c.WxSerialNo)
|
||||
httpClient.Header.Add("Accept", "*/*")
|
||||
res, bs, err = httpClient.Type(xhttp.TypeJSON).Post(url).SendBodyMap(bm).EndBytes(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
si = &SignInfo{
|
||||
HeaderTimestamp: res.Header.Get(HeaderTimestamp),
|
||||
HeaderNonce: res.Header.Get(HeaderNonce),
|
||||
HeaderSignature: res.Header.Get(HeaderSignature),
|
||||
HeaderSerial: res.Header.Get(HeaderSerial),
|
||||
SignBody: string(bs),
|
||||
}
|
||||
if c.DebugSwitch == gopay.DebugOn {
|
||||
xlog.Debugf("Wechat_Response: %d > %s", res.StatusCode, string(bs))
|
||||
xlog.Debugf("Wechat_Headers: %#v", res.Header)
|
||||
xlog.Debugf("Wechat_SignInfo: %#v", si)
|
||||
}
|
||||
return res, si, bs, nil
|
||||
}
|
||||
|
||||
func (c *ClientV3) doProdGet(ctx context.Context, uri, authorization string) (res *http.Response, si *SignInfo, bs []byte, err error) {
|
||||
var url = v3BaseUrlCh + uri
|
||||
httpClient := xhttp.NewClient()
|
||||
if c.DebugSwitch == gopay.DebugOn {
|
||||
xlog.Debugf("Wechat_V3_Url: %s", url)
|
||||
xlog.Debugf("Wechat_V3_Authorization: %s", authorization)
|
||||
}
|
||||
httpClient.Header.Add(HeaderAuthorization, authorization)
|
||||
httpClient.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix()))
|
||||
httpClient.Header.Add(HeaderSerial, c.WxSerialNo)
|
||||
httpClient.Header.Add("Accept", "*/*")
|
||||
res, bs, err = httpClient.Type(xhttp.TypeJSON).Get(url).EndBytes(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
si = &SignInfo{
|
||||
HeaderTimestamp: res.Header.Get(HeaderTimestamp),
|
||||
HeaderNonce: res.Header.Get(HeaderNonce),
|
||||
HeaderSignature: res.Header.Get(HeaderSignature),
|
||||
HeaderSerial: res.Header.Get(HeaderSerial),
|
||||
SignBody: string(bs),
|
||||
}
|
||||
if c.DebugSwitch == gopay.DebugOn {
|
||||
xlog.Debugf("Wechat_Response: %d > %s", res.StatusCode, string(bs))
|
||||
xlog.Debugf("Wechat_Headers: %#v", res.Header)
|
||||
xlog.Debugf("Wechat_SignInfo: %#v", si)
|
||||
}
|
||||
return res, si, bs, nil
|
||||
}
|
||||
|
||||
func (c *ClientV3) doProdPut(ctx context.Context, bm gopay.BodyMap, path, authorization string) (res *http.Response, si *SignInfo, bs []byte, err error) {
|
||||
var url = v3BaseUrlCh + path
|
||||
httpClient := xhttp.NewClient()
|
||||
if c.DebugSwitch == gopay.DebugOn {
|
||||
xlog.Debugf("Wechat_V3_RequestBody: %s", bm.JsonBody())
|
||||
xlog.Debugf("Wechat_V3_Authorization: %s", authorization)
|
||||
}
|
||||
httpClient.Header.Add(HeaderAuthorization, authorization)
|
||||
httpClient.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix()))
|
||||
httpClient.Header.Add(HeaderSerial, c.WxSerialNo)
|
||||
httpClient.Header.Add("Accept", "*/*")
|
||||
res, bs, err = httpClient.Type(xhttp.TypeJSON).Put(url).SendBodyMap(bm).EndBytes(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
si = &SignInfo{
|
||||
HeaderTimestamp: res.Header.Get(HeaderTimestamp),
|
||||
HeaderNonce: res.Header.Get(HeaderNonce),
|
||||
HeaderSignature: res.Header.Get(HeaderSignature),
|
||||
HeaderSerial: res.Header.Get(HeaderSerial),
|
||||
SignBody: string(bs),
|
||||
}
|
||||
if c.DebugSwitch == gopay.DebugOn {
|
||||
xlog.Debugf("Wechat_Response: %d > %s", res.StatusCode, string(bs))
|
||||
xlog.Debugf("Wechat_Headers: %#v", res.Header)
|
||||
xlog.Debugf("Wechat_SignInfo: %#v", si)
|
||||
}
|
||||
return res, si, bs, nil
|
||||
}
|
||||
|
||||
func (c *ClientV3) doProdDelete(ctx context.Context, bm gopay.BodyMap, path, authorization string) (res *http.Response, si *SignInfo, bs []byte, err error) {
|
||||
var url = v3BaseUrlCh + path
|
||||
httpClient := xhttp.NewClient()
|
||||
if c.DebugSwitch == gopay.DebugOn {
|
||||
xlog.Debugf("Wechat_V3_RequestBody: %s", bm.JsonBody())
|
||||
xlog.Debugf("Wechat_V3_Authorization: %s", authorization)
|
||||
}
|
||||
httpClient.Header.Add(HeaderAuthorization, authorization)
|
||||
httpClient.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix()))
|
||||
httpClient.Header.Add(HeaderSerial, c.WxSerialNo)
|
||||
httpClient.Header.Add("Accept", "*/*")
|
||||
res, bs, err = httpClient.Type(xhttp.TypeJSON).Delete(url).SendBodyMap(bm).EndBytes(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
si = &SignInfo{
|
||||
HeaderTimestamp: res.Header.Get(HeaderTimestamp),
|
||||
HeaderNonce: res.Header.Get(HeaderNonce),
|
||||
HeaderSignature: res.Header.Get(HeaderSignature),
|
||||
HeaderSerial: res.Header.Get(HeaderSerial),
|
||||
SignBody: string(bs),
|
||||
}
|
||||
if c.DebugSwitch == gopay.DebugOn {
|
||||
xlog.Debugf("Wechat_Response: %d > %s", res.StatusCode, string(bs))
|
||||
xlog.Debugf("Wechat_Headers: %#v", res.Header)
|
||||
xlog.Debugf("Wechat_SignInfo: %#v", si)
|
||||
}
|
||||
return res, si, bs, nil
|
||||
}
|
||||
|
||||
func (c *ClientV3) doProdPostFile(ctx context.Context, bm gopay.BodyMap, path, authorization string) (res *http.Response, si *SignInfo, bs []byte, err error) {
|
||||
var url = v3BaseUrlCh + path
|
||||
httpClient := xhttp.NewClient()
|
||||
if c.DebugSwitch == gopay.DebugOn {
|
||||
xlog.Debugf("Wechat_V3_RequestBody: %s", bm.GetString("meta"))
|
||||
xlog.Debugf("Wechat_V3_Authorization: %s", authorization)
|
||||
}
|
||||
httpClient.Header.Add(HeaderAuthorization, authorization)
|
||||
httpClient.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix()))
|
||||
httpClient.Header.Add(HeaderSerial, c.WxSerialNo)
|
||||
httpClient.Header.Add("Accept", "*/*")
|
||||
res, bs, err = httpClient.Type(xhttp.TypeMultipartFormData).Post(url).SendMultipartBodyMap(bm).EndBytes(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
si = &SignInfo{
|
||||
HeaderTimestamp: res.Header.Get(HeaderTimestamp),
|
||||
HeaderNonce: res.Header.Get(HeaderNonce),
|
||||
HeaderSignature: res.Header.Get(HeaderSignature),
|
||||
HeaderSerial: res.Header.Get(HeaderSerial),
|
||||
SignBody: string(bs),
|
||||
}
|
||||
if c.DebugSwitch == gopay.DebugOn {
|
||||
xlog.Debugf("Wechat_Response: %d > %s", res.StatusCode, string(bs))
|
||||
xlog.Debugf("Wechat_Headers: %#v", res.Header)
|
||||
xlog.Debugf("Wechat_SignInfo: %#v", si)
|
||||
}
|
||||
return res, si, bs, nil
|
||||
}
|
||||
|
||||
func (c *ClientV3) doProdPatch(ctx context.Context, bm gopay.BodyMap, path, authorization string) (res *http.Response, si *SignInfo, bs []byte, err error) {
|
||||
var url = v3BaseUrlCh + path
|
||||
httpClient := xhttp.NewClient()
|
||||
if c.DebugSwitch == gopay.DebugOn {
|
||||
xlog.Debugf("Wechat_V3_RequestBody: %s", bm.JsonBody())
|
||||
xlog.Debugf("Wechat_V3_Authorization: %s", authorization)
|
||||
}
|
||||
httpClient.Header.Add(HeaderAuthorization, authorization)
|
||||
httpClient.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix()))
|
||||
httpClient.Header.Add(HeaderSerial, c.WxSerialNo)
|
||||
httpClient.Header.Add("Accept", "*/*")
|
||||
res, bs, err = httpClient.Type(xhttp.TypeJSON).Patch(url).SendBodyMap(bm).EndBytes(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
si = &SignInfo{
|
||||
HeaderTimestamp: res.Header.Get(HeaderTimestamp),
|
||||
HeaderNonce: res.Header.Get(HeaderNonce),
|
||||
HeaderSignature: res.Header.Get(HeaderSignature),
|
||||
HeaderSerial: res.Header.Get(HeaderSerial),
|
||||
SignBody: string(bs),
|
||||
}
|
||||
if c.DebugSwitch == gopay.DebugOn {
|
||||
xlog.Debugf("Wechat_Response: %d > %s", res.StatusCode, string(bs))
|
||||
xlog.Debugf("Wechat_Headers: %#v", res.Header)
|
||||
xlog.Debugf("Wechat_SignInfo: %#v", si)
|
||||
}
|
||||
return res, si, bs, nil
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/pkg/util"
|
||||
)
|
||||
|
||||
// 创建投诉通知回调地址API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_2.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter10_2_2.shtml
|
||||
func (c *ClientV3) V3ComplaintNotifyUrlCreate(ctx context.Context, url string) (wxRsp *ComplaintNotifyUrlRsp, err error) {
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.Set("url", url)
|
||||
authorization, err := c.authorization(MethodPost, v3ComplaintNotifyUrlCreate, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3ComplaintNotifyUrlCreate, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &ComplaintNotifyUrlRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(ComplaintNotifyUrl)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询投诉通知回调地址API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_3.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter10_2_3.shtml
|
||||
func (c *ClientV3) V3ComplaintNotifyUrlQuery(ctx context.Context) (wxRsp *ComplaintNotifyUrlRsp, err error) {
|
||||
authorization, err := c.authorization(MethodGet, v3ComplaintNotifyUrlQuery, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, v3ComplaintNotifyUrlQuery, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &ComplaintNotifyUrlRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(ComplaintNotifyUrl)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 更新投诉通知回调地址API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_4.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter10_2_4.shtml
|
||||
func (c *ClientV3) V3ComplaintNotifyUrlUpdate(ctx context.Context, url string) (wxRsp *ComplaintNotifyUrlRsp, err error) {
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.Set("url", url)
|
||||
authorization, err := c.authorization(MethodPut, v3ComplaintNotifyUrlUpdate, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPut(ctx, bm, v3ComplaintNotifyUrlUpdate, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &ComplaintNotifyUrlRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(ComplaintNotifyUrl)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 删除投诉通知回调地址API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_5.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter10_2_5.shtml
|
||||
func (c *ClientV3) V3ComplaintNotifyUrlDelete(ctx context.Context) (wxRsp *EmptyRsp, err error) {
|
||||
authorization, err := c.authorization(MethodDelete, v3ComplaintNotifyUrlDelete, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdDelete(ctx, nil, v3ComplaintNotifyUrlDelete, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &EmptyRsp{Code: Success, SignInfo: si}
|
||||
if res.StatusCode != http.StatusNoContent {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 商户上传反馈图片API
|
||||
// 注意:图片不能超过2MB
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_10.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter10_2_10.shtml
|
||||
func (c *ClientV3) V3ComplaintUploadImage(ctx context.Context, fileName, fileSha256 string, img *util.File) (wxRsp *MediaUploadRsp, err error) {
|
||||
bmFile := make(gopay.BodyMap)
|
||||
bmFile.Set("filename", fileName).Set("sha256", fileSha256)
|
||||
authorization, err := c.authorization(MethodPost, v3ComplaintUploadImage, bmFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.SetBodyMap("meta", func(bm gopay.BodyMap) {
|
||||
bm.Set("filename", fileName).Set("sha256", fileSha256)
|
||||
}).SetFormFile("file", img)
|
||||
res, si, bs, err := c.doProdPostFile(ctx, bm, v3ComplaintUploadImage, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &MediaUploadRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(MediaUpload)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询投诉单列表API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_11.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter10_2_11.shtml
|
||||
func (c *ClientV3) V3ComplaintList(ctx context.Context, bm gopay.BodyMap) (wxRsp *ComplaintListRsp, err error) {
|
||||
uri := v3ComplaintList + "?" + bm.EncodeURLParams()
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &ComplaintListRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(ComplaintList)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询投诉协商历史API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_12.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter10_2_12.shtml
|
||||
func (c *ClientV3) V3ComplaintNegotiationHistory(ctx context.Context, complaintId string, bm gopay.BodyMap) (wxRsp *ComplaintNegotiationHistoryRsp, err error) {
|
||||
uri := fmt.Sprintf(v3ComplaintNegotiationHistory, complaintId) + "?" + bm.EncodeURLParams()
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &ComplaintNegotiationHistoryRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(ComplaintNegotiationHistory)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询投诉单详情API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_13.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter10_2_13.shtml
|
||||
func (c *ClientV3) V3ComplaintDetail(ctx context.Context, complaintId string) (wxRsp *ComplaintDetailRsp, err error) {
|
||||
url := fmt.Sprintf(v3ComplaintDetail, complaintId)
|
||||
authorization, err := c.authorization(MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &ComplaintDetailRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(ComplaintDetail)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 提交回复API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_14.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter10_2_14.shtml
|
||||
func (c *ClientV3) V3ComplaintResponse(ctx context.Context, complaintId string, bm gopay.BodyMap) (wxRsp *EmptyRsp, err error) {
|
||||
url := fmt.Sprintf(v3ComplaintResponse, complaintId)
|
||||
authorization, err := c.authorization(MethodPost, url, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &EmptyRsp{Code: Success, SignInfo: si}
|
||||
if res.StatusCode != http.StatusNoContent {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 反馈处理完成API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_15.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter10_2_15.shtml
|
||||
func (c *ClientV3) V3ComplaintComplete(ctx context.Context, complaintId string, bm gopay.BodyMap) (wxRsp *EmptyRsp, err error) {
|
||||
url := fmt.Sprintf(v3ComplaintComplete, complaintId)
|
||||
authorization, err := c.authorization(MethodPost, url, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &EmptyRsp{Code: Success, SignInfo: si}
|
||||
if res.StatusCode != http.StatusNoContent {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
package wechat
|
||||
|
||||
const (
|
||||
Success = 0
|
||||
SignTypeRSA = "RSA"
|
||||
|
||||
MethodGet = "GET"
|
||||
MethodPost = "POST"
|
||||
MethodPut = "PUT"
|
||||
MethodDelete = "DELETE"
|
||||
MethodPATCH = "PATCH"
|
||||
HeaderAuthorization = "Authorization"
|
||||
HeaderRequestID = "Request-ID"
|
||||
|
||||
HeaderTimestamp = "Wechatpay-Timestamp"
|
||||
HeaderNonce = "Wechatpay-Nonce"
|
||||
HeaderSignature = "Wechatpay-Signature"
|
||||
HeaderSerial = "Wechatpay-Serial"
|
||||
|
||||
Authorization = "WECHATPAY2-SHA256-RSA2048"
|
||||
|
||||
v3BaseUrlCh = "https://api.mch.weixin.qq.com" // 中国国内
|
||||
|
||||
v3GetCerts = "/v3/certificates"
|
||||
// 基础支付(直连模式)
|
||||
v3ApiApp = "/v3/pay/transactions/app" // APP 下单
|
||||
v3ApiJsapi = "/v3/pay/transactions/jsapi" // JSAPI 下单
|
||||
v3ApiNative = "/v3/pay/transactions/native" // Native 下单
|
||||
v3ApiH5 = "/v3/pay/transactions/h5" // H5 下单
|
||||
v3ApiQueryOrderTransactionId = "/v3/pay/transactions/id/%s" // transaction_id 查询订单
|
||||
v3ApiQueryOrderOutTradeNo = "/v3/pay/transactions/out-trade-no/%s" // out_trade_no 查询订单
|
||||
v3ApiCloseOrder = "/v3/pay/transactions/out-trade-no/%s/close" // out_trade_no 关闭订单
|
||||
|
||||
// 基础支付(服务商模式)
|
||||
v3ApiPartnerPayApp = "/v3/pay/partner/transactions/app" // partner APP 下单
|
||||
v3ApiPartnerJsapi = "/v3/pay/partner/transactions/jsapi" // partner JSAPI 下单
|
||||
v3ApiPartnerNative = "/v3/pay/partner/transactions/native" // partner Native 下单
|
||||
v3ApiPartnerH5 = "/v3/pay/partner/transactions/h5" // partner H5 下单
|
||||
v3ApiPartnerQueryOrderTransactionId = "/v3/pay/partner/transactions/id/%s" // partner transaction_id 查询订单
|
||||
v3ApiPartnerQueryOrderOutTradeNo = "/v3/pay/partner/transactions/out-trade-no/%s" // partner out_trade_no 查询订单
|
||||
v3ApiPartnerCloseOrder = "/v3/pay/partner/transactions/out-trade-no/%s/close" // partner out_trade_no 关闭订单
|
||||
|
||||
// 基础支付(合单支付)
|
||||
v3CombinePayApp = "/v3/combine-transactions/app"
|
||||
v3CombinePayH5 = "/v3/combine-transactions/h5"
|
||||
v3CombinePayJsapi = "/v3/combine-transactions/jsapi"
|
||||
v3CombineNative = "/v3/combine-transactions/native"
|
||||
v3CombineQuery = "/v3/combine-transactions/out-trade-no/%s"
|
||||
v3CombineClose = "/v3/combine-transactions/out-trade-no/%s/close"
|
||||
|
||||
// 退款
|
||||
v3DomesticRefund = "/v3/refund/domestic/refunds" // 申请退款
|
||||
v3DomesticRefundQuery = "/v3/refund/domestic/refunds/%s" // 查询单笔退款
|
||||
|
||||
// 账单
|
||||
v3TradeBill = "/v3/bill/tradebill" // 申请交易账单 GET
|
||||
v3FundFlowBill = "/v3/bill/fundflowbill" // 申请资金账单 GET
|
||||
v3EcommerceFundFlowBill = "/v3/ecommerce/bill/fundflowbill" // 申请特约商户资金账单 GET
|
||||
v3SubFundFlowBill = "/v3/bill/sub-merchant-fundflowbill" // 申请单个子商户资金账单 GET
|
||||
|
||||
// 提现
|
||||
v3Withdraw = "/v3/ecommerce/fund/withdraw" // 特约商户余额提现 POST
|
||||
v3WithdrawStatusById = "/v3/ecommerce/fund/withdraw/%s" // withdraw_id 查询特约商户提现状态 GET
|
||||
v3WithdrawStatusByNo = "/v3/ecommerce/fund/withdraw/out-request-no/%s" // out_request_no 查询特约商户提现状态 GET
|
||||
v3EcommerceWithdraw = "/v3/merchant/fund/withdraw" // 电商平台预约提现 POST
|
||||
v3EcommerceWithdrawStatusById = "/v3/merchant/fund/withdraw/withdraw-id/%s" // withdraw_id 电商平台查询预约提现状态 POST
|
||||
v3EcommerceWithdrawStatusByNo = "/v3/merchant/fund/withdraw/out-request-no/%s" // out_request_no 电商平台查询预约提现状态 POST
|
||||
v3WithdrawDownloadErrBill = "/v3/merchant/fund/withdraw/bill-type/%s" // bill_type 按日下载提现异常文件 GET
|
||||
|
||||
// 微信支付分(免确认模式)
|
||||
v3ScoreDirectComplete = "/payscore/serviceorder/direct-complete" // 创单结单合并 POST
|
||||
|
||||
// 微信支付分(免确认预授权模式)
|
||||
v3ScorePermission = "/v3/payscore/permissions" // 商户预授权 POST
|
||||
v3ScorePermissionQuery = "/v3/payscore/permissions/authorization-code/%s" // authorization_code 查询用户授权记录(授权协议号) GET
|
||||
v3ScorePermissionTerminate = "/v3/payscore/permissions/authorization-code/%s/terminate" // authorization_code 解除用户授权关系(授权协议号) POST
|
||||
v3ScorePermissionOpenidQuery = "/v3/payscore/permissions/openid/%s" // openid 查询用户授权记录(openid) GET
|
||||
v3ScorePermissionOpenidTerminate = "/v3/payscore/permissions/openid/%s/terminate" // openid 解除用户授权记录(openid) POST
|
||||
|
||||
// 微信支付分(公共API)
|
||||
v3ScoreOrderCreate = "/v3/payscore/serviceorder" // 创建支付分订单 POST
|
||||
v3ScoreOrderQuery = "/v3/payscore/serviceorder" // 查询支付分订单 GET
|
||||
v3ScoreOrderCancel = "/v3/payscore/serviceorder/%s/cancel" // out_trade_no 取消支付分订单 POST
|
||||
v3ScoreOrderModify = "/v3/payscore/serviceorder/%s/modify" // out_trade_no 修改订单金额 POST
|
||||
v3ScoreOrderComplete = "/v3/payscore/serviceorder/%s/complete" // out_trade_no 完结支付分订单 POST
|
||||
v3ScoreOrderPay = "/v3/payscore/serviceorder/%s/pay" // out_trade_no 商户发起催收扣款 POST
|
||||
v3ScoreOrderSync = "/v3/payscore/serviceorder/%s/sync" // out_trade_no 同步服务订单信息 POST
|
||||
|
||||
// 微信先享卡
|
||||
v3CardPre = "/v3/discount-card/cards" // 预受理领卡请求 POST
|
||||
v3CardAddUser = "/v3/discount-card/cards/%s/add-user-records" // out_card_code 增加用户记录 POST
|
||||
v3CardQuery = "/v3/discount-card/cards/%s" // out_card_code 查询先享卡订单 GET
|
||||
|
||||
// 支付即服务
|
||||
v3GuideReg = "/v3/smartguide/guides" // 服务人员注册 POST
|
||||
v3GuideAssign = "/v3/smartguide/guides/%s/assign" // guide_id 服务人员分配 POST
|
||||
v3GuideQuery = "/v3/smartguide/guides" // 服务人员查询 GET
|
||||
v3GuideUpdate = "/v3/smartguide/guides/%s" // guide_id 服务人员信息更新 PATCH
|
||||
|
||||
// 智慧商圈
|
||||
v3BusinessPointsSync = "/v3/businesscircle/points/notify" // 商圈积分同步 POST
|
||||
v3BusinessAuthPointsQuery = "/v3/businesscircle/user-authorizations/%s" // openid 商圈积分授权查询 GET
|
||||
|
||||
// 代金券
|
||||
v3FavorBatchCreate = "/v3/marketing/favor/coupon-stocks" // 创建代金券批次 POST
|
||||
v3FavorBatchStart = "/v3/marketing/favor/stocks/%s/start" // stock_id 激活代金券批次 POST
|
||||
v3FavorBatchGrant = "/v3/marketing/favor/users/%s/coupons" // openid 发放代金券批次 POST
|
||||
v3FavorBatchPause = "/v3/marketing/favor/stocks/%s/pause" // stock_id 暂停代金券批次 POST
|
||||
v3FavorBatchRestart = "/v3/marketing/favor/stocks/%s/restart" // stock_id 重启代金券批次 POST
|
||||
v3FavorBatchList = "/v3/marketing/favor/stocks" // 条件查询批次列表 GET
|
||||
v3FavorBatchDetail = "/v3/marketing/favor/stocks/%s" // stock_id 查询批次详情 GET
|
||||
v3FavorDetail = "/v3/marketing/favor/users/%s/coupons/%s" // openid、coupon_id 查询代金券详情 GET
|
||||
v3FavorMerchant = "/v3/marketing/favor/stocks/%s/merchants" // stock_id 查询代金券可用商户 GET
|
||||
v3FavorItems = "/v3/marketing/favor/stocks/%s/items" // stock_id 查询代金券可用单品 GET
|
||||
v3FavorUserCoupons = "/v3/marketing/favor/users/%s/coupons" // openid 根据商户号查用户的券 GET
|
||||
v3FavorUseFlowDownload = "/v3/marketing/favor/stocks/%s/use-flow" // stock_id 下载批次核销明细 GET
|
||||
v3FavorRefundFlowDownload = "/v3/marketing/favor/stocks/%s/refund-flow" // stock_id 下载批次退款明细 GET
|
||||
v3FavorCallbackUrlSet = "/v3/marketing/favor/callbacks" // 设置消息通知地址 POST
|
||||
v3FavorMediaUploadImage = "/v3/marketing/favor/media/image-upload" // 图片上传(营销专用) POST
|
||||
|
||||
// 商家券
|
||||
v3BusiFavorBatchCreate = "/v3/marketing/busifavor/stocks" // 创建商家券 POST
|
||||
v3BusiFavorBatchDetail = "/v3/marketing/busifavor/stocks/%s" // stock_id 查询商家券详情 GET
|
||||
v3BusiFavorUse = "/v3/marketing/busifavor/coupons/use" // 核销用户券 POST
|
||||
v3BusiFavorUserCoupons = "/v3/marketing/busifavor/users/%s/coupons" // openid 根据过滤条件查询用户券 GET
|
||||
v3BusiFavorUserCouponDetail = "/v3/marketing/busifavor/users/%s/coupons/%s/appids/%s" // openid、coupon_code、appid 查询用户单张券详情 GET
|
||||
v3BusiFavorCodeUpload = "/v3/marketing/busifavor/stocks/%s/couponcodes" // stock_id 上传预存code POST
|
||||
v3BusiFavorCallbackUrlSet = "/v3/marketing/busifavor/callbacks" // 设置商家券事件通知地址 POST
|
||||
v3BusiFavorCallbackUrl = "/v3/marketing/busifavor/callbacks" // 查询商家券事件通知地址 GET
|
||||
v3BusiFavorAssociate = "/v3/marketing/busifavor/coupons/associate" // 关联订单信息 POST
|
||||
v3BusiFavorDisassociate = "/v3/marketing/busifavor/coupons/disassociate" // 取消关联订单信息 POST
|
||||
v3BusiFavorBatchUpdate = "/v3/marketing/busifavor/stocks/%s/budget" // stock_id 修改批次预算 PATCH
|
||||
v3BusiFavorInfoUpdate = "/v3/marketing/busifavor/stocks/%s" // stock_id 修改商家券基本信息 PATCH
|
||||
v3BusiFavorSend = "/v3/marketing/busifavor/coupons/%s/send" // card_id 发放消费卡 POST
|
||||
v3BusiFavorReturn = "/v3/marketing/busifavor/coupons/return" // 申请退券 POST
|
||||
v3BusiFavorDeactivate = "/v3/marketing/busifavor/coupons/deactivate" // 使券失效 POST
|
||||
v3BusiFavorSubsidyPay = "/v3/marketing/busifavor/subsidy/pay-receipts" // 营销补差付款 POST
|
||||
v3BusiFavorSubsidyPayDetail = "/v3/marketing/busifavor/subsidy/pay-receipts/%s" // subsidy_receipt_id 查询营销补差付款单详情 GET
|
||||
|
||||
// 委托营销(合作伙伴)
|
||||
v3PartnershipsBuild = "/v3/marketing/partnerships/build" // 建立合作关系 POST
|
||||
v3PartnershipsTerminate = "/v3/marketing/partnerships/terminate" // 终止合作关系 POST
|
||||
v3PartnershipsList = "/v3/marketing/partnerships" // 查询合作关系列表 GET
|
||||
|
||||
// 点金计划(服务商)
|
||||
v3GoldPlanManage = "/v3/goldplan/merchants/changegoldplanstatus" // 点金计划管理 POST
|
||||
v3GoldPlanBillManage = "/v3/goldplan/merchants/changecustompagestatus" // 商家小票管理 POST
|
||||
v3GoldPlanFilterManage = "/v3/goldplan/merchants/set-advertising-industry-filter" // 同业过滤标签管理 POST
|
||||
v3GoldPlanOpenAdShow = "/v3/goldplan/merchants/open-advertising-show" // 开通广告展示 PATCH
|
||||
v3GoldPlanCloseAdShow = "/v3/goldplan/merchants/close-advertising-show" // 关闭广告展示 POST
|
||||
|
||||
// 消费者投诉2.0
|
||||
v3ComplaintList = "/v3/merchant-service/complaints-v2" // 查询投诉单列表 GET
|
||||
v3ComplaintDetail = "/v3/merchant-service/complaints-v2/%s" // 查询投诉单详情 GET
|
||||
v3ComplaintNegotiationHistory = "/v3/merchant-service/complaints-v2/%s/negotiation-historys" // 查询投诉协商历史 GET
|
||||
v3ComplaintNotifyUrlCreate = "/v3/merchant-service/complaint-notifications" // 创建投诉通知回调地址 POST
|
||||
v3ComplaintNotifyUrlQuery = "/v3/merchant-service/complaint-notifications" // 查询投诉通知回调地址 GET
|
||||
v3ComplaintNotifyUrlUpdate = "/v3/merchant-service/complaint-notifications" // 查询投诉通知回调地址 PUT
|
||||
v3ComplaintNotifyUrlDelete = "/v3/merchant-service/complaint-notifications" // 删除投诉通知回调地址 DELETE
|
||||
v3ComplaintResponse = "/v3/merchant-service/complaints-v2/%s/response" // 提交回复 POST
|
||||
v3ComplaintComplete = "/v3/merchant-service/complaints-v2/%s/complete" // 反馈处理完成 POST
|
||||
v3ComplaintUploadImage = "/v3/merchant-service/images/upload" // 商户上传反馈图片 POST
|
||||
|
||||
// 分账(服务商)
|
||||
v3ProfitShareOrder = "/v3/profitsharing/orders" // 请求分账 POST
|
||||
v3ProfitShareQuery = "/v3/profitsharing/orders/%s" // 查询分账结果 GET
|
||||
v3ProfitShareReturn = "/v3/profitsharing/return-orders" // 请求分账回退 POST
|
||||
v3ProfitShareReturnResult = "/v3/profitsharing/return-orders/%s" // 查询分账回退结果 GET
|
||||
v3ProfitShareUnfreeze = "/v3/profitsharing/orders/unfreeze" // 解冻剩余资金 POST
|
||||
v3ProfitShareUnsplitAmount = "/v3/profitsharing/transactions/%s/amounts" // 查询剩余待分金额 GET
|
||||
v3ProfitShareAddReceiver = "/v3/profitsharing/receivers/add" // 添加分账接收方 POST
|
||||
v3ProfitShareDeleteReceiver = "/v3/profitsharing/receivers/delete" // 删除分账接收方 POST
|
||||
v3ProfitShareMerchantConfigs = "/v3/profitsharing/merchant-configs/%s" // 查询最大分账比例API GET
|
||||
v3ProfitShareBills = "/v3/profitsharing/bills" // 申请分账账单 GET
|
||||
|
||||
// 其他能力
|
||||
v3MediaUploadImage = "/v3/merchant/media/upload" // 图片上传 POST
|
||||
v3MediaUploadVideo = "/v3/merchant/media/video_upload" // 视频上传 POST
|
||||
|
||||
// 转账
|
||||
v3Transfer = "/v3/transfer/batches" // 发起批量转账 POST
|
||||
v3TransferQuery = "/v3/transfer/batches/batch-id/%s" // batch_id 微信批次单号查询批次单 GET
|
||||
v3TransferDetail = "/v3/transfer/batches/batch-id/%s/details/detail-id/%s" // batch_id、detail_id 微信明细单号查询明细单 GET
|
||||
v3TransferMerchantQuery = "/v3/transfer/batches/out-batch-no/%s" // out_batch_no 商家批次单号查询批次单 GET
|
||||
v3TransferMerchantDetail = "/v3/transfer/batches/out-batch-no/%s/details/out-detail-no/%s" // out_batch_no、out_detail_no 商家明细单号查询明细单 GET
|
||||
v3TransferReceipt = "/v3/transfer/bill-receipt" // 转账电子回单申请受理 POST
|
||||
v3TransferReceiptQuery = "/v3/transfer/bill-receipt/%s" // out_batch_no 查询转账电子回单 GET
|
||||
v3TransferDetailReceipt = "/v3/transfer-detail/electronic-receipts" // 转账明细电子回单受理 POST
|
||||
v3TransferDetailReceiptQuery = "/v3/transfer-detail/electronic-receipts" // 查询转账明细电子回单受理结果 GET
|
||||
|
||||
// 转账(服务商)
|
||||
v3PartnerTransfer = "/v3/partner-transfer/batches" // 发起批量转账 POST
|
||||
v3PartnerTransferQuery = "/v3/partner-transfer/batches/batch-id/%s" // batch_id 微信批次单号查询批次单 GET
|
||||
v3PartnerTransferDetail = "/v3/partner-transfer/batches/batch-id/%s/details/detail-id/%s" // batch_id、detail_id 微信明细单号查询明细单 GET
|
||||
v3PartnerTransferMerchantQuery = "/v3/partner-transfer/batches/out-batch-no/%s" // out_batch_no 商家批次单号查询批次单 GET
|
||||
v3PartnerTransferMerchantDetail = "/v3/partner-transfer/batches/out-batch-no/%s/details/out-detail-no/%s" // out_batch_no、out_detail_no 商家明细单号查询明细单 GET
|
||||
|
||||
// 余额
|
||||
v3MerchantBalance = "/v3/merchant/fund/balance/%s" // account_type 查询账户实时余额 GET
|
||||
v3MerchantDayBalance = "/v3/merchant/fund/dayendbalance/%s" // account_type 查询账户日终余额 GET
|
||||
v3EcommerceBalance = "/v3/ecommerce/fund/balance/%s" // sub_mchid 查询特约商户账户实时余额 GET
|
||||
v3EcommerceDayBalance = "/v3/ecommerce/fund/enddaybalance/%s" // sub_mchid 查询二级商户账户日终余额 GET
|
||||
|
||||
// 来账识别API
|
||||
v3MerchantIncomeRecord = "/v3/merchantfund/merchant/income-records" // 商户银行来账查询 GET
|
||||
v3EcommerceIncomeRecord = "/v3/merchantfund/partner/income-records" // 特约商户银行来账查询 GET
|
||||
|
||||
// 服务商-特约商户进件
|
||||
v3Apply4SubSubmit = "/v3/applyment4sub/applyment/" // 提交申请单 POST
|
||||
v3Apply4SubQueryByBusinessCode = "/v3/applyment4sub/applyment/business_code/%s" // business_code 通过业务申请编号查询申请状态 GET
|
||||
v3Apply4SubQueryByApplyId = "/v3/applyment4sub/applyment/applyment_id/%s" // applyment_id 通过申请单号查询申请状态 GET
|
||||
v3Apply4SubModifySettlement = "/v3/apply4sub/sub_merchants/%s/modify-settlement" // sub_mchid 修改结算账号 POST
|
||||
v3Apply4SubQuerySettlement = "/v3/apply4sub/sub_merchants/%s/settlement" // sub_mchid 查询结算账户 GET
|
||||
|
||||
// 电商收付通(商户进件)
|
||||
v3EcommerceApply = "/v3/ecommerce/applyments/" // 二级商户进件 POST
|
||||
v3EcommerceApplyQueryById = "/v3/ecommerce/applyments/%d" // applyment_id 通过申请单ID查询申请状态 GET
|
||||
v3EcommerceApplyQueryByNo = "/v3/ecommerce/applyments/out-request-no/%s" // out_request_no 通过业务申请编号查询申请状态 GET
|
||||
|
||||
// 电商收付通(分账)
|
||||
v3EcommerceProfitShare = "/v3/ecommerce/profitsharing/orders" // 请求分账 POST
|
||||
v3EcommerceProfitShareQuery = "/v3/ecommerce/profitsharing/orders" // 查询分账结果 GET
|
||||
v3EcommerceProfitShareReturn = "/v3/ecommerce/profitsharing/returnorders" // 请求分账回退 POST
|
||||
v3EcommerceProfitShareReturnResult = "/v3/ecommerce/profitsharing/returnorders" // 查询分账回退结果 GET
|
||||
v3EcommerceProfitShareFinish = "/v3/ecommerce/profitsharing/finish-order" // 完结分账 POST
|
||||
v3EcommerceProfitShareUnsplitAmount = "/v3/ecommerce/profitsharing/orders/%s/amounts" // transaction_id 查询订单剩余待分金额 GET
|
||||
v3EcommerceProfitShareAddReceiver = "/v3/ecommerce/profitsharing/receivers/add" // 添加分账接收方 POST
|
||||
v3EcommerceProfitShareDeleteReceiver = "/v3/ecommerce/profitsharing/receivers/delete" // 删除分账接收方 POST
|
||||
|
||||
// 电商收付通(补差)
|
||||
v3EcommerceSubsidies = "/v3/ecommerce/subsidies/create" // 请求补差 POST
|
||||
v3EcommerceSubsidiesReturn = "/v3/ecommerce/subsidies/return" // 请求补差回退 POST
|
||||
v3EcommerceSubsidiesCancel = "/v3/ecommerce/subsidies/cancel" // 取消补差 POST
|
||||
|
||||
// 电商收付通(退款)
|
||||
v3CommerceRefund = "/v3/ecommerce/refunds/apply" // 申请退款 POST
|
||||
v3CommerceRefundQueryById = "/v3/ecommerce/refunds/id/%s" // refund_id 通过微信支付退款单号查询退款 GET
|
||||
v3CommerceRefundQueryByNo = "/v3/ecommerce/refunds/out-refund-no/%s" // out_refund_no 通过商户退款单号查询退款 GET
|
||||
v3CommerceRefundAdvance = "/v3/ecommerce/refunds/%s/return-advance" // refund_id 垫付退款回补 POST
|
||||
v3CommerceRefundAdvanceResult = "/v3/ecommerce/refunds/%s/return-advance" // refund_id 查询垫付回补结果 GET
|
||||
|
||||
// 银行组件(服务商)
|
||||
v3BankSearchBank = "/v3/capital/capitallhh/banks/search-banks-by-bank-account" // 获取对私银行卡号开户银行 GET
|
||||
v3BankSearchPersonalList = "/v3/capital/capitallhh/banks/personal-banking" // 查询支持个人业务的银行列表 GET
|
||||
v3BankSearchCorporateList = "/v3/capital/capitallhh/banks/corporate-banking" // 查询支持对公业务的银行列表 GET
|
||||
v3BankSearchProvinceList = "/v3/capital/capitallhh/areas/provinces" // 查询省份列表 GET
|
||||
v3BankSearchCityList = "/v3/capital/capitallhh/areas/provinces/%d/cities" // province_code 查询城市列表 GET
|
||||
v3BankSearchBranchList = "/v3/capital/capitallhh/banks/%s/branches" // bank_alias_code 查询支行列表 GET
|
||||
|
||||
// 特约商户进件申请单状态
|
||||
ApplyStateEditing = "APPLYMENT_STATE_EDITTING" // 编辑中
|
||||
ApplyStateAuditing = "APPLYMENT_STATE_AUDITING" // 审核中
|
||||
ApplyStateRejected = "APPLYMENT_STATE_REJECTED" // 已驳回
|
||||
ApplyStateToBeConfirmed = "APPLYMENT_STATE_TO_BE_CONFIRMED" // 待账户验证
|
||||
ApplyStateSigning = "APPLYMENT_STATE_SIGNING" // 开通权限中
|
||||
ApplyStateFinished = "APPLYMENT_STATE_FINISHED" // 已完成
|
||||
ApplyStateCanceled = "APPLYMENT_STATE_CANCELED" // 已作废
|
||||
|
||||
// 特约商户结算账号类型
|
||||
ApplySettlementAccountTypeBusiness = "ACCOUNT_TYPE_BUSINESS" // 对公银行账户
|
||||
ApplySettlementAccountTypePrivate = "ACCOUNT_TYPE_PRIVATE" // 经营者个人银行卡
|
||||
|
||||
// 特约商户结算账号汇款验证结果
|
||||
ApplySettlementVerifying = "VERIFYING" // 系统汇款验证中,商户可发起提现尝试
|
||||
ApplySettlementVerifySuccess = "VERIFY_SUCCESS" // 系统成功汇款,该账户可正常发起提现
|
||||
ApplySettlementVerifyFail = "VERIFY_FAIL" // 系统汇款失败,该账户无法发起提现,请检查修改
|
||||
|
||||
// 订单号类型,1-微信订单号,2-商户订单号,3-微信侧回跳到商户前端时用于查单的单据查询id(查询支付分订单中会使用)
|
||||
TransactionId OrderNoType = 1
|
||||
OutTradeNo OrderNoType = 2
|
||||
QueryId OrderNoType = 3
|
||||
|
||||
// v3 异步通知订单状态
|
||||
TradeStateSuccess = "SUCCESS" // 支付成功
|
||||
TradeStateRefund = "REFUND" // 转入退款
|
||||
TradeStateNoPay = "NOTPAY" // 未支付
|
||||
TradeStateClosed = "CLOSED" // 已关闭
|
||||
TradeStateRevoked = "REVOKED" // 已撤销(付款码支付)
|
||||
TradeStatePaying = "USERPAYING" // 用户支付中(付款码支付)
|
||||
TradeStatePayError = "PAYERROR" // 支付失败(其他原因,如银行返回失败)
|
||||
)
|
||||
@@ -0,0 +1,87 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
)
|
||||
|
||||
// 预受理领卡请求API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_3_1.shtml
|
||||
func (c *ClientV3) V3DiscountCardApply(ctx context.Context, bm gopay.BodyMap) (wxRsp *DiscountCardApplyRsp, err error) {
|
||||
authorization, err := c.authorization(MethodPost, v3CardPre, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3CardPre, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &DiscountCardApplyRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(DiscountCardApply)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 增加用户记录API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_3_2.shtml
|
||||
func (c *ClientV3) V3DiscountCardAddUser(ctx context.Context, bm gopay.BodyMap) (wxRsp *EmptyRsp, err error) {
|
||||
if err = bm.CheckEmptyError("out_card_code"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uri := fmt.Sprintf(v3CardAddUser, bm.GetString("out_card_code"))
|
||||
bm.Remove("out_card_code")
|
||||
authorization, err := c.authorization(MethodPost, uri, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &EmptyRsp{Code: Success, SignInfo: si}
|
||||
if res.StatusCode != http.StatusNoContent {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询先享卡订单API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_3_3.shtml
|
||||
func (c *ClientV3) V3DiscountCardQuery(ctx context.Context, outCardCode string) (wxRsp *DiscountCardQueryRsp, err error) {
|
||||
url := fmt.Sprintf(v3CardQuery, outCardCode)
|
||||
authorization, err := c.authorization(MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &DiscountCardQueryRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(DiscountCardQuery)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
)
|
||||
|
||||
// 二级商户进件API
|
||||
// 注意:本接口会提交一些敏感信息,需调用 client.V3EncryptText() 进行加密。部分图片参数,请先调用 client.V3MediaUploadImage() 上传,获取MediaId
|
||||
// Code = 0 is success
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_1_1.shtml
|
||||
func (c *ClientV3) V3EcommerceApply(ctx context.Context, bm gopay.BodyMap) (*EcommerceApplyRsp, error) {
|
||||
authorization, err := c.authorization(MethodPost, v3EcommerceApply, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3EcommerceApply, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp := &EcommerceApplyRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(EcommerceApply)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询申请状态API
|
||||
// 注意:applyId 和 outRequestNo 二选一
|
||||
// Code = 0 is success
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_1_2.shtml
|
||||
func (c *ClientV3) V3EcommerceApplyStatus(ctx context.Context, applyId int64, outRequestNo string) (*EcommerceApplyStatusRsp, error) {
|
||||
if applyId == 0 && outRequestNo == gopay.NULL {
|
||||
return nil, fmt.Errorf("applyId[%d] and outRequestNo[%s] empty at the same time", applyId, outRequestNo)
|
||||
}
|
||||
var url string
|
||||
if applyId != 0 {
|
||||
url = fmt.Sprintf(v3EcommerceApplyQueryById, applyId)
|
||||
} else {
|
||||
url = fmt.Sprintf(v3EcommerceApplyQueryByNo, outRequestNo)
|
||||
}
|
||||
authorization, err := c.authorization(MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp := &EcommerceApplyStatusRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(EcommerceApplyStatus)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 请求分账API
|
||||
// Code = 0 is success
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_4_1.shtml
|
||||
func (c *ClientV3) V3EcommerceProfitShare(ctx context.Context, bm gopay.BodyMap) (*EcommerceProfitShareRsp, error) {
|
||||
authorization, err := c.authorization(MethodPost, v3EcommerceProfitShare, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3EcommerceProfitShare, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &EcommerceProfitShareRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(EcommerceProfitShare)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询分账结果API
|
||||
// Code = 0 is success
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_4_2.shtml
|
||||
func (c *ClientV3) V3EcommerceProfitShareQuery(ctx context.Context, bm gopay.BodyMap) (*EcommerceProfitShareQueryRsp, error) {
|
||||
uri := v3EcommerceProfitShareQuery + "?" + bm.EncodeURLParams()
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &EcommerceProfitShareQueryRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(EcommerceProfitShareQuery)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 请求分账回退API
|
||||
// Code = 0 is success
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_4_3.shtml
|
||||
func (c *ClientV3) V3EcommerceProfitShareReturn(ctx context.Context, bm gopay.BodyMap) (*EcommerceProfitShareReturnRsp, error) {
|
||||
authorization, err := c.authorization(MethodPost, v3EcommerceProfitShareReturn, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3EcommerceProfitShareReturn, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &EcommerceProfitShareReturnRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(EcommerceProfitShareReturn)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询分账回退结果API
|
||||
// Code = 0 is success
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_4_4.shtml
|
||||
func (c *ClientV3) V3EcommerceProfitShareReturnResult(ctx context.Context, bm gopay.BodyMap) (*EcommerceProfitShareReturnResultRsp, error) {
|
||||
uri := v3EcommerceProfitShareReturnResult + "?" + bm.EncodeURLParams()
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &EcommerceProfitShareReturnResultRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(EcommerceProfitShareReturn)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 完结分账API
|
||||
// Code = 0 is success
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_4_5.shtml
|
||||
func (c *ClientV3) V3EcommerceProfitShareFinish(ctx context.Context, bm gopay.BodyMap) (*EcommerceProfitShareFinishRsp, error) {
|
||||
authorization, err := c.authorization(MethodPost, v3EcommerceProfitShareFinish, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3EcommerceProfitShareFinish, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &EcommerceProfitShareFinishRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(EcommerceProfitShareFinish)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询订单剩余待分金额API
|
||||
// Code = 0 is success
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_4_9.shtml
|
||||
func (c *ClientV3) V3EcommerceProfitShareUnsplitAmount(ctx context.Context, transactionId string) (*EcommerceProfitShareUnsplitAmountRsp, error) {
|
||||
url := fmt.Sprintf(v3EcommerceProfitShareUnsplitAmount, transactionId)
|
||||
authorization, err := c.authorization(MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &EcommerceProfitShareUnsplitAmountRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(EcommerceProfitShareUnsplitAmount)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 添加分账接收方API
|
||||
// Code = 0 is success
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_4_7.shtml
|
||||
func (c *ClientV3) V3EcommerceProfitShareAddReceiver(ctx context.Context, bm gopay.BodyMap) (*EcommerceProfitShareAddReceiverRsp, error) {
|
||||
authorization, err := c.authorization(MethodPost, v3EcommerceProfitShareAddReceiver, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3EcommerceProfitShareAddReceiver, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &EcommerceProfitShareAddReceiverRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(EcommerceProfitShareReceiver)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 删除分账接收方API
|
||||
// Code = 0 is success
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_4_8.shtml
|
||||
func (c *ClientV3) V3EcommerceProfitShareDeleteReceiver(ctx context.Context, bm gopay.BodyMap) (*EcommerceProfitShareDeleteReceiverRsp, error) {
|
||||
authorization, err := c.authorization(MethodPost, v3EcommerceProfitShareDeleteReceiver, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3EcommerceProfitShareDeleteReceiver, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &EcommerceProfitShareDeleteReceiverRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(EcommerceProfitShareReceiver)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 请求补差API
|
||||
// Code = 0 is success
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_5_1.shtml
|
||||
func (c *ClientV3) V3EcommerceSubsidies(ctx context.Context, bm gopay.BodyMap) (*EcommerceSubsidiesRsp, error) {
|
||||
authorization, err := c.authorization(MethodPost, v3EcommerceSubsidies, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3EcommerceSubsidies, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &EcommerceSubsidiesRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(EcommerceSubsidies)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 请求补差回退API
|
||||
// Code = 0 is success
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_5_2.shtml
|
||||
func (c *ClientV3) V3EcommerceSubsidiesReturn(ctx context.Context, bm gopay.BodyMap) (*EcommerceSubsidiesReturnRsp, error) {
|
||||
authorization, err := c.authorization(MethodPost, v3EcommerceSubsidiesReturn, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3EcommerceSubsidiesReturn, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &EcommerceSubsidiesReturnRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(EcommerceSubsidiesReturn)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 取消补差API
|
||||
// Code = 0 is success
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_5_3.shtml
|
||||
func (c *ClientV3) V3EcommerceSubsidiesCancel(ctx context.Context, bm gopay.BodyMap) (*EcommerceSubsidiesCancelRsp, error) {
|
||||
authorization, err := c.authorization(MethodPost, v3EcommerceSubsidiesCancel, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3EcommerceSubsidiesCancel, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &EcommerceSubsidiesCancelRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(EcommerceSubsidiesCancel)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/pkg/aes"
|
||||
"github.com/go-pay/gopay/pkg/util"
|
||||
"github.com/go-pay/gopay/pkg/xpem"
|
||||
)
|
||||
|
||||
// 敏感信息加密
|
||||
func (c *ClientV3) V3EncryptText(text string) (cipherText string, err error) {
|
||||
if c.wxPublicKey == nil || c.WxSerialNo == "" {
|
||||
return util.NULL, errors.New("WxPublicKey or WxSerialNo is null")
|
||||
}
|
||||
cipherByte, err := rsa.EncryptOAEP(sha1.New(), rand.Reader, c.wxPublicKey, []byte(text), nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("rsa.EncryptOAEP:%w", err)
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(cipherByte), nil
|
||||
}
|
||||
|
||||
// 敏感信息解密
|
||||
func (c *ClientV3) V3DecryptText(cipherText string) (text string, err error) {
|
||||
cipherByte, _ := base64.StdEncoding.DecodeString(cipherText)
|
||||
textByte, err := rsa.DecryptOAEP(sha1.New(), rand.Reader, c.privateKey, cipherByte, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("rsa.DecryptOAEP:%w", err)
|
||||
}
|
||||
return string(textByte), nil
|
||||
}
|
||||
|
||||
// 敏感参数信息加密
|
||||
// wxPublicKeyContent:微信平台证书内容
|
||||
func V3EncryptText(text string, wxPublicKeyContent []byte) (cipherText string, err error) {
|
||||
publicKey, err := xpem.DecodePublicKey(wxPublicKeyContent)
|
||||
if err != nil {
|
||||
return gopay.NULL, err
|
||||
}
|
||||
cipherByte, err := rsa.EncryptOAEP(sha1.New(), rand.Reader, publicKey, []byte(text), nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("rsa.EncryptOAEP:%w", err)
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(cipherByte), nil
|
||||
}
|
||||
|
||||
// 敏感参数信息解密
|
||||
// privateKeyContent:私钥 apiclient_key.pem 读取后的字符串内容
|
||||
func V3DecryptText(cipherText string, privateKeyContent []byte) (text string, err error) {
|
||||
privateKey, err := xpem.DecodePrivateKey(privateKeyContent)
|
||||
if err != nil {
|
||||
return gopay.NULL, err
|
||||
}
|
||||
cipherByte, _ := base64.StdEncoding.DecodeString(cipherText)
|
||||
textByte, err := rsa.DecryptOAEP(sha1.New(), rand.Reader, privateKey, cipherByte, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("rsa.DecryptOAEP:%w", err)
|
||||
}
|
||||
return string(textByte), nil
|
||||
}
|
||||
|
||||
// 解密 普通支付 回调中的加密信息
|
||||
func V3DecryptNotifyCipherText(ciphertext, nonce, additional, apiV3Key string) (result *V3DecryptResult, err error) {
|
||||
cipherBytes, _ := base64.StdEncoding.DecodeString(ciphertext)
|
||||
decrypt, err := aes.GCMDecrypt(cipherBytes, []byte(nonce), []byte(additional), []byte(apiV3Key))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("aes.GCMDecrypt, err:%w", err)
|
||||
}
|
||||
result = &V3DecryptResult{}
|
||||
if err = json.Unmarshal(decrypt, result); err != nil {
|
||||
return nil, fmt.Errorf("json.Unmarshal(%s), err:%w", string(decrypt), err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// 解密 服务商支付 回调中的加密信息
|
||||
func V3DecryptPartnerNotifyCipherText(ciphertext, nonce, additional, apiV3Key string) (result *V3DecryptPartnerResult, err error) {
|
||||
cipherBytes, _ := base64.StdEncoding.DecodeString(ciphertext)
|
||||
decrypt, err := aes.GCMDecrypt(cipherBytes, []byte(nonce), []byte(additional), []byte(apiV3Key))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("aes.GCMDecrypt, err:%w", err)
|
||||
}
|
||||
result = &V3DecryptPartnerResult{}
|
||||
if err = json.Unmarshal(decrypt, result); err != nil {
|
||||
return nil, fmt.Errorf("json.Unmarshal(%s), err:%w", string(decrypt), err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// 解密 普通退款 回调中的加密信息
|
||||
func V3DecryptRefundNotifyCipherText(ciphertext, nonce, additional, apiV3Key string) (result *V3DecryptRefundResult, err error) {
|
||||
cipherBytes, _ := base64.StdEncoding.DecodeString(ciphertext)
|
||||
decrypt, err := aes.GCMDecrypt(cipherBytes, []byte(nonce), []byte(additional), []byte(apiV3Key))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("aes.GCMDecrypt, err:%w", err)
|
||||
}
|
||||
result = &V3DecryptRefundResult{}
|
||||
if err = json.Unmarshal(decrypt, result); err != nil {
|
||||
return nil, fmt.Errorf("json.Unmarshal(%s), err:%w", string(decrypt), err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// 解密 服务商退款 回调中的加密信息
|
||||
func V3DecryptPartnerRefundNotifyCipherText(ciphertext, nonce, additional, apiV3Key string) (result *V3DecryptPartnerRefundResult, err error) {
|
||||
cipherBytes, _ := base64.StdEncoding.DecodeString(ciphertext)
|
||||
decrypt, err := aes.GCMDecrypt(cipherBytes, []byte(nonce), []byte(additional), []byte(apiV3Key))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("aes.GCMDecrypt, err:%w", err)
|
||||
}
|
||||
result = &V3DecryptPartnerRefundResult{}
|
||||
if err = json.Unmarshal(decrypt, result); err != nil {
|
||||
return nil, fmt.Errorf("json.Unmarshal(%s), err:%w", string(decrypt), err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// 解密 合单支付 回调中的加密信息
|
||||
func V3DecryptCombineNotifyCipherText(ciphertext, nonce, additional, apiV3Key string) (result *V3DecryptCombineResult, err error) {
|
||||
cipherBytes, _ := base64.StdEncoding.DecodeString(ciphertext)
|
||||
decrypt, err := aes.GCMDecrypt(cipherBytes, []byte(nonce), []byte(additional), []byte(apiV3Key))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("aes.GCMDecrypt, err:%w", err)
|
||||
}
|
||||
result = &V3DecryptCombineResult{}
|
||||
if err = json.Unmarshal(decrypt, result); err != nil {
|
||||
return nil, fmt.Errorf("json.Unmarshal(%s), err:%w", string(decrypt), err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// 解密分账动账回调中的加密信息
|
||||
func V3DecryptProfitShareNotifyCipherText(ciphertext, nonce, additional, apiV3Key string) (result *V3DecryptProfitShareResult, err error) {
|
||||
cipherBytes, _ := base64.StdEncoding.DecodeString(ciphertext)
|
||||
decrypt, err := aes.GCMDecrypt(cipherBytes, []byte(nonce), []byte(additional), []byte(apiV3Key))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("aes.GCMDecrypt, err:%w", err)
|
||||
}
|
||||
result = &V3DecryptProfitShareResult{}
|
||||
if err = json.Unmarshal(decrypt, result); err != nil {
|
||||
return nil, fmt.Errorf("json.Unmarshal(%s), err:%w", string(decrypt), err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// 解密 支付分 回调中的加密信息
|
||||
func V3DecryptScoreNotifyCipherText(ciphertext, nonce, additional, apiV3Key string) (result *V3DecryptScoreResult, err error) {
|
||||
cipherBytes, _ := base64.StdEncoding.DecodeString(ciphertext)
|
||||
decrypt, err := aes.GCMDecrypt(cipherBytes, []byte(nonce), []byte(additional), []byte(apiV3Key))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("aes.GCMDecrypt, err:%w", err)
|
||||
}
|
||||
result = &V3DecryptScoreResult{}
|
||||
if err = json.Unmarshal(decrypt, result); err != nil {
|
||||
return nil, fmt.Errorf("json.Unmarshal(%s), err:%w", string(decrypt), err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// 解密商家券回调中的加密信息
|
||||
func V3DecryptBusifavorNotifyCipherText(ciphertext, nonce, additional, apiV3Key string) (result *V3DecryptBusifavorResult, err error) {
|
||||
cipherBytes, _ := base64.StdEncoding.DecodeString(ciphertext)
|
||||
decrypt, err := aes.GCMDecrypt(cipherBytes, []byte(nonce), []byte(additional), []byte(apiV3Key))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("aes.GCMDecrypt, err:%w", err)
|
||||
}
|
||||
result = &V3DecryptBusifavorResult{}
|
||||
if err = json.Unmarshal(decrypt, result); err != nil {
|
||||
return nil, fmt.Errorf("json.Unmarshal(%s), err:%w", string(decrypt), err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
)
|
||||
|
||||
// 点金计划管理API
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_5_1.shtml
|
||||
func (c *ClientV3) V3GoldPlanManage(ctx context.Context, bm gopay.BodyMap) (wxRsp *GoldPlanManageRsp, err error) {
|
||||
authorization, err := c.authorization(MethodPost, v3GoldPlanManage, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3GoldPlanManage, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &GoldPlanManageRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(GoldPlanManage)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 商家小票管理API
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_5_2.shtml
|
||||
func (c *ClientV3) V3GoldPlanBillManage(ctx context.Context, bm gopay.BodyMap) (wxRsp *GoldPlanManageRsp, err error) {
|
||||
authorization, err := c.authorization(MethodPost, v3GoldPlanBillManage, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3GoldPlanBillManage, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &GoldPlanManageRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(GoldPlanManage)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 同业过滤标签管理API
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_5_3.shtml
|
||||
func (c *ClientV3) V3GoldPlanFilterManage(ctx context.Context, bm gopay.BodyMap) (wxRsp *EmptyRsp, err error) {
|
||||
authorization, err := c.authorization(MethodPost, v3GoldPlanFilterManage, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3GoldPlanFilterManage, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &EmptyRsp{Code: Success, SignInfo: si}
|
||||
if res.StatusCode != http.StatusNoContent {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 开通广告展示API
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_5_4.shtml
|
||||
func (c *ClientV3) V3GoldPlanOpenAdShow(ctx context.Context, bm gopay.BodyMap) (wxRsp *EmptyRsp, err error) {
|
||||
authorization, err := c.authorization(MethodPATCH, v3GoldPlanOpenAdShow, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPatch(ctx, bm, v3GoldPlanOpenAdShow, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &EmptyRsp{Code: Success, SignInfo: si}
|
||||
if res.StatusCode != http.StatusNoContent {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 关闭广告展示API
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_5_5.shtml
|
||||
func (c *ClientV3) V3GoldPlanCloseAdShow(ctx context.Context, bm gopay.BodyMap) (wxRsp *EmptyRsp, err error) {
|
||||
authorization, err := c.authorization(MethodPATCH, v3GoldPlanCloseAdShow, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3GoldPlanCloseAdShow, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &EmptyRsp{Code: Success, SignInfo: si}
|
||||
if res.StatusCode != http.StatusNoContent {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package wechat
|
||||
|
||||
// 支付有礼
|
||||
@@ -0,0 +1,456 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
)
|
||||
|
||||
// 创建商家券
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_1.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_1.shtml
|
||||
func (c *ClientV3) V3BusiFavorBatchCreate(ctx context.Context, bm gopay.BodyMap) (wxRsp *BusiFavorCreateRsp, err error) {
|
||||
authorization, err := c.authorization(MethodPost, v3BusiFavorBatchCreate, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3BusiFavorBatchCreate, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &BusiFavorCreateRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(FavorBatchCreate)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询商家券详情
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_2.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_2.shtml
|
||||
func (c *ClientV3) V3BusiFavorBatchDetail(ctx context.Context, stockId string) (wxRsp *BusiFavorBatchDetailRsp, err error) {
|
||||
uri := fmt.Sprintf(v3BusiFavorBatchDetail, stockId)
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &BusiFavorBatchDetailRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(BusiFavorBatchDetail)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 核销用户券
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_3.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_3.shtml
|
||||
func (c *ClientV3) V3BusiFavorUse(ctx context.Context, bm gopay.BodyMap) (wxRsp *BusiFavorUseRsp, err error) {
|
||||
authorization, err := c.authorization(MethodPost, v3BusiFavorUse, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3BusiFavorUse, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &BusiFavorUseRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(BusiFavorUse)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 根据过滤条件查询用户券
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_4.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_4.shtml
|
||||
func (c *ClientV3) V3BusiFavorUserCoupons(ctx context.Context, openid string, bm gopay.BodyMap) (wxRsp *BusiFavorUserCouponsRsp, err error) {
|
||||
uri := fmt.Sprintf(v3BusiFavorUserCoupons, openid) + "?" + bm.EncodeURLParams()
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &BusiFavorUserCouponsRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(BusiFavorUserCoupons)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询用户单张券详情
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_5.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_5.shtml
|
||||
func (c *ClientV3) V3BusiFavorUserCouponDetail(ctx context.Context, openid, couponCode, appid string) (wxRsp *BusiFavorUserCouponDetailRsp, err error) {
|
||||
uri := fmt.Sprintf(v3BusiFavorUserCouponDetail, openid, couponCode, appid)
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &BusiFavorUserCouponDetailRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(BusiUserCoupon)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 上传预存code
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_6.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_6.shtml
|
||||
func (c *ClientV3) V3BusiFavorCodeUpload(ctx context.Context, stockId string, bm gopay.BodyMap) (wxRsp *BusiFavorCodeUploadRsp, err error) {
|
||||
url := fmt.Sprintf(v3BusiFavorCodeUpload, stockId)
|
||||
authorization, err := c.authorization(MethodPost, url, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &BusiFavorCodeUploadRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(BusiFavorCodeUpload)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 设置商家券事件通知地址
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_7.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_7.shtml
|
||||
func (c *ClientV3) V3BusiFavorCallbackUrlSet(ctx context.Context, bm gopay.BodyMap) (wxRsp *BusiFavorCallbackUrlSetRsp, err error) {
|
||||
authorization, err := c.authorization(MethodPost, v3BusiFavorCallbackUrlSet, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3BusiFavorCallbackUrlSet, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &BusiFavorCallbackUrlSetRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(BusiFavorCallbackUrlSet)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询商家券事件通知地址
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_8.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_8.shtml
|
||||
func (c *ClientV3) V3BusiFavorCallbackUrl(ctx context.Context, mchid string) (wxRsp *BusiFavorCallbackUrlRsp, err error) {
|
||||
uri := v3BusiFavorCallbackUrl + "?mchid=" + mchid
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &BusiFavorCallbackUrlRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(BusiFavorCallbackUrl)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 关联订单信息
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_9.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_9.shtml
|
||||
func (c *ClientV3) V3BusiFavorAssociate(ctx context.Context, bm gopay.BodyMap) (wxRsp *BusiFavorAssociateRsp, err error) {
|
||||
authorization, err := c.authorization(MethodPost, v3BusiFavorAssociate, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3BusiFavorAssociate, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &BusiFavorAssociateRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(BusiFavorAssociate)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 取消关联订单信息
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_10.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_10.shtml
|
||||
func (c *ClientV3) V3BusiFavorDisassociate(ctx context.Context, bm gopay.BodyMap) (wxRsp *BusiFavorDisassociateRsp, err error) {
|
||||
authorization, err := c.authorization(MethodPost, v3BusiFavorDisassociate, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3BusiFavorDisassociate, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &BusiFavorDisassociateRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(BusiFavorDisassociate)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 修改批次预算
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_11.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_11.shtml
|
||||
func (c *ClientV3) V3BusiFavorBatchUpdate(ctx context.Context, stockId string, bm gopay.BodyMap) (wxRsp *BusiFavorBatchUpdateRsp, err error) {
|
||||
url := fmt.Sprintf(v3BusiFavorBatchUpdate, stockId)
|
||||
authorization, err := c.authorization(MethodPATCH, url, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPatch(ctx, bm, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &BusiFavorBatchUpdateRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(BusiFavorBatchUpdate)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 修改商家券基本信息
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_12.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_12.shtml
|
||||
func (c *ClientV3) V3BusiFavorInfoUpdate(ctx context.Context, stockId string, bm gopay.BodyMap) (wxRsp *EmptyRsp, err error) {
|
||||
url := fmt.Sprintf(v3BusiFavorInfoUpdate, stockId)
|
||||
authorization, err := c.authorization(MethodPATCH, url, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPatch(ctx, bm, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &EmptyRsp{Code: Success, SignInfo: si}
|
||||
if res.StatusCode != http.StatusNoContent {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 发放消费卡
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_6_1.shtml
|
||||
func (c *ClientV3) V3BusiFavorSend(ctx context.Context, cardId string, bm gopay.BodyMap) (wxRsp *BusiFavorSendRsp, err error) {
|
||||
url := fmt.Sprintf(v3BusiFavorSend, cardId)
|
||||
authorization, err := c.authorization(MethodPost, url, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &BusiFavorSendRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(BusiFavorSend)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 申请退券
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_13.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_13.shtml
|
||||
func (c *ClientV3) V3BusiFavorReturn(ctx context.Context, bm gopay.BodyMap) (wxRsp *BusiFavorReturnRsp, err error) {
|
||||
authorization, err := c.authorization(MethodPost, v3BusiFavorReturn, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3BusiFavorReturn, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &BusiFavorReturnRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(BusiFavorReturn)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 使券失效
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_14.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_14.shtml
|
||||
func (c *ClientV3) V3BusiFavorDeactivate(ctx context.Context, bm gopay.BodyMap) (wxRsp *BusiFavorDeactivateRsp, err error) {
|
||||
authorization, err := c.authorization(MethodPost, v3BusiFavorDeactivate, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3BusiFavorDeactivate, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &BusiFavorDeactivateRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(BusiFavorDeactivate)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 营销补差付款
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_16.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_16.shtml
|
||||
func (c *ClientV3) V3BusiFavorSubsidyPay(ctx context.Context, bm gopay.BodyMap) (wxRsp *BusiFavorSubsidyPayRsp, err error) {
|
||||
authorization, err := c.authorization(MethodPost, v3BusiFavorSubsidyPay, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3BusiFavorSubsidyPay, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &BusiFavorSubsidyPayRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(BusiFavorSubsidyPay)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询营销补差付款单详情
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_18.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_18.shtml
|
||||
func (c *ClientV3) V3BusiFavorSubsidyPayDetail(ctx context.Context, subsidyReceiptId string) (wxRsp *BusiFavorSubsidyPayDetailRsp, err error) {
|
||||
url := fmt.Sprintf(v3BusiFavorSubsidyPayDetail, subsidyReceiptId)
|
||||
authorization, err := c.authorization(MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &BusiFavorSubsidyPayDetailRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(BusiFavorSubsidyPay)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
@@ -0,0 +1,399 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/pkg/util"
|
||||
)
|
||||
|
||||
// 创建代金券批次
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_1.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_1_1.shtml
|
||||
func (c *ClientV3) V3FavorBatchCreate(ctx context.Context, bm gopay.BodyMap) (wxRsp *FavorBatchCreateRsp, err error) {
|
||||
authorization, err := c.authorization(MethodPost, v3FavorBatchCreate, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3FavorBatchCreate, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &FavorBatchCreateRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(FavorBatchCreate)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 发放代金券批次
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_2.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_1_2.shtml
|
||||
func (c *ClientV3) V3FavorBatchGrant(ctx context.Context, openid string, bm gopay.BodyMap) (wxRsp *FavorBatchGrantRsp, err error) {
|
||||
url := fmt.Sprintf(v3FavorBatchGrant, openid)
|
||||
authorization, err := c.authorization(MethodPost, url, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &FavorBatchGrantRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(FavorBatchGrant)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 激活代金券批次
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_3.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_1_3.shtml
|
||||
func (c *ClientV3) V3FavorBatchStart(ctx context.Context, stockId, stockCreatorMchid string) (wxRsp *FavorBatchStartRsp, err error) {
|
||||
url := fmt.Sprintf(v3FavorBatchStart, stockId)
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.Set("stock_creator_mchid", stockCreatorMchid)
|
||||
authorization, err := c.authorization(MethodPost, url, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &FavorBatchStartRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(FavorBatchStart)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 条件查询批次列表
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_4.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_1_4.shtml
|
||||
func (c *ClientV3) V3FavorBatchList(ctx context.Context, bm gopay.BodyMap) (wxRsp *FavorBatchListRsp, err error) {
|
||||
uri := v3FavorBatchList + "?" + bm.EncodeURLParams()
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &FavorBatchListRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(FavorBatchList)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询批次详情
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_5.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_1_5.shtml
|
||||
func (c *ClientV3) V3FavorBatchDetail(ctx context.Context, stockId, stockCreatorMchid string) (wxRsp *FavorBatchDetailRsp, err error) {
|
||||
uri := fmt.Sprintf(v3FavorBatchDetail, stockId) + "?stock_creator_mchid=" + stockCreatorMchid
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &FavorBatchDetailRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(FavorBatch)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询代金券详情
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_6.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_1_6.shtml
|
||||
func (c *ClientV3) V3FavorDetail(ctx context.Context, appid, couponId, openid string) (wxRsp *FavorDetailRsp, err error) {
|
||||
uri := fmt.Sprintf(v3FavorDetail, openid, couponId) + "?appid=" + appid
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &FavorDetailRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(FavorDetail)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询代金券可用商户
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_7.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_1_7.shtml
|
||||
func (c *ClientV3) V3FavorMerchant(ctx context.Context, stockId, stockCreatorMchid string, limit, offset int) (wxRsp *FavorMerchantRsp, err error) {
|
||||
if limit == 0 {
|
||||
limit = 20
|
||||
}
|
||||
uri := fmt.Sprintf(v3FavorMerchant, stockId) + "?stock_creator_mchid=" + stockCreatorMchid + "&limit=" + util.Int2String(limit) + "&offset=" + util.Int2String(offset)
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &FavorMerchantRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(FavorMerchant)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询代金券可用单品
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_8.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_1_8.shtml
|
||||
func (c *ClientV3) V3FavorItems(ctx context.Context, stockId, stockCreatorMchid string, limit, offset int) (wxRsp *FavorItemsRsp, err error) {
|
||||
if limit == 0 {
|
||||
limit = 20
|
||||
}
|
||||
uri := fmt.Sprintf(v3FavorItems, stockId) + "?stock_creator_mchid=" + stockCreatorMchid + "&limit=" + util.Int2String(limit) + "&offset=" + util.Int2String(offset)
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &FavorItemsRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(FavorItems)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 根据商户号查用户的券
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_9.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_1_9.shtml
|
||||
func (c *ClientV3) V3FavorUserCoupons(ctx context.Context, openid string, bm gopay.BodyMap) (wxRsp *FavorUserCouponsRsp, err error) {
|
||||
uri := fmt.Sprintf(v3FavorUserCoupons, openid) + "?" + bm.EncodeURLParams()
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &FavorUserCouponsRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(FavorUserCoupons)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 下载批次核销明细
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_10.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_1_10.shtml
|
||||
func (c *ClientV3) V3FavorUseFlowDownload(ctx context.Context, stockId string) (wxRsp *FavorUseFlowDownloadRsp, err error) {
|
||||
url := fmt.Sprintf(v3FavorUseFlowDownload, stockId)
|
||||
authorization, err := c.authorization(MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &FavorUseFlowDownloadRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(FavorFlowDownload)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 下载批次退款明细
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_11.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_1_11.shtml
|
||||
func (c *ClientV3) V3FavorRefundFlowDownload(ctx context.Context, stockId string) (wxRsp *FavorRefundFlowDownloadRsp, err error) {
|
||||
url := fmt.Sprintf(v3FavorRefundFlowDownload, stockId)
|
||||
authorization, err := c.authorization(MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &FavorRefundFlowDownloadRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(FavorFlowDownload)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 设置消息通知地址
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_12.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_1_12.shtml
|
||||
func (c *ClientV3) V3FavorCallbackUrlSet(ctx context.Context, bm gopay.BodyMap) (wxRsp *FavorCallbackUrlSetRsp, err error) {
|
||||
authorization, err := c.authorization(MethodPost, v3FavorCallbackUrlSet, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3FavorCallbackUrlSet, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &FavorCallbackUrlSetRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(FavorCallbackUrl)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 暂停代金券批次
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_13.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_1_13.shtml
|
||||
func (c *ClientV3) V3FavorBatchPause(ctx context.Context, stockId, stockCreatorMchid string) (wxRsp *FavorBatchPauseRsp, err error) {
|
||||
url := fmt.Sprintf(v3FavorBatchPause, stockId)
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.Set("stock_creator_mchid", stockCreatorMchid)
|
||||
authorization, err := c.authorization(MethodPost, url, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &FavorBatchPauseRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(FavorBatchPause)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 重启代金券批次
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_14.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_1_14.shtml
|
||||
func (c *ClientV3) V3FavorBatchRestart(ctx context.Context, stockId, stockCreatorMchid string) (wxRsp *FavorBatchRestartRsp, err error) {
|
||||
url := fmt.Sprintf(v3FavorBatchRestart, stockId)
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.Set("stock_creator_mchid", stockCreatorMchid)
|
||||
authorization, err := c.authorization(MethodPost, url, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &FavorBatchRestartRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(FavorBatchRestart)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/pkg/util"
|
||||
)
|
||||
|
||||
// 图片上传(营销专用)
|
||||
// 注意:图片不能超过2MB
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_0_1.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_0_1.shtml
|
||||
func (c *ClientV3) V3FavorMediaUploadImage(ctx context.Context, fileName, fileSha256 string, img *util.File) (wxRsp *MarketMediaUploadRsp, err error) {
|
||||
bmFile := make(gopay.BodyMap)
|
||||
bmFile.Set("filename", fileName).Set("sha256", fileSha256)
|
||||
authorization, err := c.authorization(MethodPost, v3FavorMediaUploadImage, bmFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.SetBodyMap("meta", func(bm gopay.BodyMap) {
|
||||
bm.Set("filename", fileName).Set("sha256", fileSha256)
|
||||
}).SetFormFile("file", img)
|
||||
res, si, bs, err := c.doProdPostFile(ctx, bm, v3FavorMediaUploadImage, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &MarketMediaUploadRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(MarketMediaUpload)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
)
|
||||
|
||||
// 建立合作关系
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_5_1.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_5_1.shtml
|
||||
func (c *ClientV3) V3PartnershipsBuild(ctx context.Context, idempotencyKey string, bm gopay.BodyMap) (wxRsp *PartnershipsBuildRsp, err error) {
|
||||
authorization, err := c.authorization(MethodPost, v3PartnershipsBuild, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPostWithHeader(ctx, map[string]string{"Idempotency-Key": idempotencyKey}, bm, v3PartnershipsBuild, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &PartnershipsBuildRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(PartnershipsBuild)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 终止合作关系
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_5_2.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_5_2.shtml
|
||||
func (c *ClientV3) V3PartnershipsTerminate(ctx context.Context, idempotencyKey string, bm gopay.BodyMap) (wxRsp *PartnershipsTerminateRsp, err error) {
|
||||
authorization, err := c.authorization(MethodPost, v3PartnershipsTerminate, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPostWithHeader(ctx, map[string]string{"Idempotency-Key": idempotencyKey}, bm, v3PartnershipsTerminate, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &PartnershipsTerminateRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(PartnershipsTerminate)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询合作关系列表
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_5_3.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_5_3.shtml
|
||||
func (c *ClientV3) V3PartnershipsList(ctx context.Context, bm gopay.BodyMap) (wxRsp *PartnershipsListRsp, err error) {
|
||||
uri := v3PartnershipsList + "?" + bm.EncodeURLParams()
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &PartnershipsListRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(PartnershipsList)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/pkg/util"
|
||||
)
|
||||
|
||||
// 图片上传API
|
||||
// 注意:图片不能超过2MB
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter2_1_1.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter2_1_1.shtml
|
||||
func (c *ClientV3) V3MediaUploadImage(ctx context.Context, fileName, fileSha256 string, img *util.File) (wxRsp *MediaUploadRsp, err error) {
|
||||
bmFile := make(gopay.BodyMap)
|
||||
bmFile.Set("filename", fileName).Set("sha256", fileSha256)
|
||||
authorization, err := c.authorization(MethodPost, v3MediaUploadImage, bmFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.SetBodyMap("meta", func(bm gopay.BodyMap) {
|
||||
bm.Set("filename", fileName).Set("sha256", fileSha256)
|
||||
}).SetFormFile("file", img)
|
||||
res, si, bs, err := c.doProdPostFile(ctx, bm, v3MediaUploadImage, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &MediaUploadRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(MediaUpload)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 视频上传API
|
||||
// 注意:媒体视频只支持avi、wmv、mpeg、mp4、mov、mkv、flv、f4v、m4v、rmvb格式,文件大小不能超过5M。
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter2_1_2.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter2_1_2.shtml
|
||||
func (c *ClientV3) V3MediaUploadVideo(ctx context.Context, fileName, fileSha256 string, img *util.File) (wxRsp *MediaUploadRsp, err error) {
|
||||
bmFile := make(gopay.BodyMap)
|
||||
bmFile.Set("filename", fileName).Set("sha256", fileSha256)
|
||||
authorization, err := c.authorization(MethodPost, v3MediaUploadVideo, bmFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.SetBodyMap("meta", func(bm gopay.BodyMap) {
|
||||
bm.Set("filename", fileName).Set("sha256", fileSha256)
|
||||
}).SetFormFile("file", img)
|
||||
res, si, bs, err := c.doProdPostFile(ctx, bm, v3MediaUploadVideo, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &MediaUploadRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(MediaUpload)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
)
|
||||
|
||||
// 查询特约商户账户实时余额、查询二级商户账户实时余额
|
||||
// Code = 0 is success
|
||||
// 注意:服务商时,bm参数传 nil
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter5_1.shtml
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_7_1.shtml
|
||||
func (c *ClientV3) V3EcommerceBalance(ctx context.Context, subMchid string, bm gopay.BodyMap) (*EcommerceBalanceRsp, error) {
|
||||
url := fmt.Sprintf(v3EcommerceBalance, subMchid) + "?" + bm.EncodeURLParams()
|
||||
authorization, err := c.authorization(MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &EcommerceBalanceRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(EcommerceBalance)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询二级商户账户日终余额
|
||||
// date示例值:2019-08-17
|
||||
// Code = 0 is success
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_7_2.shtml
|
||||
func (c *ClientV3) V3EcommerceDayBalance(ctx context.Context, subMchid, date string) (*EcommerceBalanceRsp, error) {
|
||||
uri := fmt.Sprintf(v3EcommerceDayBalance, subMchid) + "?date=" + date
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &EcommerceBalanceRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(EcommerceBalance)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询账户实时余额
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter5_1.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter5_2.shtml
|
||||
// 电商平台:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_7_3.shtml
|
||||
func (c *ClientV3) V3MerchantBalance(ctx context.Context, accountType string) (*MerchantBalanceRsp, error) {
|
||||
url := fmt.Sprintf(v3MerchantBalance, accountType)
|
||||
authorization, err := c.authorization(MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &MerchantBalanceRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(MerchantBalance)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询账户日终余额
|
||||
// date示例值:2019-08-17
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter5_2.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter5_3.shtml
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_7_4.shtml
|
||||
func (c *ClientV3) V3MerchantDayBalance(ctx context.Context, accountType, date string) (*MerchantBalanceRsp, error) {
|
||||
uri := fmt.Sprintf(v3MerchantDayBalance, accountType) + "?date=" + date
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &MerchantBalanceRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(MerchantBalance)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 特约商户银行来账查询API
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter3_6.shtml
|
||||
func (c *ClientV3) V3EcommerceIncomeRecord(ctx context.Context, bm gopay.BodyMap) (*PartnerIncomeRecordRsp, error) {
|
||||
uri := v3EcommerceIncomeRecord + "?" + bm.EncodeURLParams()
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &PartnerIncomeRecordRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(PartnerIncomeRecord)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 商户/服务商银行来账查询API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter3_7.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter3_7.shtml
|
||||
func (c *ClientV3) V3MerchantIncomeRecord(ctx context.Context, bm gopay.BodyMap) (*MerchantIncomeRecordRsp, error) {
|
||||
uri := v3MerchantIncomeRecord + "?" + bm.EncodeURLParams()
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &MerchantIncomeRecordRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(MerchantIncomeRecord)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/pkg/xlog"
|
||||
)
|
||||
|
||||
type Resource struct {
|
||||
OriginalType string `json:"original_type"`
|
||||
Algorithm string `json:"algorithm"`
|
||||
Ciphertext string `json:"ciphertext"`
|
||||
AssociatedData string `json:"associated_data"`
|
||||
Nonce string `json:"nonce"`
|
||||
}
|
||||
|
||||
type V3DecryptResult struct {
|
||||
Appid string `json:"appid"`
|
||||
Mchid string `json:"mchid"`
|
||||
OutTradeNo string `json:"out_trade_no"`
|
||||
TransactionId string `json:"transaction_id"`
|
||||
TradeType string `json:"trade_type"`
|
||||
TradeState string `json:"trade_state"`
|
||||
TradeStateDesc string `json:"trade_state_desc"`
|
||||
BankType string `json:"bank_type"`
|
||||
Attach string `json:"attach"`
|
||||
SuccessTime string `json:"success_time"`
|
||||
Payer *Payer `json:"payer"`
|
||||
Amount *Amount `json:"amount"`
|
||||
SceneInfo *SceneInfo `json:"scene_info"`
|
||||
PromotionDetail []*PromotionDetail `json:"promotion_detail"`
|
||||
}
|
||||
|
||||
type V3DecryptPartnerResult struct {
|
||||
SpAppid string `json:"sp_appid"`
|
||||
SpMchid string `json:"sp_mchid"`
|
||||
SubAppid string `json:"sub_appid"`
|
||||
SubMchid string `json:"sub_mchid"`
|
||||
OutTradeNo string `json:"out_trade_no"`
|
||||
TransactionId string `json:"transaction_id"`
|
||||
TradeType string `json:"trade_type"`
|
||||
TradeState string `json:"trade_state"`
|
||||
TradeStateDesc string `json:"trade_state_desc"`
|
||||
BankType string `json:"bank_type"`
|
||||
Attach string `json:"attach"`
|
||||
SuccessTime string `json:"success_time"`
|
||||
Payer *PartnerPayer `json:"payer"`
|
||||
Amount *Amount `json:"amount"`
|
||||
SceneInfo *SceneInfo `json:"scene_info"`
|
||||
PromotionDetail []*PromotionDetail `json:"promotion_detail"`
|
||||
}
|
||||
|
||||
type V3DecryptRefundResult struct {
|
||||
Mchid string `json:"mchid"`
|
||||
OutTradeNo string `json:"out_trade_no"`
|
||||
TransactionId string `json:"transaction_id"`
|
||||
OutRefundNo string `json:"out_refund_no"`
|
||||
RefundId string `json:"refund_id"`
|
||||
RefundStatus string `json:"refund_status"`
|
||||
SuccessTime string `json:"success_time"`
|
||||
UserReceivedAccount string `json:"user_received_account"`
|
||||
Amount *RefundAmount `json:"amount"`
|
||||
}
|
||||
|
||||
type V3DecryptPartnerRefundResult struct {
|
||||
SpMchid string `json:"sp_mchid"`
|
||||
SubMchid string `json:"sub_mchid"`
|
||||
OutTradeNo string `json:"out_trade_no"`
|
||||
TransactionId string `json:"transaction_id"`
|
||||
OutRefundNo string `json:"out_refund_no"`
|
||||
RefundId string `json:"refund_id"`
|
||||
RefundStatus string `json:"refund_status"`
|
||||
SuccessTime string `json:"success_time"`
|
||||
UserReceivedAccount string `json:"user_received_account"`
|
||||
Amount *RefundAmount `json:"amount"`
|
||||
}
|
||||
|
||||
type V3DecryptCombineResult struct {
|
||||
CombineAppid string `json:"combine_appid"`
|
||||
CombineMchid string `json:"combine_mchid"`
|
||||
CombineOutTradeNo string `json:"combine_out_trade_no"`
|
||||
SceneInfo *SceneInfo `json:"scene_info"`
|
||||
SubOrders []*SubOrders `json:"sub_orders"` // 最多支持子单条数:50
|
||||
CombinePayerInfo *Payer `json:"combine_payer_info"` // 支付者信息
|
||||
}
|
||||
|
||||
type V3DecryptScoreResult struct {
|
||||
Appid string `json:"appid"`
|
||||
Mchid string `json:"mchid"`
|
||||
OutOrderNo string `json:"out_order_no"`
|
||||
ServiceId string `json:"service_id"`
|
||||
Openid string `json:"openid"`
|
||||
State string `json:"state"`
|
||||
StateDescription string `json:"state_description"`
|
||||
TotalAmount int `json:"total_amount"`
|
||||
ServiceIntroduction string `json:"service_introduction"`
|
||||
PostPayments []*PostPayments `json:"post_payments"`
|
||||
PostDiscounts []*PostDiscounts `json:"post_discounts"`
|
||||
RiskFund *RiskFund `json:"risk_fund"`
|
||||
TimeRange *TimeRange `json:"time_range"`
|
||||
Location *Location `json:"location"`
|
||||
Attach string `json:"attach"`
|
||||
NotifyUrl string `json:"notify_url"`
|
||||
OrderId string `json:"order_id"`
|
||||
NeedCollection bool `json:"need_collection"`
|
||||
Collection *Collection `json:"collection"`
|
||||
}
|
||||
|
||||
type V3DecryptProfitShareResult struct {
|
||||
SpMchid string `json:"sp_mchid"` // 服务商商户号
|
||||
SubMchid string `json:"sub_mchid"` // 子商户号
|
||||
TransactionId string `json:"transaction_id"` // 微信订单号
|
||||
OrderId string `json:"order_id"` // 微信分账/回退单号
|
||||
OutOrderNo string `json:"out_order_no"` // 商户分账/回退单号
|
||||
Receiver *Receiver `json:"receiver"`
|
||||
SuccessTime string `json:"success_time"` // 成功时间
|
||||
}
|
||||
|
||||
type Receiver struct {
|
||||
Type string `json:"type"` // 分账接收方类型
|
||||
Account string `json:"account"` // 分账接收方账号
|
||||
Amount int `json:"amount"` // 分账动账金额
|
||||
Description string `json:"description"` // 分账/回退描述
|
||||
}
|
||||
|
||||
type V3DecryptBusifavorResult struct {
|
||||
EventType string `json:"event_type"` // 事件类型
|
||||
CouponCode string `json:"coupon_code"` // 券code
|
||||
StockId string `json:"stock_id"` // 批次号
|
||||
SendTime string `json:"send_time"` // 发放时间
|
||||
Openid string `json:"openid"` // 用户标识
|
||||
Unionid string `json:"unionid"` // 用户统一标识
|
||||
SendChannel string `json:"send_channel"` // 发放渠道
|
||||
SendMerchant string `json:"send_merchant"` // 发券商户号
|
||||
AttachInfo *BusifavorAttachInfo `json:"attach_info"` // 发券附加信息
|
||||
}
|
||||
|
||||
type BusifavorAttachInfo struct {
|
||||
TransactionId string `json:"transaction_id"` // 交易订单编号
|
||||
ActCode string `json:"act_code"` // 支付有礼活动编号/营销馆活动ID
|
||||
HallCode string `json:"hall_code"` // 营销馆ID
|
||||
HallBelongMchID int `json:"hall_belong_mch_id"` // 营销馆所属商户号
|
||||
CardID string `json:"card_id"` // 会员卡ID
|
||||
Code string `json:"code"` // 会员卡code
|
||||
ActivityID string `json:"activity_id"` // 会员活动ID
|
||||
}
|
||||
|
||||
type V3NotifyReq struct {
|
||||
Id string `json:"id"`
|
||||
CreateTime string `json:"create_time"`
|
||||
ResourceType string `json:"resource_type"`
|
||||
EventType string `json:"event_type"`
|
||||
Summary string `json:"summary"`
|
||||
Resource *Resource `json:"resource"`
|
||||
SignInfo *SignInfo `json:"-"`
|
||||
}
|
||||
|
||||
type V3NotifyRsp struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// 解析微信回调请求的参数到 V3NotifyReq 结构体
|
||||
func V3ParseNotify(req *http.Request) (notifyReq *V3NotifyReq, err error) {
|
||||
bs, err := ioutil.ReadAll(io.LimitReader(req.Body, int64(3<<20))) // default 3MB change the size you want;
|
||||
defer req.Body.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read request body error:%w", err)
|
||||
}
|
||||
si := &SignInfo{
|
||||
HeaderTimestamp: req.Header.Get(HeaderTimestamp),
|
||||
HeaderNonce: req.Header.Get(HeaderNonce),
|
||||
HeaderSignature: req.Header.Get(HeaderSignature),
|
||||
HeaderSerial: req.Header.Get(HeaderSerial),
|
||||
SignBody: string(bs),
|
||||
}
|
||||
notifyReq = &V3NotifyReq{SignInfo: si}
|
||||
if err = json.Unmarshal(bs, notifyReq); err != nil {
|
||||
return nil, fmt.Errorf("json.Unmarshal(%s, %+v):%w", string(bs), notifyReq, err)
|
||||
}
|
||||
return notifyReq, nil
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
// 推荐使用 VerifySignByPK()
|
||||
func (v *V3NotifyReq) VerifySign(wxPkContent string) (err error) {
|
||||
if v.SignInfo != nil {
|
||||
return V3VerifySign(v.SignInfo.HeaderTimestamp, v.SignInfo.HeaderNonce, v.SignInfo.SignBody, v.SignInfo.HeaderSignature, wxPkContent)
|
||||
}
|
||||
return errors.New("verify notify sign, bug SignInfo is nil")
|
||||
}
|
||||
|
||||
// 异步通知验签
|
||||
// wxPublicKey:微信平台证书公钥内容,通过 client.WxPublicKey() 获取
|
||||
func (v *V3NotifyReq) VerifySignByPK(wxPublicKey *rsa.PublicKey) (err error) {
|
||||
if v.SignInfo != nil {
|
||||
return V3VerifySignByPK(v.SignInfo.HeaderTimestamp, v.SignInfo.HeaderNonce, v.SignInfo.SignBody, v.SignInfo.HeaderSignature, wxPublicKey)
|
||||
}
|
||||
return errors.New("verify notify sign, bug SignInfo is nil")
|
||||
}
|
||||
|
||||
// 解密 普通支付 回调中的加密信息
|
||||
func (v *V3NotifyReq) DecryptCipherText(apiV3Key string) (result *V3DecryptResult, err error) {
|
||||
if v.Resource != nil {
|
||||
result, err = V3DecryptNotifyCipherText(v.Resource.Ciphertext, v.Resource.Nonce, v.Resource.AssociatedData, apiV3Key)
|
||||
if err != nil {
|
||||
bytes, _ := json.Marshal(v)
|
||||
return nil, fmt.Errorf("V3NotifyReq(%s) decrypt cipher text error(%w)", string(bytes), err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
return nil, errors.New("notify data Resource is nil")
|
||||
}
|
||||
|
||||
// 解密 服务商支付 回调中的加密信息
|
||||
func (v *V3NotifyReq) DecryptPartnerCipherText(apiV3Key string) (result *V3DecryptPartnerResult, err error) {
|
||||
if v.Resource != nil {
|
||||
result, err = V3DecryptPartnerNotifyCipherText(v.Resource.Ciphertext, v.Resource.Nonce, v.Resource.AssociatedData, apiV3Key)
|
||||
if err != nil {
|
||||
bytes, _ := json.Marshal(v)
|
||||
return nil, fmt.Errorf("V3NotifyReq(%s) decrypt cipher text error(%w)", string(bytes), err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
return nil, errors.New("notify data Resource is nil")
|
||||
}
|
||||
|
||||
// 解密 普通退款 回调中的加密信息
|
||||
func (v *V3NotifyReq) DecryptRefundCipherText(apiV3Key string) (result *V3DecryptRefundResult, err error) {
|
||||
if v.Resource != nil {
|
||||
result, err = V3DecryptRefundNotifyCipherText(v.Resource.Ciphertext, v.Resource.Nonce, v.Resource.AssociatedData, apiV3Key)
|
||||
if err != nil {
|
||||
bytes, _ := json.Marshal(v)
|
||||
return nil, fmt.Errorf("V3NotifyReq(%s) decrypt cipher text error(%w)", string(bytes), err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
return nil, errors.New("notify data Resource is nil")
|
||||
}
|
||||
|
||||
// 解密 服务商退款 回调中的加密信息
|
||||
func (v *V3NotifyReq) DecryptPartnerRefundCipherText(apiV3Key string) (result *V3DecryptPartnerRefundResult, err error) {
|
||||
if v.Resource != nil {
|
||||
result, err = V3DecryptPartnerRefundNotifyCipherText(v.Resource.Ciphertext, v.Resource.Nonce, v.Resource.AssociatedData, apiV3Key)
|
||||
if err != nil {
|
||||
bytes, _ := json.Marshal(v)
|
||||
return nil, fmt.Errorf("V3NotifyReq(%s) decrypt cipher text error(%w)", string(bytes), err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
return nil, errors.New("notify data Resource is nil")
|
||||
}
|
||||
|
||||
// 解密 合单支付 回调中的加密信息
|
||||
func (v *V3NotifyReq) DecryptCombineCipherText(apiV3Key string) (result *V3DecryptCombineResult, err error) {
|
||||
if v.Resource != nil {
|
||||
result, err = V3DecryptCombineNotifyCipherText(v.Resource.Ciphertext, v.Resource.Nonce, v.Resource.AssociatedData, apiV3Key)
|
||||
if err != nil {
|
||||
bytes, _ := json.Marshal(v)
|
||||
return nil, fmt.Errorf("V3NotifyReq(%s) decrypt cipher text error(%w)", string(bytes), err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
return nil, errors.New("notify data Resource is nil")
|
||||
}
|
||||
|
||||
// 解密 支付分 回调中的加密信息
|
||||
func (v *V3NotifyReq) DecryptScoreCipherText(apiV3Key string) (result *V3DecryptScoreResult, err error) {
|
||||
if v.Resource != nil {
|
||||
result, err = V3DecryptScoreNotifyCipherText(v.Resource.Ciphertext, v.Resource.Nonce, v.Resource.AssociatedData, apiV3Key)
|
||||
if err != nil {
|
||||
bytes, _ := json.Marshal(v)
|
||||
return nil, fmt.Errorf("V3NotifyReq(%s) decrypt cipher text error(%w)", string(bytes), err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
return nil, errors.New("notify data Resource is nil")
|
||||
}
|
||||
|
||||
// 解密分账动账回调中的加密信息
|
||||
func (v *V3NotifyReq) DecryptProfitShareCipherText(apiV3Key string) (result *V3DecryptProfitShareResult, err error) {
|
||||
if v.Resource != nil {
|
||||
result, err = V3DecryptProfitShareNotifyCipherText(v.Resource.Ciphertext, v.Resource.Nonce, v.Resource.AssociatedData, apiV3Key)
|
||||
if err != nil {
|
||||
bytes, _ := json.Marshal(v)
|
||||
return nil, fmt.Errorf("V3NotifyReq(%s) decrypt cipher text error(%w)", string(bytes), err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
return nil, errors.New("notify data Resource is nil")
|
||||
}
|
||||
|
||||
// 解密商家券回调中的加密信息
|
||||
func (v *V3NotifyReq) DecryptBusifavorCipherText(apiV3Key string) (result *V3DecryptBusifavorResult, err error) {
|
||||
if v.Resource != nil {
|
||||
result, err = V3DecryptBusifavorNotifyCipherText(v.Resource.Ciphertext, v.Resource.Nonce, v.Resource.AssociatedData, apiV3Key)
|
||||
if err != nil {
|
||||
bytes, _ := json.Marshal(v)
|
||||
return nil, fmt.Errorf("V3NotifyReq(%s) decrypt cipher text error(%w)", string(bytes), err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
return nil, errors.New("notify data Resource is nil")
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
// 暂时不推荐此方法,请使用 wechat.V3ParseNotify()
|
||||
// 解析微信回调请求的参数到 gopay.BodyMap
|
||||
func V3ParseNotifyToBodyMap(req *http.Request) (bm gopay.BodyMap, err error) {
|
||||
bs, err := ioutil.ReadAll(io.LimitReader(req.Body, int64(3<<20))) // default 3MB change the size you want;
|
||||
defer req.Body.Close()
|
||||
if err != nil {
|
||||
xlog.Error("err:", err)
|
||||
return
|
||||
}
|
||||
bm = make(gopay.BodyMap)
|
||||
if err = json.Unmarshal(bs, &bm); err != nil {
|
||||
return nil, fmt.Errorf("json.Unmarshal(%s):%w", string(bs), err)
|
||||
}
|
||||
return bm, nil
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/pkg/util"
|
||||
)
|
||||
|
||||
// APP下单API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_1.shtml
|
||||
func (c *ClientV3) V3TransactionApp(ctx context.Context, bm gopay.BodyMap) (wxRsp *PrepayRsp, err error) {
|
||||
if bm.GetString("mchid") == util.NULL {
|
||||
bm.Set("mchid", c.Mchid)
|
||||
}
|
||||
authorization, err := c.authorization(MethodPost, v3ApiApp, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3ApiApp, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &PrepayRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(Prepay)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// JSAPI/小程序下单API
|
||||
// Code = 0 is success
|
||||
// 商户JSAPI文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_1.shtml
|
||||
// 商户小程序文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_1.shtml
|
||||
func (c *ClientV3) V3TransactionJsapi(ctx context.Context, bm gopay.BodyMap) (wxRsp *PrepayRsp, err error) {
|
||||
if bm.GetString("mchid") == util.NULL {
|
||||
bm.Set("mchid", c.Mchid)
|
||||
}
|
||||
authorization, err := c.authorization(MethodPost, v3ApiJsapi, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3ApiJsapi, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &PrepayRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(Prepay)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// Native下单API
|
||||
// Code = 0 is success
|
||||
// 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_1.shtml
|
||||
func (c *ClientV3) V3TransactionNative(ctx context.Context, bm gopay.BodyMap) (wxRsp *NativeRsp, err error) {
|
||||
if bm.GetString("mchid") == util.NULL {
|
||||
bm.Set("mchid", c.Mchid)
|
||||
}
|
||||
authorization, err := c.authorization(MethodPost, v3ApiNative, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3ApiNative, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &NativeRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(Native)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// H5下单API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_3_1.shtml
|
||||
func (c *ClientV3) V3TransactionH5(ctx context.Context, bm gopay.BodyMap) (wxRsp *H5Rsp, err error) {
|
||||
if bm.GetString("mchid") == util.NULL {
|
||||
bm.Set("mchid", c.Mchid)
|
||||
}
|
||||
authorization, err := c.authorization(MethodPost, v3ApiH5, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3ApiH5, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &H5Rsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(H5Url)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询订单API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_2.shtml
|
||||
func (c *ClientV3) V3TransactionQueryOrder(ctx context.Context, orderNoType OrderNoType, orderNo string) (wxRsp *QueryOrderRsp, err error) {
|
||||
var uri string
|
||||
switch orderNoType {
|
||||
case TransactionId:
|
||||
uri = fmt.Sprintf(v3ApiQueryOrderTransactionId, orderNo) + "?mchid=" + c.Mchid
|
||||
case OutTradeNo:
|
||||
uri = fmt.Sprintf(v3ApiQueryOrderOutTradeNo, orderNo) + "?mchid=" + c.Mchid
|
||||
default:
|
||||
return nil, errors.New("unsupported order number type")
|
||||
}
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp = &QueryOrderRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(QueryOrder)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 关闭订单API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_3.shtml
|
||||
func (c *ClientV3) V3TransactionCloseOrder(ctx context.Context, tradeNo string) (wxRsp *CloseOrderRsp, err error) {
|
||||
url := fmt.Sprintf(v3ApiCloseOrder, tradeNo)
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.Set("mchid", c.Mchid)
|
||||
authorization, err := c.authorization(MethodPost, url, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp = &CloseOrderRsp{Code: Success, SignInfo: si}
|
||||
if res.StatusCode != http.StatusNoContent {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/pkg/util"
|
||||
)
|
||||
|
||||
// 合单APP下单API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_1.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter5_1_1.shtml
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_3_1.shtml
|
||||
func (c *ClientV3) V3CombineTransactionApp(ctx context.Context, bm gopay.BodyMap) (wxRsp *PrepayRsp, err error) {
|
||||
if bm.GetString("combine_mchid") == util.NULL {
|
||||
bm.Set("combine_mchid", c.Mchid)
|
||||
}
|
||||
authorization, err := c.authorization(MethodPost, v3CombinePayApp, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3CombinePayApp, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &PrepayRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(Prepay)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 合单JSAPI/小程序下单API
|
||||
// Code = 0 is success
|
||||
// 商户JSAPI文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_3.shtml
|
||||
// 商户小程序文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_4.shtml
|
||||
// 服务商JSAPI文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter5_1_3.shtml
|
||||
// 服务商小程序文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter5_1_4.shtml
|
||||
// 电商JSAPI文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_3_3.shtml
|
||||
// 电商小程序文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_3_4.shtml
|
||||
func (c *ClientV3) V3CombineTransactionJsapi(ctx context.Context, bm gopay.BodyMap) (wxRsp *PrepayRsp, err error) {
|
||||
if bm.GetString("combine_mchid") == util.NULL {
|
||||
bm.Set("combine_mchid", c.Mchid)
|
||||
}
|
||||
authorization, err := c.authorization(MethodPost, v3CombinePayJsapi, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3CombinePayJsapi, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &PrepayRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(Prepay)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 合单Native下单API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_5.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter5_1_5.shtml
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_3_5.shtml
|
||||
func (c *ClientV3) V3CombineTransactionNative(ctx context.Context, bm gopay.BodyMap) (wxRsp *NativeRsp, err error) {
|
||||
if bm.GetString("combine_mchid") == util.NULL {
|
||||
bm.Set("combine_mchid", c.Mchid)
|
||||
}
|
||||
authorization, err := c.authorization(MethodPost, v3CombineNative, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3CombineNative, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &NativeRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(Native)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 合单H5下单API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_2.shtml
|
||||
func (c *ClientV3) V3CombineTransactionH5(ctx context.Context, bm gopay.BodyMap) (wxRsp *H5Rsp, err error) {
|
||||
if bm.GetString("combine_mchid") == util.NULL {
|
||||
bm.Set("combine_mchid", c.Mchid)
|
||||
}
|
||||
authorization, err := c.authorization(MethodPost, v3CombinePayH5, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3CombinePayH5, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &H5Rsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(H5Url)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 合单查询订单API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_11.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter5_1_11.shtml
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_3_11.shtml
|
||||
func (c *ClientV3) V3CombineQueryOrder(ctx context.Context, traderNo string) (wxRsp *CombineQueryOrderRsp, err error) {
|
||||
uri := fmt.Sprintf(v3CombineQuery, traderNo)
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp = &CombineQueryOrderRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(CombineQueryOrder)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 合单关闭订单API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_12.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter5_1_12.shtml
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_3_12.shtml
|
||||
func (c *ClientV3) V3CombineCloseOrder(ctx context.Context, tradeNo string, bm gopay.BodyMap) (wxRsp *CloseOrderRsp, err error) {
|
||||
url := fmt.Sprintf(v3CombineClose, tradeNo)
|
||||
authorization, err := c.authorization(MethodPost, url, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp = &CloseOrderRsp{Code: Success, SignInfo: si}
|
||||
if res.StatusCode != http.StatusNoContent {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/pkg/util"
|
||||
)
|
||||
|
||||
// (服务商、电商模式)APP下单API
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_2_1.shtml
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_2_1.shtml
|
||||
func (c *ClientV3) V3PartnerTransactionApp(ctx context.Context, bm gopay.BodyMap) (wxRsp *PrepayRsp, err error) {
|
||||
if bm.GetString("sp_mchid") == util.NULL {
|
||||
bm.Set("sp_mchid", c.Mchid)
|
||||
}
|
||||
authorization, err := c.authorization(MethodPost, v3ApiPartnerPayApp, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3ApiPartnerPayApp, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &PrepayRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(Prepay)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// (服务商、电商模式)JSAPI/小程序下单API
|
||||
// Code = 0 is success
|
||||
// 服务商JSAPI文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_1.shtml
|
||||
// 服务商小程序文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_5_1.shtml
|
||||
// 电商JSAPI文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_2_2.shtml
|
||||
// 电商小程序文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_2_3.shtml
|
||||
func (c *ClientV3) V3PartnerTransactionJsapi(ctx context.Context, bm gopay.BodyMap) (wxRsp *PrepayRsp, err error) {
|
||||
if bm.GetString("sp_mchid") == util.NULL {
|
||||
bm.Set("sp_mchid", c.Mchid)
|
||||
}
|
||||
authorization, err := c.authorization(MethodPost, v3ApiPartnerJsapi, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3ApiPartnerJsapi, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &PrepayRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(Prepay)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// (服务商、电商模式)Native下单API
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_4_1.shtml
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_2_12.shtml
|
||||
func (c *ClientV3) V3PartnerTransactionNative(ctx context.Context, bm gopay.BodyMap) (wxRsp *NativeRsp, err error) {
|
||||
if bm.GetString("sp_mchid") == util.NULL {
|
||||
bm.Set("sp_mchid", c.Mchid)
|
||||
}
|
||||
authorization, err := c.authorization(MethodPost, v3ApiPartnerNative, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3ApiPartnerNative, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &NativeRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(Native)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// (服务商模式)H5下单API
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_3_1.shtml
|
||||
func (c *ClientV3) V3PartnerTransactionH5(ctx context.Context, bm gopay.BodyMap) (wxRsp *H5Rsp, err error) {
|
||||
if bm.GetString("sp_mchid") == util.NULL {
|
||||
bm.Set("sp_mchid", c.Mchid)
|
||||
}
|
||||
authorization, err := c.authorization(MethodPost, v3ApiPartnerH5, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3ApiPartnerH5, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &H5Rsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(H5Url)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// (服务商、电商模式)查询订单API
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_2.shtml
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_2_5.shtml
|
||||
func (c *ClientV3) V3PartnerQueryOrder(ctx context.Context, orderNoType OrderNoType, orderNo string, bm gopay.BodyMap) (wxRsp *PartnerQueryOrderRsp, err error) {
|
||||
var uri string
|
||||
if bm.GetString("sp_mchid") == gopay.NULL {
|
||||
bm.Set("sp_mchid", c.Mchid)
|
||||
}
|
||||
switch orderNoType {
|
||||
case TransactionId:
|
||||
uri = fmt.Sprintf(v3ApiPartnerQueryOrderTransactionId, orderNo) + "?" + bm.EncodeURLParams()
|
||||
case OutTradeNo:
|
||||
uri = fmt.Sprintf(v3ApiPartnerQueryOrderOutTradeNo, orderNo) + "?" + bm.EncodeURLParams()
|
||||
default:
|
||||
return nil, errors.New("unsupported order number type")
|
||||
}
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp = &PartnerQueryOrderRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(PartnerQueryOrder)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// (服务商、电商模式)关单API
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_3.shtml
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_2_6.shtml
|
||||
func (c *ClientV3) V3PartnerCloseOrder(ctx context.Context, tradeNo string, bm gopay.BodyMap) (wxRsp *CloseOrderRsp, err error) {
|
||||
url := fmt.Sprintf(v3ApiPartnerCloseOrder, tradeNo)
|
||||
if bm.GetString("sp_mchid") == gopay.NULL {
|
||||
bm.Set("sp_mchid", c.Mchid)
|
||||
}
|
||||
authorization, err := c.authorization(MethodPost, url, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp = &CloseOrderRsp{Code: Success, SignInfo: si}
|
||||
if res.StatusCode != http.StatusNoContent {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
)
|
||||
|
||||
// 请求分账API
|
||||
// 微信会在接到请求后立刻返回请求接收结果,分账结果需要自行调用查询接口来获取
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_1.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_1_1.shtml
|
||||
func (c *ClientV3) V3ProfitShareOrder(ctx context.Context, bm gopay.BodyMap) (*ProfitShareOrderRsp, error) {
|
||||
authorization, err := c.authorization(MethodPost, v3ProfitShareOrder, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3ProfitShareOrder, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &ProfitShareOrderRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(ProfitShareOrder)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询分账结果API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_2.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_1_2.shtml
|
||||
func (c *ClientV3) V3ProfitShareOrderQuery(ctx context.Context, orderNo string, bm gopay.BodyMap) (*ProfitShareOrderQueryRsp, error) {
|
||||
uri := fmt.Sprintf(v3ProfitShareQuery, orderNo) + "?" + bm.EncodeURLParams()
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &ProfitShareOrderQueryRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(ProfitShareOrderQuery)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 请求分账回退API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_3.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_1_3.shtml
|
||||
func (c *ClientV3) V3ProfitShareReturn(ctx context.Context, bm gopay.BodyMap) (*ProfitShareReturnRsp, error) {
|
||||
authorization, err := c.authorization(MethodPost, v3ProfitShareReturn, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3ProfitShareReturn, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &ProfitShareReturnRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(ProfitShareReturn)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询分账回退结果API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_4.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_1_4.shtml
|
||||
func (c *ClientV3) V3ProfitShareReturnResult(ctx context.Context, returnNo string, bm gopay.BodyMap) (*ProfitShareReturnResultRsp, error) {
|
||||
uri := fmt.Sprintf(v3ProfitShareReturnResult, returnNo) + "?" + bm.EncodeURLParams()
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &ProfitShareReturnResultRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(ProfitShareReturnResult)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 解冻剩余资金API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_5.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_1_5.shtml
|
||||
func (c *ClientV3) V3ProfitShareOrderUnfreeze(ctx context.Context, bm gopay.BodyMap) (*ProfitShareOrderUnfreezeRsp, error) {
|
||||
authorization, err := c.authorization(MethodPost, v3ProfitShareUnfreeze, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3ProfitShareUnfreeze, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &ProfitShareOrderUnfreezeRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(ProfitShareOrderUnfreeze)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询剩余待分金额API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_6.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_1_6.shtml
|
||||
func (c *ClientV3) V3ProfitShareUnsplitAmount(ctx context.Context, transId string) (*ProfitShareUnsplitAmountRsp, error) {
|
||||
url := fmt.Sprintf(v3ProfitShareUnsplitAmount, transId)
|
||||
authorization, err := c.authorization(MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &ProfitShareUnsplitAmountRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(ProfitShareUnsplitAmount)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询最大分账比例API
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_1_7.shtml
|
||||
func (c *ClientV3) V3ProfitShareMerchantConfigs(ctx context.Context, subMchId string) (*ProfitShareMerchantConfigsRsp, error) {
|
||||
uri := fmt.Sprintf(v3ProfitShareMerchantConfigs, subMchId)
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &ProfitShareMerchantConfigsRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(ProfitShareMerchantConfigs)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 新增分账接收方API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_8.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_1_8.shtml
|
||||
func (c *ClientV3) V3ProfitShareAddReceiver(ctx context.Context, bm gopay.BodyMap) (*ProfitShareAddReceiverRsp, error) {
|
||||
authorization, err := c.authorization(MethodPost, v3ProfitShareAddReceiver, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3ProfitShareAddReceiver, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &ProfitShareAddReceiverRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(ProfitShareAddReceiver)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 删除分账接收方API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_9.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_1_9.shtml
|
||||
func (c *ClientV3) V3ProfitShareDeleteReceiver(ctx context.Context, bm gopay.BodyMap) (*ProfitShareDeleteReceiverRsp, error) {
|
||||
authorization, err := c.authorization(MethodPost, v3ProfitShareDeleteReceiver, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3ProfitShareDeleteReceiver, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &ProfitShareDeleteReceiverRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(ProfitShareDeleteReceiver)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 申请分账账单
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_11.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_1_11.shtml
|
||||
func (c *ClientV3) V3ProfitShareBills(ctx context.Context, bm gopay.BodyMap) (*ProfitShareBillsRsp, error) {
|
||||
uri := v3ProfitShareBills + "?" + bm.EncodeURLParams()
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &ProfitShareBillsRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(ProfitShareBills)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
)
|
||||
|
||||
// 申请退款API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_9.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_9.shtml
|
||||
func (c *ClientV3) V3Refund(ctx context.Context, bm gopay.BodyMap) (wxRsp *RefundRsp, err error) {
|
||||
authorization, err := c.authorization(MethodPost, v3DomesticRefund, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3DomesticRefund, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp = &RefundRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(RefundOrderResponse)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询单笔退款API
|
||||
// 注意:商户查询时,bm 可传 nil;服务商时,传相应query参数
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_10.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_10.shtml
|
||||
func (c *ClientV3) V3RefundQuery(ctx context.Context, outRefundNo string, bm gopay.BodyMap) (wxRsp *RefundQueryRsp, err error) {
|
||||
uri := fmt.Sprintf(v3DomesticRefundQuery, outRefundNo)
|
||||
if bm != nil {
|
||||
uri = fmt.Sprintf(v3DomesticRefundQuery, outRefundNo) + "?" + bm.EncodeURLParams()
|
||||
}
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp = &RefundQueryRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(RefundQueryResponse)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 申请退款API
|
||||
// Code = 0 is success
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_6_1.shtml
|
||||
func (c *ClientV3) V3EcommerceRefund(ctx context.Context, bm gopay.BodyMap) (wxRsp *EcommerceRefundRsp, err error) {
|
||||
authorization, err := c.authorization(MethodPost, v3CommerceRefund, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3CommerceRefund, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp = &EcommerceRefundRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(EcommerceRefund)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 通过微信支付退款单号查询退款API
|
||||
// Code = 0 is success
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_6_2.shtml
|
||||
func (c *ClientV3) V3EcommerceRefundQueryById(ctx context.Context, refundId string, bm gopay.BodyMap) (wxRsp *EcommerceRefundQueryRsp, err error) {
|
||||
uri := fmt.Sprintf(v3CommerceRefundQueryById, refundId) + "?" + bm.EncodeURLParams()
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp = &EcommerceRefundQueryRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(EcommerceRefundQuery)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 通过商户退款单号查询退款API
|
||||
// Code = 0 is success
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_6_2.shtml
|
||||
func (c *ClientV3) V3EcommerceRefundQueryByNo(ctx context.Context, outRefundNo string, bm gopay.BodyMap) (wxRsp *EcommerceRefundQueryRsp, err error) {
|
||||
uri := fmt.Sprintf(v3CommerceRefundQueryByNo, outRefundNo) + "?" + bm.EncodeURLParams()
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp = &EcommerceRefundQueryRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(EcommerceRefundQuery)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 垫付退款回补API
|
||||
// Code = 0 is success
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_6_4.shtml
|
||||
func (c *ClientV3) V3EcommerceRefundAdvance(ctx context.Context, refundId string, bm gopay.BodyMap) (wxRsp *EcommerceRefundAdvanceRsp, err error) {
|
||||
url := fmt.Sprintf(v3CommerceRefundAdvance, refundId)
|
||||
authorization, err := c.authorization(MethodPost, url, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp = &EcommerceRefundAdvanceRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(EcommerceRefundAdvance)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询垫付回补结果API
|
||||
// Code = 0 is success
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_6_5.shtml
|
||||
func (c *ClientV3) V3EcommerceRefundAdvanceResult(ctx context.Context, refundId string, bm gopay.BodyMap) (wxRsp *EcommerceRefundAdvanceRsp, err error) {
|
||||
uri := fmt.Sprintf(v3CommerceRefundAdvanceResult, refundId) + "?" + bm.EncodeURLParams()
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp = &EcommerceRefundAdvanceRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(EcommerceRefundAdvance)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
)
|
||||
|
||||
// 创单结单合并API
|
||||
// Code = 0 is success
|
||||
// 注意:限制条件:【免确认订单模式】,用户已授权状态下,可调用该接口。
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_1.shtml
|
||||
func (c *ClientV3) V3ScoreDirectComplete(ctx context.Context, bm gopay.BodyMap) (wxRsp *ScoreDirectCompleteRsp, err error) {
|
||||
authorization, err := c.authorization(MethodPost, v3ScoreDirectComplete, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3ScoreDirectComplete, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &ScoreDirectCompleteRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(ScoreDirectComplete)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 商户预授权API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_2.shtml
|
||||
func (c *ClientV3) V3ScorePermission(ctx context.Context, bm gopay.BodyMap) (wxRsp *ScorePermissionRsp, err error) {
|
||||
authorization, err := c.authorization(MethodPost, v3ScorePermission, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3ScorePermission, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &ScorePermissionRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(ScorePermission)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询用户授权记录(授权协议号)API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_3.shtml
|
||||
func (c *ClientV3) V3ScorePermissionQuery(ctx context.Context, authCode, serviceId string) (wxRsp *ScorePermissionQueryRsp, err error) {
|
||||
uri := fmt.Sprintf(v3ScorePermissionQuery, authCode) + "?service_id=" + serviceId
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &ScorePermissionQueryRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(ScorePermissionQuery)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 解除用户授权关系(授权协议号)API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_4.shtml
|
||||
func (c *ClientV3) V3ScorePermissionTerminate(ctx context.Context, authCode, serviceId, reason string) (wxRsp *EmptyRsp, err error) {
|
||||
url := fmt.Sprintf(v3ScorePermissionTerminate, authCode)
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.Set("service_id", serviceId).
|
||||
Set("reason", reason)
|
||||
authorization, err := c.authorization(MethodPost, url, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &EmptyRsp{Code: Success, SignInfo: si}
|
||||
if res.StatusCode != http.StatusNoContent {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询用户授权记录(openid)API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_5.shtml
|
||||
func (c *ClientV3) V3ScorePermissionOpenidQuery(ctx context.Context, appid, openid, serviceid string) (wxRsp *ScorePermissionOpenidQueryRsp, err error) {
|
||||
uri := fmt.Sprintf(v3ScorePermissionOpenidQuery, openid) + "?appid=" + appid + "&service_id=" + serviceid
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &ScorePermissionOpenidQueryRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(ScorePermissionOpenidQuery)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 解除用户授权关系(openid)API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_6.shtml
|
||||
func (c *ClientV3) V3ScorePermissionOpenidTerminate(ctx context.Context, appid, openid, serviceid, reason string) (wxRsp *EmptyRsp, err error) {
|
||||
url := fmt.Sprintf(v3ScorePermissionOpenidTerminate, openid)
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.Set("service_id", serviceid).
|
||||
Set("appid", appid).
|
||||
Set("reason", reason)
|
||||
authorization, err := c.authorization(MethodPost, url, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &EmptyRsp{Code: Success, SignInfo: si}
|
||||
if res.StatusCode != http.StatusNoContent {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 创建支付分订单API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_14.shtml
|
||||
func (c *ClientV3) V3ScoreOrderCreate(ctx context.Context, bm gopay.BodyMap) (wxRsp *ScoreOrderCreateRsp, err error) {
|
||||
authorization, err := c.authorization(MethodPost, v3ScoreOrderCreate, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3ScoreOrderCreate, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &ScoreOrderCreateRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(ScoreOrderCreate)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询支付分订单API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_15.shtml
|
||||
func (c *ClientV3) V3ScoreOrderQuery(ctx context.Context, orderNoType OrderNoType, appid, orderNo, serviceid string) (wxRsp *ScoreOrderQueryRsp, err error) {
|
||||
var uri string
|
||||
switch orderNoType {
|
||||
case OutTradeNo:
|
||||
uri = v3ScoreOrderQuery + "?appid=" + appid + "&out_order_no=" + orderNo + "&service_id=" + serviceid
|
||||
case QueryId:
|
||||
uri = v3ScoreOrderQuery + "?appid=" + appid + "&query_id=" + orderNo + "&service_id=" + serviceid
|
||||
default:
|
||||
return nil, errors.New("unsupported order number type")
|
||||
}
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &ScoreOrderQueryRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(ScoreOrderQuery)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 取消支付分订单API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_16.shtml
|
||||
func (c *ClientV3) V3ScoreOrderCancel(ctx context.Context, appid, tradeNo, serviceid, reason string) (wxRsp *ScoreOrderCancelRsp, err error) {
|
||||
url := fmt.Sprintf(v3ScoreOrderCancel, tradeNo)
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.Set("appid", appid).
|
||||
Set("service_id", serviceid).
|
||||
Set("reason", reason)
|
||||
authorization, err := c.authorization(MethodPost, url, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &ScoreOrderCancelRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(ScoreOrderCancel)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 修改订单金额API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_17.shtml
|
||||
func (c *ClientV3) V3ScoreOrderModify(ctx context.Context, tradeNo string, bm gopay.BodyMap) (wxRsp *ScoreOrderModifyRsp, err error) {
|
||||
url := fmt.Sprintf(v3ScoreOrderModify, tradeNo)
|
||||
authorization, err := c.authorization(MethodPost, url, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &ScoreOrderModifyRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(ScoreOrderModify)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 完结支付分订单API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_18.shtml
|
||||
func (c *ClientV3) V3ScoreOrderComplete(ctx context.Context, tradeNo string, bm gopay.BodyMap) (wxRsp *ScoreOrderCompleteRsp, err error) {
|
||||
url := fmt.Sprintf(v3ScoreOrderComplete, tradeNo)
|
||||
authorization, err := c.authorization(MethodPost, url, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &ScoreOrderCompleteRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(ScoreOrderComplete)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 商户发起催收扣款API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_19.shtml
|
||||
func (c *ClientV3) V3ScoreOrderPay(ctx context.Context, appid, tradeNo, serviceid string) (wxRsp *ScoreOrderPayRsp, err error) {
|
||||
url := fmt.Sprintf(v3ScoreOrderPay, tradeNo)
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.Set("appid", appid).
|
||||
Set("service_id", serviceid)
|
||||
authorization, err := c.authorization(MethodPost, url, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &ScoreOrderPayRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(ScoreOrderPay)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 同步服务订单信息API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_20.shtml
|
||||
func (c *ClientV3) V3ScoreOrderSync(ctx context.Context, tradeNo string, bm gopay.BodyMap) (wxRsp *ScoreOrderSyncRsp, err error) {
|
||||
url := fmt.Sprintf(v3ScoreOrderSync, tradeNo)
|
||||
authorization, err := c.authorization(MethodPost, url, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &ScoreOrderSyncRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(ScoreOrderSync)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/pkg/util"
|
||||
"github.com/go-pay/gopay/pkg/xlog"
|
||||
"github.com/go-pay/gopay/pkg/xpem"
|
||||
)
|
||||
|
||||
// Deprecated
|
||||
// 推荐使用 wechat.V3VerifySignByPK()
|
||||
func V3VerifySign(timestamp, nonce, signBody, sign, wxPubKeyContent string) (err error) {
|
||||
publicKey, err := xpem.DecodePublicKey([]byte(wxPubKeyContent))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
str := timestamp + "\n" + nonce + "\n" + signBody + "\n"
|
||||
signBytes, _ := base64.StdEncoding.DecodeString(sign)
|
||||
|
||||
h := sha256.New()
|
||||
h.Write([]byte(str))
|
||||
if err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, h.Sum(nil), signBytes); err != nil {
|
||||
return fmt.Errorf("[%w]: %v", gopay.VerifySignatureErr, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 微信V3 版本验签(同步/异步)
|
||||
// wxPublicKey:微信平台证书公钥内容,通过 client.WxPublicKey() 获取
|
||||
func V3VerifySignByPK(timestamp, nonce, signBody, sign string, wxPublicKey *rsa.PublicKey) (err error) {
|
||||
str := timestamp + "\n" + nonce + "\n" + signBody + "\n"
|
||||
signBytes, _ := base64.StdEncoding.DecodeString(sign)
|
||||
|
||||
h := sha256.New()
|
||||
h.Write([]byte(str))
|
||||
if err = rsa.VerifyPKCS1v15(wxPublicKey, crypto.SHA256, h.Sum(nil), signBytes); err != nil {
|
||||
return fmt.Errorf("[%w]: %v", gopay.VerifySignatureErr, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PaySignOfJSAPI 获取 JSAPI paySign
|
||||
// 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_4.shtml
|
||||
func (c *ClientV3) PaySignOfJSAPI(appid, prepayid string) (jsapi *JSAPIPayParams, err error) {
|
||||
ts := util.Int642String(time.Now().Unix())
|
||||
nonceStr := util.RandomString(32)
|
||||
pkg := "prepay_id=" + prepayid
|
||||
|
||||
_str := appid + "\n" + ts + "\n" + nonceStr + "\n" + pkg + "\n"
|
||||
sign, err := c.rsaSign(_str)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
jsapi = &JSAPIPayParams{
|
||||
AppId: appid,
|
||||
TimeStamp: ts,
|
||||
NonceStr: nonceStr,
|
||||
Package: pkg,
|
||||
SignType: SignTypeRSA,
|
||||
PaySign: sign,
|
||||
}
|
||||
return jsapi, nil
|
||||
}
|
||||
|
||||
// PaySignOfApp 获取 App sign
|
||||
// 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_4.shtml
|
||||
func (c *ClientV3) PaySignOfApp(appid, prepayid string) (app *AppPayParams, err error) {
|
||||
ts := util.Int642String(time.Now().Unix())
|
||||
nonceStr := util.RandomString(32)
|
||||
|
||||
_str := appid + "\n" + ts + "\n" + nonceStr + "\n" + prepayid + "\n"
|
||||
sign, err := c.rsaSign(_str)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
app = &AppPayParams{
|
||||
Appid: appid,
|
||||
Partnerid: c.Mchid,
|
||||
Prepayid: prepayid,
|
||||
Package: "Sign=WXPay",
|
||||
Noncestr: nonceStr,
|
||||
Timestamp: ts,
|
||||
Sign: sign,
|
||||
}
|
||||
return app, nil
|
||||
}
|
||||
|
||||
// PaySignOfApplet 获取 小程序 paySign
|
||||
// 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_4.shtml
|
||||
func (c *ClientV3) PaySignOfApplet(appid, prepayid string) (applet *AppletParams, err error) {
|
||||
jsapi, err := c.PaySignOfJSAPI(appid, prepayid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
applet = &AppletParams{
|
||||
AppId: jsapi.AppId,
|
||||
TimeStamp: jsapi.TimeStamp,
|
||||
NonceStr: jsapi.NonceStr,
|
||||
Package: jsapi.Package,
|
||||
SignType: jsapi.SignType,
|
||||
PaySign: jsapi.PaySign,
|
||||
}
|
||||
return applet, nil
|
||||
}
|
||||
|
||||
// v3 鉴权请求Header
|
||||
func (c *ClientV3) authorization(method, path string, bm gopay.BodyMap) (string, error) {
|
||||
var (
|
||||
jb = ""
|
||||
timestamp = time.Now().Unix()
|
||||
nonceStr = util.RandomString(32)
|
||||
)
|
||||
if bm != nil {
|
||||
jb = bm.JsonBody()
|
||||
}
|
||||
ts := util.Int642String(timestamp)
|
||||
_str := method + "\n" + path + "\n" + ts + "\n" + nonceStr + "\n" + jb + "\n"
|
||||
if c.DebugSwitch == gopay.DebugOn {
|
||||
xlog.Debugf("Wechat_V3_SignString:\n%s", _str)
|
||||
}
|
||||
sign, err := c.rsaSign(_str)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return Authorization + ` mchid="` + c.Mchid + `",nonce_str="` + nonceStr + `",timestamp="` + ts + `",serial_no="` + c.SerialNo + `",signature="` + sign + `"`, nil
|
||||
}
|
||||
|
||||
func (c *ClientV3) rsaSign(str string) (string, error) {
|
||||
if c.privateKey == nil {
|
||||
return "", errors.New("privateKey can't be nil")
|
||||
}
|
||||
h := sha256.New()
|
||||
h.Write([]byte(str))
|
||||
result, err := rsa.SignPKCS1v15(rand.Reader, c.privateKey, crypto.SHA256, h.Sum(nil))
|
||||
if err != nil {
|
||||
return util.NULL, fmt.Errorf("[%w]: %+v", gopay.SignatureErr, err)
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(result), nil
|
||||
}
|
||||
|
||||
// 自动同步请求验签
|
||||
func (c *ClientV3) verifySyncSign(si *SignInfo) (err error) {
|
||||
if c.autoSign && c.wxPublicKey != nil {
|
||||
if si != nil {
|
||||
str := si.HeaderTimestamp + "\n" + si.HeaderNonce + "\n" + si.SignBody + "\n"
|
||||
signBytes, _ := base64.StdEncoding.DecodeString(si.HeaderSignature)
|
||||
|
||||
h := sha256.New()
|
||||
h.Write([]byte(str))
|
||||
if err = rsa.VerifyPKCS1v15(c.wxPublicKey, crypto.SHA256, h.Sum(nil), signBytes); err != nil {
|
||||
return fmt.Errorf("[%w]: %v", gopay.VerifySignatureErr, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return errors.New("auto verify sign, bug SignInfo is nil")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
)
|
||||
|
||||
// 服务人员注册API
|
||||
// 注意:入参加密字段数据加密:client.V3EncryptText()
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_4_1.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_4_1.shtml
|
||||
func (c *ClientV3) V3SmartGuideReg(ctx context.Context, bm gopay.BodyMap) (wxRsp *SmartGuideRegRsp, err error) {
|
||||
authorization, err := c.authorization(MethodPost, v3GuideReg, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3GuideReg, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &SmartGuideRegRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(SmartGuideReg)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 服务人员分配API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_4_2.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_4_2.shtml
|
||||
func (c *ClientV3) V3SmartGuideAssign(ctx context.Context, guideId, tradeNo string) (wxRsp *EmptyRsp, err error) {
|
||||
url := fmt.Sprintf(v3GuideAssign, guideId)
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.Set("out_trade_no", tradeNo)
|
||||
authorization, err := c.authorization(MethodPost, url, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &EmptyRsp{Code: Success, SignInfo: si}
|
||||
if res.StatusCode != http.StatusNoContent {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 服务人员查询API
|
||||
// 注意:入参加密字段数据加密:client.V3EncryptText(),返回参数加密字段解密:client.V3DecryptText()
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_4_3.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_4_3.shtml
|
||||
func (c *ClientV3) V3SmartGuideQuery(ctx context.Context, bm gopay.BodyMap) (wxRsp *SmartGuideQueryRsp, err error) {
|
||||
if err = bm.CheckEmptyError("store_id"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uri := v3GuideQuery + "?" + bm.EncodeURLParams()
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &SmartGuideQueryRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(SmartGuideQuery)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 服务人员信息更新API
|
||||
// 注意:入参加密字段数据加密:client.V3EncryptText()
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_4_4.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_4_4.shtml
|
||||
func (c *ClientV3) V3SmartGuideUpdate(ctx context.Context, guideId string, bm gopay.BodyMap) (wxRsp *EmptyRsp, err error) {
|
||||
url := fmt.Sprintf(v3GuideUpdate, guideId)
|
||||
authorization, err := c.authorization(MethodPATCH, url, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPatch(ctx, bm, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp = &EmptyRsp{Code: Success, SignInfo: si}
|
||||
if res.StatusCode != http.StatusNoContent {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
@@ -0,0 +1,413 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
)
|
||||
|
||||
// 发起批量转账API
|
||||
// 注意:入参加密字段数据加密:client.V3EncryptText()
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter3_1.shtml
|
||||
func (c *ClientV3) V3Transfer(ctx context.Context, bm gopay.BodyMap) (*TransferRsp, error) {
|
||||
authorization, err := c.authorization(MethodPost, v3Transfer, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3Transfer, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &TransferRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(Transfer)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 发起批量转账API(服务商)
|
||||
// 注意:入参加密字段数据加密:client.V3EncryptText()
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter3_1.shtml
|
||||
func (c *ClientV3) V3PartnerTransfer(ctx context.Context, bm gopay.BodyMap) (*TransferRsp, error) {
|
||||
authorization, err := c.authorization(MethodPost, v3PartnerTransfer, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3PartnerTransfer, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &TransferRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(Transfer)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 微信批次单号查询批次单API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter3_2.shtml
|
||||
func (c *ClientV3) V3TransferQuery(ctx context.Context, batchId string, bm gopay.BodyMap) (*TransferQueryRsp, error) {
|
||||
url := fmt.Sprintf(v3TransferQuery, batchId)
|
||||
bm.Remove("batch_id")
|
||||
uri := url + "?" + bm.EncodeURLParams()
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &TransferQueryRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(TransferQuery)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 微信批次单号查询批次单API(服务商)
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter3_2.shtml
|
||||
func (c *ClientV3) V3PartnerTransferQuery(ctx context.Context, batchId string, bm gopay.BodyMap) (*PartnerTransferQueryRsp, error) {
|
||||
url := fmt.Sprintf(v3PartnerTransferQuery, batchId)
|
||||
bm.Remove("batch_id")
|
||||
uri := url + "?" + bm.EncodeURLParams()
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &PartnerTransferQueryRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(PartnerTransferQuery)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 微信明细单号查询明细单API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter3_3.shtml
|
||||
func (c *ClientV3) V3TransferDetail(ctx context.Context, batchId, detailId string) (*TransferDetailRsp, error) {
|
||||
url := fmt.Sprintf(v3TransferDetail, batchId, detailId)
|
||||
authorization, err := c.authorization(MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &TransferDetailRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(TransferDetailQuery)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 微信明细单号查询明细单API(服务商)
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter3_3.shtml
|
||||
func (c *ClientV3) V3PartnerTransferDetail(ctx context.Context, batchId, detailId string) (*PartnerTransferDetailRsp, error) {
|
||||
url := fmt.Sprintf(v3PartnerTransferDetail, batchId, detailId)
|
||||
authorization, err := c.authorization(MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &PartnerTransferDetailRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(PartnerTransferDetail)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
// 推荐直接使用 client.V3TransferDetail() 方法
|
||||
func (c *ClientV3) V3TransferDetailQuery(ctx context.Context, batchId, detailId string) (*TransferDetailRsp, error) {
|
||||
return c.V3TransferDetail(ctx, batchId, detailId)
|
||||
}
|
||||
|
||||
// 商家批次单号查询批次单API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter3_4.shtml
|
||||
func (c *ClientV3) V3TransferMerchantQuery(ctx context.Context, outBatchNo string, bm gopay.BodyMap) (*TransferMerchantQueryRsp, error) {
|
||||
url := fmt.Sprintf(v3TransferMerchantQuery, outBatchNo)
|
||||
bm.Remove("out_batch_no")
|
||||
uri := url + "?" + bm.EncodeURLParams()
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &TransferMerchantQueryRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(TransferMerchantQuery)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 商家批次单号查询批次单API(服务商)
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter3_4.shtml
|
||||
func (c *ClientV3) V3PartnerTransferMerchantQuery(ctx context.Context, outBatchNo string, bm gopay.BodyMap) (*PartnerTransferMerchantQueryRsp, error) {
|
||||
url := fmt.Sprintf(v3PartnerTransferMerchantQuery, outBatchNo)
|
||||
bm.Remove("out_batch_no")
|
||||
uri := url + "?" + bm.EncodeURLParams()
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &PartnerTransferMerchantQueryRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(PartnerTransferMerchantQuery)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 商家明细单号查询明细单API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter3_5.shtml
|
||||
func (c *ClientV3) V3TransferMerchantDetail(ctx context.Context, outBatchNo, outDetailNo string) (*TransferMerchantDetailRsp, error) {
|
||||
url := fmt.Sprintf(v3TransferMerchantDetail, outBatchNo, outDetailNo)
|
||||
authorization, err := c.authorization(MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &TransferMerchantDetailRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(TransferMerchantDetail)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 商家明细单号查询明细单API(服务商)
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter3_5.shtml
|
||||
func (c *ClientV3) V3PartnerTransferMerchantDetail(ctx context.Context, outBatchNo, outDetailNo string) (*PartnerTransferMerchantDetailRsp, error) {
|
||||
url := fmt.Sprintf(v3PartnerTransferMerchantDetail, outBatchNo, outDetailNo)
|
||||
authorization, err := c.authorization(MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &PartnerTransferMerchantDetailRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(PartnerTransferMerchantDetail)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
// 推荐直接使用 client.V3TransferMerchantDetail() 方法
|
||||
func (c *ClientV3) V3TransferMerchantDetailQuery(ctx context.Context, outBatchNo, outDetailNo string) (*TransferMerchantDetailRsp, error) {
|
||||
return c.V3TransferMerchantDetail(ctx, outBatchNo, outDetailNo)
|
||||
}
|
||||
|
||||
// 转账电子回单申请受理API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter4_1.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter4_1.shtml
|
||||
func (c *ClientV3) V3TransferReceipt(ctx context.Context, outBatchNo string) (*TransferReceiptRsp, error) {
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.Set("out_batch_no", outBatchNo)
|
||||
|
||||
authorization, err := c.authorization(MethodPost, v3TransferReceipt, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3TransferReceipt, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &TransferReceiptRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(TransferReceipt)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询转账电子回单API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter4_2.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter4_2.shtml
|
||||
func (c *ClientV3) V3TransferReceiptQuery(ctx context.Context, outBatchNo string) (*TransferReceiptQueryRsp, error) {
|
||||
url := fmt.Sprintf(v3TransferReceiptQuery, outBatchNo)
|
||||
authorization, err := c.authorization(MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, url, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &TransferReceiptQueryRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(TransferReceiptQuery)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 转账明细电子回单受理API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter4_4.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter4_4.shtml
|
||||
func (c *ClientV3) V3TransferDetailReceipt(ctx context.Context, bm gopay.BodyMap) (*TransferDetailReceiptRsp, error) {
|
||||
authorization, err := c.authorization(MethodPost, v3TransferDetailReceipt, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3TransferDetailReceipt, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &TransferDetailReceiptRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(TransferDetailReceipt)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询转账明细电子回单受理结果API
|
||||
// Code = 0 is success
|
||||
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter4_5.shtml
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter4_5.shtml
|
||||
func (c *ClientV3) V3TransferDetailReceiptQuery(ctx context.Context, bm gopay.BodyMap) (*TransferDetailReceiptQueryRsp, error) {
|
||||
uri := v3TransferDetailReceiptQuery + "?" + bm.EncodeURLParams()
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &TransferDetailReceiptQueryRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(TransferDetailReceiptQuery)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/pkg/util"
|
||||
)
|
||||
|
||||
// 特约商户余额提现、二级商户预约提现
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter6_1.shtml
|
||||
func (c *ClientV3) V3Withdraw(ctx context.Context, bm gopay.BodyMap) (*WithdrawRsp, error) {
|
||||
if err := bm.CheckEmptyError("sub_mchid", "out_request_no", "amount"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authorization, err := c.authorization(MethodPost, v3Withdraw, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3Withdraw, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &WithdrawRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(Withdraw)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 查询特约商户提现状态、二级商户查询预约提现状态
|
||||
// 注意:withdrawId 和 outRequestNo 二选一
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter6_2.shtml
|
||||
func (c *ClientV3) V3WithdrawStatus(ctx context.Context, withdrawId, outRequestNo string, bm gopay.BodyMap) (*WithdrawStatusRsp, error) {
|
||||
if withdrawId == gopay.NULL && outRequestNo == gopay.NULL {
|
||||
return nil, fmt.Errorf("[%w]: withdrawId[%s] and outRequestNo[%s] empty at the same time", gopay.MissParamErr, withdrawId, outRequestNo)
|
||||
}
|
||||
var uri string
|
||||
if withdrawId != gopay.NULL {
|
||||
uri = fmt.Sprintf(v3WithdrawStatusById, withdrawId) + "?" + bm.EncodeURLParams()
|
||||
} else {
|
||||
uri = fmt.Sprintf(v3WithdrawStatusByNo, outRequestNo) + "?" + bm.EncodeURLParams()
|
||||
}
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp := &WithdrawStatusRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(WithdrawStatus)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 电商平台预约提现
|
||||
// Code = 0 is success
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_8_2.shtml
|
||||
func (c *ClientV3) V3EcommerceWithdraw(ctx context.Context, bm gopay.BodyMap) (*EcommerceWithdrawRsp, error) {
|
||||
if err := bm.CheckEmptyError("out_request_no", "amount", "account_type"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authorization, err := c.authorization(MethodPost, v3EcommerceWithdraw, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdPost(ctx, bm, v3EcommerceWithdraw, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp := &EcommerceWithdrawRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(EcommerceWithdraw)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 电商平台查询预约提现状态
|
||||
// 注意:withdrawId 和 outRequestNo 二选一
|
||||
// Code = 0 is success
|
||||
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_8_6.shtml
|
||||
func (c *ClientV3) V3EcommerceWithdrawStatus(ctx context.Context, withdrawId, outRequestNo string) (*EcommerceWithdrawStatusRsp, error) {
|
||||
if withdrawId == gopay.NULL && outRequestNo == gopay.NULL {
|
||||
return nil, fmt.Errorf("[%w]: withdrawId[%s] and outRequestNo[%s] empty at the same time", gopay.MissParamErr, withdrawId, outRequestNo)
|
||||
}
|
||||
var uri string
|
||||
if withdrawId != gopay.NULL {
|
||||
uri = fmt.Sprintf(v3EcommerceWithdrawStatusById, withdrawId)
|
||||
} else {
|
||||
uri = fmt.Sprintf(v3EcommerceWithdrawStatusByNo, outRequestNo)
|
||||
}
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wxRsp := &EcommerceWithdrawStatusRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(EcommerceWithdrawStatus)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
||||
// 按日下载提现异常文件
|
||||
// 注意:如 bill_date 为空,默认查前一天的
|
||||
// Code = 0 is success
|
||||
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter6_3.shtml
|
||||
func (c *ClientV3) V3WithdrawDownloadErrBill(ctx context.Context, bm gopay.BodyMap) (wxRsp *BillRsp, err error) {
|
||||
if bm != nil {
|
||||
if bm.GetString("bill_date") == util.NULL {
|
||||
now := time.Now()
|
||||
yesterday := time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, time.Local).Format(util.DateLayout)
|
||||
bm.Set("bill_date", yesterday)
|
||||
}
|
||||
bm.Remove("bill_type")
|
||||
}
|
||||
uri := fmt.Sprintf(v3WithdrawDownloadErrBill, "NO_SUCC") + "?" + bm.EncodeURLParams()
|
||||
authorization, err := c.authorization(MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wxRsp = &BillRsp{Code: Success, SignInfo: si}
|
||||
wxRsp.Response = new(TradeBill)
|
||||
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
|
||||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
wxRsp.Code = res.StatusCode
|
||||
wxRsp.Error = string(bs)
|
||||
return wxRsp, nil
|
||||
}
|
||||
return wxRsp, c.verifySyncSign(si)
|
||||
}
|
||||
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 165 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 31 KiB |